1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod edit_prediction_tests;
47#[cfg(test)]
48mod editor_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
55pub use edit_prediction::Direction;
56pub use editor_settings::{
57 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
58 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
59};
60pub use editor_settings_controls::*;
61pub use element::{
62 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
63};
64pub use git::blame::BlameRenderer;
65pub use hover_popover::hover_markdown_style;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
71 RowInfo, ToOffset, ToPoint,
72};
73pub use proposed_changes_editor::{
74 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
75};
76pub use text::Bias;
77
78use ::git::{
79 Restore,
80 blame::{BlameEntry, ParsedCommitMessage},
81};
82use aho_corasick::AhoCorasick;
83use anyhow::{Context as _, Result, anyhow};
84use blink_manager::BlinkManager;
85use buffer_diff::DiffHunkStatus;
86use client::{Collaborator, ParticipantIndex};
87use clock::{AGENT_REPLICA_ID, ReplicaId};
88use code_context_menus::{
89 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
90 CompletionsMenu, ContextMenuOrigin,
91};
92use collections::{BTreeMap, HashMap, HashSet, VecDeque};
93use convert_case::{Case, Casing};
94use dap::TelemetrySpawnLocation;
95use display_map::*;
96use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
97use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
98use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
99use futures::{
100 FutureExt, StreamExt as _,
101 future::{self, Shared, join},
102 stream::FuturesUnordered,
103};
104use fuzzy::{StringMatch, StringMatchCandidate};
105use git::blame::{GitBlame, GlobalBlameRenderer};
106use gpui::{
107 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
108 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
109 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
110 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
111 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
112 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
113 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
114 div, point, prelude::*, pulsating_between, px, relative, size,
115};
116use highlight_matching_bracket::refresh_matching_bracket_highlights;
117use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
118use hover_popover::{HoverState, hide_hover};
119use indent_guides::ActiveIndentGuidesState;
120use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
121use itertools::{Either, Itertools};
122use language::{
123 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
124 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
125 DiagnosticEntry, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
126 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
127 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
152 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
153 ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::{GitStoreEvent, RepositoryEvent},
162 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{
169 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
170};
171use serde::{Deserialize, Serialize};
172use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
173use smallvec::{SmallVec, smallvec};
174use snippet::Snippet;
175use std::{
176 any::{Any, TypeId},
177 borrow::Cow,
178 cell::{OnceCell, RefCell},
179 cmp::{self, Ordering, Reverse},
180 iter::{self, Peekable},
181 mem,
182 num::NonZeroU32,
183 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
184 path::{Path, PathBuf},
185 rc::Rc,
186 sync::Arc,
187 time::{Duration, Instant},
188};
189use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
190use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
191use theme::{
192 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
193 observe_buffer_font_size_adjustment,
194};
195use ui::{
196 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
197 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
198};
199use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
200use workspace::{
201 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
202 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
203 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
204 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
205 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
206 searchable::SearchEvent,
207};
208
209use crate::{
210 code_context_menus::CompletionsMenuSource,
211 editor_settings::MultiCursorModifier,
212 hover_links::{find_url, find_url_from_range},
213 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
214};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230
231pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
232pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
233pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
234
235pub type RenderDiffHunkControlsFn = Arc<
236 dyn Fn(
237 u32,
238 &DiffHunkStatus,
239 Range<Anchor>,
240 bool,
241 Pixels,
242 &Entity<Editor>,
243 &mut Window,
244 &mut App,
245 ) -> AnyElement,
246>;
247
248enum ReportEditorEvent {
249 Saved { auto_saved: bool },
250 EditorOpened,
251 Closed,
252}
253
254impl ReportEditorEvent {
255 pub fn event_type(&self) -> &'static str {
256 match self {
257 Self::Saved { .. } => "Editor Saved",
258 Self::EditorOpened => "Editor Opened",
259 Self::Closed => "Editor Closed",
260 }
261 }
262}
263
264struct InlineValueCache {
265 enabled: bool,
266 inlays: Vec<InlayId>,
267 refresh_task: Task<Option<()>>,
268}
269
270impl InlineValueCache {
271 fn new(enabled: bool) -> Self {
272 Self {
273 enabled,
274 inlays: Vec::new(),
275 refresh_task: Task::ready(None),
276 }
277 }
278}
279
280#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum InlayId {
282 EditPrediction(usize),
283 DebuggerValue(usize),
284 // LSP
285 Hint(usize),
286 Color(usize),
287}
288
289impl InlayId {
290 fn id(&self) -> usize {
291 match self {
292 Self::EditPrediction(id) => *id,
293 Self::DebuggerValue(id) => *id,
294 Self::Hint(id) => *id,
295 Self::Color(id) => *id,
296 }
297 }
298}
299
300pub enum ActiveDebugLine {}
301pub enum DebugStackFrameLine {}
302enum DocumentHighlightRead {}
303enum DocumentHighlightWrite {}
304enum InputComposition {}
305pub enum PendingInput {}
306enum SelectedTextHighlight {}
307
308pub enum ConflictsOuter {}
309pub enum ConflictsOurs {}
310pub enum ConflictsTheirs {}
311pub enum ConflictsOursMarker {}
312pub enum ConflictsTheirsMarker {}
313
314#[derive(Debug, Copy, Clone, PartialEq, Eq)]
315pub enum Navigated {
316 Yes,
317 No,
318}
319
320impl Navigated {
321 pub fn from_bool(yes: bool) -> Navigated {
322 if yes { Navigated::Yes } else { Navigated::No }
323 }
324}
325
326#[derive(Debug, Clone, PartialEq, Eq)]
327enum DisplayDiffHunk {
328 Folded {
329 display_row: DisplayRow,
330 },
331 Unfolded {
332 is_created_file: bool,
333 diff_base_byte_range: Range<usize>,
334 display_row_range: Range<DisplayRow>,
335 multi_buffer_range: Range<Anchor>,
336 status: DiffHunkStatus,
337 },
338}
339
340pub enum HideMouseCursorOrigin {
341 TypingAction,
342 MovementAction,
343}
344
345pub fn init_settings(cx: &mut App) {
346 EditorSettings::register(cx);
347}
348
349pub fn init(cx: &mut App) {
350 init_settings(cx);
351
352 cx.set_global(GlobalBlameRenderer(Arc::new(())));
353
354 workspace::register_project_item::<Editor>(cx);
355 workspace::FollowableViewRegistry::register::<Editor>(cx);
356 workspace::register_serializable_item::<Editor>(cx);
357
358 cx.observe_new(
359 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
360 workspace.register_action(Editor::new_file);
361 workspace.register_action(Editor::new_file_vertical);
362 workspace.register_action(Editor::new_file_horizontal);
363 workspace.register_action(Editor::cancel_language_server_work);
364 workspace.register_action(Editor::toggle_focus);
365 },
366 )
367 .detach();
368
369 cx.on_action(move |_: &workspace::NewFile, cx| {
370 let app_state = workspace::AppState::global(cx);
371 if let Some(app_state) = app_state.upgrade() {
372 workspace::open_new(
373 Default::default(),
374 app_state,
375 cx,
376 |workspace, window, cx| {
377 Editor::new_file(workspace, &Default::default(), window, cx)
378 },
379 )
380 .detach();
381 }
382 });
383 cx.on_action(move |_: &workspace::NewWindow, cx| {
384 let app_state = workspace::AppState::global(cx);
385 if let Some(app_state) = app_state.upgrade() {
386 workspace::open_new(
387 Default::default(),
388 app_state,
389 cx,
390 |workspace, window, cx| {
391 cx.activate(true);
392 Editor::new_file(workspace, &Default::default(), window, cx)
393 },
394 )
395 .detach();
396 }
397 });
398}
399
400pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
401 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
402}
403
404pub trait DiagnosticRenderer {
405 fn render_group(
406 &self,
407 diagnostic_group: Vec<DiagnosticEntry<Point>>,
408 buffer_id: BufferId,
409 snapshot: EditorSnapshot,
410 editor: WeakEntity<Editor>,
411 cx: &mut App,
412 ) -> Vec<BlockProperties<Anchor>>;
413
414 fn render_hover(
415 &self,
416 diagnostic_group: Vec<DiagnosticEntry<Point>>,
417 range: Range<Point>,
418 buffer_id: BufferId,
419 cx: &mut App,
420 ) -> Option<Entity<markdown::Markdown>>;
421
422 fn open_link(
423 &self,
424 editor: &mut Editor,
425 link: SharedString,
426 window: &mut Window,
427 cx: &mut Context<Editor>,
428 );
429}
430
431pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
432
433impl GlobalDiagnosticRenderer {
434 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
435 cx.try_global::<Self>().map(|g| g.0.clone())
436 }
437}
438
439impl gpui::Global for GlobalDiagnosticRenderer {}
440pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
441 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
442}
443
444pub struct SearchWithinRange;
445
446trait InvalidationRegion {
447 fn ranges(&self) -> &[Range<Anchor>];
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum SelectPhase {
452 Begin {
453 position: DisplayPoint,
454 add: bool,
455 click_count: usize,
456 },
457 BeginColumnar {
458 position: DisplayPoint,
459 reset: bool,
460 mode: ColumnarMode,
461 goal_column: u32,
462 },
463 Extend {
464 position: DisplayPoint,
465 click_count: usize,
466 },
467 Update {
468 position: DisplayPoint,
469 goal_column: u32,
470 scroll_delta: gpui::Point<f32>,
471 },
472 End,
473}
474
475#[derive(Clone, Debug, PartialEq)]
476pub enum ColumnarMode {
477 FromMouse,
478 FromSelection,
479}
480
481#[derive(Clone, Debug)]
482pub enum SelectMode {
483 Character,
484 Word(Range<Anchor>),
485 Line(Range<Anchor>),
486 All,
487}
488
489#[derive(Clone, PartialEq, Eq, Debug)]
490pub enum EditorMode {
491 SingleLine,
492 AutoHeight {
493 min_lines: usize,
494 max_lines: Option<usize>,
495 },
496 Full {
497 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
498 scale_ui_elements_with_buffer_font_size: bool,
499 /// When set to `true`, the editor will render a background for the active line.
500 show_active_line_background: bool,
501 /// When set to `true`, the editor's height will be determined by its content.
502 sized_by_content: bool,
503 },
504 Minimap {
505 parent: WeakEntity<Editor>,
506 },
507}
508
509impl EditorMode {
510 pub fn full() -> Self {
511 Self::Full {
512 scale_ui_elements_with_buffer_font_size: true,
513 show_active_line_background: true,
514 sized_by_content: false,
515 }
516 }
517
518 #[inline]
519 pub fn is_full(&self) -> bool {
520 matches!(self, Self::Full { .. })
521 }
522
523 #[inline]
524 pub fn is_single_line(&self) -> bool {
525 matches!(self, Self::SingleLine { .. })
526 }
527
528 #[inline]
529 fn is_minimap(&self) -> bool {
530 matches!(self, Self::Minimap { .. })
531 }
532}
533
534#[derive(Copy, Clone, Debug)]
535pub enum SoftWrap {
536 /// Prefer not to wrap at all.
537 ///
538 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
539 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
540 GitDiff,
541 /// Prefer a single line generally, unless an overly long line is encountered.
542 None,
543 /// Soft wrap lines that exceed the editor width.
544 EditorWidth,
545 /// Soft wrap lines at the preferred line length.
546 Column(u32),
547 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
548 Bounded(u32),
549}
550
551#[derive(Clone)]
552pub struct EditorStyle {
553 pub background: Hsla,
554 pub border: Hsla,
555 pub local_player: PlayerColor,
556 pub text: TextStyle,
557 pub scrollbar_width: Pixels,
558 pub syntax: Arc<SyntaxTheme>,
559 pub status: StatusColors,
560 pub inlay_hints_style: HighlightStyle,
561 pub edit_prediction_styles: EditPredictionStyles,
562 pub unnecessary_code_fade: f32,
563 pub show_underlines: bool,
564}
565
566impl Default for EditorStyle {
567 fn default() -> Self {
568 Self {
569 background: Hsla::default(),
570 border: Hsla::default(),
571 local_player: PlayerColor::default(),
572 text: TextStyle::default(),
573 scrollbar_width: Pixels::default(),
574 syntax: Default::default(),
575 // HACK: Status colors don't have a real default.
576 // We should look into removing the status colors from the editor
577 // style and retrieve them directly from the theme.
578 status: StatusColors::dark(),
579 inlay_hints_style: HighlightStyle::default(),
580 edit_prediction_styles: EditPredictionStyles {
581 insertion: HighlightStyle::default(),
582 whitespace: HighlightStyle::default(),
583 },
584 unnecessary_code_fade: Default::default(),
585 show_underlines: true,
586 }
587 }
588}
589
590pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
591 let show_background = language_settings::language_settings(None, None, cx)
592 .inlay_hints
593 .show_background;
594
595 let mut style = cx.theme().syntax().get("hint");
596
597 if style.color.is_none() {
598 style.color = Some(cx.theme().status().hint);
599 }
600
601 if !show_background {
602 style.background_color = None;
603 return style;
604 }
605
606 if style.background_color.is_none() {
607 style.background_color = Some(cx.theme().status().hint_background);
608 }
609
610 style
611}
612
613pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
614 EditPredictionStyles {
615 insertion: HighlightStyle {
616 color: Some(cx.theme().status().predictive),
617 ..HighlightStyle::default()
618 },
619 whitespace: HighlightStyle {
620 background_color: Some(cx.theme().status().created_background),
621 ..HighlightStyle::default()
622 },
623 }
624}
625
626type CompletionId = usize;
627
628pub(crate) enum EditDisplayMode {
629 TabAccept,
630 DiffPopover,
631 Inline,
632}
633
634enum EditPrediction {
635 Edit {
636 edits: Vec<(Range<Anchor>, String)>,
637 edit_preview: Option<EditPreview>,
638 display_mode: EditDisplayMode,
639 snapshot: BufferSnapshot,
640 },
641 Move {
642 target: Anchor,
643 snapshot: BufferSnapshot,
644 },
645}
646
647struct EditPredictionState {
648 inlay_ids: Vec<InlayId>,
649 completion: EditPrediction,
650 completion_id: Option<SharedString>,
651 invalidation_range: Range<Anchor>,
652}
653
654enum EditPredictionSettings {
655 Disabled,
656 Enabled {
657 show_in_menu: bool,
658 preview_requires_modifier: bool,
659 },
660}
661
662enum EditPredictionHighlight {}
663
664#[derive(Debug, Clone)]
665struct InlineDiagnostic {
666 message: SharedString,
667 group_id: usize,
668 is_primary: bool,
669 start: Point,
670 severity: lsp::DiagnosticSeverity,
671}
672
673pub enum MenuEditPredictionsPolicy {
674 Never,
675 ByProvider,
676}
677
678pub enum EditPredictionPreview {
679 /// Modifier is not pressed
680 Inactive { released_too_fast: bool },
681 /// Modifier pressed
682 Active {
683 since: Instant,
684 previous_scroll_position: Option<ScrollAnchor>,
685 },
686}
687
688impl EditPredictionPreview {
689 pub fn released_too_fast(&self) -> bool {
690 match self {
691 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
692 EditPredictionPreview::Active { .. } => false,
693 }
694 }
695
696 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
697 if let EditPredictionPreview::Active {
698 previous_scroll_position,
699 ..
700 } = self
701 {
702 *previous_scroll_position = scroll_position;
703 }
704 }
705}
706
707pub struct ContextMenuOptions {
708 pub min_entries_visible: usize,
709 pub max_entries_visible: usize,
710 pub placement: Option<ContextMenuPlacement>,
711}
712
713#[derive(Debug, Clone, PartialEq, Eq)]
714pub enum ContextMenuPlacement {
715 Above,
716 Below,
717}
718
719#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
720struct EditorActionId(usize);
721
722impl EditorActionId {
723 pub fn post_inc(&mut self) -> Self {
724 let answer = self.0;
725
726 *self = Self(answer + 1);
727
728 Self(answer)
729 }
730}
731
732// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
733// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
734
735type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
736type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
737
738#[derive(Default)]
739struct ScrollbarMarkerState {
740 scrollbar_size: Size<Pixels>,
741 dirty: bool,
742 markers: Arc<[PaintQuad]>,
743 pending_refresh: Option<Task<Result<()>>>,
744}
745
746impl ScrollbarMarkerState {
747 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
748 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
749 }
750}
751
752#[derive(Clone, Copy, PartialEq, Eq)]
753pub enum MinimapVisibility {
754 Disabled,
755 Enabled {
756 /// The configuration currently present in the users settings.
757 setting_configuration: bool,
758 /// Whether to override the currently set visibility from the users setting.
759 toggle_override: bool,
760 },
761}
762
763impl MinimapVisibility {
764 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
765 if mode.is_full() {
766 Self::Enabled {
767 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
768 toggle_override: false,
769 }
770 } else {
771 Self::Disabled
772 }
773 }
774
775 fn hidden(&self) -> Self {
776 match *self {
777 Self::Enabled {
778 setting_configuration,
779 ..
780 } => Self::Enabled {
781 setting_configuration,
782 toggle_override: setting_configuration,
783 },
784 Self::Disabled => Self::Disabled,
785 }
786 }
787
788 fn disabled(&self) -> bool {
789 matches!(*self, Self::Disabled)
790 }
791
792 fn settings_visibility(&self) -> bool {
793 match *self {
794 Self::Enabled {
795 setting_configuration,
796 ..
797 } => setting_configuration,
798 _ => false,
799 }
800 }
801
802 fn visible(&self) -> bool {
803 match *self {
804 Self::Enabled {
805 setting_configuration,
806 toggle_override,
807 } => setting_configuration ^ toggle_override,
808 _ => false,
809 }
810 }
811
812 fn toggle_visibility(&self) -> Self {
813 match *self {
814 Self::Enabled {
815 toggle_override,
816 setting_configuration,
817 } => Self::Enabled {
818 setting_configuration,
819 toggle_override: !toggle_override,
820 },
821 Self::Disabled => Self::Disabled,
822 }
823 }
824}
825
826#[derive(Clone, Debug)]
827struct RunnableTasks {
828 templates: Vec<(TaskSourceKind, TaskTemplate)>,
829 offset: multi_buffer::Anchor,
830 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
831 column: u32,
832 // Values of all named captures, including those starting with '_'
833 extra_variables: HashMap<String, String>,
834 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
835 context_range: Range<BufferOffset>,
836}
837
838impl RunnableTasks {
839 fn resolve<'a>(
840 &'a self,
841 cx: &'a task::TaskContext,
842 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
843 self.templates.iter().filter_map(|(kind, template)| {
844 template
845 .resolve_task(&kind.to_id_base(), cx)
846 .map(|task| (kind.clone(), task))
847 })
848 }
849}
850
851#[derive(Clone)]
852pub struct ResolvedTasks {
853 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
854 position: Anchor,
855}
856
857#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
858struct BufferOffset(usize);
859
860/// Addons allow storing per-editor state in other crates (e.g. Vim)
861pub trait Addon: 'static {
862 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
863
864 fn render_buffer_header_controls(
865 &self,
866 _: &ExcerptInfo,
867 _: &Window,
868 _: &App,
869 ) -> Option<AnyElement> {
870 None
871 }
872
873 fn to_any(&self) -> &dyn std::any::Any;
874
875 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
876 None
877 }
878}
879
880struct ChangeLocation {
881 current: Option<Vec<Anchor>>,
882 original: Vec<Anchor>,
883}
884impl ChangeLocation {
885 fn locations(&self) -> &[Anchor] {
886 self.current.as_ref().unwrap_or(&self.original)
887 }
888}
889
890/// A set of caret positions, registered when the editor was edited.
891pub struct ChangeList {
892 changes: Vec<ChangeLocation>,
893 /// Currently "selected" change.
894 position: Option<usize>,
895}
896
897impl ChangeList {
898 pub fn new() -> Self {
899 Self {
900 changes: Vec::new(),
901 position: None,
902 }
903 }
904
905 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
906 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
907 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
908 if self.changes.is_empty() {
909 return None;
910 }
911
912 let prev = self.position.unwrap_or(self.changes.len());
913 let next = if direction == Direction::Prev {
914 prev.saturating_sub(count)
915 } else {
916 (prev + count).min(self.changes.len() - 1)
917 };
918 self.position = Some(next);
919 self.changes.get(next).map(|change| change.locations())
920 }
921
922 /// Adds a new change to the list, resetting the change list position.
923 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
924 self.position.take();
925 if let Some(last) = self.changes.last_mut()
926 && group
927 {
928 last.current = Some(new_positions)
929 } else {
930 self.changes.push(ChangeLocation {
931 original: new_positions,
932 current: None,
933 });
934 }
935 }
936
937 pub fn last(&self) -> Option<&[Anchor]> {
938 self.changes.last().map(|change| change.locations())
939 }
940
941 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
942 self.changes.last().map(|change| change.original.as_slice())
943 }
944
945 pub fn invert_last_group(&mut self) {
946 if let Some(last) = self.changes.last_mut()
947 && let Some(current) = last.current.as_mut()
948 {
949 mem::swap(&mut last.original, current);
950 }
951 }
952}
953
954#[derive(Clone)]
955struct InlineBlamePopoverState {
956 scroll_handle: ScrollHandle,
957 commit_message: Option<ParsedCommitMessage>,
958 markdown: Entity<Markdown>,
959}
960
961struct InlineBlamePopover {
962 position: gpui::Point<Pixels>,
963 hide_task: Option<Task<()>>,
964 popover_bounds: Option<Bounds<Pixels>>,
965 popover_state: InlineBlamePopoverState,
966 keyboard_grace: bool,
967}
968
969enum SelectionDragState {
970 /// State when no drag related activity is detected.
971 None,
972 /// State when the mouse is down on a selection that is about to be dragged.
973 ReadyToDrag {
974 selection: Selection<Anchor>,
975 click_position: gpui::Point<Pixels>,
976 mouse_down_time: Instant,
977 },
978 /// State when the mouse is dragging the selection in the editor.
979 Dragging {
980 selection: Selection<Anchor>,
981 drop_cursor: Selection<Anchor>,
982 hide_drop_cursor: bool,
983 },
984}
985
986enum ColumnarSelectionState {
987 FromMouse {
988 selection_tail: Anchor,
989 display_point: Option<DisplayPoint>,
990 },
991 FromSelection {
992 selection_tail: Anchor,
993 },
994}
995
996/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
997/// a breakpoint on them.
998#[derive(Clone, Copy, Debug, PartialEq, Eq)]
999struct PhantomBreakpointIndicator {
1000 display_row: DisplayRow,
1001 /// There's a small debounce between hovering over the line and showing the indicator.
1002 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1003 is_active: bool,
1004 collides_with_existing_breakpoint: bool,
1005}
1006
1007/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1008///
1009/// See the [module level documentation](self) for more information.
1010pub struct Editor {
1011 focus_handle: FocusHandle,
1012 last_focused_descendant: Option<WeakFocusHandle>,
1013 /// The text buffer being edited
1014 buffer: Entity<MultiBuffer>,
1015 /// Map of how text in the buffer should be displayed.
1016 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1017 pub display_map: Entity<DisplayMap>,
1018 placeholder_display_map: Option<Entity<DisplayMap>>,
1019 pub selections: SelectionsCollection,
1020 pub scroll_manager: ScrollManager,
1021 /// When inline assist editors are linked, they all render cursors because
1022 /// typing enters text into each of them, even the ones that aren't focused.
1023 pub(crate) show_cursor_when_unfocused: bool,
1024 columnar_selection_state: Option<ColumnarSelectionState>,
1025 add_selections_state: Option<AddSelectionsState>,
1026 select_next_state: Option<SelectNextState>,
1027 select_prev_state: Option<SelectNextState>,
1028 selection_history: SelectionHistory,
1029 defer_selection_effects: bool,
1030 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1031 autoclose_regions: Vec<AutocloseRegion>,
1032 snippet_stack: InvalidationStack<SnippetState>,
1033 select_syntax_node_history: SelectSyntaxNodeHistory,
1034 ime_transaction: Option<TransactionId>,
1035 pub diagnostics_max_severity: DiagnosticSeverity,
1036 active_diagnostics: ActiveDiagnostic,
1037 show_inline_diagnostics: bool,
1038 inline_diagnostics_update: Task<()>,
1039 inline_diagnostics_enabled: bool,
1040 diagnostics_enabled: bool,
1041 word_completions_enabled: bool,
1042 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1043 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1044 hard_wrap: Option<usize>,
1045 project: Option<Entity<Project>>,
1046 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1047 completion_provider: Option<Rc<dyn CompletionProvider>>,
1048 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1049 blink_manager: Entity<BlinkManager>,
1050 show_cursor_names: bool,
1051 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1052 pub show_local_selections: bool,
1053 mode: EditorMode,
1054 show_breadcrumbs: bool,
1055 show_gutter: bool,
1056 show_scrollbars: ScrollbarAxes,
1057 minimap_visibility: MinimapVisibility,
1058 offset_content: bool,
1059 disable_expand_excerpt_buttons: bool,
1060 show_line_numbers: Option<bool>,
1061 use_relative_line_numbers: Option<bool>,
1062 show_git_diff_gutter: Option<bool>,
1063 show_code_actions: Option<bool>,
1064 show_runnables: Option<bool>,
1065 show_breakpoints: Option<bool>,
1066 show_wrap_guides: Option<bool>,
1067 show_indent_guides: Option<bool>,
1068 highlight_order: usize,
1069 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1070 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1071 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1072 scrollbar_marker_state: ScrollbarMarkerState,
1073 active_indent_guides_state: ActiveIndentGuidesState,
1074 nav_history: Option<ItemNavHistory>,
1075 context_menu: RefCell<Option<CodeContextMenu>>,
1076 context_menu_options: Option<ContextMenuOptions>,
1077 mouse_context_menu: Option<MouseContextMenu>,
1078 completion_tasks: Vec<(CompletionId, Task<()>)>,
1079 inline_blame_popover: Option<InlineBlamePopover>,
1080 inline_blame_popover_show_task: Option<Task<()>>,
1081 signature_help_state: SignatureHelpState,
1082 auto_signature_help: Option<bool>,
1083 find_all_references_task_sources: Vec<Anchor>,
1084 next_completion_id: CompletionId,
1085 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1086 code_actions_task: Option<Task<Result<()>>>,
1087 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1088 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1089 document_highlights_task: Option<Task<()>>,
1090 linked_editing_range_task: Option<Task<Option<()>>>,
1091 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1092 pending_rename: Option<RenameState>,
1093 searchable: bool,
1094 cursor_shape: CursorShape,
1095 current_line_highlight: Option<CurrentLineHighlight>,
1096 collapse_matches: bool,
1097 autoindent_mode: Option<AutoindentMode>,
1098 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1099 input_enabled: bool,
1100 use_modal_editing: bool,
1101 read_only: bool,
1102 leader_id: Option<CollaboratorId>,
1103 remote_id: Option<ViewId>,
1104 pub hover_state: HoverState,
1105 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1106 gutter_hovered: bool,
1107 hovered_link_state: Option<HoveredLinkState>,
1108 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1109 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1110 active_edit_prediction: Option<EditPredictionState>,
1111 /// Used to prevent flickering as the user types while the menu is open
1112 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1113 edit_prediction_settings: EditPredictionSettings,
1114 edit_predictions_hidden_for_vim_mode: bool,
1115 show_edit_predictions_override: Option<bool>,
1116 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1117 edit_prediction_preview: EditPredictionPreview,
1118 edit_prediction_indent_conflict: bool,
1119 edit_prediction_requires_modifier_in_indent_conflict: bool,
1120 inlay_hint_cache: InlayHintCache,
1121 next_inlay_id: usize,
1122 _subscriptions: Vec<Subscription>,
1123 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1124 gutter_dimensions: GutterDimensions,
1125 style: Option<EditorStyle>,
1126 text_style_refinement: Option<TextStyleRefinement>,
1127 next_editor_action_id: EditorActionId,
1128 editor_actions: Rc<
1129 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1130 >,
1131 use_autoclose: bool,
1132 use_auto_surround: bool,
1133 auto_replace_emoji_shortcode: bool,
1134 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1135 show_git_blame_gutter: bool,
1136 show_git_blame_inline: bool,
1137 show_git_blame_inline_delay_task: Option<Task<()>>,
1138 git_blame_inline_enabled: bool,
1139 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1140 serialize_dirty_buffers: bool,
1141 show_selection_menu: Option<bool>,
1142 blame: Option<Entity<GitBlame>>,
1143 blame_subscription: Option<Subscription>,
1144 custom_context_menu: Option<
1145 Box<
1146 dyn 'static
1147 + Fn(
1148 &mut Self,
1149 DisplayPoint,
1150 &mut Window,
1151 &mut Context<Self>,
1152 ) -> Option<Entity<ui::ContextMenu>>,
1153 >,
1154 >,
1155 last_bounds: Option<Bounds<Pixels>>,
1156 last_position_map: Option<Rc<PositionMap>>,
1157 expect_bounds_change: Option<Bounds<Pixels>>,
1158 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1159 tasks_update_task: Option<Task<()>>,
1160 breakpoint_store: Option<Entity<BreakpointStore>>,
1161 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1162 hovered_diff_hunk_row: Option<DisplayRow>,
1163 pull_diagnostics_task: Task<()>,
1164 in_project_search: bool,
1165 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1166 breadcrumb_header: Option<String>,
1167 focused_block: Option<FocusedBlock>,
1168 next_scroll_position: NextScrollCursorCenterTopBottom,
1169 addons: HashMap<TypeId, Box<dyn Addon>>,
1170 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1171 load_diff_task: Option<Shared<Task<()>>>,
1172 /// Whether we are temporarily displaying a diff other than git's
1173 temporary_diff_override: bool,
1174 selection_mark_mode: bool,
1175 toggle_fold_multiple_buffers: Task<()>,
1176 _scroll_cursor_center_top_bottom_task: Task<()>,
1177 serialize_selections: Task<()>,
1178 serialize_folds: Task<()>,
1179 mouse_cursor_hidden: bool,
1180 minimap: Option<Entity<Self>>,
1181 hide_mouse_mode: HideMouseMode,
1182 pub change_list: ChangeList,
1183 inline_value_cache: InlineValueCache,
1184 selection_drag_state: SelectionDragState,
1185 next_color_inlay_id: usize,
1186 colors: Option<LspColorData>,
1187 folding_newlines: Task<()>,
1188 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1189}
1190
1191#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1192enum NextScrollCursorCenterTopBottom {
1193 #[default]
1194 Center,
1195 Top,
1196 Bottom,
1197}
1198
1199impl NextScrollCursorCenterTopBottom {
1200 fn next(&self) -> Self {
1201 match self {
1202 Self::Center => Self::Top,
1203 Self::Top => Self::Bottom,
1204 Self::Bottom => Self::Center,
1205 }
1206 }
1207}
1208
1209#[derive(Clone)]
1210pub struct EditorSnapshot {
1211 pub mode: EditorMode,
1212 show_gutter: bool,
1213 show_line_numbers: Option<bool>,
1214 show_git_diff_gutter: Option<bool>,
1215 show_code_actions: Option<bool>,
1216 show_runnables: Option<bool>,
1217 show_breakpoints: Option<bool>,
1218 git_blame_gutter_max_author_length: Option<usize>,
1219 pub display_snapshot: DisplaySnapshot,
1220 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1221 is_focused: bool,
1222 scroll_anchor: ScrollAnchor,
1223 ongoing_scroll: OngoingScroll,
1224 current_line_highlight: CurrentLineHighlight,
1225 gutter_hovered: bool,
1226}
1227
1228#[derive(Default, Debug, Clone, Copy)]
1229pub struct GutterDimensions {
1230 pub left_padding: Pixels,
1231 pub right_padding: Pixels,
1232 pub width: Pixels,
1233 pub margin: Pixels,
1234 pub git_blame_entries_width: Option<Pixels>,
1235}
1236
1237impl GutterDimensions {
1238 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1239 Self {
1240 margin: Self::default_gutter_margin(font_id, font_size, cx),
1241 ..Default::default()
1242 }
1243 }
1244
1245 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1246 -cx.text_system().descent(font_id, font_size)
1247 }
1248 /// The full width of the space taken up by the gutter.
1249 pub fn full_width(&self) -> Pixels {
1250 self.margin + self.width
1251 }
1252
1253 /// The width of the space reserved for the fold indicators,
1254 /// use alongside 'justify_end' and `gutter_width` to
1255 /// right align content with the line numbers
1256 pub fn fold_area_width(&self) -> Pixels {
1257 self.margin + self.right_padding
1258 }
1259}
1260
1261struct CharacterDimensions {
1262 em_width: Pixels,
1263 em_advance: Pixels,
1264 line_height: Pixels,
1265}
1266
1267#[derive(Debug)]
1268pub struct RemoteSelection {
1269 pub replica_id: ReplicaId,
1270 pub selection: Selection<Anchor>,
1271 pub cursor_shape: CursorShape,
1272 pub collaborator_id: CollaboratorId,
1273 pub line_mode: bool,
1274 pub user_name: Option<SharedString>,
1275 pub color: PlayerColor,
1276}
1277
1278#[derive(Clone, Debug)]
1279struct SelectionHistoryEntry {
1280 selections: Arc<[Selection<Anchor>]>,
1281 select_next_state: Option<SelectNextState>,
1282 select_prev_state: Option<SelectNextState>,
1283 add_selections_state: Option<AddSelectionsState>,
1284}
1285
1286#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1287enum SelectionHistoryMode {
1288 Normal,
1289 Undoing,
1290 Redoing,
1291 Skipping,
1292}
1293
1294#[derive(Clone, PartialEq, Eq, Hash)]
1295struct HoveredCursor {
1296 replica_id: u16,
1297 selection_id: usize,
1298}
1299
1300impl Default for SelectionHistoryMode {
1301 fn default() -> Self {
1302 Self::Normal
1303 }
1304}
1305
1306#[derive(Debug)]
1307/// SelectionEffects controls the side-effects of updating the selection.
1308///
1309/// The default behaviour does "what you mostly want":
1310/// - it pushes to the nav history if the cursor moved by >10 lines
1311/// - it re-triggers completion requests
1312/// - it scrolls to fit
1313///
1314/// You might want to modify these behaviours. For example when doing a "jump"
1315/// like go to definition, we always want to add to nav history; but when scrolling
1316/// in vim mode we never do.
1317///
1318/// Similarly, you might want to disable scrolling if you don't want the viewport to
1319/// move.
1320#[derive(Clone)]
1321pub struct SelectionEffects {
1322 nav_history: Option<bool>,
1323 completions: bool,
1324 scroll: Option<Autoscroll>,
1325}
1326
1327impl Default for SelectionEffects {
1328 fn default() -> Self {
1329 Self {
1330 nav_history: None,
1331 completions: true,
1332 scroll: Some(Autoscroll::fit()),
1333 }
1334 }
1335}
1336impl SelectionEffects {
1337 pub fn scroll(scroll: Autoscroll) -> Self {
1338 Self {
1339 scroll: Some(scroll),
1340 ..Default::default()
1341 }
1342 }
1343
1344 pub fn no_scroll() -> Self {
1345 Self {
1346 scroll: None,
1347 ..Default::default()
1348 }
1349 }
1350
1351 pub fn completions(self, completions: bool) -> Self {
1352 Self {
1353 completions,
1354 ..self
1355 }
1356 }
1357
1358 pub fn nav_history(self, nav_history: bool) -> Self {
1359 Self {
1360 nav_history: Some(nav_history),
1361 ..self
1362 }
1363 }
1364}
1365
1366struct DeferredSelectionEffectsState {
1367 changed: bool,
1368 effects: SelectionEffects,
1369 old_cursor_position: Anchor,
1370 history_entry: SelectionHistoryEntry,
1371}
1372
1373#[derive(Default)]
1374struct SelectionHistory {
1375 #[allow(clippy::type_complexity)]
1376 selections_by_transaction:
1377 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1378 mode: SelectionHistoryMode,
1379 undo_stack: VecDeque<SelectionHistoryEntry>,
1380 redo_stack: VecDeque<SelectionHistoryEntry>,
1381}
1382
1383impl SelectionHistory {
1384 #[track_caller]
1385 fn insert_transaction(
1386 &mut self,
1387 transaction_id: TransactionId,
1388 selections: Arc<[Selection<Anchor>]>,
1389 ) {
1390 if selections.is_empty() {
1391 log::error!(
1392 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1393 std::panic::Location::caller()
1394 );
1395 return;
1396 }
1397 self.selections_by_transaction
1398 .insert(transaction_id, (selections, None));
1399 }
1400
1401 #[allow(clippy::type_complexity)]
1402 fn transaction(
1403 &self,
1404 transaction_id: TransactionId,
1405 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1406 self.selections_by_transaction.get(&transaction_id)
1407 }
1408
1409 #[allow(clippy::type_complexity)]
1410 fn transaction_mut(
1411 &mut self,
1412 transaction_id: TransactionId,
1413 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1414 self.selections_by_transaction.get_mut(&transaction_id)
1415 }
1416
1417 fn push(&mut self, entry: SelectionHistoryEntry) {
1418 if !entry.selections.is_empty() {
1419 match self.mode {
1420 SelectionHistoryMode::Normal => {
1421 self.push_undo(entry);
1422 self.redo_stack.clear();
1423 }
1424 SelectionHistoryMode::Undoing => self.push_redo(entry),
1425 SelectionHistoryMode::Redoing => self.push_undo(entry),
1426 SelectionHistoryMode::Skipping => {}
1427 }
1428 }
1429 }
1430
1431 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1432 if self
1433 .undo_stack
1434 .back()
1435 .is_none_or(|e| e.selections != entry.selections)
1436 {
1437 self.undo_stack.push_back(entry);
1438 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1439 self.undo_stack.pop_front();
1440 }
1441 }
1442 }
1443
1444 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1445 if self
1446 .redo_stack
1447 .back()
1448 .is_none_or(|e| e.selections != entry.selections)
1449 {
1450 self.redo_stack.push_back(entry);
1451 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1452 self.redo_stack.pop_front();
1453 }
1454 }
1455 }
1456}
1457
1458#[derive(Clone, Copy)]
1459pub struct RowHighlightOptions {
1460 pub autoscroll: bool,
1461 pub include_gutter: bool,
1462}
1463
1464impl Default for RowHighlightOptions {
1465 fn default() -> Self {
1466 Self {
1467 autoscroll: Default::default(),
1468 include_gutter: true,
1469 }
1470 }
1471}
1472
1473struct RowHighlight {
1474 index: usize,
1475 range: Range<Anchor>,
1476 color: Hsla,
1477 options: RowHighlightOptions,
1478 type_id: TypeId,
1479}
1480
1481#[derive(Clone, Debug)]
1482struct AddSelectionsState {
1483 groups: Vec<AddSelectionsGroup>,
1484}
1485
1486#[derive(Clone, Debug)]
1487struct AddSelectionsGroup {
1488 above: bool,
1489 stack: Vec<usize>,
1490}
1491
1492#[derive(Clone)]
1493struct SelectNextState {
1494 query: AhoCorasick,
1495 wordwise: bool,
1496 done: bool,
1497}
1498
1499impl std::fmt::Debug for SelectNextState {
1500 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1501 f.debug_struct(std::any::type_name::<Self>())
1502 .field("wordwise", &self.wordwise)
1503 .field("done", &self.done)
1504 .finish()
1505 }
1506}
1507
1508#[derive(Debug)]
1509struct AutocloseRegion {
1510 selection_id: usize,
1511 range: Range<Anchor>,
1512 pair: BracketPair,
1513}
1514
1515#[derive(Debug)]
1516struct SnippetState {
1517 ranges: Vec<Vec<Range<Anchor>>>,
1518 active_index: usize,
1519 choices: Vec<Option<Vec<String>>>,
1520}
1521
1522#[doc(hidden)]
1523pub struct RenameState {
1524 pub range: Range<Anchor>,
1525 pub old_name: Arc<str>,
1526 pub editor: Entity<Editor>,
1527 block_id: CustomBlockId,
1528}
1529
1530struct InvalidationStack<T>(Vec<T>);
1531
1532struct RegisteredEditPredictionProvider {
1533 provider: Arc<dyn EditPredictionProviderHandle>,
1534 _subscription: Subscription,
1535}
1536
1537#[derive(Debug, PartialEq, Eq)]
1538pub struct ActiveDiagnosticGroup {
1539 pub active_range: Range<Anchor>,
1540 pub active_message: String,
1541 pub group_id: usize,
1542 pub blocks: HashSet<CustomBlockId>,
1543}
1544
1545#[derive(Debug, PartialEq, Eq)]
1546
1547pub(crate) enum ActiveDiagnostic {
1548 None,
1549 All,
1550 Group(ActiveDiagnosticGroup),
1551}
1552
1553#[derive(Serialize, Deserialize, Clone, Debug)]
1554pub struct ClipboardSelection {
1555 /// The number of bytes in this selection.
1556 pub len: usize,
1557 /// Whether this was a full-line selection.
1558 pub is_entire_line: bool,
1559 /// The indentation of the first line when this content was originally copied.
1560 pub first_line_indent: u32,
1561}
1562
1563// selections, scroll behavior, was newest selection reversed
1564type SelectSyntaxNodeHistoryState = (
1565 Box<[Selection<usize>]>,
1566 SelectSyntaxNodeScrollBehavior,
1567 bool,
1568);
1569
1570#[derive(Default)]
1571struct SelectSyntaxNodeHistory {
1572 stack: Vec<SelectSyntaxNodeHistoryState>,
1573 // disable temporarily to allow changing selections without losing the stack
1574 pub disable_clearing: bool,
1575}
1576
1577impl SelectSyntaxNodeHistory {
1578 pub fn try_clear(&mut self) {
1579 if !self.disable_clearing {
1580 self.stack.clear();
1581 }
1582 }
1583
1584 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1585 self.stack.push(selection);
1586 }
1587
1588 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1589 self.stack.pop()
1590 }
1591}
1592
1593enum SelectSyntaxNodeScrollBehavior {
1594 CursorTop,
1595 FitSelection,
1596 CursorBottom,
1597}
1598
1599#[derive(Debug)]
1600pub(crate) struct NavigationData {
1601 cursor_anchor: Anchor,
1602 cursor_position: Point,
1603 scroll_anchor: ScrollAnchor,
1604 scroll_top_row: u32,
1605}
1606
1607#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1608pub enum GotoDefinitionKind {
1609 Symbol,
1610 Declaration,
1611 Type,
1612 Implementation,
1613}
1614
1615#[derive(Debug, Clone)]
1616enum InlayHintRefreshReason {
1617 ModifiersChanged(bool),
1618 Toggle(bool),
1619 SettingsChange(InlayHintSettings),
1620 NewLinesShown,
1621 BufferEdited(HashSet<Arc<Language>>),
1622 RefreshRequested,
1623 ExcerptsRemoved(Vec<ExcerptId>),
1624}
1625
1626impl InlayHintRefreshReason {
1627 fn description(&self) -> &'static str {
1628 match self {
1629 Self::ModifiersChanged(_) => "modifiers changed",
1630 Self::Toggle(_) => "toggle",
1631 Self::SettingsChange(_) => "settings change",
1632 Self::NewLinesShown => "new lines shown",
1633 Self::BufferEdited(_) => "buffer edited",
1634 Self::RefreshRequested => "refresh requested",
1635 Self::ExcerptsRemoved(_) => "excerpts removed",
1636 }
1637 }
1638}
1639
1640pub enum FormatTarget {
1641 Buffers(HashSet<Entity<Buffer>>),
1642 Ranges(Vec<Range<MultiBufferPoint>>),
1643}
1644
1645pub(crate) struct FocusedBlock {
1646 id: BlockId,
1647 focus_handle: WeakFocusHandle,
1648}
1649
1650#[derive(Clone)]
1651enum JumpData {
1652 MultiBufferRow {
1653 row: MultiBufferRow,
1654 line_offset_from_top: u32,
1655 },
1656 MultiBufferPoint {
1657 excerpt_id: ExcerptId,
1658 position: Point,
1659 anchor: text::Anchor,
1660 line_offset_from_top: u32,
1661 },
1662}
1663
1664pub enum MultibufferSelectionMode {
1665 First,
1666 All,
1667}
1668
1669#[derive(Clone, Copy, Debug, Default)]
1670pub struct RewrapOptions {
1671 pub override_language_settings: bool,
1672 pub preserve_existing_whitespace: bool,
1673}
1674
1675impl Editor {
1676 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1677 let buffer = cx.new(|cx| Buffer::local("", cx));
1678 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1679 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1680 }
1681
1682 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1683 let buffer = cx.new(|cx| Buffer::local("", cx));
1684 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1685 Self::new(EditorMode::full(), buffer, None, window, cx)
1686 }
1687
1688 pub fn auto_height(
1689 min_lines: usize,
1690 max_lines: usize,
1691 window: &mut Window,
1692 cx: &mut Context<Self>,
1693 ) -> Self {
1694 let buffer = cx.new(|cx| Buffer::local("", cx));
1695 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1696 Self::new(
1697 EditorMode::AutoHeight {
1698 min_lines,
1699 max_lines: Some(max_lines),
1700 },
1701 buffer,
1702 None,
1703 window,
1704 cx,
1705 )
1706 }
1707
1708 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1709 /// The editor grows as tall as needed to fit its content.
1710 pub fn auto_height_unbounded(
1711 min_lines: usize,
1712 window: &mut Window,
1713 cx: &mut Context<Self>,
1714 ) -> Self {
1715 let buffer = cx.new(|cx| Buffer::local("", cx));
1716 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1717 Self::new(
1718 EditorMode::AutoHeight {
1719 min_lines,
1720 max_lines: None,
1721 },
1722 buffer,
1723 None,
1724 window,
1725 cx,
1726 )
1727 }
1728
1729 pub fn for_buffer(
1730 buffer: Entity<Buffer>,
1731 project: Option<Entity<Project>>,
1732 window: &mut Window,
1733 cx: &mut Context<Self>,
1734 ) -> Self {
1735 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1736 Self::new(EditorMode::full(), buffer, project, window, cx)
1737 }
1738
1739 pub fn for_multibuffer(
1740 buffer: Entity<MultiBuffer>,
1741 project: Option<Entity<Project>>,
1742 window: &mut Window,
1743 cx: &mut Context<Self>,
1744 ) -> Self {
1745 Self::new(EditorMode::full(), buffer, project, window, cx)
1746 }
1747
1748 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1749 let mut clone = Self::new(
1750 self.mode.clone(),
1751 self.buffer.clone(),
1752 self.project.clone(),
1753 window,
1754 cx,
1755 );
1756 self.display_map.update(cx, |display_map, cx| {
1757 let snapshot = display_map.snapshot(cx);
1758 clone.display_map.update(cx, |display_map, cx| {
1759 display_map.set_state(&snapshot, cx);
1760 });
1761 });
1762 clone.folds_did_change(cx);
1763 clone.selections.clone_state(&self.selections);
1764 clone.scroll_manager.clone_state(&self.scroll_manager);
1765 clone.searchable = self.searchable;
1766 clone.read_only = self.read_only;
1767 clone
1768 }
1769
1770 pub fn new(
1771 mode: EditorMode,
1772 buffer: Entity<MultiBuffer>,
1773 project: Option<Entity<Project>>,
1774 window: &mut Window,
1775 cx: &mut Context<Self>,
1776 ) -> Self {
1777 Editor::new_internal(mode, buffer, project, None, window, cx)
1778 }
1779
1780 fn new_internal(
1781 mode: EditorMode,
1782 buffer: Entity<MultiBuffer>,
1783 project: Option<Entity<Project>>,
1784 display_map: Option<Entity<DisplayMap>>,
1785 window: &mut Window,
1786 cx: &mut Context<Self>,
1787 ) -> Self {
1788 debug_assert!(
1789 display_map.is_none() || mode.is_minimap(),
1790 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1791 );
1792
1793 let full_mode = mode.is_full();
1794 let is_minimap = mode.is_minimap();
1795 let diagnostics_max_severity = if full_mode {
1796 EditorSettings::get_global(cx)
1797 .diagnostics_max_severity
1798 .unwrap_or(DiagnosticSeverity::Hint)
1799 } else {
1800 DiagnosticSeverity::Off
1801 };
1802 let style = window.text_style();
1803 let font_size = style.font_size.to_pixels(window.rem_size());
1804 let editor = cx.entity().downgrade();
1805 let fold_placeholder = FoldPlaceholder {
1806 constrain_width: false,
1807 render: Arc::new(move |fold_id, fold_range, cx| {
1808 let editor = editor.clone();
1809 div()
1810 .id(fold_id)
1811 .bg(cx.theme().colors().ghost_element_background)
1812 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1813 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1814 .rounded_xs()
1815 .size_full()
1816 .cursor_pointer()
1817 .child("⋯")
1818 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1819 .on_click(move |_, _window, cx| {
1820 editor
1821 .update(cx, |editor, cx| {
1822 editor.unfold_ranges(
1823 &[fold_range.start..fold_range.end],
1824 true,
1825 false,
1826 cx,
1827 );
1828 cx.stop_propagation();
1829 })
1830 .ok();
1831 })
1832 .into_any()
1833 }),
1834 merge_adjacent: true,
1835 ..FoldPlaceholder::default()
1836 };
1837 let display_map = display_map.unwrap_or_else(|| {
1838 cx.new(|cx| {
1839 DisplayMap::new(
1840 buffer.clone(),
1841 style.font(),
1842 font_size,
1843 None,
1844 FILE_HEADER_HEIGHT,
1845 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1846 fold_placeholder,
1847 diagnostics_max_severity,
1848 cx,
1849 )
1850 })
1851 });
1852
1853 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1854
1855 let blink_manager = cx.new(|cx| {
1856 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1857 if is_minimap {
1858 blink_manager.disable(cx);
1859 }
1860 blink_manager
1861 });
1862
1863 let soft_wrap_mode_override =
1864 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1865
1866 let mut project_subscriptions = Vec::new();
1867 if full_mode && let Some(project) = project.as_ref() {
1868 project_subscriptions.push(cx.subscribe_in(
1869 project,
1870 window,
1871 |editor, _, event, window, cx| match event {
1872 project::Event::RefreshCodeLens => {
1873 // we always query lens with actions, without storing them, always refreshing them
1874 }
1875 project::Event::RefreshInlayHints => {
1876 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1877 }
1878 project::Event::LanguageServerAdded(..)
1879 | project::Event::LanguageServerRemoved(..) => {
1880 if editor.tasks_update_task.is_none() {
1881 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1882 }
1883 }
1884 project::Event::SnippetEdit(id, snippet_edits) => {
1885 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1886 let focus_handle = editor.focus_handle(cx);
1887 if focus_handle.is_focused(window) {
1888 let snapshot = buffer.read(cx).snapshot();
1889 for (range, snippet) in snippet_edits {
1890 let editor_range =
1891 language::range_from_lsp(*range).to_offset(&snapshot);
1892 editor
1893 .insert_snippet(
1894 &[editor_range],
1895 snippet.clone(),
1896 window,
1897 cx,
1898 )
1899 .ok();
1900 }
1901 }
1902 }
1903 }
1904 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1905 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1906 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1907 }
1908 }
1909
1910 project::Event::EntryRenamed(transaction) => {
1911 let Some(workspace) = editor.workspace() else {
1912 return;
1913 };
1914 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1915 else {
1916 return;
1917 };
1918 if active_editor.entity_id() == cx.entity_id() {
1919 let edited_buffers_already_open = {
1920 let other_editors: Vec<Entity<Editor>> = workspace
1921 .read(cx)
1922 .panes()
1923 .iter()
1924 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1925 .filter(|editor| editor.entity_id() != cx.entity_id())
1926 .collect();
1927
1928 transaction.0.keys().all(|buffer| {
1929 other_editors.iter().any(|editor| {
1930 let multi_buffer = editor.read(cx).buffer();
1931 multi_buffer.read(cx).is_singleton()
1932 && multi_buffer.read(cx).as_singleton().map_or(
1933 false,
1934 |singleton| {
1935 singleton.entity_id() == buffer.entity_id()
1936 },
1937 )
1938 })
1939 })
1940 };
1941
1942 if !edited_buffers_already_open {
1943 let workspace = workspace.downgrade();
1944 let transaction = transaction.clone();
1945 cx.defer_in(window, move |_, window, cx| {
1946 cx.spawn_in(window, async move |editor, cx| {
1947 Self::open_project_transaction(
1948 &editor,
1949 workspace,
1950 transaction,
1951 "Rename".to_string(),
1952 cx,
1953 )
1954 .await
1955 .ok()
1956 })
1957 .detach();
1958 });
1959 }
1960 }
1961 }
1962
1963 _ => {}
1964 },
1965 ));
1966 if let Some(task_inventory) = project
1967 .read(cx)
1968 .task_store()
1969 .read(cx)
1970 .task_inventory()
1971 .cloned()
1972 {
1973 project_subscriptions.push(cx.observe_in(
1974 &task_inventory,
1975 window,
1976 |editor, _, window, cx| {
1977 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1978 },
1979 ));
1980 };
1981
1982 project_subscriptions.push(cx.subscribe_in(
1983 &project.read(cx).breakpoint_store(),
1984 window,
1985 |editor, _, event, window, cx| match event {
1986 BreakpointStoreEvent::ClearDebugLines => {
1987 editor.clear_row_highlights::<ActiveDebugLine>();
1988 editor.refresh_inline_values(cx);
1989 }
1990 BreakpointStoreEvent::SetDebugLine => {
1991 if editor.go_to_active_debug_line(window, cx) {
1992 cx.stop_propagation();
1993 }
1994
1995 editor.refresh_inline_values(cx);
1996 }
1997 _ => {}
1998 },
1999 ));
2000 let git_store = project.read(cx).git_store().clone();
2001 let project = project.clone();
2002 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2003 if let GitStoreEvent::RepositoryUpdated(
2004 _,
2005 RepositoryEvent::Updated {
2006 new_instance: true, ..
2007 },
2008 _,
2009 ) = event
2010 {
2011 this.load_diff_task = Some(
2012 update_uncommitted_diff_for_buffer(
2013 cx.entity(),
2014 &project,
2015 this.buffer.read(cx).all_buffers(),
2016 this.buffer.clone(),
2017 cx,
2018 )
2019 .shared(),
2020 );
2021 }
2022 }));
2023 }
2024
2025 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2026
2027 let inlay_hint_settings =
2028 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2029 let focus_handle = cx.focus_handle();
2030 if !is_minimap {
2031 cx.on_focus(&focus_handle, window, Self::handle_focus)
2032 .detach();
2033 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2034 .detach();
2035 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2036 .detach();
2037 cx.on_blur(&focus_handle, window, Self::handle_blur)
2038 .detach();
2039 cx.observe_pending_input(window, Self::observe_pending_input)
2040 .detach();
2041 }
2042
2043 let show_indent_guides =
2044 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2045 Some(false)
2046 } else {
2047 None
2048 };
2049
2050 let breakpoint_store = match (&mode, project.as_ref()) {
2051 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2052 _ => None,
2053 };
2054
2055 let mut code_action_providers = Vec::new();
2056 let mut load_uncommitted_diff = None;
2057 if let Some(project) = project.clone() {
2058 load_uncommitted_diff = Some(
2059 update_uncommitted_diff_for_buffer(
2060 cx.entity(),
2061 &project,
2062 buffer.read(cx).all_buffers(),
2063 buffer.clone(),
2064 cx,
2065 )
2066 .shared(),
2067 );
2068 code_action_providers.push(Rc::new(project) as Rc<_>);
2069 }
2070
2071 let mut editor = Self {
2072 focus_handle,
2073 show_cursor_when_unfocused: false,
2074 last_focused_descendant: None,
2075 buffer: buffer.clone(),
2076 display_map: display_map.clone(),
2077 placeholder_display_map: None,
2078 selections,
2079 scroll_manager: ScrollManager::new(cx),
2080 columnar_selection_state: None,
2081 add_selections_state: None,
2082 select_next_state: None,
2083 select_prev_state: None,
2084 selection_history: SelectionHistory::default(),
2085 defer_selection_effects: false,
2086 deferred_selection_effects_state: None,
2087 autoclose_regions: Vec::new(),
2088 snippet_stack: InvalidationStack::default(),
2089 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2090 ime_transaction: None,
2091 active_diagnostics: ActiveDiagnostic::None,
2092 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2093 inline_diagnostics_update: Task::ready(()),
2094 inline_diagnostics: Vec::new(),
2095 soft_wrap_mode_override,
2096 diagnostics_max_severity,
2097 hard_wrap: None,
2098 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2099 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2100 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2101 project,
2102 blink_manager: blink_manager.clone(),
2103 show_local_selections: true,
2104 show_scrollbars: ScrollbarAxes {
2105 horizontal: full_mode,
2106 vertical: full_mode,
2107 },
2108 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2109 offset_content: !matches!(mode, EditorMode::SingleLine),
2110 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2111 show_gutter: full_mode,
2112 show_line_numbers: (!full_mode).then_some(false),
2113 use_relative_line_numbers: None,
2114 disable_expand_excerpt_buttons: !full_mode,
2115 show_git_diff_gutter: None,
2116 show_code_actions: None,
2117 show_runnables: None,
2118 show_breakpoints: None,
2119 show_wrap_guides: None,
2120 show_indent_guides,
2121 highlight_order: 0,
2122 highlighted_rows: HashMap::default(),
2123 background_highlights: HashMap::default(),
2124 gutter_highlights: HashMap::default(),
2125 scrollbar_marker_state: ScrollbarMarkerState::default(),
2126 active_indent_guides_state: ActiveIndentGuidesState::default(),
2127 nav_history: None,
2128 context_menu: RefCell::new(None),
2129 context_menu_options: None,
2130 mouse_context_menu: None,
2131 completion_tasks: Vec::new(),
2132 inline_blame_popover: None,
2133 inline_blame_popover_show_task: None,
2134 signature_help_state: SignatureHelpState::default(),
2135 auto_signature_help: None,
2136 find_all_references_task_sources: Vec::new(),
2137 next_completion_id: 0,
2138 next_inlay_id: 0,
2139 code_action_providers,
2140 available_code_actions: None,
2141 code_actions_task: None,
2142 quick_selection_highlight_task: None,
2143 debounced_selection_highlight_task: None,
2144 document_highlights_task: None,
2145 linked_editing_range_task: None,
2146 pending_rename: None,
2147 searchable: !is_minimap,
2148 cursor_shape: EditorSettings::get_global(cx)
2149 .cursor_shape
2150 .unwrap_or_default(),
2151 current_line_highlight: None,
2152 autoindent_mode: Some(AutoindentMode::EachLine),
2153 collapse_matches: false,
2154 workspace: None,
2155 input_enabled: !is_minimap,
2156 use_modal_editing: full_mode,
2157 read_only: is_minimap,
2158 use_autoclose: true,
2159 use_auto_surround: true,
2160 auto_replace_emoji_shortcode: false,
2161 jsx_tag_auto_close_enabled_in_any_buffer: false,
2162 leader_id: None,
2163 remote_id: None,
2164 hover_state: HoverState::default(),
2165 pending_mouse_down: None,
2166 hovered_link_state: None,
2167 edit_prediction_provider: None,
2168 active_edit_prediction: None,
2169 stale_edit_prediction_in_menu: None,
2170 edit_prediction_preview: EditPredictionPreview::Inactive {
2171 released_too_fast: false,
2172 },
2173 inline_diagnostics_enabled: full_mode,
2174 diagnostics_enabled: full_mode,
2175 word_completions_enabled: full_mode,
2176 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2177 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2178 gutter_hovered: false,
2179 pixel_position_of_newest_cursor: None,
2180 last_bounds: None,
2181 last_position_map: None,
2182 expect_bounds_change: None,
2183 gutter_dimensions: GutterDimensions::default(),
2184 style: None,
2185 show_cursor_names: false,
2186 hovered_cursors: HashMap::default(),
2187 next_editor_action_id: EditorActionId::default(),
2188 editor_actions: Rc::default(),
2189 edit_predictions_hidden_for_vim_mode: false,
2190 show_edit_predictions_override: None,
2191 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2192 edit_prediction_settings: EditPredictionSettings::Disabled,
2193 edit_prediction_indent_conflict: false,
2194 edit_prediction_requires_modifier_in_indent_conflict: true,
2195 custom_context_menu: None,
2196 show_git_blame_gutter: false,
2197 show_git_blame_inline: false,
2198 show_selection_menu: None,
2199 show_git_blame_inline_delay_task: None,
2200 git_blame_inline_enabled: full_mode
2201 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2202 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2203 serialize_dirty_buffers: !is_minimap
2204 && ProjectSettings::get_global(cx)
2205 .session
2206 .restore_unsaved_buffers,
2207 blame: None,
2208 blame_subscription: None,
2209 tasks: BTreeMap::default(),
2210
2211 breakpoint_store,
2212 gutter_breakpoint_indicator: (None, None),
2213 hovered_diff_hunk_row: None,
2214 _subscriptions: (!is_minimap)
2215 .then(|| {
2216 vec![
2217 cx.observe(&buffer, Self::on_buffer_changed),
2218 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2219 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2220 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2221 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2222 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2223 cx.observe_window_activation(window, |editor, window, cx| {
2224 let active = window.is_window_active();
2225 editor.blink_manager.update(cx, |blink_manager, cx| {
2226 if active {
2227 blink_manager.enable(cx);
2228 } else {
2229 blink_manager.disable(cx);
2230 }
2231 });
2232 if active {
2233 editor.show_mouse_cursor(cx);
2234 }
2235 }),
2236 ]
2237 })
2238 .unwrap_or_default(),
2239 tasks_update_task: None,
2240 pull_diagnostics_task: Task::ready(()),
2241 colors: None,
2242 next_color_inlay_id: 0,
2243 linked_edit_ranges: Default::default(),
2244 in_project_search: false,
2245 previous_search_ranges: None,
2246 breadcrumb_header: None,
2247 focused_block: None,
2248 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2249 addons: HashMap::default(),
2250 registered_buffers: HashMap::default(),
2251 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2252 selection_mark_mode: false,
2253 toggle_fold_multiple_buffers: Task::ready(()),
2254 serialize_selections: Task::ready(()),
2255 serialize_folds: Task::ready(()),
2256 text_style_refinement: None,
2257 load_diff_task: load_uncommitted_diff,
2258 temporary_diff_override: false,
2259 mouse_cursor_hidden: false,
2260 minimap: None,
2261 hide_mouse_mode: EditorSettings::get_global(cx)
2262 .hide_mouse
2263 .unwrap_or_default(),
2264 change_list: ChangeList::new(),
2265 mode,
2266 selection_drag_state: SelectionDragState::None,
2267 folding_newlines: Task::ready(()),
2268 lookup_key: None,
2269 };
2270
2271 if is_minimap {
2272 return editor;
2273 }
2274
2275 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2276 editor
2277 ._subscriptions
2278 .push(cx.observe(breakpoints, |_, _, cx| {
2279 cx.notify();
2280 }));
2281 }
2282 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2283 editor._subscriptions.extend(project_subscriptions);
2284
2285 editor._subscriptions.push(cx.subscribe_in(
2286 &cx.entity(),
2287 window,
2288 |editor, _, e: &EditorEvent, window, cx| match e {
2289 EditorEvent::ScrollPositionChanged { local, .. } => {
2290 if *local {
2291 let new_anchor = editor.scroll_manager.anchor();
2292 let snapshot = editor.snapshot(window, cx);
2293 editor.update_restoration_data(cx, move |data| {
2294 data.scroll_position = (
2295 new_anchor.top_row(&snapshot.buffer_snapshot),
2296 new_anchor.offset,
2297 );
2298 });
2299 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2300 editor.inline_blame_popover.take();
2301 }
2302 }
2303 EditorEvent::Edited { .. } => {
2304 if !vim_enabled(cx) {
2305 let (map, selections) = editor.selections.all_adjusted_display(cx);
2306 let pop_state = editor
2307 .change_list
2308 .last()
2309 .map(|previous| {
2310 previous.len() == selections.len()
2311 && previous.iter().enumerate().all(|(ix, p)| {
2312 p.to_display_point(&map).row()
2313 == selections[ix].head().row()
2314 })
2315 })
2316 .unwrap_or(false);
2317 let new_positions = selections
2318 .into_iter()
2319 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2320 .collect();
2321 editor
2322 .change_list
2323 .push_to_change_list(pop_state, new_positions);
2324 }
2325 }
2326 _ => (),
2327 },
2328 ));
2329
2330 if let Some(dap_store) = editor
2331 .project
2332 .as_ref()
2333 .map(|project| project.read(cx).dap_store())
2334 {
2335 let weak_editor = cx.weak_entity();
2336
2337 editor
2338 ._subscriptions
2339 .push(
2340 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2341 let session_entity = cx.entity();
2342 weak_editor
2343 .update(cx, |editor, cx| {
2344 editor._subscriptions.push(
2345 cx.subscribe(&session_entity, Self::on_debug_session_event),
2346 );
2347 })
2348 .ok();
2349 }),
2350 );
2351
2352 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2353 editor
2354 ._subscriptions
2355 .push(cx.subscribe(&session, Self::on_debug_session_event));
2356 }
2357 }
2358
2359 // skip adding the initial selection to selection history
2360 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2361 editor.end_selection(window, cx);
2362 editor.selection_history.mode = SelectionHistoryMode::Normal;
2363
2364 editor.scroll_manager.show_scrollbars(window, cx);
2365 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2366
2367 if full_mode {
2368 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2369 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2370
2371 if editor.git_blame_inline_enabled {
2372 editor.start_git_blame_inline(false, window, cx);
2373 }
2374
2375 editor.go_to_active_debug_line(window, cx);
2376
2377 if let Some(buffer) = buffer.read(cx).as_singleton()
2378 && let Some(project) = editor.project()
2379 {
2380 let handle = project.update(cx, |project, cx| {
2381 project.register_buffer_with_language_servers(&buffer, cx)
2382 });
2383 editor
2384 .registered_buffers
2385 .insert(buffer.read(cx).remote_id(), handle);
2386 }
2387
2388 editor.minimap =
2389 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2390 editor.colors = Some(LspColorData::new(cx));
2391 editor.update_lsp_data(false, None, window, cx);
2392 }
2393
2394 if editor.mode.is_full() {
2395 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2396 }
2397
2398 editor
2399 }
2400
2401 pub fn deploy_mouse_context_menu(
2402 &mut self,
2403 position: gpui::Point<Pixels>,
2404 context_menu: Entity<ContextMenu>,
2405 window: &mut Window,
2406 cx: &mut Context<Self>,
2407 ) {
2408 self.mouse_context_menu = Some(MouseContextMenu::new(
2409 self,
2410 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2411 context_menu,
2412 window,
2413 cx,
2414 ));
2415 }
2416
2417 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2418 self.mouse_context_menu
2419 .as_ref()
2420 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2421 }
2422
2423 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2424 if self
2425 .selections
2426 .pending_anchor()
2427 .is_some_and(|pending_selection| {
2428 let snapshot = self.buffer().read(cx).snapshot(cx);
2429 pending_selection.range().includes(range, &snapshot)
2430 })
2431 {
2432 return true;
2433 }
2434
2435 self.selections
2436 .disjoint_in_range::<usize>(range.clone(), cx)
2437 .into_iter()
2438 .any(|selection| {
2439 // This is needed to cover a corner case, if we just check for an existing
2440 // selection in the fold range, having a cursor at the start of the fold
2441 // marks it as selected. Non-empty selections don't cause this.
2442 let length = selection.end - selection.start;
2443 length > 0
2444 })
2445 }
2446
2447 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2448 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2449 }
2450
2451 fn key_context_internal(
2452 &self,
2453 has_active_edit_prediction: bool,
2454 window: &Window,
2455 cx: &App,
2456 ) -> KeyContext {
2457 let mut key_context = KeyContext::new_with_defaults();
2458 key_context.add("Editor");
2459 let mode = match self.mode {
2460 EditorMode::SingleLine => "single_line",
2461 EditorMode::AutoHeight { .. } => "auto_height",
2462 EditorMode::Minimap { .. } => "minimap",
2463 EditorMode::Full { .. } => "full",
2464 };
2465
2466 if EditorSettings::jupyter_enabled(cx) {
2467 key_context.add("jupyter");
2468 }
2469
2470 key_context.set("mode", mode);
2471 if self.pending_rename.is_some() {
2472 key_context.add("renaming");
2473 }
2474
2475 match self.context_menu.borrow().as_ref() {
2476 Some(CodeContextMenu::Completions(menu)) => {
2477 if menu.visible() {
2478 key_context.add("menu");
2479 key_context.add("showing_completions");
2480 }
2481 }
2482 Some(CodeContextMenu::CodeActions(menu)) => {
2483 if menu.visible() {
2484 key_context.add("menu");
2485 key_context.add("showing_code_actions")
2486 }
2487 }
2488 None => {}
2489 }
2490
2491 if self.signature_help_state.has_multiple_signatures() {
2492 key_context.add("showing_signature_help");
2493 }
2494
2495 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2496 if !self.focus_handle(cx).contains_focused(window, cx)
2497 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2498 {
2499 for addon in self.addons.values() {
2500 addon.extend_key_context(&mut key_context, cx)
2501 }
2502 }
2503
2504 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2505 if let Some(extension) = singleton_buffer
2506 .read(cx)
2507 .file()
2508 .and_then(|file| file.path().extension())
2509 {
2510 key_context.set("extension", extension.to_string());
2511 }
2512 } else {
2513 key_context.add("multibuffer");
2514 }
2515
2516 if has_active_edit_prediction {
2517 if self.edit_prediction_in_conflict() {
2518 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2519 } else {
2520 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2521 key_context.add("copilot_suggestion");
2522 }
2523 }
2524
2525 if self.selection_mark_mode {
2526 key_context.add("selection_mode");
2527 }
2528
2529 key_context
2530 }
2531
2532 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2533 self.last_bounds.as_ref()
2534 }
2535
2536 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2537 if self.mouse_cursor_hidden {
2538 self.mouse_cursor_hidden = false;
2539 cx.notify();
2540 }
2541 }
2542
2543 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2544 let hide_mouse_cursor = match origin {
2545 HideMouseCursorOrigin::TypingAction => {
2546 matches!(
2547 self.hide_mouse_mode,
2548 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2549 )
2550 }
2551 HideMouseCursorOrigin::MovementAction => {
2552 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2553 }
2554 };
2555 if self.mouse_cursor_hidden != hide_mouse_cursor {
2556 self.mouse_cursor_hidden = hide_mouse_cursor;
2557 cx.notify();
2558 }
2559 }
2560
2561 pub fn edit_prediction_in_conflict(&self) -> bool {
2562 if !self.show_edit_predictions_in_menu() {
2563 return false;
2564 }
2565
2566 let showing_completions = self
2567 .context_menu
2568 .borrow()
2569 .as_ref()
2570 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2571
2572 showing_completions
2573 || self.edit_prediction_requires_modifier()
2574 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2575 // bindings to insert tab characters.
2576 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2577 }
2578
2579 pub fn accept_edit_prediction_keybind(
2580 &self,
2581 accept_partial: bool,
2582 window: &Window,
2583 cx: &App,
2584 ) -> AcceptEditPredictionBinding {
2585 let key_context = self.key_context_internal(true, window, cx);
2586 let in_conflict = self.edit_prediction_in_conflict();
2587
2588 let bindings = if accept_partial {
2589 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2590 } else {
2591 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2592 };
2593
2594 // TODO: if the binding contains multiple keystrokes, display all of them, not
2595 // just the first one.
2596 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2597 !in_conflict
2598 || binding
2599 .keystrokes()
2600 .first()
2601 .is_some_and(|keystroke| keystroke.modifiers().modified())
2602 }))
2603 }
2604
2605 pub fn new_file(
2606 workspace: &mut Workspace,
2607 _: &workspace::NewFile,
2608 window: &mut Window,
2609 cx: &mut Context<Workspace>,
2610 ) {
2611 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2612 "Failed to create buffer",
2613 window,
2614 cx,
2615 |e, _, _| match e.error_code() {
2616 ErrorCode::RemoteUpgradeRequired => Some(format!(
2617 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2618 e.error_tag("required").unwrap_or("the latest version")
2619 )),
2620 _ => None,
2621 },
2622 );
2623 }
2624
2625 pub fn new_in_workspace(
2626 workspace: &mut Workspace,
2627 window: &mut Window,
2628 cx: &mut Context<Workspace>,
2629 ) -> Task<Result<Entity<Editor>>> {
2630 let project = workspace.project().clone();
2631 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2632
2633 cx.spawn_in(window, async move |workspace, cx| {
2634 let buffer = create.await?;
2635 workspace.update_in(cx, |workspace, window, cx| {
2636 let editor =
2637 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2638 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2639 editor
2640 })
2641 })
2642 }
2643
2644 fn new_file_vertical(
2645 workspace: &mut Workspace,
2646 _: &workspace::NewFileSplitVertical,
2647 window: &mut Window,
2648 cx: &mut Context<Workspace>,
2649 ) {
2650 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2651 }
2652
2653 fn new_file_horizontal(
2654 workspace: &mut Workspace,
2655 _: &workspace::NewFileSplitHorizontal,
2656 window: &mut Window,
2657 cx: &mut Context<Workspace>,
2658 ) {
2659 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2660 }
2661
2662 fn new_file_in_direction(
2663 workspace: &mut Workspace,
2664 direction: SplitDirection,
2665 window: &mut Window,
2666 cx: &mut Context<Workspace>,
2667 ) {
2668 let project = workspace.project().clone();
2669 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2670
2671 cx.spawn_in(window, async move |workspace, cx| {
2672 let buffer = create.await?;
2673 workspace.update_in(cx, move |workspace, window, cx| {
2674 workspace.split_item(
2675 direction,
2676 Box::new(
2677 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2678 ),
2679 window,
2680 cx,
2681 )
2682 })?;
2683 anyhow::Ok(())
2684 })
2685 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2686 match e.error_code() {
2687 ErrorCode::RemoteUpgradeRequired => Some(format!(
2688 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2689 e.error_tag("required").unwrap_or("the latest version")
2690 )),
2691 _ => None,
2692 }
2693 });
2694 }
2695
2696 pub fn leader_id(&self) -> Option<CollaboratorId> {
2697 self.leader_id
2698 }
2699
2700 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2701 &self.buffer
2702 }
2703
2704 pub fn project(&self) -> Option<&Entity<Project>> {
2705 self.project.as_ref()
2706 }
2707
2708 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2709 self.workspace.as_ref()?.0.upgrade()
2710 }
2711
2712 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2713 self.buffer().read(cx).title(cx)
2714 }
2715
2716 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2717 let git_blame_gutter_max_author_length = self
2718 .render_git_blame_gutter(cx)
2719 .then(|| {
2720 if let Some(blame) = self.blame.as_ref() {
2721 let max_author_length =
2722 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2723 Some(max_author_length)
2724 } else {
2725 None
2726 }
2727 })
2728 .flatten();
2729
2730 EditorSnapshot {
2731 mode: self.mode.clone(),
2732 show_gutter: self.show_gutter,
2733 show_line_numbers: self.show_line_numbers,
2734 show_git_diff_gutter: self.show_git_diff_gutter,
2735 show_code_actions: self.show_code_actions,
2736 show_runnables: self.show_runnables,
2737 show_breakpoints: self.show_breakpoints,
2738 git_blame_gutter_max_author_length,
2739 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2740 placeholder_display_snapshot: self
2741 .placeholder_display_map
2742 .as_ref()
2743 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2744 scroll_anchor: self.scroll_manager.anchor(),
2745 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2746 is_focused: self.focus_handle.is_focused(window),
2747 current_line_highlight: self
2748 .current_line_highlight
2749 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2750 gutter_hovered: self.gutter_hovered,
2751 }
2752 }
2753
2754 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2755 self.buffer.read(cx).language_at(point, cx)
2756 }
2757
2758 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2759 self.buffer.read(cx).read(cx).file_at(point).cloned()
2760 }
2761
2762 pub fn active_excerpt(
2763 &self,
2764 cx: &App,
2765 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2766 self.buffer
2767 .read(cx)
2768 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2769 }
2770
2771 pub fn mode(&self) -> &EditorMode {
2772 &self.mode
2773 }
2774
2775 pub fn set_mode(&mut self, mode: EditorMode) {
2776 self.mode = mode;
2777 }
2778
2779 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2780 self.collaboration_hub.as_deref()
2781 }
2782
2783 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2784 self.collaboration_hub = Some(hub);
2785 }
2786
2787 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2788 self.in_project_search = in_project_search;
2789 }
2790
2791 pub fn set_custom_context_menu(
2792 &mut self,
2793 f: impl 'static
2794 + Fn(
2795 &mut Self,
2796 DisplayPoint,
2797 &mut Window,
2798 &mut Context<Self>,
2799 ) -> Option<Entity<ui::ContextMenu>>,
2800 ) {
2801 self.custom_context_menu = Some(Box::new(f))
2802 }
2803
2804 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2805 self.completion_provider = provider;
2806 }
2807
2808 #[cfg(any(test, feature = "test-support"))]
2809 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2810 self.completion_provider.clone()
2811 }
2812
2813 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2814 self.semantics_provider.clone()
2815 }
2816
2817 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2818 self.semantics_provider = provider;
2819 }
2820
2821 pub fn set_edit_prediction_provider<T>(
2822 &mut self,
2823 provider: Option<Entity<T>>,
2824 window: &mut Window,
2825 cx: &mut Context<Self>,
2826 ) where
2827 T: EditPredictionProvider,
2828 {
2829 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2830 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2831 if this.focus_handle.is_focused(window) {
2832 this.update_visible_edit_prediction(window, cx);
2833 }
2834 }),
2835 provider: Arc::new(provider),
2836 });
2837 self.update_edit_prediction_settings(cx);
2838 self.refresh_edit_prediction(false, false, window, cx);
2839 }
2840
2841 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2842 self.placeholder_display_map
2843 .as_ref()
2844 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2845 }
2846
2847 pub fn set_placeholder_text(
2848 &mut self,
2849 placeholder_text: &str,
2850 window: &mut Window,
2851 cx: &mut Context<Self>,
2852 ) {
2853 let multibuffer = cx
2854 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2855
2856 let style = window.text_style();
2857
2858 self.placeholder_display_map = Some(cx.new(|cx| {
2859 DisplayMap::new(
2860 multibuffer,
2861 style.font(),
2862 style.font_size.to_pixels(window.rem_size()),
2863 None,
2864 FILE_HEADER_HEIGHT,
2865 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2866 Default::default(),
2867 DiagnosticSeverity::Off,
2868 cx,
2869 )
2870 }));
2871 cx.notify();
2872 }
2873
2874 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2875 self.cursor_shape = cursor_shape;
2876
2877 // Disrupt blink for immediate user feedback that the cursor shape has changed
2878 self.blink_manager.update(cx, BlinkManager::show_cursor);
2879
2880 cx.notify();
2881 }
2882
2883 pub fn set_current_line_highlight(
2884 &mut self,
2885 current_line_highlight: Option<CurrentLineHighlight>,
2886 ) {
2887 self.current_line_highlight = current_line_highlight;
2888 }
2889
2890 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2891 self.collapse_matches = collapse_matches;
2892 }
2893
2894 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2895 let buffers = self.buffer.read(cx).all_buffers();
2896 let Some(project) = self.project.as_ref() else {
2897 return;
2898 };
2899 project.update(cx, |project, cx| {
2900 for buffer in buffers {
2901 self.registered_buffers
2902 .entry(buffer.read(cx).remote_id())
2903 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2904 }
2905 })
2906 }
2907
2908 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2909 if self.collapse_matches {
2910 return range.start..range.start;
2911 }
2912 range.clone()
2913 }
2914
2915 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2916 if self.display_map.read(cx).clip_at_line_ends != clip {
2917 self.display_map
2918 .update(cx, |map, _| map.clip_at_line_ends = clip);
2919 }
2920 }
2921
2922 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2923 self.input_enabled = input_enabled;
2924 }
2925
2926 pub fn set_edit_predictions_hidden_for_vim_mode(
2927 &mut self,
2928 hidden: bool,
2929 window: &mut Window,
2930 cx: &mut Context<Self>,
2931 ) {
2932 if hidden != self.edit_predictions_hidden_for_vim_mode {
2933 self.edit_predictions_hidden_for_vim_mode = hidden;
2934 if hidden {
2935 self.update_visible_edit_prediction(window, cx);
2936 } else {
2937 self.refresh_edit_prediction(true, false, window, cx);
2938 }
2939 }
2940 }
2941
2942 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2943 self.menu_edit_predictions_policy = value;
2944 }
2945
2946 pub fn set_autoindent(&mut self, autoindent: bool) {
2947 if autoindent {
2948 self.autoindent_mode = Some(AutoindentMode::EachLine);
2949 } else {
2950 self.autoindent_mode = None;
2951 }
2952 }
2953
2954 pub fn read_only(&self, cx: &App) -> bool {
2955 self.read_only || self.buffer.read(cx).read_only()
2956 }
2957
2958 pub fn set_read_only(&mut self, read_only: bool) {
2959 self.read_only = read_only;
2960 }
2961
2962 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2963 self.use_autoclose = autoclose;
2964 }
2965
2966 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2967 self.use_auto_surround = auto_surround;
2968 }
2969
2970 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2971 self.auto_replace_emoji_shortcode = auto_replace;
2972 }
2973
2974 pub fn toggle_edit_predictions(
2975 &mut self,
2976 _: &ToggleEditPrediction,
2977 window: &mut Window,
2978 cx: &mut Context<Self>,
2979 ) {
2980 if self.show_edit_predictions_override.is_some() {
2981 self.set_show_edit_predictions(None, window, cx);
2982 } else {
2983 let show_edit_predictions = !self.edit_predictions_enabled();
2984 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2985 }
2986 }
2987
2988 pub fn set_show_edit_predictions(
2989 &mut self,
2990 show_edit_predictions: Option<bool>,
2991 window: &mut Window,
2992 cx: &mut Context<Self>,
2993 ) {
2994 self.show_edit_predictions_override = show_edit_predictions;
2995 self.update_edit_prediction_settings(cx);
2996
2997 if let Some(false) = show_edit_predictions {
2998 self.discard_edit_prediction(false, cx);
2999 } else {
3000 self.refresh_edit_prediction(false, true, window, cx);
3001 }
3002 }
3003
3004 fn edit_predictions_disabled_in_scope(
3005 &self,
3006 buffer: &Entity<Buffer>,
3007 buffer_position: language::Anchor,
3008 cx: &App,
3009 ) -> bool {
3010 let snapshot = buffer.read(cx).snapshot();
3011 let settings = snapshot.settings_at(buffer_position, cx);
3012
3013 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3014 return false;
3015 };
3016
3017 scope.override_name().is_some_and(|scope_name| {
3018 settings
3019 .edit_predictions_disabled_in
3020 .iter()
3021 .any(|s| s == scope_name)
3022 })
3023 }
3024
3025 pub fn set_use_modal_editing(&mut self, to: bool) {
3026 self.use_modal_editing = to;
3027 }
3028
3029 pub fn use_modal_editing(&self) -> bool {
3030 self.use_modal_editing
3031 }
3032
3033 fn selections_did_change(
3034 &mut self,
3035 local: bool,
3036 old_cursor_position: &Anchor,
3037 effects: SelectionEffects,
3038 window: &mut Window,
3039 cx: &mut Context<Self>,
3040 ) {
3041 window.invalidate_character_coordinates();
3042
3043 // Copy selections to primary selection buffer
3044 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3045 if local {
3046 let selections = self.selections.all::<usize>(cx);
3047 let buffer_handle = self.buffer.read(cx).read(cx);
3048
3049 let mut text = String::new();
3050 for (index, selection) in selections.iter().enumerate() {
3051 let text_for_selection = buffer_handle
3052 .text_for_range(selection.start..selection.end)
3053 .collect::<String>();
3054
3055 text.push_str(&text_for_selection);
3056 if index != selections.len() - 1 {
3057 text.push('\n');
3058 }
3059 }
3060
3061 if !text.is_empty() {
3062 cx.write_to_primary(ClipboardItem::new_string(text));
3063 }
3064 }
3065
3066 let selection_anchors = self.selections.disjoint_anchors_arc();
3067
3068 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3069 self.buffer.update(cx, |buffer, cx| {
3070 buffer.set_active_selections(
3071 &selection_anchors,
3072 self.selections.line_mode(),
3073 self.cursor_shape,
3074 cx,
3075 )
3076 });
3077 }
3078 let display_map = self
3079 .display_map
3080 .update(cx, |display_map, cx| display_map.snapshot(cx));
3081 let buffer = &display_map.buffer_snapshot;
3082 if self.selections.count() == 1 {
3083 self.add_selections_state = None;
3084 }
3085 self.select_next_state = None;
3086 self.select_prev_state = None;
3087 self.select_syntax_node_history.try_clear();
3088 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3089 self.snippet_stack.invalidate(&selection_anchors, buffer);
3090 self.take_rename(false, window, cx);
3091
3092 let newest_selection = self.selections.newest_anchor();
3093 let new_cursor_position = newest_selection.head();
3094 let selection_start = newest_selection.start;
3095
3096 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3097 self.push_to_nav_history(
3098 *old_cursor_position,
3099 Some(new_cursor_position.to_point(buffer)),
3100 false,
3101 effects.nav_history == Some(true),
3102 cx,
3103 );
3104 }
3105
3106 if local {
3107 if let Some(buffer_id) = new_cursor_position.buffer_id
3108 && !self.registered_buffers.contains_key(&buffer_id)
3109 && let Some(project) = self.project.as_ref()
3110 {
3111 project.update(cx, |project, cx| {
3112 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3113 return;
3114 };
3115 self.registered_buffers.insert(
3116 buffer_id,
3117 project.register_buffer_with_language_servers(&buffer, cx),
3118 );
3119 })
3120 }
3121
3122 let mut context_menu = self.context_menu.borrow_mut();
3123 let completion_menu = match context_menu.as_ref() {
3124 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3125 Some(CodeContextMenu::CodeActions(_)) => {
3126 *context_menu = None;
3127 None
3128 }
3129 None => None,
3130 };
3131 let completion_position = completion_menu.map(|menu| menu.initial_position);
3132 drop(context_menu);
3133
3134 if effects.completions
3135 && let Some(completion_position) = completion_position
3136 {
3137 let start_offset = selection_start.to_offset(buffer);
3138 let position_matches = start_offset == completion_position.to_offset(buffer);
3139 let continue_showing = if position_matches {
3140 if self.snippet_stack.is_empty() {
3141 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3142 == Some(CharKind::Word)
3143 } else {
3144 // Snippet choices can be shown even when the cursor is in whitespace.
3145 // Dismissing the menu with actions like backspace is handled by
3146 // invalidation regions.
3147 true
3148 }
3149 } else {
3150 false
3151 };
3152
3153 if continue_showing {
3154 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3155 } else {
3156 self.hide_context_menu(window, cx);
3157 }
3158 }
3159
3160 hide_hover(self, cx);
3161
3162 if old_cursor_position.to_display_point(&display_map).row()
3163 != new_cursor_position.to_display_point(&display_map).row()
3164 {
3165 self.available_code_actions.take();
3166 }
3167 self.refresh_code_actions(window, cx);
3168 self.refresh_document_highlights(cx);
3169 self.refresh_selected_text_highlights(false, window, cx);
3170 refresh_matching_bracket_highlights(self, window, cx);
3171 self.update_visible_edit_prediction(window, cx);
3172 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3173 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3174 self.inline_blame_popover.take();
3175 if self.git_blame_inline_enabled {
3176 self.start_inline_blame_timer(window, cx);
3177 }
3178 }
3179
3180 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3181 cx.emit(EditorEvent::SelectionsChanged { local });
3182
3183 let selections = &self.selections.disjoint_anchors_arc();
3184 if selections.len() == 1 {
3185 cx.emit(SearchEvent::ActiveMatchChanged)
3186 }
3187 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3188 let inmemory_selections = selections
3189 .iter()
3190 .map(|s| {
3191 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3192 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3193 })
3194 .collect();
3195 self.update_restoration_data(cx, |data| {
3196 data.selections = inmemory_selections;
3197 });
3198
3199 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3200 && let Some(workspace_id) =
3201 self.workspace.as_ref().and_then(|workspace| workspace.1)
3202 {
3203 let snapshot = self.buffer().read(cx).snapshot(cx);
3204 let selections = selections.clone();
3205 let background_executor = cx.background_executor().clone();
3206 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3207 self.serialize_selections = cx.background_spawn(async move {
3208 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3209 let db_selections = selections
3210 .iter()
3211 .map(|selection| {
3212 (
3213 selection.start.to_offset(&snapshot),
3214 selection.end.to_offset(&snapshot),
3215 )
3216 })
3217 .collect();
3218
3219 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3220 .await
3221 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3222 .log_err();
3223 });
3224 }
3225 }
3226
3227 cx.notify();
3228 }
3229
3230 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3231 use text::ToOffset as _;
3232 use text::ToPoint as _;
3233
3234 if self.mode.is_minimap()
3235 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3236 {
3237 return;
3238 }
3239
3240 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3241 return;
3242 };
3243
3244 let snapshot = singleton.read(cx).snapshot();
3245 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3246 let display_snapshot = display_map.snapshot(cx);
3247
3248 display_snapshot
3249 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3250 .map(|fold| {
3251 fold.range.start.text_anchor.to_point(&snapshot)
3252 ..fold.range.end.text_anchor.to_point(&snapshot)
3253 })
3254 .collect()
3255 });
3256 self.update_restoration_data(cx, |data| {
3257 data.folds = inmemory_folds;
3258 });
3259
3260 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3261 return;
3262 };
3263 let background_executor = cx.background_executor().clone();
3264 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3265 let db_folds = self.display_map.update(cx, |display_map, cx| {
3266 display_map
3267 .snapshot(cx)
3268 .folds_in_range(0..snapshot.len())
3269 .map(|fold| {
3270 (
3271 fold.range.start.text_anchor.to_offset(&snapshot),
3272 fold.range.end.text_anchor.to_offset(&snapshot),
3273 )
3274 })
3275 .collect()
3276 });
3277 self.serialize_folds = cx.background_spawn(async move {
3278 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3279 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3280 .await
3281 .with_context(|| {
3282 format!(
3283 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3284 )
3285 })
3286 .log_err();
3287 });
3288 }
3289
3290 pub fn sync_selections(
3291 &mut self,
3292 other: Entity<Editor>,
3293 cx: &mut Context<Self>,
3294 ) -> gpui::Subscription {
3295 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3296 self.selections.change_with(cx, |selections| {
3297 selections.select_anchors(other_selections);
3298 });
3299
3300 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3301 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3302 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3303 if other_selections.is_empty() {
3304 return;
3305 }
3306 this.selections.change_with(cx, |selections| {
3307 selections.select_anchors(other_selections);
3308 });
3309 }
3310 });
3311
3312 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3313 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3314 let these_selections = this.selections.disjoint_anchors().to_vec();
3315 if these_selections.is_empty() {
3316 return;
3317 }
3318 other.update(cx, |other_editor, cx| {
3319 other_editor.selections.change_with(cx, |selections| {
3320 selections.select_anchors(these_selections);
3321 })
3322 });
3323 }
3324 });
3325
3326 Subscription::join(other_subscription, this_subscription)
3327 }
3328
3329 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3330 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3331 /// effects of selection change occur at the end of the transaction.
3332 pub fn change_selections<R>(
3333 &mut self,
3334 effects: SelectionEffects,
3335 window: &mut Window,
3336 cx: &mut Context<Self>,
3337 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3338 ) -> R {
3339 if let Some(state) = &mut self.deferred_selection_effects_state {
3340 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3341 state.effects.completions = effects.completions;
3342 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3343 let (changed, result) = self.selections.change_with(cx, change);
3344 state.changed |= changed;
3345 return result;
3346 }
3347 let mut state = DeferredSelectionEffectsState {
3348 changed: false,
3349 effects,
3350 old_cursor_position: self.selections.newest_anchor().head(),
3351 history_entry: SelectionHistoryEntry {
3352 selections: self.selections.disjoint_anchors_arc(),
3353 select_next_state: self.select_next_state.clone(),
3354 select_prev_state: self.select_prev_state.clone(),
3355 add_selections_state: self.add_selections_state.clone(),
3356 },
3357 };
3358 let (changed, result) = self.selections.change_with(cx, change);
3359 state.changed = state.changed || changed;
3360 if self.defer_selection_effects {
3361 self.deferred_selection_effects_state = Some(state);
3362 } else {
3363 self.apply_selection_effects(state, window, cx);
3364 }
3365 result
3366 }
3367
3368 /// Defers the effects of selection change, so that the effects of multiple calls to
3369 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3370 /// to selection history and the state of popovers based on selection position aren't
3371 /// erroneously updated.
3372 pub fn with_selection_effects_deferred<R>(
3373 &mut self,
3374 window: &mut Window,
3375 cx: &mut Context<Self>,
3376 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3377 ) -> R {
3378 let already_deferred = self.defer_selection_effects;
3379 self.defer_selection_effects = true;
3380 let result = update(self, window, cx);
3381 if !already_deferred {
3382 self.defer_selection_effects = false;
3383 if let Some(state) = self.deferred_selection_effects_state.take() {
3384 self.apply_selection_effects(state, window, cx);
3385 }
3386 }
3387 result
3388 }
3389
3390 fn apply_selection_effects(
3391 &mut self,
3392 state: DeferredSelectionEffectsState,
3393 window: &mut Window,
3394 cx: &mut Context<Self>,
3395 ) {
3396 if state.changed {
3397 self.selection_history.push(state.history_entry);
3398
3399 if let Some(autoscroll) = state.effects.scroll {
3400 self.request_autoscroll(autoscroll, cx);
3401 }
3402
3403 let old_cursor_position = &state.old_cursor_position;
3404
3405 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3406
3407 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3408 self.show_signature_help(&ShowSignatureHelp, window, cx);
3409 }
3410 }
3411 }
3412
3413 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3414 where
3415 I: IntoIterator<Item = (Range<S>, T)>,
3416 S: ToOffset,
3417 T: Into<Arc<str>>,
3418 {
3419 if self.read_only(cx) {
3420 return;
3421 }
3422
3423 self.buffer
3424 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3425 }
3426
3427 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3428 where
3429 I: IntoIterator<Item = (Range<S>, T)>,
3430 S: ToOffset,
3431 T: Into<Arc<str>>,
3432 {
3433 if self.read_only(cx) {
3434 return;
3435 }
3436
3437 self.buffer.update(cx, |buffer, cx| {
3438 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3439 });
3440 }
3441
3442 pub fn edit_with_block_indent<I, S, T>(
3443 &mut self,
3444 edits: I,
3445 original_indent_columns: Vec<Option<u32>>,
3446 cx: &mut Context<Self>,
3447 ) where
3448 I: IntoIterator<Item = (Range<S>, T)>,
3449 S: ToOffset,
3450 T: Into<Arc<str>>,
3451 {
3452 if self.read_only(cx) {
3453 return;
3454 }
3455
3456 self.buffer.update(cx, |buffer, cx| {
3457 buffer.edit(
3458 edits,
3459 Some(AutoindentMode::Block {
3460 original_indent_columns,
3461 }),
3462 cx,
3463 )
3464 });
3465 }
3466
3467 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3468 self.hide_context_menu(window, cx);
3469
3470 match phase {
3471 SelectPhase::Begin {
3472 position,
3473 add,
3474 click_count,
3475 } => self.begin_selection(position, add, click_count, window, cx),
3476 SelectPhase::BeginColumnar {
3477 position,
3478 goal_column,
3479 reset,
3480 mode,
3481 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3482 SelectPhase::Extend {
3483 position,
3484 click_count,
3485 } => self.extend_selection(position, click_count, window, cx),
3486 SelectPhase::Update {
3487 position,
3488 goal_column,
3489 scroll_delta,
3490 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3491 SelectPhase::End => self.end_selection(window, cx),
3492 }
3493 }
3494
3495 fn extend_selection(
3496 &mut self,
3497 position: DisplayPoint,
3498 click_count: usize,
3499 window: &mut Window,
3500 cx: &mut Context<Self>,
3501 ) {
3502 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3503 let tail = self.selections.newest::<usize>(cx).tail();
3504 self.begin_selection(position, false, click_count, window, cx);
3505
3506 let position = position.to_offset(&display_map, Bias::Left);
3507 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3508
3509 let mut pending_selection = self
3510 .selections
3511 .pending_anchor()
3512 .cloned()
3513 .expect("extend_selection not called with pending selection");
3514 if position >= tail {
3515 pending_selection.start = tail_anchor;
3516 } else {
3517 pending_selection.end = tail_anchor;
3518 pending_selection.reversed = true;
3519 }
3520
3521 let mut pending_mode = self.selections.pending_mode().unwrap();
3522 match &mut pending_mode {
3523 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3524 _ => {}
3525 }
3526
3527 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3528 SelectionEffects::scroll(Autoscroll::fit())
3529 } else {
3530 SelectionEffects::no_scroll()
3531 };
3532
3533 self.change_selections(effects, window, cx, |s| {
3534 s.set_pending(pending_selection.clone(), pending_mode)
3535 });
3536 }
3537
3538 fn begin_selection(
3539 &mut self,
3540 position: DisplayPoint,
3541 add: bool,
3542 click_count: usize,
3543 window: &mut Window,
3544 cx: &mut Context<Self>,
3545 ) {
3546 if !self.focus_handle.is_focused(window) {
3547 self.last_focused_descendant = None;
3548 window.focus(&self.focus_handle);
3549 }
3550
3551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3552 let buffer = &display_map.buffer_snapshot;
3553 let position = display_map.clip_point(position, Bias::Left);
3554
3555 let start;
3556 let end;
3557 let mode;
3558 let mut auto_scroll;
3559 match click_count {
3560 1 => {
3561 start = buffer.anchor_before(position.to_point(&display_map));
3562 end = start;
3563 mode = SelectMode::Character;
3564 auto_scroll = true;
3565 }
3566 2 => {
3567 let position = display_map
3568 .clip_point(position, Bias::Left)
3569 .to_offset(&display_map, Bias::Left);
3570 let (range, _) = buffer.surrounding_word(position, None);
3571 start = buffer.anchor_before(range.start);
3572 end = buffer.anchor_before(range.end);
3573 mode = SelectMode::Word(start..end);
3574 auto_scroll = true;
3575 }
3576 3 => {
3577 let position = display_map
3578 .clip_point(position, Bias::Left)
3579 .to_point(&display_map);
3580 let line_start = display_map.prev_line_boundary(position).0;
3581 let next_line_start = buffer.clip_point(
3582 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3583 Bias::Left,
3584 );
3585 start = buffer.anchor_before(line_start);
3586 end = buffer.anchor_before(next_line_start);
3587 mode = SelectMode::Line(start..end);
3588 auto_scroll = true;
3589 }
3590 _ => {
3591 start = buffer.anchor_before(0);
3592 end = buffer.anchor_before(buffer.len());
3593 mode = SelectMode::All;
3594 auto_scroll = false;
3595 }
3596 }
3597 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3598
3599 let point_to_delete: Option<usize> = {
3600 let selected_points: Vec<Selection<Point>> =
3601 self.selections.disjoint_in_range(start..end, cx);
3602
3603 if !add || click_count > 1 {
3604 None
3605 } else if !selected_points.is_empty() {
3606 Some(selected_points[0].id)
3607 } else {
3608 let clicked_point_already_selected =
3609 self.selections.disjoint_anchors().iter().find(|selection| {
3610 selection.start.to_point(buffer) == start.to_point(buffer)
3611 || selection.end.to_point(buffer) == end.to_point(buffer)
3612 });
3613
3614 clicked_point_already_selected.map(|selection| selection.id)
3615 }
3616 };
3617
3618 let selections_count = self.selections.count();
3619 let effects = if auto_scroll {
3620 SelectionEffects::default()
3621 } else {
3622 SelectionEffects::no_scroll()
3623 };
3624
3625 self.change_selections(effects, window, cx, |s| {
3626 if let Some(point_to_delete) = point_to_delete {
3627 s.delete(point_to_delete);
3628
3629 if selections_count == 1 {
3630 s.set_pending_anchor_range(start..end, mode);
3631 }
3632 } else {
3633 if !add {
3634 s.clear_disjoint();
3635 }
3636
3637 s.set_pending_anchor_range(start..end, mode);
3638 }
3639 });
3640 }
3641
3642 fn begin_columnar_selection(
3643 &mut self,
3644 position: DisplayPoint,
3645 goal_column: u32,
3646 reset: bool,
3647 mode: ColumnarMode,
3648 window: &mut Window,
3649 cx: &mut Context<Self>,
3650 ) {
3651 if !self.focus_handle.is_focused(window) {
3652 self.last_focused_descendant = None;
3653 window.focus(&self.focus_handle);
3654 }
3655
3656 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3657
3658 if reset {
3659 let pointer_position = display_map
3660 .buffer_snapshot
3661 .anchor_before(position.to_point(&display_map));
3662
3663 self.change_selections(
3664 SelectionEffects::scroll(Autoscroll::newest()),
3665 window,
3666 cx,
3667 |s| {
3668 s.clear_disjoint();
3669 s.set_pending_anchor_range(
3670 pointer_position..pointer_position,
3671 SelectMode::Character,
3672 );
3673 },
3674 );
3675 };
3676
3677 let tail = self.selections.newest::<Point>(cx).tail();
3678 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3679 self.columnar_selection_state = match mode {
3680 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3681 selection_tail: selection_anchor,
3682 display_point: if reset {
3683 if position.column() != goal_column {
3684 Some(DisplayPoint::new(position.row(), goal_column))
3685 } else {
3686 None
3687 }
3688 } else {
3689 None
3690 },
3691 }),
3692 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3693 selection_tail: selection_anchor,
3694 }),
3695 };
3696
3697 if !reset {
3698 self.select_columns(position, goal_column, &display_map, window, cx);
3699 }
3700 }
3701
3702 fn update_selection(
3703 &mut self,
3704 position: DisplayPoint,
3705 goal_column: u32,
3706 scroll_delta: gpui::Point<f32>,
3707 window: &mut Window,
3708 cx: &mut Context<Self>,
3709 ) {
3710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3711
3712 if self.columnar_selection_state.is_some() {
3713 self.select_columns(position, goal_column, &display_map, window, cx);
3714 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3715 let buffer = &display_map.buffer_snapshot;
3716 let head;
3717 let tail;
3718 let mode = self.selections.pending_mode().unwrap();
3719 match &mode {
3720 SelectMode::Character => {
3721 head = position.to_point(&display_map);
3722 tail = pending.tail().to_point(buffer);
3723 }
3724 SelectMode::Word(original_range) => {
3725 let offset = display_map
3726 .clip_point(position, Bias::Left)
3727 .to_offset(&display_map, Bias::Left);
3728 let original_range = original_range.to_offset(buffer);
3729
3730 let head_offset = if buffer.is_inside_word(offset, None)
3731 || original_range.contains(&offset)
3732 {
3733 let (word_range, _) = buffer.surrounding_word(offset, None);
3734 if word_range.start < original_range.start {
3735 word_range.start
3736 } else {
3737 word_range.end
3738 }
3739 } else {
3740 offset
3741 };
3742
3743 head = head_offset.to_point(buffer);
3744 if head_offset <= original_range.start {
3745 tail = original_range.end.to_point(buffer);
3746 } else {
3747 tail = original_range.start.to_point(buffer);
3748 }
3749 }
3750 SelectMode::Line(original_range) => {
3751 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3752
3753 let position = display_map
3754 .clip_point(position, Bias::Left)
3755 .to_point(&display_map);
3756 let line_start = display_map.prev_line_boundary(position).0;
3757 let next_line_start = buffer.clip_point(
3758 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3759 Bias::Left,
3760 );
3761
3762 if line_start < original_range.start {
3763 head = line_start
3764 } else {
3765 head = next_line_start
3766 }
3767
3768 if head <= original_range.start {
3769 tail = original_range.end;
3770 } else {
3771 tail = original_range.start;
3772 }
3773 }
3774 SelectMode::All => {
3775 return;
3776 }
3777 };
3778
3779 if head < tail {
3780 pending.start = buffer.anchor_before(head);
3781 pending.end = buffer.anchor_before(tail);
3782 pending.reversed = true;
3783 } else {
3784 pending.start = buffer.anchor_before(tail);
3785 pending.end = buffer.anchor_before(head);
3786 pending.reversed = false;
3787 }
3788
3789 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3790 s.set_pending(pending.clone(), mode);
3791 });
3792 } else {
3793 log::error!("update_selection dispatched with no pending selection");
3794 return;
3795 }
3796
3797 self.apply_scroll_delta(scroll_delta, window, cx);
3798 cx.notify();
3799 }
3800
3801 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3802 self.columnar_selection_state.take();
3803 if self.selections.pending_anchor().is_some() {
3804 let selections = self.selections.all::<usize>(cx);
3805 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3806 s.select(selections);
3807 s.clear_pending();
3808 });
3809 }
3810 }
3811
3812 fn select_columns(
3813 &mut self,
3814 head: DisplayPoint,
3815 goal_column: u32,
3816 display_map: &DisplaySnapshot,
3817 window: &mut Window,
3818 cx: &mut Context<Self>,
3819 ) {
3820 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3821 return;
3822 };
3823
3824 let tail = match columnar_state {
3825 ColumnarSelectionState::FromMouse {
3826 selection_tail,
3827 display_point,
3828 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3829 ColumnarSelectionState::FromSelection { selection_tail } => {
3830 selection_tail.to_display_point(display_map)
3831 }
3832 };
3833
3834 let start_row = cmp::min(tail.row(), head.row());
3835 let end_row = cmp::max(tail.row(), head.row());
3836 let start_column = cmp::min(tail.column(), goal_column);
3837 let end_column = cmp::max(tail.column(), goal_column);
3838 let reversed = start_column < tail.column();
3839
3840 let selection_ranges = (start_row.0..=end_row.0)
3841 .map(DisplayRow)
3842 .filter_map(|row| {
3843 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3844 || start_column <= display_map.line_len(row))
3845 && !display_map.is_block_line(row)
3846 {
3847 let start = display_map
3848 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3849 .to_point(display_map);
3850 let end = display_map
3851 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3852 .to_point(display_map);
3853 if reversed {
3854 Some(end..start)
3855 } else {
3856 Some(start..end)
3857 }
3858 } else {
3859 None
3860 }
3861 })
3862 .collect::<Vec<_>>();
3863
3864 let ranges = match columnar_state {
3865 ColumnarSelectionState::FromMouse { .. } => {
3866 let mut non_empty_ranges = selection_ranges
3867 .iter()
3868 .filter(|selection_range| selection_range.start != selection_range.end)
3869 .peekable();
3870 if non_empty_ranges.peek().is_some() {
3871 non_empty_ranges.cloned().collect()
3872 } else {
3873 selection_ranges
3874 }
3875 }
3876 _ => selection_ranges,
3877 };
3878
3879 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3880 s.select_ranges(ranges);
3881 });
3882 cx.notify();
3883 }
3884
3885 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3886 self.selections
3887 .all_adjusted(cx)
3888 .iter()
3889 .any(|selection| !selection.is_empty())
3890 }
3891
3892 pub fn has_pending_nonempty_selection(&self) -> bool {
3893 let pending_nonempty_selection = match self.selections.pending_anchor() {
3894 Some(Selection { start, end, .. }) => start != end,
3895 None => false,
3896 };
3897
3898 pending_nonempty_selection
3899 || (self.columnar_selection_state.is_some()
3900 && self.selections.disjoint_anchors().len() > 1)
3901 }
3902
3903 pub fn has_pending_selection(&self) -> bool {
3904 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3905 }
3906
3907 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3908 self.selection_mark_mode = false;
3909 self.selection_drag_state = SelectionDragState::None;
3910
3911 if self.clear_expanded_diff_hunks(cx) {
3912 cx.notify();
3913 return;
3914 }
3915 if self.dismiss_menus_and_popups(true, window, cx) {
3916 return;
3917 }
3918
3919 if self.mode.is_full()
3920 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3921 {
3922 return;
3923 }
3924
3925 cx.propagate();
3926 }
3927
3928 pub fn dismiss_menus_and_popups(
3929 &mut self,
3930 is_user_requested: bool,
3931 window: &mut Window,
3932 cx: &mut Context<Self>,
3933 ) -> bool {
3934 if self.take_rename(false, window, cx).is_some() {
3935 return true;
3936 }
3937
3938 if hide_hover(self, cx) {
3939 return true;
3940 }
3941
3942 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3943 return true;
3944 }
3945
3946 if self.hide_context_menu(window, cx).is_some() {
3947 return true;
3948 }
3949
3950 if self.mouse_context_menu.take().is_some() {
3951 return true;
3952 }
3953
3954 if is_user_requested && self.discard_edit_prediction(true, cx) {
3955 return true;
3956 }
3957
3958 if self.snippet_stack.pop().is_some() {
3959 return true;
3960 }
3961
3962 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3963 self.dismiss_diagnostics(cx);
3964 return true;
3965 }
3966
3967 false
3968 }
3969
3970 fn linked_editing_ranges_for(
3971 &self,
3972 selection: Range<text::Anchor>,
3973 cx: &App,
3974 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3975 if self.linked_edit_ranges.is_empty() {
3976 return None;
3977 }
3978 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3979 selection.end.buffer_id.and_then(|end_buffer_id| {
3980 if selection.start.buffer_id != Some(end_buffer_id) {
3981 return None;
3982 }
3983 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3984 let snapshot = buffer.read(cx).snapshot();
3985 self.linked_edit_ranges
3986 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3987 .map(|ranges| (ranges, snapshot, buffer))
3988 })?;
3989 use text::ToOffset as TO;
3990 // find offset from the start of current range to current cursor position
3991 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3992
3993 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3994 let start_difference = start_offset - start_byte_offset;
3995 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3996 let end_difference = end_offset - start_byte_offset;
3997 // Current range has associated linked ranges.
3998 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3999 for range in linked_ranges.iter() {
4000 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4001 let end_offset = start_offset + end_difference;
4002 let start_offset = start_offset + start_difference;
4003 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4004 continue;
4005 }
4006 if self.selections.disjoint_anchor_ranges().any(|s| {
4007 if s.start.buffer_id != selection.start.buffer_id
4008 || s.end.buffer_id != selection.end.buffer_id
4009 {
4010 return false;
4011 }
4012 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4013 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4014 }) {
4015 continue;
4016 }
4017 let start = buffer_snapshot.anchor_after(start_offset);
4018 let end = buffer_snapshot.anchor_after(end_offset);
4019 linked_edits
4020 .entry(buffer.clone())
4021 .or_default()
4022 .push(start..end);
4023 }
4024 Some(linked_edits)
4025 }
4026
4027 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4028 let text: Arc<str> = text.into();
4029
4030 if self.read_only(cx) {
4031 return;
4032 }
4033
4034 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4035
4036 let selections = self.selections.all_adjusted(cx);
4037 let mut bracket_inserted = false;
4038 let mut edits = Vec::new();
4039 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4040 let mut new_selections = Vec::with_capacity(selections.len());
4041 let mut new_autoclose_regions = Vec::new();
4042 let snapshot = self.buffer.read(cx).read(cx);
4043 let mut clear_linked_edit_ranges = false;
4044
4045 for (selection, autoclose_region) in
4046 self.selections_with_autoclose_regions(selections, &snapshot)
4047 {
4048 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4049 // Determine if the inserted text matches the opening or closing
4050 // bracket of any of this language's bracket pairs.
4051 let mut bracket_pair = None;
4052 let mut is_bracket_pair_start = false;
4053 let mut is_bracket_pair_end = false;
4054 if !text.is_empty() {
4055 let mut bracket_pair_matching_end = None;
4056 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4057 // and they are removing the character that triggered IME popup.
4058 for (pair, enabled) in scope.brackets() {
4059 if !pair.close && !pair.surround {
4060 continue;
4061 }
4062
4063 if enabled && pair.start.ends_with(text.as_ref()) {
4064 let prefix_len = pair.start.len() - text.len();
4065 let preceding_text_matches_prefix = prefix_len == 0
4066 || (selection.start.column >= (prefix_len as u32)
4067 && snapshot.contains_str_at(
4068 Point::new(
4069 selection.start.row,
4070 selection.start.column - (prefix_len as u32),
4071 ),
4072 &pair.start[..prefix_len],
4073 ));
4074 if preceding_text_matches_prefix {
4075 bracket_pair = Some(pair.clone());
4076 is_bracket_pair_start = true;
4077 break;
4078 }
4079 }
4080 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4081 {
4082 // take first bracket pair matching end, but don't break in case a later bracket
4083 // pair matches start
4084 bracket_pair_matching_end = Some(pair.clone());
4085 }
4086 }
4087 if let Some(end) = bracket_pair_matching_end
4088 && bracket_pair.is_none()
4089 {
4090 bracket_pair = Some(end);
4091 is_bracket_pair_end = true;
4092 }
4093 }
4094
4095 if let Some(bracket_pair) = bracket_pair {
4096 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4097 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4098 let auto_surround =
4099 self.use_auto_surround && snapshot_settings.use_auto_surround;
4100 if selection.is_empty() {
4101 if is_bracket_pair_start {
4102 // If the inserted text is a suffix of an opening bracket and the
4103 // selection is preceded by the rest of the opening bracket, then
4104 // insert the closing bracket.
4105 let following_text_allows_autoclose = snapshot
4106 .chars_at(selection.start)
4107 .next()
4108 .is_none_or(|c| scope.should_autoclose_before(c));
4109
4110 let preceding_text_allows_autoclose = selection.start.column == 0
4111 || snapshot
4112 .reversed_chars_at(selection.start)
4113 .next()
4114 .is_none_or(|c| {
4115 bracket_pair.start != bracket_pair.end
4116 || !snapshot
4117 .char_classifier_at(selection.start)
4118 .is_word(c)
4119 });
4120
4121 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4122 && bracket_pair.start.len() == 1
4123 {
4124 let target = bracket_pair.start.chars().next().unwrap();
4125 let current_line_count = snapshot
4126 .reversed_chars_at(selection.start)
4127 .take_while(|&c| c != '\n')
4128 .filter(|&c| c == target)
4129 .count();
4130 current_line_count % 2 == 1
4131 } else {
4132 false
4133 };
4134
4135 if autoclose
4136 && bracket_pair.close
4137 && following_text_allows_autoclose
4138 && preceding_text_allows_autoclose
4139 && !is_closing_quote
4140 {
4141 let anchor = snapshot.anchor_before(selection.end);
4142 new_selections.push((selection.map(|_| anchor), text.len()));
4143 new_autoclose_regions.push((
4144 anchor,
4145 text.len(),
4146 selection.id,
4147 bracket_pair.clone(),
4148 ));
4149 edits.push((
4150 selection.range(),
4151 format!("{}{}", text, bracket_pair.end).into(),
4152 ));
4153 bracket_inserted = true;
4154 continue;
4155 }
4156 }
4157
4158 if let Some(region) = autoclose_region {
4159 // If the selection is followed by an auto-inserted closing bracket,
4160 // then don't insert that closing bracket again; just move the selection
4161 // past the closing bracket.
4162 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4163 && text.as_ref() == region.pair.end.as_str()
4164 && snapshot.contains_str_at(region.range.end, text.as_ref());
4165 if should_skip {
4166 let anchor = snapshot.anchor_after(selection.end);
4167 new_selections
4168 .push((selection.map(|_| anchor), region.pair.end.len()));
4169 continue;
4170 }
4171 }
4172
4173 let always_treat_brackets_as_autoclosed = snapshot
4174 .language_settings_at(selection.start, cx)
4175 .always_treat_brackets_as_autoclosed;
4176 if always_treat_brackets_as_autoclosed
4177 && is_bracket_pair_end
4178 && snapshot.contains_str_at(selection.end, text.as_ref())
4179 {
4180 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4181 // and the inserted text is a closing bracket and the selection is followed
4182 // by the closing bracket then move the selection past the closing bracket.
4183 let anchor = snapshot.anchor_after(selection.end);
4184 new_selections.push((selection.map(|_| anchor), text.len()));
4185 continue;
4186 }
4187 }
4188 // If an opening bracket is 1 character long and is typed while
4189 // text is selected, then surround that text with the bracket pair.
4190 else if auto_surround
4191 && bracket_pair.surround
4192 && is_bracket_pair_start
4193 && bracket_pair.start.chars().count() == 1
4194 {
4195 edits.push((selection.start..selection.start, text.clone()));
4196 edits.push((
4197 selection.end..selection.end,
4198 bracket_pair.end.as_str().into(),
4199 ));
4200 bracket_inserted = true;
4201 new_selections.push((
4202 Selection {
4203 id: selection.id,
4204 start: snapshot.anchor_after(selection.start),
4205 end: snapshot.anchor_before(selection.end),
4206 reversed: selection.reversed,
4207 goal: selection.goal,
4208 },
4209 0,
4210 ));
4211 continue;
4212 }
4213 }
4214 }
4215
4216 if self.auto_replace_emoji_shortcode
4217 && selection.is_empty()
4218 && text.as_ref().ends_with(':')
4219 && let Some(possible_emoji_short_code) =
4220 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4221 && !possible_emoji_short_code.is_empty()
4222 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4223 {
4224 let emoji_shortcode_start = Point::new(
4225 selection.start.row,
4226 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4227 );
4228
4229 // Remove shortcode from buffer
4230 edits.push((
4231 emoji_shortcode_start..selection.start,
4232 "".to_string().into(),
4233 ));
4234 new_selections.push((
4235 Selection {
4236 id: selection.id,
4237 start: snapshot.anchor_after(emoji_shortcode_start),
4238 end: snapshot.anchor_before(selection.start),
4239 reversed: selection.reversed,
4240 goal: selection.goal,
4241 },
4242 0,
4243 ));
4244
4245 // Insert emoji
4246 let selection_start_anchor = snapshot.anchor_after(selection.start);
4247 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4248 edits.push((selection.start..selection.end, emoji.to_string().into()));
4249
4250 continue;
4251 }
4252
4253 // If not handling any auto-close operation, then just replace the selected
4254 // text with the given input and move the selection to the end of the
4255 // newly inserted text.
4256 let anchor = snapshot.anchor_after(selection.end);
4257 if !self.linked_edit_ranges.is_empty() {
4258 let start_anchor = snapshot.anchor_before(selection.start);
4259
4260 let is_word_char = text.chars().next().is_none_or(|char| {
4261 let classifier = snapshot
4262 .char_classifier_at(start_anchor.to_offset(&snapshot))
4263 .scope_context(Some(CharScopeContext::LinkedEdit));
4264 classifier.is_word(char)
4265 });
4266
4267 if is_word_char {
4268 if let Some(ranges) = self
4269 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4270 {
4271 for (buffer, edits) in ranges {
4272 linked_edits
4273 .entry(buffer.clone())
4274 .or_default()
4275 .extend(edits.into_iter().map(|range| (range, text.clone())));
4276 }
4277 }
4278 } else {
4279 clear_linked_edit_ranges = true;
4280 }
4281 }
4282
4283 new_selections.push((selection.map(|_| anchor), 0));
4284 edits.push((selection.start..selection.end, text.clone()));
4285 }
4286
4287 drop(snapshot);
4288
4289 self.transact(window, cx, |this, window, cx| {
4290 if clear_linked_edit_ranges {
4291 this.linked_edit_ranges.clear();
4292 }
4293 let initial_buffer_versions =
4294 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4295
4296 this.buffer.update(cx, |buffer, cx| {
4297 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4298 });
4299 for (buffer, edits) in linked_edits {
4300 buffer.update(cx, |buffer, cx| {
4301 let snapshot = buffer.snapshot();
4302 let edits = edits
4303 .into_iter()
4304 .map(|(range, text)| {
4305 use text::ToPoint as TP;
4306 let end_point = TP::to_point(&range.end, &snapshot);
4307 let start_point = TP::to_point(&range.start, &snapshot);
4308 (start_point..end_point, text)
4309 })
4310 .sorted_by_key(|(range, _)| range.start);
4311 buffer.edit(edits, None, cx);
4312 })
4313 }
4314 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4315 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4316 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4317 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4318 .zip(new_selection_deltas)
4319 .map(|(selection, delta)| Selection {
4320 id: selection.id,
4321 start: selection.start + delta,
4322 end: selection.end + delta,
4323 reversed: selection.reversed,
4324 goal: SelectionGoal::None,
4325 })
4326 .collect::<Vec<_>>();
4327
4328 let mut i = 0;
4329 for (position, delta, selection_id, pair) in new_autoclose_regions {
4330 let position = position.to_offset(&map.buffer_snapshot) + delta;
4331 let start = map.buffer_snapshot.anchor_before(position);
4332 let end = map.buffer_snapshot.anchor_after(position);
4333 while let Some(existing_state) = this.autoclose_regions.get(i) {
4334 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4335 Ordering::Less => i += 1,
4336 Ordering::Greater => break,
4337 Ordering::Equal => {
4338 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4339 Ordering::Less => i += 1,
4340 Ordering::Equal => break,
4341 Ordering::Greater => break,
4342 }
4343 }
4344 }
4345 }
4346 this.autoclose_regions.insert(
4347 i,
4348 AutocloseRegion {
4349 selection_id,
4350 range: start..end,
4351 pair,
4352 },
4353 );
4354 }
4355
4356 let had_active_edit_prediction = this.has_active_edit_prediction();
4357 this.change_selections(
4358 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4359 window,
4360 cx,
4361 |s| s.select(new_selections),
4362 );
4363
4364 if !bracket_inserted
4365 && let Some(on_type_format_task) =
4366 this.trigger_on_type_formatting(text.to_string(), window, cx)
4367 {
4368 on_type_format_task.detach_and_log_err(cx);
4369 }
4370
4371 let editor_settings = EditorSettings::get_global(cx);
4372 if bracket_inserted
4373 && (editor_settings.auto_signature_help
4374 || editor_settings.show_signature_help_after_edits)
4375 {
4376 this.show_signature_help(&ShowSignatureHelp, window, cx);
4377 }
4378
4379 let trigger_in_words =
4380 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4381 if this.hard_wrap.is_some() {
4382 let latest: Range<Point> = this.selections.newest(cx).range();
4383 if latest.is_empty()
4384 && this
4385 .buffer()
4386 .read(cx)
4387 .snapshot(cx)
4388 .line_len(MultiBufferRow(latest.start.row))
4389 == latest.start.column
4390 {
4391 this.rewrap_impl(
4392 RewrapOptions {
4393 override_language_settings: true,
4394 preserve_existing_whitespace: true,
4395 },
4396 cx,
4397 )
4398 }
4399 }
4400 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4401 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4402 this.refresh_edit_prediction(true, false, window, cx);
4403 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4404 });
4405 }
4406
4407 fn find_possible_emoji_shortcode_at_position(
4408 snapshot: &MultiBufferSnapshot,
4409 position: Point,
4410 ) -> Option<String> {
4411 let mut chars = Vec::new();
4412 let mut found_colon = false;
4413 for char in snapshot.reversed_chars_at(position).take(100) {
4414 // Found a possible emoji shortcode in the middle of the buffer
4415 if found_colon {
4416 if char.is_whitespace() {
4417 chars.reverse();
4418 return Some(chars.iter().collect());
4419 }
4420 // If the previous character is not a whitespace, we are in the middle of a word
4421 // and we only want to complete the shortcode if the word is made up of other emojis
4422 let mut containing_word = String::new();
4423 for ch in snapshot
4424 .reversed_chars_at(position)
4425 .skip(chars.len() + 1)
4426 .take(100)
4427 {
4428 if ch.is_whitespace() {
4429 break;
4430 }
4431 containing_word.push(ch);
4432 }
4433 let containing_word = containing_word.chars().rev().collect::<String>();
4434 if util::word_consists_of_emojis(containing_word.as_str()) {
4435 chars.reverse();
4436 return Some(chars.iter().collect());
4437 }
4438 }
4439
4440 if char.is_whitespace() || !char.is_ascii() {
4441 return None;
4442 }
4443 if char == ':' {
4444 found_colon = true;
4445 } else {
4446 chars.push(char);
4447 }
4448 }
4449 // Found a possible emoji shortcode at the beginning of the buffer
4450 chars.reverse();
4451 Some(chars.iter().collect())
4452 }
4453
4454 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4455 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4456 self.transact(window, cx, |this, window, cx| {
4457 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4458 let selections = this.selections.all::<usize>(cx);
4459 let multi_buffer = this.buffer.read(cx);
4460 let buffer = multi_buffer.snapshot(cx);
4461 selections
4462 .iter()
4463 .map(|selection| {
4464 let start_point = selection.start.to_point(&buffer);
4465 let mut existing_indent =
4466 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4467 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4468 let start = selection.start;
4469 let end = selection.end;
4470 let selection_is_empty = start == end;
4471 let language_scope = buffer.language_scope_at(start);
4472 let (
4473 comment_delimiter,
4474 doc_delimiter,
4475 insert_extra_newline,
4476 indent_on_newline,
4477 indent_on_extra_newline,
4478 ) = if let Some(language) = &language_scope {
4479 let mut insert_extra_newline =
4480 insert_extra_newline_brackets(&buffer, start..end, language)
4481 || insert_extra_newline_tree_sitter(&buffer, start..end);
4482
4483 // Comment extension on newline is allowed only for cursor selections
4484 let comment_delimiter = maybe!({
4485 if !selection_is_empty {
4486 return None;
4487 }
4488
4489 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4490 return None;
4491 }
4492
4493 let delimiters = language.line_comment_prefixes();
4494 let max_len_of_delimiter =
4495 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4496 let (snapshot, range) =
4497 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4498
4499 let num_of_whitespaces = snapshot
4500 .chars_for_range(range.clone())
4501 .take_while(|c| c.is_whitespace())
4502 .count();
4503 let comment_candidate = snapshot
4504 .chars_for_range(range.clone())
4505 .skip(num_of_whitespaces)
4506 .take(max_len_of_delimiter)
4507 .collect::<String>();
4508 let (delimiter, trimmed_len) = delimiters
4509 .iter()
4510 .filter_map(|delimiter| {
4511 let prefix = delimiter.trim_end();
4512 if comment_candidate.starts_with(prefix) {
4513 Some((delimiter, prefix.len()))
4514 } else {
4515 None
4516 }
4517 })
4518 .max_by_key(|(_, len)| *len)?;
4519
4520 if let Some(BlockCommentConfig {
4521 start: block_start, ..
4522 }) = language.block_comment()
4523 {
4524 let block_start_trimmed = block_start.trim_end();
4525 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4526 let line_content = snapshot
4527 .chars_for_range(range)
4528 .skip(num_of_whitespaces)
4529 .take(block_start_trimmed.len())
4530 .collect::<String>();
4531
4532 if line_content.starts_with(block_start_trimmed) {
4533 return None;
4534 }
4535 }
4536 }
4537
4538 let cursor_is_placed_after_comment_marker =
4539 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4540 if cursor_is_placed_after_comment_marker {
4541 Some(delimiter.clone())
4542 } else {
4543 None
4544 }
4545 });
4546
4547 let mut indent_on_newline = IndentSize::spaces(0);
4548 let mut indent_on_extra_newline = IndentSize::spaces(0);
4549
4550 let doc_delimiter = maybe!({
4551 if !selection_is_empty {
4552 return None;
4553 }
4554
4555 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4556 return None;
4557 }
4558
4559 let BlockCommentConfig {
4560 start: start_tag,
4561 end: end_tag,
4562 prefix: delimiter,
4563 tab_size: len,
4564 } = language.documentation_comment()?;
4565 let is_within_block_comment = buffer
4566 .language_scope_at(start_point)
4567 .is_some_and(|scope| scope.override_name() == Some("comment"));
4568 if !is_within_block_comment {
4569 return None;
4570 }
4571
4572 let (snapshot, range) =
4573 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4574
4575 let num_of_whitespaces = snapshot
4576 .chars_for_range(range.clone())
4577 .take_while(|c| c.is_whitespace())
4578 .count();
4579
4580 // 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.
4581 let column = start_point.column;
4582 let cursor_is_after_start_tag = {
4583 let start_tag_len = start_tag.len();
4584 let start_tag_line = snapshot
4585 .chars_for_range(range.clone())
4586 .skip(num_of_whitespaces)
4587 .take(start_tag_len)
4588 .collect::<String>();
4589 if start_tag_line.starts_with(start_tag.as_ref()) {
4590 num_of_whitespaces + start_tag_len <= column as usize
4591 } else {
4592 false
4593 }
4594 };
4595
4596 let cursor_is_after_delimiter = {
4597 let delimiter_trim = delimiter.trim_end();
4598 let delimiter_line = snapshot
4599 .chars_for_range(range.clone())
4600 .skip(num_of_whitespaces)
4601 .take(delimiter_trim.len())
4602 .collect::<String>();
4603 if delimiter_line.starts_with(delimiter_trim) {
4604 num_of_whitespaces + delimiter_trim.len() <= column as usize
4605 } else {
4606 false
4607 }
4608 };
4609
4610 let cursor_is_before_end_tag_if_exists = {
4611 let mut char_position = 0u32;
4612 let mut end_tag_offset = None;
4613
4614 'outer: for chunk in snapshot.text_for_range(range) {
4615 if let Some(byte_pos) = chunk.find(&**end_tag) {
4616 let chars_before_match =
4617 chunk[..byte_pos].chars().count() as u32;
4618 end_tag_offset =
4619 Some(char_position + chars_before_match);
4620 break 'outer;
4621 }
4622 char_position += chunk.chars().count() as u32;
4623 }
4624
4625 if let Some(end_tag_offset) = end_tag_offset {
4626 let cursor_is_before_end_tag = column <= end_tag_offset;
4627 if cursor_is_after_start_tag {
4628 if cursor_is_before_end_tag {
4629 insert_extra_newline = true;
4630 }
4631 let cursor_is_at_start_of_end_tag =
4632 column == end_tag_offset;
4633 if cursor_is_at_start_of_end_tag {
4634 indent_on_extra_newline.len = *len;
4635 }
4636 }
4637 cursor_is_before_end_tag
4638 } else {
4639 true
4640 }
4641 };
4642
4643 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4644 && cursor_is_before_end_tag_if_exists
4645 {
4646 if cursor_is_after_start_tag {
4647 indent_on_newline.len = *len;
4648 }
4649 Some(delimiter.clone())
4650 } else {
4651 None
4652 }
4653 });
4654
4655 (
4656 comment_delimiter,
4657 doc_delimiter,
4658 insert_extra_newline,
4659 indent_on_newline,
4660 indent_on_extra_newline,
4661 )
4662 } else {
4663 (
4664 None,
4665 None,
4666 false,
4667 IndentSize::default(),
4668 IndentSize::default(),
4669 )
4670 };
4671
4672 let prevent_auto_indent = doc_delimiter.is_some();
4673 let delimiter = comment_delimiter.or(doc_delimiter);
4674
4675 let capacity_for_delimiter =
4676 delimiter.as_deref().map(str::len).unwrap_or_default();
4677 let mut new_text = String::with_capacity(
4678 1 + capacity_for_delimiter
4679 + existing_indent.len as usize
4680 + indent_on_newline.len as usize
4681 + indent_on_extra_newline.len as usize,
4682 );
4683 new_text.push('\n');
4684 new_text.extend(existing_indent.chars());
4685 new_text.extend(indent_on_newline.chars());
4686
4687 if let Some(delimiter) = &delimiter {
4688 new_text.push_str(delimiter);
4689 }
4690
4691 if insert_extra_newline {
4692 new_text.push('\n');
4693 new_text.extend(existing_indent.chars());
4694 new_text.extend(indent_on_extra_newline.chars());
4695 }
4696
4697 let anchor = buffer.anchor_after(end);
4698 let new_selection = selection.map(|_| anchor);
4699 (
4700 ((start..end, new_text), prevent_auto_indent),
4701 (insert_extra_newline, new_selection),
4702 )
4703 })
4704 .unzip()
4705 };
4706
4707 let mut auto_indent_edits = Vec::new();
4708 let mut edits = Vec::new();
4709 for (edit, prevent_auto_indent) in edits_with_flags {
4710 if prevent_auto_indent {
4711 edits.push(edit);
4712 } else {
4713 auto_indent_edits.push(edit);
4714 }
4715 }
4716 if !edits.is_empty() {
4717 this.edit(edits, cx);
4718 }
4719 if !auto_indent_edits.is_empty() {
4720 this.edit_with_autoindent(auto_indent_edits, cx);
4721 }
4722
4723 let buffer = this.buffer.read(cx).snapshot(cx);
4724 let new_selections = selection_info
4725 .into_iter()
4726 .map(|(extra_newline_inserted, new_selection)| {
4727 let mut cursor = new_selection.end.to_point(&buffer);
4728 if extra_newline_inserted {
4729 cursor.row -= 1;
4730 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4731 }
4732 new_selection.map(|_| cursor)
4733 })
4734 .collect();
4735
4736 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4737 this.refresh_edit_prediction(true, false, window, cx);
4738 });
4739 }
4740
4741 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4743
4744 let buffer = self.buffer.read(cx);
4745 let snapshot = buffer.snapshot(cx);
4746
4747 let mut edits = Vec::new();
4748 let mut rows = Vec::new();
4749
4750 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4751 let cursor = selection.head();
4752 let row = cursor.row;
4753
4754 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4755
4756 let newline = "\n".to_string();
4757 edits.push((start_of_line..start_of_line, newline));
4758
4759 rows.push(row + rows_inserted as u32);
4760 }
4761
4762 self.transact(window, cx, |editor, window, cx| {
4763 editor.edit(edits, cx);
4764
4765 editor.change_selections(Default::default(), window, cx, |s| {
4766 let mut index = 0;
4767 s.move_cursors_with(|map, _, _| {
4768 let row = rows[index];
4769 index += 1;
4770
4771 let point = Point::new(row, 0);
4772 let boundary = map.next_line_boundary(point).1;
4773 let clipped = map.clip_point(boundary, Bias::Left);
4774
4775 (clipped, SelectionGoal::None)
4776 });
4777 });
4778
4779 let mut indent_edits = Vec::new();
4780 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4781 for row in rows {
4782 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4783 for (row, indent) in indents {
4784 if indent.len == 0 {
4785 continue;
4786 }
4787
4788 let text = match indent.kind {
4789 IndentKind::Space => " ".repeat(indent.len as usize),
4790 IndentKind::Tab => "\t".repeat(indent.len as usize),
4791 };
4792 let point = Point::new(row.0, 0);
4793 indent_edits.push((point..point, text));
4794 }
4795 }
4796 editor.edit(indent_edits, cx);
4797 });
4798 }
4799
4800 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4801 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4802
4803 let buffer = self.buffer.read(cx);
4804 let snapshot = buffer.snapshot(cx);
4805
4806 let mut edits = Vec::new();
4807 let mut rows = Vec::new();
4808 let mut rows_inserted = 0;
4809
4810 for selection in self.selections.all_adjusted(cx) {
4811 let cursor = selection.head();
4812 let row = cursor.row;
4813
4814 let point = Point::new(row + 1, 0);
4815 let start_of_line = snapshot.clip_point(point, Bias::Left);
4816
4817 let newline = "\n".to_string();
4818 edits.push((start_of_line..start_of_line, newline));
4819
4820 rows_inserted += 1;
4821 rows.push(row + rows_inserted);
4822 }
4823
4824 self.transact(window, cx, |editor, window, cx| {
4825 editor.edit(edits, cx);
4826
4827 editor.change_selections(Default::default(), window, cx, |s| {
4828 let mut index = 0;
4829 s.move_cursors_with(|map, _, _| {
4830 let row = rows[index];
4831 index += 1;
4832
4833 let point = Point::new(row, 0);
4834 let boundary = map.next_line_boundary(point).1;
4835 let clipped = map.clip_point(boundary, Bias::Left);
4836
4837 (clipped, SelectionGoal::None)
4838 });
4839 });
4840
4841 let mut indent_edits = Vec::new();
4842 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4843 for row in rows {
4844 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4845 for (row, indent) in indents {
4846 if indent.len == 0 {
4847 continue;
4848 }
4849
4850 let text = match indent.kind {
4851 IndentKind::Space => " ".repeat(indent.len as usize),
4852 IndentKind::Tab => "\t".repeat(indent.len as usize),
4853 };
4854 let point = Point::new(row.0, 0);
4855 indent_edits.push((point..point, text));
4856 }
4857 }
4858 editor.edit(indent_edits, cx);
4859 });
4860 }
4861
4862 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4863 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4864 original_indent_columns: Vec::new(),
4865 });
4866 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4867 }
4868
4869 fn insert_with_autoindent_mode(
4870 &mut self,
4871 text: &str,
4872 autoindent_mode: Option<AutoindentMode>,
4873 window: &mut Window,
4874 cx: &mut Context<Self>,
4875 ) {
4876 if self.read_only(cx) {
4877 return;
4878 }
4879
4880 let text: Arc<str> = text.into();
4881 self.transact(window, cx, |this, window, cx| {
4882 let old_selections = this.selections.all_adjusted(cx);
4883 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4884 let anchors = {
4885 let snapshot = buffer.read(cx);
4886 old_selections
4887 .iter()
4888 .map(|s| {
4889 let anchor = snapshot.anchor_after(s.head());
4890 s.map(|_| anchor)
4891 })
4892 .collect::<Vec<_>>()
4893 };
4894 buffer.edit(
4895 old_selections
4896 .iter()
4897 .map(|s| (s.start..s.end, text.clone())),
4898 autoindent_mode,
4899 cx,
4900 );
4901 anchors
4902 });
4903
4904 this.change_selections(Default::default(), window, cx, |s| {
4905 s.select_anchors(selection_anchors);
4906 });
4907
4908 cx.notify();
4909 });
4910 }
4911
4912 fn trigger_completion_on_input(
4913 &mut self,
4914 text: &str,
4915 trigger_in_words: bool,
4916 window: &mut Window,
4917 cx: &mut Context<Self>,
4918 ) {
4919 let completions_source = self
4920 .context_menu
4921 .borrow()
4922 .as_ref()
4923 .and_then(|menu| match menu {
4924 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4925 CodeContextMenu::CodeActions(_) => None,
4926 });
4927
4928 match completions_source {
4929 Some(CompletionsMenuSource::Words { .. }) => {
4930 self.open_or_update_completions_menu(
4931 Some(CompletionsMenuSource::Words {
4932 ignore_threshold: false,
4933 }),
4934 None,
4935 window,
4936 cx,
4937 );
4938 }
4939 Some(CompletionsMenuSource::Normal)
4940 | Some(CompletionsMenuSource::SnippetChoices)
4941 | None
4942 if self.is_completion_trigger(
4943 text,
4944 trigger_in_words,
4945 completions_source.is_some(),
4946 cx,
4947 ) =>
4948 {
4949 self.show_completions(
4950 &ShowCompletions {
4951 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4952 },
4953 window,
4954 cx,
4955 )
4956 }
4957 _ => {
4958 self.hide_context_menu(window, cx);
4959 }
4960 }
4961 }
4962
4963 fn is_completion_trigger(
4964 &self,
4965 text: &str,
4966 trigger_in_words: bool,
4967 menu_is_open: bool,
4968 cx: &mut Context<Self>,
4969 ) -> bool {
4970 let position = self.selections.newest_anchor().head();
4971 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4972 return false;
4973 };
4974
4975 if let Some(completion_provider) = &self.completion_provider {
4976 completion_provider.is_completion_trigger(
4977 &buffer,
4978 position.text_anchor,
4979 text,
4980 trigger_in_words,
4981 menu_is_open,
4982 cx,
4983 )
4984 } else {
4985 false
4986 }
4987 }
4988
4989 /// If any empty selections is touching the start of its innermost containing autoclose
4990 /// region, expand it to select the brackets.
4991 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4992 let selections = self.selections.all::<usize>(cx);
4993 let buffer = self.buffer.read(cx).read(cx);
4994 let new_selections = self
4995 .selections_with_autoclose_regions(selections, &buffer)
4996 .map(|(mut selection, region)| {
4997 if !selection.is_empty() {
4998 return selection;
4999 }
5000
5001 if let Some(region) = region {
5002 let mut range = region.range.to_offset(&buffer);
5003 if selection.start == range.start && range.start >= region.pair.start.len() {
5004 range.start -= region.pair.start.len();
5005 if buffer.contains_str_at(range.start, ®ion.pair.start)
5006 && buffer.contains_str_at(range.end, ®ion.pair.end)
5007 {
5008 range.end += region.pair.end.len();
5009 selection.start = range.start;
5010 selection.end = range.end;
5011
5012 return selection;
5013 }
5014 }
5015 }
5016
5017 let always_treat_brackets_as_autoclosed = buffer
5018 .language_settings_at(selection.start, cx)
5019 .always_treat_brackets_as_autoclosed;
5020
5021 if !always_treat_brackets_as_autoclosed {
5022 return selection;
5023 }
5024
5025 if let Some(scope) = buffer.language_scope_at(selection.start) {
5026 for (pair, enabled) in scope.brackets() {
5027 if !enabled || !pair.close {
5028 continue;
5029 }
5030
5031 if buffer.contains_str_at(selection.start, &pair.end) {
5032 let pair_start_len = pair.start.len();
5033 if buffer.contains_str_at(
5034 selection.start.saturating_sub(pair_start_len),
5035 &pair.start,
5036 ) {
5037 selection.start -= pair_start_len;
5038 selection.end += pair.end.len();
5039
5040 return selection;
5041 }
5042 }
5043 }
5044 }
5045
5046 selection
5047 })
5048 .collect();
5049
5050 drop(buffer);
5051 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5052 selections.select(new_selections)
5053 });
5054 }
5055
5056 /// Iterate the given selections, and for each one, find the smallest surrounding
5057 /// autoclose region. This uses the ordering of the selections and the autoclose
5058 /// regions to avoid repeated comparisons.
5059 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5060 &'a self,
5061 selections: impl IntoIterator<Item = Selection<D>>,
5062 buffer: &'a MultiBufferSnapshot,
5063 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5064 let mut i = 0;
5065 let mut regions = self.autoclose_regions.as_slice();
5066 selections.into_iter().map(move |selection| {
5067 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5068
5069 let mut enclosing = None;
5070 while let Some(pair_state) = regions.get(i) {
5071 if pair_state.range.end.to_offset(buffer) < range.start {
5072 regions = ®ions[i + 1..];
5073 i = 0;
5074 } else if pair_state.range.start.to_offset(buffer) > range.end {
5075 break;
5076 } else {
5077 if pair_state.selection_id == selection.id {
5078 enclosing = Some(pair_state);
5079 }
5080 i += 1;
5081 }
5082 }
5083
5084 (selection, enclosing)
5085 })
5086 }
5087
5088 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5089 fn invalidate_autoclose_regions(
5090 &mut self,
5091 mut selections: &[Selection<Anchor>],
5092 buffer: &MultiBufferSnapshot,
5093 ) {
5094 self.autoclose_regions.retain(|state| {
5095 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5096 return false;
5097 }
5098
5099 let mut i = 0;
5100 while let Some(selection) = selections.get(i) {
5101 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5102 selections = &selections[1..];
5103 continue;
5104 }
5105 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5106 break;
5107 }
5108 if selection.id == state.selection_id {
5109 return true;
5110 } else {
5111 i += 1;
5112 }
5113 }
5114 false
5115 });
5116 }
5117
5118 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5119 let offset = position.to_offset(buffer);
5120 let (word_range, kind) =
5121 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5122 if offset > word_range.start && kind == Some(CharKind::Word) {
5123 Some(
5124 buffer
5125 .text_for_range(word_range.start..offset)
5126 .collect::<String>(),
5127 )
5128 } else {
5129 None
5130 }
5131 }
5132
5133 pub fn toggle_inline_values(
5134 &mut self,
5135 _: &ToggleInlineValues,
5136 _: &mut Window,
5137 cx: &mut Context<Self>,
5138 ) {
5139 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5140
5141 self.refresh_inline_values(cx);
5142 }
5143
5144 pub fn toggle_inlay_hints(
5145 &mut self,
5146 _: &ToggleInlayHints,
5147 _: &mut Window,
5148 cx: &mut Context<Self>,
5149 ) {
5150 self.refresh_inlay_hints(
5151 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5152 cx,
5153 );
5154 }
5155
5156 pub fn inlay_hints_enabled(&self) -> bool {
5157 self.inlay_hint_cache.enabled
5158 }
5159
5160 pub fn inline_values_enabled(&self) -> bool {
5161 self.inline_value_cache.enabled
5162 }
5163
5164 #[cfg(any(test, feature = "test-support"))]
5165 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5166 self.display_map
5167 .read(cx)
5168 .current_inlays()
5169 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5170 .cloned()
5171 .collect()
5172 }
5173
5174 #[cfg(any(test, feature = "test-support"))]
5175 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5176 self.display_map
5177 .read(cx)
5178 .current_inlays()
5179 .cloned()
5180 .collect()
5181 }
5182
5183 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5184 if self.semantics_provider.is_none() || !self.mode.is_full() {
5185 return;
5186 }
5187
5188 let reason_description = reason.description();
5189 let ignore_debounce = matches!(
5190 reason,
5191 InlayHintRefreshReason::SettingsChange(_)
5192 | InlayHintRefreshReason::Toggle(_)
5193 | InlayHintRefreshReason::ExcerptsRemoved(_)
5194 | InlayHintRefreshReason::ModifiersChanged(_)
5195 );
5196 let (invalidate_cache, required_languages) = match reason {
5197 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5198 match self.inlay_hint_cache.modifiers_override(enabled) {
5199 Some(enabled) => {
5200 if enabled {
5201 (InvalidationStrategy::RefreshRequested, None)
5202 } else {
5203 self.splice_inlays(
5204 &self
5205 .visible_inlay_hints(cx)
5206 .iter()
5207 .map(|inlay| inlay.id)
5208 .collect::<Vec<InlayId>>(),
5209 Vec::new(),
5210 cx,
5211 );
5212 return;
5213 }
5214 }
5215 None => return,
5216 }
5217 }
5218 InlayHintRefreshReason::Toggle(enabled) => {
5219 if self.inlay_hint_cache.toggle(enabled) {
5220 if enabled {
5221 (InvalidationStrategy::RefreshRequested, None)
5222 } else {
5223 self.splice_inlays(
5224 &self
5225 .visible_inlay_hints(cx)
5226 .iter()
5227 .map(|inlay| inlay.id)
5228 .collect::<Vec<InlayId>>(),
5229 Vec::new(),
5230 cx,
5231 );
5232 return;
5233 }
5234 } else {
5235 return;
5236 }
5237 }
5238 InlayHintRefreshReason::SettingsChange(new_settings) => {
5239 match self.inlay_hint_cache.update_settings(
5240 &self.buffer,
5241 new_settings,
5242 self.visible_inlay_hints(cx),
5243 cx,
5244 ) {
5245 ControlFlow::Break(Some(InlaySplice {
5246 to_remove,
5247 to_insert,
5248 })) => {
5249 self.splice_inlays(&to_remove, to_insert, cx);
5250 return;
5251 }
5252 ControlFlow::Break(None) => return,
5253 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5254 }
5255 }
5256 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5257 if let Some(InlaySplice {
5258 to_remove,
5259 to_insert,
5260 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5261 {
5262 self.splice_inlays(&to_remove, to_insert, cx);
5263 }
5264 self.display_map.update(cx, |display_map, _| {
5265 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5266 });
5267 return;
5268 }
5269 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5270 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5271 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5272 }
5273 InlayHintRefreshReason::RefreshRequested => {
5274 (InvalidationStrategy::RefreshRequested, None)
5275 }
5276 };
5277
5278 if let Some(InlaySplice {
5279 to_remove,
5280 to_insert,
5281 }) = self.inlay_hint_cache.spawn_hint_refresh(
5282 reason_description,
5283 self.visible_excerpts(required_languages.as_ref(), cx),
5284 invalidate_cache,
5285 ignore_debounce,
5286 cx,
5287 ) {
5288 self.splice_inlays(&to_remove, to_insert, cx);
5289 }
5290 }
5291
5292 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5293 self.display_map
5294 .read(cx)
5295 .current_inlays()
5296 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5297 .cloned()
5298 .collect()
5299 }
5300
5301 pub fn visible_excerpts(
5302 &self,
5303 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5304 cx: &mut Context<Editor>,
5305 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5306 let Some(project) = self.project() else {
5307 return HashMap::default();
5308 };
5309 let project = project.read(cx);
5310 let multi_buffer = self.buffer().read(cx);
5311 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5312 let multi_buffer_visible_start = self
5313 .scroll_manager
5314 .anchor()
5315 .anchor
5316 .to_point(&multi_buffer_snapshot);
5317 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5318 multi_buffer_visible_start
5319 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5320 Bias::Left,
5321 );
5322 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5323 multi_buffer_snapshot
5324 .range_to_buffer_ranges(multi_buffer_visible_range)
5325 .into_iter()
5326 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5327 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5328 let buffer_file = project::File::from_dyn(buffer.file())?;
5329 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5330 let worktree_entry = buffer_worktree
5331 .read(cx)
5332 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5333 if worktree_entry.is_ignored {
5334 return None;
5335 }
5336
5337 let language = buffer.language()?;
5338 if let Some(restrict_to_languages) = restrict_to_languages
5339 && !restrict_to_languages.contains(language)
5340 {
5341 return None;
5342 }
5343 Some((
5344 excerpt_id,
5345 (
5346 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5347 buffer.version().clone(),
5348 excerpt_visible_range,
5349 ),
5350 ))
5351 })
5352 .collect()
5353 }
5354
5355 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5356 TextLayoutDetails {
5357 text_system: window.text_system().clone(),
5358 editor_style: self.style.clone().unwrap(),
5359 rem_size: window.rem_size(),
5360 scroll_anchor: self.scroll_manager.anchor(),
5361 visible_rows: self.visible_line_count(),
5362 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5363 }
5364 }
5365
5366 pub fn splice_inlays(
5367 &self,
5368 to_remove: &[InlayId],
5369 to_insert: Vec<Inlay>,
5370 cx: &mut Context<Self>,
5371 ) {
5372 self.display_map.update(cx, |display_map, cx| {
5373 display_map.splice_inlays(to_remove, to_insert, cx)
5374 });
5375 cx.notify();
5376 }
5377
5378 fn trigger_on_type_formatting(
5379 &self,
5380 input: String,
5381 window: &mut Window,
5382 cx: &mut Context<Self>,
5383 ) -> Option<Task<Result<()>>> {
5384 if input.len() != 1 {
5385 return None;
5386 }
5387
5388 let project = self.project()?;
5389 let position = self.selections.newest_anchor().head();
5390 let (buffer, buffer_position) = self
5391 .buffer
5392 .read(cx)
5393 .text_anchor_for_position(position, cx)?;
5394
5395 let settings = language_settings::language_settings(
5396 buffer
5397 .read(cx)
5398 .language_at(buffer_position)
5399 .map(|l| l.name()),
5400 buffer.read(cx).file(),
5401 cx,
5402 );
5403 if !settings.use_on_type_format {
5404 return None;
5405 }
5406
5407 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5408 // hence we do LSP request & edit on host side only — add formats to host's history.
5409 let push_to_lsp_host_history = true;
5410 // If this is not the host, append its history with new edits.
5411 let push_to_client_history = project.read(cx).is_via_collab();
5412
5413 let on_type_formatting = project.update(cx, |project, cx| {
5414 project.on_type_format(
5415 buffer.clone(),
5416 buffer_position,
5417 input,
5418 push_to_lsp_host_history,
5419 cx,
5420 )
5421 });
5422 Some(cx.spawn_in(window, async move |editor, cx| {
5423 if let Some(transaction) = on_type_formatting.await? {
5424 if push_to_client_history {
5425 buffer
5426 .update(cx, |buffer, _| {
5427 buffer.push_transaction(transaction, Instant::now());
5428 buffer.finalize_last_transaction();
5429 })
5430 .ok();
5431 }
5432 editor.update(cx, |editor, cx| {
5433 editor.refresh_document_highlights(cx);
5434 })?;
5435 }
5436 Ok(())
5437 }))
5438 }
5439
5440 pub fn show_word_completions(
5441 &mut self,
5442 _: &ShowWordCompletions,
5443 window: &mut Window,
5444 cx: &mut Context<Self>,
5445 ) {
5446 self.open_or_update_completions_menu(
5447 Some(CompletionsMenuSource::Words {
5448 ignore_threshold: true,
5449 }),
5450 None,
5451 window,
5452 cx,
5453 );
5454 }
5455
5456 pub fn show_completions(
5457 &mut self,
5458 options: &ShowCompletions,
5459 window: &mut Window,
5460 cx: &mut Context<Self>,
5461 ) {
5462 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5463 }
5464
5465 fn open_or_update_completions_menu(
5466 &mut self,
5467 requested_source: Option<CompletionsMenuSource>,
5468 trigger: Option<&str>,
5469 window: &mut Window,
5470 cx: &mut Context<Self>,
5471 ) {
5472 if self.pending_rename.is_some() {
5473 return;
5474 }
5475
5476 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5477
5478 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5479 // inserted and selected. To handle that case, the start of the selection is used so that
5480 // the menu starts with all choices.
5481 let position = self
5482 .selections
5483 .newest_anchor()
5484 .start
5485 .bias_right(&multibuffer_snapshot);
5486 if position.diff_base_anchor.is_some() {
5487 return;
5488 }
5489 let buffer_position = multibuffer_snapshot.anchor_before(position);
5490 let Some(buffer) = buffer_position
5491 .buffer_id
5492 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5493 else {
5494 return;
5495 };
5496 let buffer_snapshot = buffer.read(cx).snapshot();
5497
5498 let query: Option<Arc<String>> =
5499 Self::completion_query(&multibuffer_snapshot, buffer_position)
5500 .map(|query| query.into());
5501
5502 drop(multibuffer_snapshot);
5503
5504 // Hide the current completions menu when query is empty. Without this, cached
5505 // completions from before the trigger char may be reused (#32774).
5506 if query.is_none() {
5507 let menu_is_open = matches!(
5508 self.context_menu.borrow().as_ref(),
5509 Some(CodeContextMenu::Completions(_))
5510 );
5511 if menu_is_open {
5512 self.hide_context_menu(window, cx);
5513 }
5514 }
5515
5516 let mut ignore_word_threshold = false;
5517 let provider = match requested_source {
5518 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5519 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5520 ignore_word_threshold = ignore_threshold;
5521 None
5522 }
5523 Some(CompletionsMenuSource::SnippetChoices) => {
5524 log::error!("bug: SnippetChoices requested_source is not handled");
5525 None
5526 }
5527 };
5528
5529 let sort_completions = provider
5530 .as_ref()
5531 .is_some_and(|provider| provider.sort_completions());
5532
5533 let filter_completions = provider
5534 .as_ref()
5535 .is_none_or(|provider| provider.filter_completions());
5536
5537 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5538 if filter_completions {
5539 menu.filter(query.clone(), provider.clone(), window, cx);
5540 }
5541 // When `is_incomplete` is false, no need to re-query completions when the current query
5542 // is a suffix of the initial query.
5543 if !menu.is_incomplete {
5544 // If the new query is a suffix of the old query (typing more characters) and
5545 // the previous result was complete, the existing completions can be filtered.
5546 //
5547 // Note that this is always true for snippet completions.
5548 let query_matches = match (&menu.initial_query, &query) {
5549 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5550 (None, _) => true,
5551 _ => false,
5552 };
5553 if query_matches {
5554 let position_matches = if menu.initial_position == position {
5555 true
5556 } else {
5557 let snapshot = self.buffer.read(cx).read(cx);
5558 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5559 };
5560 if position_matches {
5561 return;
5562 }
5563 }
5564 }
5565 };
5566
5567 let trigger_kind = match trigger {
5568 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5569 CompletionTriggerKind::TRIGGER_CHARACTER
5570 }
5571 _ => CompletionTriggerKind::INVOKED,
5572 };
5573 let completion_context = CompletionContext {
5574 trigger_character: trigger.and_then(|trigger| {
5575 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5576 Some(String::from(trigger))
5577 } else {
5578 None
5579 }
5580 }),
5581 trigger_kind,
5582 };
5583
5584 let Anchor {
5585 excerpt_id: buffer_excerpt_id,
5586 text_anchor: buffer_position,
5587 ..
5588 } = buffer_position;
5589
5590 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5591 buffer_snapshot.surrounding_word(buffer_position, None)
5592 {
5593 let word_to_exclude = buffer_snapshot
5594 .text_for_range(word_range.clone())
5595 .collect::<String>();
5596 (
5597 buffer_snapshot.anchor_before(word_range.start)
5598 ..buffer_snapshot.anchor_after(buffer_position),
5599 Some(word_to_exclude),
5600 )
5601 } else {
5602 (buffer_position..buffer_position, None)
5603 };
5604
5605 let language = buffer_snapshot
5606 .language_at(buffer_position)
5607 .map(|language| language.name());
5608
5609 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5610 .completions
5611 .clone();
5612
5613 let show_completion_documentation = buffer_snapshot
5614 .settings_at(buffer_position, cx)
5615 .show_completion_documentation;
5616
5617 // The document can be large, so stay in reasonable bounds when searching for words,
5618 // otherwise completion pop-up might be slow to appear.
5619 const WORD_LOOKUP_ROWS: u32 = 5_000;
5620 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5621 let min_word_search = buffer_snapshot.clip_point(
5622 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5623 Bias::Left,
5624 );
5625 let max_word_search = buffer_snapshot.clip_point(
5626 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5627 Bias::Right,
5628 );
5629 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5630 ..buffer_snapshot.point_to_offset(max_word_search);
5631
5632 let skip_digits = query
5633 .as_ref()
5634 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5635
5636 let omit_word_completions = !self.word_completions_enabled
5637 || (!ignore_word_threshold
5638 && match &query {
5639 Some(query) => query.chars().count() < completion_settings.words_min_length,
5640 None => completion_settings.words_min_length != 0,
5641 });
5642
5643 let (mut words, provider_responses) = match &provider {
5644 Some(provider) => {
5645 let provider_responses = provider.completions(
5646 buffer_excerpt_id,
5647 &buffer,
5648 buffer_position,
5649 completion_context,
5650 window,
5651 cx,
5652 );
5653
5654 let words = match (omit_word_completions, completion_settings.words) {
5655 (true, _) | (_, WordsCompletionMode::Disabled) => {
5656 Task::ready(BTreeMap::default())
5657 }
5658 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5659 .background_spawn(async move {
5660 buffer_snapshot.words_in_range(WordsQuery {
5661 fuzzy_contents: None,
5662 range: word_search_range,
5663 skip_digits,
5664 })
5665 }),
5666 };
5667
5668 (words, provider_responses)
5669 }
5670 None => {
5671 let words = if omit_word_completions {
5672 Task::ready(BTreeMap::default())
5673 } else {
5674 cx.background_spawn(async move {
5675 buffer_snapshot.words_in_range(WordsQuery {
5676 fuzzy_contents: None,
5677 range: word_search_range,
5678 skip_digits,
5679 })
5680 })
5681 };
5682 (words, Task::ready(Ok(Vec::new())))
5683 }
5684 };
5685
5686 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5687
5688 let id = post_inc(&mut self.next_completion_id);
5689 let task = cx.spawn_in(window, async move |editor, cx| {
5690 let Ok(()) = editor.update(cx, |this, _| {
5691 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5692 }) else {
5693 return;
5694 };
5695
5696 // TODO: Ideally completions from different sources would be selectively re-queried, so
5697 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5698 let mut completions = Vec::new();
5699 let mut is_incomplete = false;
5700 let mut display_options: Option<CompletionDisplayOptions> = None;
5701 if let Some(provider_responses) = provider_responses.await.log_err()
5702 && !provider_responses.is_empty()
5703 {
5704 for response in provider_responses {
5705 completions.extend(response.completions);
5706 is_incomplete = is_incomplete || response.is_incomplete;
5707 match display_options.as_mut() {
5708 None => {
5709 display_options = Some(response.display_options);
5710 }
5711 Some(options) => options.merge(&response.display_options),
5712 }
5713 }
5714 if completion_settings.words == WordsCompletionMode::Fallback {
5715 words = Task::ready(BTreeMap::default());
5716 }
5717 }
5718 let display_options = display_options.unwrap_or_default();
5719
5720 let mut words = words.await;
5721 if let Some(word_to_exclude) = &word_to_exclude {
5722 words.remove(word_to_exclude);
5723 }
5724 for lsp_completion in &completions {
5725 words.remove(&lsp_completion.new_text);
5726 }
5727 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5728 replace_range: word_replace_range.clone(),
5729 new_text: word.clone(),
5730 label: CodeLabel::plain(word, None),
5731 icon_path: None,
5732 documentation: None,
5733 source: CompletionSource::BufferWord {
5734 word_range,
5735 resolved: false,
5736 },
5737 insert_text_mode: Some(InsertTextMode::AS_IS),
5738 confirm: None,
5739 }));
5740
5741 let menu = if completions.is_empty() {
5742 None
5743 } else {
5744 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5745 let languages = editor
5746 .workspace
5747 .as_ref()
5748 .and_then(|(workspace, _)| workspace.upgrade())
5749 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5750 let menu = CompletionsMenu::new(
5751 id,
5752 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5753 sort_completions,
5754 show_completion_documentation,
5755 position,
5756 query.clone(),
5757 is_incomplete,
5758 buffer.clone(),
5759 completions.into(),
5760 display_options,
5761 snippet_sort_order,
5762 languages,
5763 language,
5764 cx,
5765 );
5766
5767 let query = if filter_completions { query } else { None };
5768 let matches_task = if let Some(query) = query {
5769 menu.do_async_filtering(query, cx)
5770 } else {
5771 Task::ready(menu.unfiltered_matches())
5772 };
5773 (menu, matches_task)
5774 }) else {
5775 return;
5776 };
5777
5778 let matches = matches_task.await;
5779
5780 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5781 // Newer menu already set, so exit.
5782 if let Some(CodeContextMenu::Completions(prev_menu)) =
5783 editor.context_menu.borrow().as_ref()
5784 && prev_menu.id > id
5785 {
5786 return;
5787 };
5788
5789 // Only valid to take prev_menu because it the new menu is immediately set
5790 // below, or the menu is hidden.
5791 if let Some(CodeContextMenu::Completions(prev_menu)) =
5792 editor.context_menu.borrow_mut().take()
5793 {
5794 let position_matches =
5795 if prev_menu.initial_position == menu.initial_position {
5796 true
5797 } else {
5798 let snapshot = editor.buffer.read(cx).read(cx);
5799 prev_menu.initial_position.to_offset(&snapshot)
5800 == menu.initial_position.to_offset(&snapshot)
5801 };
5802 if position_matches {
5803 // Preserve markdown cache before `set_filter_results` because it will
5804 // try to populate the documentation cache.
5805 menu.preserve_markdown_cache(prev_menu);
5806 }
5807 };
5808
5809 menu.set_filter_results(matches, provider, window, cx);
5810 }) else {
5811 return;
5812 };
5813
5814 menu.visible().then_some(menu)
5815 };
5816
5817 editor
5818 .update_in(cx, |editor, window, cx| {
5819 if editor.focus_handle.is_focused(window)
5820 && let Some(menu) = menu
5821 {
5822 *editor.context_menu.borrow_mut() =
5823 Some(CodeContextMenu::Completions(menu));
5824
5825 crate::hover_popover::hide_hover(editor, cx);
5826 if editor.show_edit_predictions_in_menu() {
5827 editor.update_visible_edit_prediction(window, cx);
5828 } else {
5829 editor.discard_edit_prediction(false, cx);
5830 }
5831
5832 cx.notify();
5833 return;
5834 }
5835
5836 if editor.completion_tasks.len() <= 1 {
5837 // If there are no more completion tasks and the last menu was empty, we should hide it.
5838 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5839 // If it was already hidden and we don't show edit predictions in the menu,
5840 // we should also show the edit prediction when available.
5841 if was_hidden && editor.show_edit_predictions_in_menu() {
5842 editor.update_visible_edit_prediction(window, cx);
5843 }
5844 }
5845 })
5846 .ok();
5847 });
5848
5849 self.completion_tasks.push((id, task));
5850 }
5851
5852 #[cfg(feature = "test-support")]
5853 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5854 let menu = self.context_menu.borrow();
5855 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5856 let completions = menu.completions.borrow();
5857 Some(completions.to_vec())
5858 } else {
5859 None
5860 }
5861 }
5862
5863 pub fn with_completions_menu_matching_id<R>(
5864 &self,
5865 id: CompletionId,
5866 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5867 ) -> R {
5868 let mut context_menu = self.context_menu.borrow_mut();
5869 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5870 return f(None);
5871 };
5872 if completions_menu.id != id {
5873 return f(None);
5874 }
5875 f(Some(completions_menu))
5876 }
5877
5878 pub fn confirm_completion(
5879 &mut self,
5880 action: &ConfirmCompletion,
5881 window: &mut Window,
5882 cx: &mut Context<Self>,
5883 ) -> Option<Task<Result<()>>> {
5884 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5885 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5886 }
5887
5888 pub fn confirm_completion_insert(
5889 &mut self,
5890 _: &ConfirmCompletionInsert,
5891 window: &mut Window,
5892 cx: &mut Context<Self>,
5893 ) -> Option<Task<Result<()>>> {
5894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5895 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5896 }
5897
5898 pub fn confirm_completion_replace(
5899 &mut self,
5900 _: &ConfirmCompletionReplace,
5901 window: &mut Window,
5902 cx: &mut Context<Self>,
5903 ) -> Option<Task<Result<()>>> {
5904 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5905 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5906 }
5907
5908 pub fn compose_completion(
5909 &mut self,
5910 action: &ComposeCompletion,
5911 window: &mut Window,
5912 cx: &mut Context<Self>,
5913 ) -> Option<Task<Result<()>>> {
5914 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5915 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5916 }
5917
5918 fn do_completion(
5919 &mut self,
5920 item_ix: Option<usize>,
5921 intent: CompletionIntent,
5922 window: &mut Window,
5923 cx: &mut Context<Editor>,
5924 ) -> Option<Task<Result<()>>> {
5925 use language::ToOffset as _;
5926
5927 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5928 else {
5929 return None;
5930 };
5931
5932 let candidate_id = {
5933 let entries = completions_menu.entries.borrow();
5934 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5935 if self.show_edit_predictions_in_menu() {
5936 self.discard_edit_prediction(true, cx);
5937 }
5938 mat.candidate_id
5939 };
5940
5941 let completion = completions_menu
5942 .completions
5943 .borrow()
5944 .get(candidate_id)?
5945 .clone();
5946 cx.stop_propagation();
5947
5948 let buffer_handle = completions_menu.buffer.clone();
5949
5950 let CompletionEdit {
5951 new_text,
5952 snippet,
5953 replace_range,
5954 } = process_completion_for_edit(
5955 &completion,
5956 intent,
5957 &buffer_handle,
5958 &completions_menu.initial_position.text_anchor,
5959 cx,
5960 );
5961
5962 let buffer = buffer_handle.read(cx);
5963 let snapshot = self.buffer.read(cx).snapshot(cx);
5964 let newest_anchor = self.selections.newest_anchor();
5965 let replace_range_multibuffer = {
5966 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5967 let multibuffer_anchor = snapshot
5968 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5969 .unwrap()
5970 ..snapshot
5971 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5972 .unwrap();
5973 multibuffer_anchor.start.to_offset(&snapshot)
5974 ..multibuffer_anchor.end.to_offset(&snapshot)
5975 };
5976 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5977 return None;
5978 }
5979
5980 let old_text = buffer
5981 .text_for_range(replace_range.clone())
5982 .collect::<String>();
5983 let lookbehind = newest_anchor
5984 .start
5985 .text_anchor
5986 .to_offset(buffer)
5987 .saturating_sub(replace_range.start);
5988 let lookahead = replace_range
5989 .end
5990 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5991 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5992 let suffix = &old_text[lookbehind.min(old_text.len())..];
5993
5994 let selections = self.selections.all::<usize>(cx);
5995 let mut ranges = Vec::new();
5996 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5997
5998 for selection in &selections {
5999 let range = if selection.id == newest_anchor.id {
6000 replace_range_multibuffer.clone()
6001 } else {
6002 let mut range = selection.range();
6003
6004 // if prefix is present, don't duplicate it
6005 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6006 range.start = range.start.saturating_sub(lookbehind);
6007
6008 // if suffix is also present, mimic the newest cursor and replace it
6009 if selection.id != newest_anchor.id
6010 && snapshot.contains_str_at(range.end, suffix)
6011 {
6012 range.end += lookahead;
6013 }
6014 }
6015 range
6016 };
6017
6018 ranges.push(range.clone());
6019
6020 if !self.linked_edit_ranges.is_empty() {
6021 let start_anchor = snapshot.anchor_before(range.start);
6022 let end_anchor = snapshot.anchor_after(range.end);
6023 if let Some(ranges) = self
6024 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6025 {
6026 for (buffer, edits) in ranges {
6027 linked_edits
6028 .entry(buffer.clone())
6029 .or_default()
6030 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6031 }
6032 }
6033 }
6034 }
6035
6036 let common_prefix_len = old_text
6037 .chars()
6038 .zip(new_text.chars())
6039 .take_while(|(a, b)| a == b)
6040 .map(|(a, _)| a.len_utf8())
6041 .sum::<usize>();
6042
6043 cx.emit(EditorEvent::InputHandled {
6044 utf16_range_to_replace: None,
6045 text: new_text[common_prefix_len..].into(),
6046 });
6047
6048 self.transact(window, cx, |editor, window, cx| {
6049 if let Some(mut snippet) = snippet {
6050 snippet.text = new_text.to_string();
6051 editor
6052 .insert_snippet(&ranges, snippet, window, cx)
6053 .log_err();
6054 } else {
6055 editor.buffer.update(cx, |multi_buffer, cx| {
6056 let auto_indent = match completion.insert_text_mode {
6057 Some(InsertTextMode::AS_IS) => None,
6058 _ => editor.autoindent_mode.clone(),
6059 };
6060 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6061 multi_buffer.edit(edits, auto_indent, cx);
6062 });
6063 }
6064 for (buffer, edits) in linked_edits {
6065 buffer.update(cx, |buffer, cx| {
6066 let snapshot = buffer.snapshot();
6067 let edits = edits
6068 .into_iter()
6069 .map(|(range, text)| {
6070 use text::ToPoint as TP;
6071 let end_point = TP::to_point(&range.end, &snapshot);
6072 let start_point = TP::to_point(&range.start, &snapshot);
6073 (start_point..end_point, text)
6074 })
6075 .sorted_by_key(|(range, _)| range.start);
6076 buffer.edit(edits, None, cx);
6077 })
6078 }
6079
6080 editor.refresh_edit_prediction(true, false, window, cx);
6081 });
6082 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6083
6084 let show_new_completions_on_confirm = completion
6085 .confirm
6086 .as_ref()
6087 .is_some_and(|confirm| confirm(intent, window, cx));
6088 if show_new_completions_on_confirm {
6089 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6090 }
6091
6092 let provider = self.completion_provider.as_ref()?;
6093 drop(completion);
6094 let apply_edits = provider.apply_additional_edits_for_completion(
6095 buffer_handle,
6096 completions_menu.completions.clone(),
6097 candidate_id,
6098 true,
6099 cx,
6100 );
6101
6102 let editor_settings = EditorSettings::get_global(cx);
6103 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6104 // After the code completion is finished, users often want to know what signatures are needed.
6105 // so we should automatically call signature_help
6106 self.show_signature_help(&ShowSignatureHelp, window, cx);
6107 }
6108
6109 Some(cx.foreground_executor().spawn(async move {
6110 apply_edits.await?;
6111 Ok(())
6112 }))
6113 }
6114
6115 pub fn toggle_code_actions(
6116 &mut self,
6117 action: &ToggleCodeActions,
6118 window: &mut Window,
6119 cx: &mut Context<Self>,
6120 ) {
6121 let quick_launch = action.quick_launch;
6122 let mut context_menu = self.context_menu.borrow_mut();
6123 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6124 if code_actions.deployed_from == action.deployed_from {
6125 // Toggle if we're selecting the same one
6126 *context_menu = None;
6127 cx.notify();
6128 return;
6129 } else {
6130 // Otherwise, clear it and start a new one
6131 *context_menu = None;
6132 cx.notify();
6133 }
6134 }
6135 drop(context_menu);
6136 let snapshot = self.snapshot(window, cx);
6137 let deployed_from = action.deployed_from.clone();
6138 let action = action.clone();
6139 self.completion_tasks.clear();
6140 self.discard_edit_prediction(false, cx);
6141
6142 let multibuffer_point = match &action.deployed_from {
6143 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6144 DisplayPoint::new(*row, 0).to_point(&snapshot)
6145 }
6146 _ => self.selections.newest::<Point>(cx).head(),
6147 };
6148 let Some((buffer, buffer_row)) = snapshot
6149 .buffer_snapshot
6150 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6151 .and_then(|(buffer_snapshot, range)| {
6152 self.buffer()
6153 .read(cx)
6154 .buffer(buffer_snapshot.remote_id())
6155 .map(|buffer| (buffer, range.start.row))
6156 })
6157 else {
6158 return;
6159 };
6160 let buffer_id = buffer.read(cx).remote_id();
6161 let tasks = self
6162 .tasks
6163 .get(&(buffer_id, buffer_row))
6164 .map(|t| Arc::new(t.to_owned()));
6165
6166 if !self.focus_handle.is_focused(window) {
6167 return;
6168 }
6169 let project = self.project.clone();
6170
6171 let code_actions_task = match deployed_from {
6172 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6173 _ => self.code_actions(buffer_row, window, cx),
6174 };
6175
6176 let runnable_task = match deployed_from {
6177 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6178 _ => {
6179 let mut task_context_task = Task::ready(None);
6180 if let Some(tasks) = &tasks
6181 && let Some(project) = project
6182 {
6183 task_context_task =
6184 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6185 }
6186
6187 cx.spawn_in(window, {
6188 let buffer = buffer.clone();
6189 async move |editor, cx| {
6190 let task_context = task_context_task.await;
6191
6192 let resolved_tasks =
6193 tasks
6194 .zip(task_context.clone())
6195 .map(|(tasks, task_context)| ResolvedTasks {
6196 templates: tasks.resolve(&task_context).collect(),
6197 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6198 multibuffer_point.row,
6199 tasks.column,
6200 )),
6201 });
6202 let debug_scenarios = editor
6203 .update(cx, |editor, cx| {
6204 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6205 })?
6206 .await;
6207 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6208 }
6209 })
6210 }
6211 };
6212
6213 cx.spawn_in(window, async move |editor, cx| {
6214 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6215 let code_actions = code_actions_task.await;
6216 let spawn_straight_away = quick_launch
6217 && resolved_tasks
6218 .as_ref()
6219 .is_some_and(|tasks| tasks.templates.len() == 1)
6220 && code_actions
6221 .as_ref()
6222 .is_none_or(|actions| actions.is_empty())
6223 && debug_scenarios.is_empty();
6224
6225 editor.update_in(cx, |editor, window, cx| {
6226 crate::hover_popover::hide_hover(editor, cx);
6227 let actions = CodeActionContents::new(
6228 resolved_tasks,
6229 code_actions,
6230 debug_scenarios,
6231 task_context.unwrap_or_default(),
6232 );
6233
6234 // Don't show the menu if there are no actions available
6235 if actions.is_empty() {
6236 cx.notify();
6237 return Task::ready(Ok(()));
6238 }
6239
6240 *editor.context_menu.borrow_mut() =
6241 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6242 buffer,
6243 actions,
6244 selected_item: Default::default(),
6245 scroll_handle: UniformListScrollHandle::default(),
6246 deployed_from,
6247 }));
6248 cx.notify();
6249 if spawn_straight_away
6250 && let Some(task) = editor.confirm_code_action(
6251 &ConfirmCodeAction { item_ix: Some(0) },
6252 window,
6253 cx,
6254 )
6255 {
6256 return task;
6257 }
6258
6259 Task::ready(Ok(()))
6260 })
6261 })
6262 .detach_and_log_err(cx);
6263 }
6264
6265 fn debug_scenarios(
6266 &mut self,
6267 resolved_tasks: &Option<ResolvedTasks>,
6268 buffer: &Entity<Buffer>,
6269 cx: &mut App,
6270 ) -> Task<Vec<task::DebugScenario>> {
6271 maybe!({
6272 let project = self.project()?;
6273 let dap_store = project.read(cx).dap_store();
6274 let mut scenarios = vec![];
6275 let resolved_tasks = resolved_tasks.as_ref()?;
6276 let buffer = buffer.read(cx);
6277 let language = buffer.language()?;
6278 let file = buffer.file();
6279 let debug_adapter = language_settings(language.name().into(), file, cx)
6280 .debuggers
6281 .first()
6282 .map(SharedString::from)
6283 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6284
6285 dap_store.update(cx, |dap_store, cx| {
6286 for (_, task) in &resolved_tasks.templates {
6287 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6288 task.original_task().clone(),
6289 debug_adapter.clone().into(),
6290 task.display_label().to_owned().into(),
6291 cx,
6292 );
6293 scenarios.push(maybe_scenario);
6294 }
6295 });
6296 Some(cx.background_spawn(async move {
6297 futures::future::join_all(scenarios)
6298 .await
6299 .into_iter()
6300 .flatten()
6301 .collect::<Vec<_>>()
6302 }))
6303 })
6304 .unwrap_or_else(|| Task::ready(vec![]))
6305 }
6306
6307 fn code_actions(
6308 &mut self,
6309 buffer_row: u32,
6310 window: &mut Window,
6311 cx: &mut Context<Self>,
6312 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6313 let mut task = self.code_actions_task.take();
6314 cx.spawn_in(window, async move |editor, cx| {
6315 while let Some(prev_task) = task {
6316 prev_task.await.log_err();
6317 task = editor
6318 .update(cx, |this, _| this.code_actions_task.take())
6319 .ok()?;
6320 }
6321
6322 editor
6323 .update(cx, |editor, cx| {
6324 editor
6325 .available_code_actions
6326 .clone()
6327 .and_then(|(location, code_actions)| {
6328 let snapshot = location.buffer.read(cx).snapshot();
6329 let point_range = location.range.to_point(&snapshot);
6330 let point_range = point_range.start.row..=point_range.end.row;
6331 if point_range.contains(&buffer_row) {
6332 Some(code_actions)
6333 } else {
6334 None
6335 }
6336 })
6337 })
6338 .ok()
6339 .flatten()
6340 })
6341 }
6342
6343 pub fn confirm_code_action(
6344 &mut self,
6345 action: &ConfirmCodeAction,
6346 window: &mut Window,
6347 cx: &mut Context<Self>,
6348 ) -> Option<Task<Result<()>>> {
6349 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6350
6351 let actions_menu =
6352 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6353 menu
6354 } else {
6355 return None;
6356 };
6357
6358 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6359 let action = actions_menu.actions.get(action_ix)?;
6360 let title = action.label();
6361 let buffer = actions_menu.buffer;
6362 let workspace = self.workspace()?;
6363
6364 match action {
6365 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6366 workspace.update(cx, |workspace, cx| {
6367 workspace.schedule_resolved_task(
6368 task_source_kind,
6369 resolved_task,
6370 false,
6371 window,
6372 cx,
6373 );
6374
6375 Some(Task::ready(Ok(())))
6376 })
6377 }
6378 CodeActionsItem::CodeAction {
6379 excerpt_id,
6380 action,
6381 provider,
6382 } => {
6383 let apply_code_action =
6384 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6385 let workspace = workspace.downgrade();
6386 Some(cx.spawn_in(window, async move |editor, cx| {
6387 let project_transaction = apply_code_action.await?;
6388 Self::open_project_transaction(
6389 &editor,
6390 workspace,
6391 project_transaction,
6392 title,
6393 cx,
6394 )
6395 .await
6396 }))
6397 }
6398 CodeActionsItem::DebugScenario(scenario) => {
6399 let context = actions_menu.actions.context;
6400
6401 workspace.update(cx, |workspace, cx| {
6402 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6403 workspace.start_debug_session(
6404 scenario,
6405 context,
6406 Some(buffer),
6407 None,
6408 window,
6409 cx,
6410 );
6411 });
6412 Some(Task::ready(Ok(())))
6413 }
6414 }
6415 }
6416
6417 pub async fn open_project_transaction(
6418 editor: &WeakEntity<Editor>,
6419 workspace: WeakEntity<Workspace>,
6420 transaction: ProjectTransaction,
6421 title: String,
6422 cx: &mut AsyncWindowContext,
6423 ) -> Result<()> {
6424 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6425 cx.update(|_, cx| {
6426 entries.sort_unstable_by_key(|(buffer, _)| {
6427 buffer.read(cx).file().map(|f| f.path().clone())
6428 });
6429 })?;
6430
6431 // If the project transaction's edits are all contained within this editor, then
6432 // avoid opening a new editor to display them.
6433
6434 if let Some((buffer, transaction)) = entries.first() {
6435 if entries.len() == 1 {
6436 let excerpt = editor.update(cx, |editor, cx| {
6437 editor
6438 .buffer()
6439 .read(cx)
6440 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6441 })?;
6442 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6443 && excerpted_buffer == *buffer
6444 {
6445 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6446 let excerpt_range = excerpt_range.to_offset(buffer);
6447 buffer
6448 .edited_ranges_for_transaction::<usize>(transaction)
6449 .all(|range| {
6450 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6451 })
6452 })?;
6453
6454 if all_edits_within_excerpt {
6455 return Ok(());
6456 }
6457 }
6458 }
6459 } else {
6460 return Ok(());
6461 }
6462
6463 let mut ranges_to_highlight = Vec::new();
6464 let excerpt_buffer = cx.new(|cx| {
6465 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6466 for (buffer_handle, transaction) in &entries {
6467 let edited_ranges = buffer_handle
6468 .read(cx)
6469 .edited_ranges_for_transaction::<Point>(transaction)
6470 .collect::<Vec<_>>();
6471 let (ranges, _) = multibuffer.set_excerpts_for_path(
6472 PathKey::for_buffer(buffer_handle, cx),
6473 buffer_handle.clone(),
6474 edited_ranges,
6475 multibuffer_context_lines(cx),
6476 cx,
6477 );
6478
6479 ranges_to_highlight.extend(ranges);
6480 }
6481 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6482 multibuffer
6483 })?;
6484
6485 workspace.update_in(cx, |workspace, window, cx| {
6486 let project = workspace.project().clone();
6487 let editor =
6488 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6489 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6490 editor.update(cx, |editor, cx| {
6491 editor.highlight_background::<Self>(
6492 &ranges_to_highlight,
6493 |theme| theme.colors().editor_highlighted_line_background,
6494 cx,
6495 );
6496 });
6497 })?;
6498
6499 Ok(())
6500 }
6501
6502 pub fn clear_code_action_providers(&mut self) {
6503 self.code_action_providers.clear();
6504 self.available_code_actions.take();
6505 }
6506
6507 pub fn add_code_action_provider(
6508 &mut self,
6509 provider: Rc<dyn CodeActionProvider>,
6510 window: &mut Window,
6511 cx: &mut Context<Self>,
6512 ) {
6513 if self
6514 .code_action_providers
6515 .iter()
6516 .any(|existing_provider| existing_provider.id() == provider.id())
6517 {
6518 return;
6519 }
6520
6521 self.code_action_providers.push(provider);
6522 self.refresh_code_actions(window, cx);
6523 }
6524
6525 pub fn remove_code_action_provider(
6526 &mut self,
6527 id: Arc<str>,
6528 window: &mut Window,
6529 cx: &mut Context<Self>,
6530 ) {
6531 self.code_action_providers
6532 .retain(|provider| provider.id() != id);
6533 self.refresh_code_actions(window, cx);
6534 }
6535
6536 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6537 !self.code_action_providers.is_empty()
6538 && EditorSettings::get_global(cx).toolbar.code_actions
6539 }
6540
6541 pub fn has_available_code_actions(&self) -> bool {
6542 self.available_code_actions
6543 .as_ref()
6544 .is_some_and(|(_, actions)| !actions.is_empty())
6545 }
6546
6547 fn render_inline_code_actions(
6548 &self,
6549 icon_size: ui::IconSize,
6550 display_row: DisplayRow,
6551 is_active: bool,
6552 cx: &mut Context<Self>,
6553 ) -> AnyElement {
6554 let show_tooltip = !self.context_menu_visible();
6555 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6556 .icon_size(icon_size)
6557 .shape(ui::IconButtonShape::Square)
6558 .icon_color(ui::Color::Hidden)
6559 .toggle_state(is_active)
6560 .when(show_tooltip, |this| {
6561 this.tooltip({
6562 let focus_handle = self.focus_handle.clone();
6563 move |window, cx| {
6564 Tooltip::for_action_in(
6565 "Toggle Code Actions",
6566 &ToggleCodeActions {
6567 deployed_from: None,
6568 quick_launch: false,
6569 },
6570 &focus_handle,
6571 window,
6572 cx,
6573 )
6574 }
6575 })
6576 })
6577 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6578 window.focus(&editor.focus_handle(cx));
6579 editor.toggle_code_actions(
6580 &crate::actions::ToggleCodeActions {
6581 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6582 display_row,
6583 )),
6584 quick_launch: false,
6585 },
6586 window,
6587 cx,
6588 );
6589 }))
6590 .into_any_element()
6591 }
6592
6593 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6594 &self.context_menu
6595 }
6596
6597 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6598 let newest_selection = self.selections.newest_anchor().clone();
6599 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6600 let buffer = self.buffer.read(cx);
6601 if newest_selection.head().diff_base_anchor.is_some() {
6602 return None;
6603 }
6604 let (start_buffer, start) =
6605 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6606 let (end_buffer, end) =
6607 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6608 if start_buffer != end_buffer {
6609 return None;
6610 }
6611
6612 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6613 cx.background_executor()
6614 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6615 .await;
6616
6617 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6618 let providers = this.code_action_providers.clone();
6619 let tasks = this
6620 .code_action_providers
6621 .iter()
6622 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6623 .collect::<Vec<_>>();
6624 (providers, tasks)
6625 })?;
6626
6627 let mut actions = Vec::new();
6628 for (provider, provider_actions) in
6629 providers.into_iter().zip(future::join_all(tasks).await)
6630 {
6631 if let Some(provider_actions) = provider_actions.log_err() {
6632 actions.extend(provider_actions.into_iter().map(|action| {
6633 AvailableCodeAction {
6634 excerpt_id: newest_selection.start.excerpt_id,
6635 action,
6636 provider: provider.clone(),
6637 }
6638 }));
6639 }
6640 }
6641
6642 this.update(cx, |this, cx| {
6643 this.available_code_actions = if actions.is_empty() {
6644 None
6645 } else {
6646 Some((
6647 Location {
6648 buffer: start_buffer,
6649 range: start..end,
6650 },
6651 actions.into(),
6652 ))
6653 };
6654 cx.notify();
6655 })
6656 }));
6657 None
6658 }
6659
6660 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6661 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6662 self.show_git_blame_inline = false;
6663
6664 self.show_git_blame_inline_delay_task =
6665 Some(cx.spawn_in(window, async move |this, cx| {
6666 cx.background_executor().timer(delay).await;
6667
6668 this.update(cx, |this, cx| {
6669 this.show_git_blame_inline = true;
6670 cx.notify();
6671 })
6672 .log_err();
6673 }));
6674 }
6675 }
6676
6677 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6678 let snapshot = self.snapshot(window, cx);
6679 let cursor = self.selections.newest::<Point>(cx).head();
6680 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6681 else {
6682 return;
6683 };
6684
6685 let Some(blame) = self.blame.as_ref() else {
6686 return;
6687 };
6688
6689 let row_info = RowInfo {
6690 buffer_id: Some(buffer.remote_id()),
6691 buffer_row: Some(point.row),
6692 ..Default::default()
6693 };
6694 let Some((buffer, blame_entry)) = blame
6695 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6696 .flatten()
6697 else {
6698 return;
6699 };
6700
6701 let anchor = self.selections.newest_anchor().head();
6702 let position = self.to_pixel_point(anchor, &snapshot, window);
6703 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6704 self.show_blame_popover(
6705 buffer,
6706 &blame_entry,
6707 position + last_bounds.origin,
6708 true,
6709 cx,
6710 );
6711 };
6712 }
6713
6714 fn show_blame_popover(
6715 &mut self,
6716 buffer: BufferId,
6717 blame_entry: &BlameEntry,
6718 position: gpui::Point<Pixels>,
6719 ignore_timeout: bool,
6720 cx: &mut Context<Self>,
6721 ) {
6722 if let Some(state) = &mut self.inline_blame_popover {
6723 state.hide_task.take();
6724 } else {
6725 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6726 let blame_entry = blame_entry.clone();
6727 let show_task = cx.spawn(async move |editor, cx| {
6728 if !ignore_timeout {
6729 cx.background_executor()
6730 .timer(std::time::Duration::from_millis(blame_popover_delay))
6731 .await;
6732 }
6733 editor
6734 .update(cx, |editor, cx| {
6735 editor.inline_blame_popover_show_task.take();
6736 let Some(blame) = editor.blame.as_ref() else {
6737 return;
6738 };
6739 let blame = blame.read(cx);
6740 let details = blame.details_for_entry(buffer, &blame_entry);
6741 let markdown = cx.new(|cx| {
6742 Markdown::new(
6743 details
6744 .as_ref()
6745 .map(|message| message.message.clone())
6746 .unwrap_or_default(),
6747 None,
6748 None,
6749 cx,
6750 )
6751 });
6752 editor.inline_blame_popover = Some(InlineBlamePopover {
6753 position,
6754 hide_task: None,
6755 popover_bounds: None,
6756 popover_state: InlineBlamePopoverState {
6757 scroll_handle: ScrollHandle::new(),
6758 commit_message: details,
6759 markdown,
6760 },
6761 keyboard_grace: ignore_timeout,
6762 });
6763 cx.notify();
6764 })
6765 .ok();
6766 });
6767 self.inline_blame_popover_show_task = Some(show_task);
6768 }
6769 }
6770
6771 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6772 self.inline_blame_popover_show_task.take();
6773 if let Some(state) = &mut self.inline_blame_popover {
6774 let hide_task = cx.spawn(async move |editor, cx| {
6775 cx.background_executor()
6776 .timer(std::time::Duration::from_millis(100))
6777 .await;
6778 editor
6779 .update(cx, |editor, cx| {
6780 editor.inline_blame_popover.take();
6781 cx.notify();
6782 })
6783 .ok();
6784 });
6785 state.hide_task = Some(hide_task);
6786 }
6787 }
6788
6789 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6790 if self.pending_rename.is_some() {
6791 return None;
6792 }
6793
6794 let provider = self.semantics_provider.clone()?;
6795 let buffer = self.buffer.read(cx);
6796 let newest_selection = self.selections.newest_anchor().clone();
6797 let cursor_position = newest_selection.head();
6798 let (cursor_buffer, cursor_buffer_position) =
6799 buffer.text_anchor_for_position(cursor_position, cx)?;
6800 let (tail_buffer, tail_buffer_position) =
6801 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6802 if cursor_buffer != tail_buffer {
6803 return None;
6804 }
6805
6806 let snapshot = cursor_buffer.read(cx).snapshot();
6807 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6808 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6809 if start_word_range != end_word_range {
6810 self.document_highlights_task.take();
6811 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6812 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6813 return None;
6814 }
6815
6816 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6817 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6818 cx.background_executor()
6819 .timer(Duration::from_millis(debounce))
6820 .await;
6821
6822 let highlights = if let Some(highlights) = cx
6823 .update(|cx| {
6824 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6825 })
6826 .ok()
6827 .flatten()
6828 {
6829 highlights.await.log_err()
6830 } else {
6831 None
6832 };
6833
6834 if let Some(highlights) = highlights {
6835 this.update(cx, |this, cx| {
6836 if this.pending_rename.is_some() {
6837 return;
6838 }
6839
6840 let buffer = this.buffer.read(cx);
6841 if buffer
6842 .text_anchor_for_position(cursor_position, cx)
6843 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6844 {
6845 return;
6846 }
6847
6848 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6849 let mut write_ranges = Vec::new();
6850 let mut read_ranges = Vec::new();
6851 for highlight in highlights {
6852 let buffer_id = cursor_buffer.read(cx).remote_id();
6853 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6854 {
6855 let start = highlight
6856 .range
6857 .start
6858 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6859 let end = highlight
6860 .range
6861 .end
6862 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6863 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6864 continue;
6865 }
6866
6867 let range = Anchor {
6868 buffer_id: Some(buffer_id),
6869 excerpt_id,
6870 text_anchor: start,
6871 diff_base_anchor: None,
6872 }..Anchor {
6873 buffer_id: Some(buffer_id),
6874 excerpt_id,
6875 text_anchor: end,
6876 diff_base_anchor: None,
6877 };
6878 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6879 write_ranges.push(range);
6880 } else {
6881 read_ranges.push(range);
6882 }
6883 }
6884 }
6885
6886 this.highlight_background::<DocumentHighlightRead>(
6887 &read_ranges,
6888 |theme| theme.colors().editor_document_highlight_read_background,
6889 cx,
6890 );
6891 this.highlight_background::<DocumentHighlightWrite>(
6892 &write_ranges,
6893 |theme| theme.colors().editor_document_highlight_write_background,
6894 cx,
6895 );
6896 cx.notify();
6897 })
6898 .log_err();
6899 }
6900 }));
6901 None
6902 }
6903
6904 fn prepare_highlight_query_from_selection(
6905 &mut self,
6906 cx: &mut Context<Editor>,
6907 ) -> Option<(String, Range<Anchor>)> {
6908 if matches!(self.mode, EditorMode::SingleLine) {
6909 return None;
6910 }
6911 if !EditorSettings::get_global(cx).selection_highlight {
6912 return None;
6913 }
6914 if self.selections.count() != 1 || self.selections.line_mode() {
6915 return None;
6916 }
6917 let selection = self.selections.newest::<Point>(cx);
6918 if selection.is_empty() || selection.start.row != selection.end.row {
6919 return None;
6920 }
6921 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6922 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6923 let query = multi_buffer_snapshot
6924 .text_for_range(selection_anchor_range.clone())
6925 .collect::<String>();
6926 if query.trim().is_empty() {
6927 return None;
6928 }
6929 Some((query, selection_anchor_range))
6930 }
6931
6932 fn update_selection_occurrence_highlights(
6933 &mut self,
6934 query_text: String,
6935 query_range: Range<Anchor>,
6936 multi_buffer_range_to_query: Range<Point>,
6937 use_debounce: bool,
6938 window: &mut Window,
6939 cx: &mut Context<Editor>,
6940 ) -> Task<()> {
6941 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6942 cx.spawn_in(window, async move |editor, cx| {
6943 if use_debounce {
6944 cx.background_executor()
6945 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6946 .await;
6947 }
6948 let match_task = cx.background_spawn(async move {
6949 let buffer_ranges = multi_buffer_snapshot
6950 .range_to_buffer_ranges(multi_buffer_range_to_query)
6951 .into_iter()
6952 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6953 let mut match_ranges = Vec::new();
6954 let Ok(regex) = project::search::SearchQuery::text(
6955 query_text.clone(),
6956 false,
6957 false,
6958 false,
6959 Default::default(),
6960 Default::default(),
6961 false,
6962 None,
6963 ) else {
6964 return Vec::default();
6965 };
6966 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6967 match_ranges.extend(
6968 regex
6969 .search(buffer_snapshot, Some(search_range.clone()))
6970 .await
6971 .into_iter()
6972 .filter_map(|match_range| {
6973 let match_start = buffer_snapshot
6974 .anchor_after(search_range.start + match_range.start);
6975 let match_end = buffer_snapshot
6976 .anchor_before(search_range.start + match_range.end);
6977 let match_anchor_range = Anchor::range_in_buffer(
6978 excerpt_id,
6979 buffer_snapshot.remote_id(),
6980 match_start..match_end,
6981 );
6982 (match_anchor_range != query_range).then_some(match_anchor_range)
6983 }),
6984 );
6985 }
6986 match_ranges
6987 });
6988 let match_ranges = match_task.await;
6989 editor
6990 .update_in(cx, |editor, _, cx| {
6991 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6992 if !match_ranges.is_empty() {
6993 editor.highlight_background::<SelectedTextHighlight>(
6994 &match_ranges,
6995 |theme| theme.colors().editor_document_highlight_bracket_background,
6996 cx,
6997 )
6998 }
6999 })
7000 .log_err();
7001 })
7002 }
7003
7004 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7005 struct NewlineFold;
7006 let type_id = std::any::TypeId::of::<NewlineFold>();
7007 if !self.mode.is_single_line() {
7008 return;
7009 }
7010 let snapshot = self.snapshot(window, cx);
7011 if snapshot.buffer_snapshot.max_point().row == 0 {
7012 return;
7013 }
7014 let task = cx.background_spawn(async move {
7015 let new_newlines = snapshot
7016 .buffer_chars_at(0)
7017 .filter_map(|(c, i)| {
7018 if c == '\n' {
7019 Some(
7020 snapshot.buffer_snapshot.anchor_after(i)
7021 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7022 )
7023 } else {
7024 None
7025 }
7026 })
7027 .collect::<Vec<_>>();
7028 let existing_newlines = snapshot
7029 .folds_in_range(0..snapshot.buffer_snapshot.len())
7030 .filter_map(|fold| {
7031 if fold.placeholder.type_tag == Some(type_id) {
7032 Some(fold.range.start..fold.range.end)
7033 } else {
7034 None
7035 }
7036 })
7037 .collect::<Vec<_>>();
7038
7039 (new_newlines, existing_newlines)
7040 });
7041 self.folding_newlines = cx.spawn(async move |this, cx| {
7042 let (new_newlines, existing_newlines) = task.await;
7043 if new_newlines == existing_newlines {
7044 return;
7045 }
7046 let placeholder = FoldPlaceholder {
7047 render: Arc::new(move |_, _, cx| {
7048 div()
7049 .bg(cx.theme().status().hint_background)
7050 .border_b_1()
7051 .size_full()
7052 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7053 .border_color(cx.theme().status().hint)
7054 .child("\\n")
7055 .into_any()
7056 }),
7057 constrain_width: false,
7058 merge_adjacent: false,
7059 type_tag: Some(type_id),
7060 };
7061 let creases = new_newlines
7062 .into_iter()
7063 .map(|range| Crease::simple(range, placeholder.clone()))
7064 .collect();
7065 this.update(cx, |this, cx| {
7066 this.display_map.update(cx, |display_map, cx| {
7067 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7068 display_map.fold(creases, cx);
7069 });
7070 })
7071 .ok();
7072 });
7073 }
7074
7075 fn refresh_selected_text_highlights(
7076 &mut self,
7077 on_buffer_edit: bool,
7078 window: &mut Window,
7079 cx: &mut Context<Editor>,
7080 ) {
7081 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7082 else {
7083 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7084 self.quick_selection_highlight_task.take();
7085 self.debounced_selection_highlight_task.take();
7086 return;
7087 };
7088 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7089 if on_buffer_edit
7090 || self
7091 .quick_selection_highlight_task
7092 .as_ref()
7093 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7094 {
7095 let multi_buffer_visible_start = self
7096 .scroll_manager
7097 .anchor()
7098 .anchor
7099 .to_point(&multi_buffer_snapshot);
7100 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7101 multi_buffer_visible_start
7102 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7103 Bias::Left,
7104 );
7105 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7106 self.quick_selection_highlight_task = Some((
7107 query_range.clone(),
7108 self.update_selection_occurrence_highlights(
7109 query_text.clone(),
7110 query_range.clone(),
7111 multi_buffer_visible_range,
7112 false,
7113 window,
7114 cx,
7115 ),
7116 ));
7117 }
7118 if on_buffer_edit
7119 || self
7120 .debounced_selection_highlight_task
7121 .as_ref()
7122 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7123 {
7124 let multi_buffer_start = multi_buffer_snapshot
7125 .anchor_before(0)
7126 .to_point(&multi_buffer_snapshot);
7127 let multi_buffer_end = multi_buffer_snapshot
7128 .anchor_after(multi_buffer_snapshot.len())
7129 .to_point(&multi_buffer_snapshot);
7130 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7131 self.debounced_selection_highlight_task = Some((
7132 query_range.clone(),
7133 self.update_selection_occurrence_highlights(
7134 query_text,
7135 query_range,
7136 multi_buffer_full_range,
7137 true,
7138 window,
7139 cx,
7140 ),
7141 ));
7142 }
7143 }
7144
7145 pub fn refresh_edit_prediction(
7146 &mut self,
7147 debounce: bool,
7148 user_requested: bool,
7149 window: &mut Window,
7150 cx: &mut Context<Self>,
7151 ) -> Option<()> {
7152 if DisableAiSettings::get_global(cx).disable_ai {
7153 return None;
7154 }
7155
7156 let provider = self.edit_prediction_provider()?;
7157 let cursor = self.selections.newest_anchor().head();
7158 let (buffer, cursor_buffer_position) =
7159 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7160
7161 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7162 self.discard_edit_prediction(false, cx);
7163 return None;
7164 }
7165
7166 self.update_visible_edit_prediction(window, cx);
7167
7168 if !user_requested
7169 && (!self.should_show_edit_predictions()
7170 || !self.is_focused(window)
7171 || buffer.read(cx).is_empty())
7172 {
7173 self.discard_edit_prediction(false, cx);
7174 return None;
7175 }
7176
7177 provider.refresh(
7178 self.project.clone(),
7179 buffer,
7180 cursor_buffer_position,
7181 debounce,
7182 cx,
7183 );
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.is_singleton(cx)
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 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7427
7428 match &active_edit_prediction.completion {
7429 EditPrediction::Move { target, .. } => {
7430 let target = *target;
7431
7432 if let Some(position_map) = &self.last_position_map {
7433 if position_map
7434 .visible_row_range
7435 .contains(&target.to_display_point(&position_map.snapshot).row())
7436 || !self.edit_prediction_requires_modifier()
7437 {
7438 self.unfold_ranges(&[target..target], true, false, cx);
7439 // Note that this is also done in vim's handler of the Tab action.
7440 self.change_selections(
7441 SelectionEffects::scroll(Autoscroll::newest()),
7442 window,
7443 cx,
7444 |selections| {
7445 selections.select_anchor_ranges([target..target]);
7446 },
7447 );
7448 self.clear_row_highlights::<EditPredictionPreview>();
7449
7450 self.edit_prediction_preview
7451 .set_previous_scroll_position(None);
7452 } else {
7453 self.edit_prediction_preview
7454 .set_previous_scroll_position(Some(
7455 position_map.snapshot.scroll_anchor,
7456 ));
7457
7458 self.highlight_rows::<EditPredictionPreview>(
7459 target..target,
7460 cx.theme().colors().editor_highlighted_line_background,
7461 RowHighlightOptions {
7462 autoscroll: true,
7463 ..Default::default()
7464 },
7465 cx,
7466 );
7467 self.request_autoscroll(Autoscroll::fit(), cx);
7468 }
7469 }
7470 }
7471 EditPrediction::Edit { edits, .. } => {
7472 if let Some(provider) = self.edit_prediction_provider() {
7473 provider.accept(cx);
7474 }
7475
7476 // Store the transaction ID and selections before applying the edit
7477 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7478
7479 let snapshot = self.buffer.read(cx).snapshot(cx);
7480 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7481
7482 self.buffer.update(cx, |buffer, cx| {
7483 buffer.edit(edits.iter().cloned(), None, cx)
7484 });
7485
7486 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7487 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7488 });
7489
7490 let selections = self.selections.disjoint_anchors_arc();
7491 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7492 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7493 if has_new_transaction {
7494 self.selection_history
7495 .insert_transaction(transaction_id_now, selections);
7496 }
7497 }
7498
7499 self.update_visible_edit_prediction(window, cx);
7500 if self.active_edit_prediction.is_none() {
7501 self.refresh_edit_prediction(true, true, window, cx);
7502 }
7503
7504 cx.notify();
7505 }
7506 }
7507
7508 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7509 }
7510
7511 pub fn accept_partial_edit_prediction(
7512 &mut self,
7513 _: &AcceptPartialEditPrediction,
7514 window: &mut Window,
7515 cx: &mut Context<Self>,
7516 ) {
7517 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7518 return;
7519 };
7520 if self.selections.count() != 1 {
7521 return;
7522 }
7523
7524 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7525
7526 match &active_edit_prediction.completion {
7527 EditPrediction::Move { target, .. } => {
7528 let target = *target;
7529 self.change_selections(
7530 SelectionEffects::scroll(Autoscroll::newest()),
7531 window,
7532 cx,
7533 |selections| {
7534 selections.select_anchor_ranges([target..target]);
7535 },
7536 );
7537 }
7538 EditPrediction::Edit { edits, .. } => {
7539 // Find an insertion that starts at the cursor position.
7540 let snapshot = self.buffer.read(cx).snapshot(cx);
7541 let cursor_offset = self.selections.newest::<usize>(cx).head();
7542 let insertion = edits.iter().find_map(|(range, text)| {
7543 let range = range.to_offset(&snapshot);
7544 if range.is_empty() && range.start == cursor_offset {
7545 Some(text)
7546 } else {
7547 None
7548 }
7549 });
7550
7551 if let Some(text) = insertion {
7552 let mut partial_completion = text
7553 .chars()
7554 .by_ref()
7555 .take_while(|c| c.is_alphabetic())
7556 .collect::<String>();
7557 if partial_completion.is_empty() {
7558 partial_completion = text
7559 .chars()
7560 .by_ref()
7561 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7562 .collect::<String>();
7563 }
7564
7565 cx.emit(EditorEvent::InputHandled {
7566 utf16_range_to_replace: None,
7567 text: partial_completion.clone().into(),
7568 });
7569
7570 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7571
7572 self.refresh_edit_prediction(true, true, window, cx);
7573 cx.notify();
7574 } else {
7575 self.accept_edit_prediction(&Default::default(), window, cx);
7576 }
7577 }
7578 }
7579 }
7580
7581 fn discard_edit_prediction(
7582 &mut self,
7583 should_report_edit_prediction_event: bool,
7584 cx: &mut Context<Self>,
7585 ) -> bool {
7586 if should_report_edit_prediction_event {
7587 let completion_id = self
7588 .active_edit_prediction
7589 .as_ref()
7590 .and_then(|active_completion| active_completion.completion_id.clone());
7591
7592 self.report_edit_prediction_event(completion_id, false, cx);
7593 }
7594
7595 if let Some(provider) = self.edit_prediction_provider() {
7596 provider.discard(cx);
7597 }
7598
7599 self.take_active_edit_prediction(cx)
7600 }
7601
7602 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7603 let Some(provider) = self.edit_prediction_provider() else {
7604 return;
7605 };
7606
7607 let Some((_, buffer, _)) = self
7608 .buffer
7609 .read(cx)
7610 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7611 else {
7612 return;
7613 };
7614
7615 let extension = buffer
7616 .read(cx)
7617 .file()
7618 .and_then(|file| Some(file.path().extension()?.to_string()));
7619
7620 let event_type = match accepted {
7621 true => "Edit Prediction Accepted",
7622 false => "Edit Prediction Discarded",
7623 };
7624 telemetry::event!(
7625 event_type,
7626 provider = provider.name(),
7627 prediction_id = id,
7628 suggestion_accepted = accepted,
7629 file_extension = extension,
7630 );
7631 }
7632
7633 pub fn has_active_edit_prediction(&self) -> bool {
7634 self.active_edit_prediction.is_some()
7635 }
7636
7637 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7638 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7639 return false;
7640 };
7641
7642 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7643 self.clear_highlights::<EditPredictionHighlight>(cx);
7644 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7645 true
7646 }
7647
7648 /// Returns true when we're displaying the edit prediction popover below the cursor
7649 /// like we are not previewing and the LSP autocomplete menu is visible
7650 /// or we are in `when_holding_modifier` mode.
7651 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7652 if self.edit_prediction_preview_is_active()
7653 || !self.show_edit_predictions_in_menu()
7654 || !self.edit_predictions_enabled()
7655 {
7656 return false;
7657 }
7658
7659 if self.has_visible_completions_menu() {
7660 return true;
7661 }
7662
7663 has_completion && self.edit_prediction_requires_modifier()
7664 }
7665
7666 fn handle_modifiers_changed(
7667 &mut self,
7668 modifiers: Modifiers,
7669 position_map: &PositionMap,
7670 window: &mut Window,
7671 cx: &mut Context<Self>,
7672 ) {
7673 if self.show_edit_predictions_in_menu() {
7674 self.update_edit_prediction_preview(&modifiers, window, cx);
7675 }
7676
7677 self.update_selection_mode(&modifiers, position_map, window, cx);
7678
7679 let mouse_position = window.mouse_position();
7680 if !position_map.text_hitbox.is_hovered(window) {
7681 return;
7682 }
7683
7684 self.update_hovered_link(
7685 position_map.point_for_position(mouse_position),
7686 &position_map.snapshot,
7687 modifiers,
7688 window,
7689 cx,
7690 )
7691 }
7692
7693 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7694 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7695 if invert {
7696 match multi_cursor_setting {
7697 MultiCursorModifier::Alt => modifiers.alt,
7698 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7699 }
7700 } else {
7701 match multi_cursor_setting {
7702 MultiCursorModifier::Alt => modifiers.secondary(),
7703 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7704 }
7705 }
7706 }
7707
7708 fn columnar_selection_mode(
7709 modifiers: &Modifiers,
7710 cx: &mut Context<Self>,
7711 ) -> Option<ColumnarMode> {
7712 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7713 if Self::multi_cursor_modifier(false, modifiers, cx) {
7714 Some(ColumnarMode::FromMouse)
7715 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7716 Some(ColumnarMode::FromSelection)
7717 } else {
7718 None
7719 }
7720 } else {
7721 None
7722 }
7723 }
7724
7725 fn update_selection_mode(
7726 &mut self,
7727 modifiers: &Modifiers,
7728 position_map: &PositionMap,
7729 window: &mut Window,
7730 cx: &mut Context<Self>,
7731 ) {
7732 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7733 return;
7734 };
7735 if self.selections.pending_anchor().is_none() {
7736 return;
7737 }
7738
7739 let mouse_position = window.mouse_position();
7740 let point_for_position = position_map.point_for_position(mouse_position);
7741 let position = point_for_position.previous_valid;
7742
7743 self.select(
7744 SelectPhase::BeginColumnar {
7745 position,
7746 reset: false,
7747 mode,
7748 goal_column: point_for_position.exact_unclipped.column(),
7749 },
7750 window,
7751 cx,
7752 );
7753 }
7754
7755 fn update_edit_prediction_preview(
7756 &mut self,
7757 modifiers: &Modifiers,
7758 window: &mut Window,
7759 cx: &mut Context<Self>,
7760 ) {
7761 let mut modifiers_held = false;
7762 if let Some(accept_keystroke) = self
7763 .accept_edit_prediction_keybind(false, window, cx)
7764 .keystroke()
7765 {
7766 modifiers_held = modifiers_held
7767 || (accept_keystroke.modifiers() == modifiers
7768 && accept_keystroke.modifiers().modified());
7769 };
7770 if let Some(accept_partial_keystroke) = self
7771 .accept_edit_prediction_keybind(true, window, cx)
7772 .keystroke()
7773 {
7774 modifiers_held = modifiers_held
7775 || (accept_partial_keystroke.modifiers() == modifiers
7776 && accept_partial_keystroke.modifiers().modified());
7777 }
7778
7779 if modifiers_held {
7780 if matches!(
7781 self.edit_prediction_preview,
7782 EditPredictionPreview::Inactive { .. }
7783 ) {
7784 self.edit_prediction_preview = EditPredictionPreview::Active {
7785 previous_scroll_position: None,
7786 since: Instant::now(),
7787 };
7788
7789 self.update_visible_edit_prediction(window, cx);
7790 cx.notify();
7791 }
7792 } else if let EditPredictionPreview::Active {
7793 previous_scroll_position,
7794 since,
7795 } = self.edit_prediction_preview
7796 {
7797 if let (Some(previous_scroll_position), Some(position_map)) =
7798 (previous_scroll_position, self.last_position_map.as_ref())
7799 {
7800 self.set_scroll_position(
7801 previous_scroll_position
7802 .scroll_position(&position_map.snapshot.display_snapshot),
7803 window,
7804 cx,
7805 );
7806 }
7807
7808 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7809 released_too_fast: since.elapsed() < Duration::from_millis(200),
7810 };
7811 self.clear_row_highlights::<EditPredictionPreview>();
7812 self.update_visible_edit_prediction(window, cx);
7813 cx.notify();
7814 }
7815 }
7816
7817 fn update_visible_edit_prediction(
7818 &mut self,
7819 _window: &mut Window,
7820 cx: &mut Context<Self>,
7821 ) -> Option<()> {
7822 if DisableAiSettings::get_global(cx).disable_ai {
7823 return None;
7824 }
7825
7826 if self.ime_transaction.is_some() {
7827 self.discard_edit_prediction(false, cx);
7828 return None;
7829 }
7830
7831 let selection = self.selections.newest_anchor();
7832 let cursor = selection.head();
7833 let multibuffer = self.buffer.read(cx).snapshot(cx);
7834 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7835 let excerpt_id = cursor.excerpt_id;
7836
7837 let show_in_menu = self.show_edit_predictions_in_menu();
7838 let completions_menu_has_precedence = !show_in_menu
7839 && (self.context_menu.borrow().is_some()
7840 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7841
7842 if completions_menu_has_precedence
7843 || !offset_selection.is_empty()
7844 || self
7845 .active_edit_prediction
7846 .as_ref()
7847 .is_some_and(|completion| {
7848 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7849 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7850 !invalidation_range.contains(&offset_selection.head())
7851 })
7852 {
7853 self.discard_edit_prediction(false, cx);
7854 return None;
7855 }
7856
7857 self.take_active_edit_prediction(cx);
7858 let Some(provider) = self.edit_prediction_provider() else {
7859 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7860 return None;
7861 };
7862
7863 let (buffer, cursor_buffer_position) =
7864 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7865
7866 self.edit_prediction_settings =
7867 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7868
7869 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7870
7871 if self.edit_prediction_indent_conflict {
7872 let cursor_point = cursor.to_point(&multibuffer);
7873
7874 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7875
7876 if let Some((_, indent)) = indents.iter().next()
7877 && indent.len == cursor_point.column
7878 {
7879 self.edit_prediction_indent_conflict = false;
7880 }
7881 }
7882
7883 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7884 let edits = edit_prediction
7885 .edits
7886 .into_iter()
7887 .flat_map(|(range, new_text)| {
7888 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7889 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7890 Some((start..end, new_text))
7891 })
7892 .collect::<Vec<_>>();
7893 if edits.is_empty() {
7894 return None;
7895 }
7896
7897 let first_edit_start = edits.first().unwrap().0.start;
7898 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7899 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7900
7901 let last_edit_end = edits.last().unwrap().0.end;
7902 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7903 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7904
7905 let cursor_row = cursor.to_point(&multibuffer).row;
7906
7907 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7908
7909 let mut inlay_ids = Vec::new();
7910 let invalidation_row_range;
7911 let move_invalidation_row_range = if cursor_row < edit_start_row {
7912 Some(cursor_row..edit_end_row)
7913 } else if cursor_row > edit_end_row {
7914 Some(edit_start_row..cursor_row)
7915 } else {
7916 None
7917 };
7918 let supports_jump = self
7919 .edit_prediction_provider
7920 .as_ref()
7921 .map(|provider| provider.provider.supports_jump_to_edit())
7922 .unwrap_or(true);
7923
7924 let is_move = supports_jump
7925 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7926 let completion = if is_move {
7927 invalidation_row_range =
7928 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7929 let target = first_edit_start;
7930 EditPrediction::Move { target, snapshot }
7931 } else {
7932 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7933 && !self.edit_predictions_hidden_for_vim_mode;
7934
7935 if show_completions_in_buffer {
7936 if edits
7937 .iter()
7938 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7939 {
7940 let mut inlays = Vec::new();
7941 for (range, new_text) in &edits {
7942 let inlay = Inlay::edit_prediction(
7943 post_inc(&mut self.next_inlay_id),
7944 range.start,
7945 new_text.as_str(),
7946 );
7947 inlay_ids.push(inlay.id);
7948 inlays.push(inlay);
7949 }
7950
7951 self.splice_inlays(&[], inlays, cx);
7952 } else {
7953 let background_color = cx.theme().status().deleted_background;
7954 self.highlight_text::<EditPredictionHighlight>(
7955 edits.iter().map(|(range, _)| range.clone()).collect(),
7956 HighlightStyle {
7957 background_color: Some(background_color),
7958 ..Default::default()
7959 },
7960 cx,
7961 );
7962 }
7963 }
7964
7965 invalidation_row_range = edit_start_row..edit_end_row;
7966
7967 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7968 if provider.show_tab_accept_marker() {
7969 EditDisplayMode::TabAccept
7970 } else {
7971 EditDisplayMode::Inline
7972 }
7973 } else {
7974 EditDisplayMode::DiffPopover
7975 };
7976
7977 EditPrediction::Edit {
7978 edits,
7979 edit_preview: edit_prediction.edit_preview,
7980 display_mode,
7981 snapshot,
7982 }
7983 };
7984
7985 let invalidation_range = multibuffer
7986 .anchor_before(Point::new(invalidation_row_range.start, 0))
7987 ..multibuffer.anchor_after(Point::new(
7988 invalidation_row_range.end,
7989 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7990 ));
7991
7992 self.stale_edit_prediction_in_menu = None;
7993 self.active_edit_prediction = Some(EditPredictionState {
7994 inlay_ids,
7995 completion,
7996 completion_id: edit_prediction.id,
7997 invalidation_range,
7998 });
7999
8000 cx.notify();
8001
8002 Some(())
8003 }
8004
8005 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8006 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8007 }
8008
8009 fn clear_tasks(&mut self) {
8010 self.tasks.clear()
8011 }
8012
8013 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8014 if self.tasks.insert(key, value).is_some() {
8015 // This case should hopefully be rare, but just in case...
8016 log::error!(
8017 "multiple different run targets found on a single line, only the last target will be rendered"
8018 )
8019 }
8020 }
8021
8022 /// Get all display points of breakpoints that will be rendered within editor
8023 ///
8024 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8025 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8026 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8027 fn active_breakpoints(
8028 &self,
8029 range: Range<DisplayRow>,
8030 window: &mut Window,
8031 cx: &mut Context<Self>,
8032 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8033 let mut breakpoint_display_points = HashMap::default();
8034
8035 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8036 return breakpoint_display_points;
8037 };
8038
8039 let snapshot = self.snapshot(window, cx);
8040
8041 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8042 let Some(project) = self.project() else {
8043 return breakpoint_display_points;
8044 };
8045
8046 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8047 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8048
8049 for (buffer_snapshot, range, excerpt_id) in
8050 multi_buffer_snapshot.range_to_buffer_ranges(range)
8051 {
8052 let Some(buffer) = project
8053 .read(cx)
8054 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8055 else {
8056 continue;
8057 };
8058 let breakpoints = breakpoint_store.read(cx).breakpoints(
8059 &buffer,
8060 Some(
8061 buffer_snapshot.anchor_before(range.start)
8062 ..buffer_snapshot.anchor_after(range.end),
8063 ),
8064 buffer_snapshot,
8065 cx,
8066 );
8067 for (breakpoint, state) in breakpoints {
8068 let multi_buffer_anchor =
8069 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8070 let position = multi_buffer_anchor
8071 .to_point(multi_buffer_snapshot)
8072 .to_display_point(&snapshot);
8073
8074 breakpoint_display_points.insert(
8075 position.row(),
8076 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8077 );
8078 }
8079 }
8080
8081 breakpoint_display_points
8082 }
8083
8084 fn breakpoint_context_menu(
8085 &self,
8086 anchor: Anchor,
8087 window: &mut Window,
8088 cx: &mut Context<Self>,
8089 ) -> Entity<ui::ContextMenu> {
8090 let weak_editor = cx.weak_entity();
8091 let focus_handle = self.focus_handle(cx);
8092
8093 let row = self
8094 .buffer
8095 .read(cx)
8096 .snapshot(cx)
8097 .summary_for_anchor::<Point>(&anchor)
8098 .row;
8099
8100 let breakpoint = self
8101 .breakpoint_at_row(row, window, cx)
8102 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8103
8104 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8105 "Edit Log Breakpoint"
8106 } else {
8107 "Set Log Breakpoint"
8108 };
8109
8110 let condition_breakpoint_msg = if breakpoint
8111 .as_ref()
8112 .is_some_and(|bp| bp.1.condition.is_some())
8113 {
8114 "Edit Condition Breakpoint"
8115 } else {
8116 "Set Condition Breakpoint"
8117 };
8118
8119 let hit_condition_breakpoint_msg = if breakpoint
8120 .as_ref()
8121 .is_some_and(|bp| bp.1.hit_condition.is_some())
8122 {
8123 "Edit Hit Condition Breakpoint"
8124 } else {
8125 "Set Hit Condition Breakpoint"
8126 };
8127
8128 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8129 "Unset Breakpoint"
8130 } else {
8131 "Set Breakpoint"
8132 };
8133
8134 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8135
8136 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8137 BreakpointState::Enabled => Some("Disable"),
8138 BreakpointState::Disabled => Some("Enable"),
8139 });
8140
8141 let (anchor, breakpoint) =
8142 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8143
8144 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8145 menu.on_blur_subscription(Subscription::new(|| {}))
8146 .context(focus_handle)
8147 .when(run_to_cursor, |this| {
8148 let weak_editor = weak_editor.clone();
8149 this.entry("Run to cursor", None, move |window, cx| {
8150 weak_editor
8151 .update(cx, |editor, cx| {
8152 editor.change_selections(
8153 SelectionEffects::no_scroll(),
8154 window,
8155 cx,
8156 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8157 );
8158 })
8159 .ok();
8160
8161 window.dispatch_action(Box::new(RunToCursor), cx);
8162 })
8163 .separator()
8164 })
8165 .when_some(toggle_state_msg, |this, msg| {
8166 this.entry(msg, None, {
8167 let weak_editor = weak_editor.clone();
8168 let breakpoint = breakpoint.clone();
8169 move |_window, cx| {
8170 weak_editor
8171 .update(cx, |this, cx| {
8172 this.edit_breakpoint_at_anchor(
8173 anchor,
8174 breakpoint.as_ref().clone(),
8175 BreakpointEditAction::InvertState,
8176 cx,
8177 );
8178 })
8179 .log_err();
8180 }
8181 })
8182 })
8183 .entry(set_breakpoint_msg, None, {
8184 let weak_editor = weak_editor.clone();
8185 let breakpoint = breakpoint.clone();
8186 move |_window, cx| {
8187 weak_editor
8188 .update(cx, |this, cx| {
8189 this.edit_breakpoint_at_anchor(
8190 anchor,
8191 breakpoint.as_ref().clone(),
8192 BreakpointEditAction::Toggle,
8193 cx,
8194 );
8195 })
8196 .log_err();
8197 }
8198 })
8199 .entry(log_breakpoint_msg, None, {
8200 let breakpoint = breakpoint.clone();
8201 let weak_editor = weak_editor.clone();
8202 move |window, cx| {
8203 weak_editor
8204 .update(cx, |this, cx| {
8205 this.add_edit_breakpoint_block(
8206 anchor,
8207 breakpoint.as_ref(),
8208 BreakpointPromptEditAction::Log,
8209 window,
8210 cx,
8211 );
8212 })
8213 .log_err();
8214 }
8215 })
8216 .entry(condition_breakpoint_msg, None, {
8217 let breakpoint = breakpoint.clone();
8218 let weak_editor = weak_editor.clone();
8219 move |window, cx| {
8220 weak_editor
8221 .update(cx, |this, cx| {
8222 this.add_edit_breakpoint_block(
8223 anchor,
8224 breakpoint.as_ref(),
8225 BreakpointPromptEditAction::Condition,
8226 window,
8227 cx,
8228 );
8229 })
8230 .log_err();
8231 }
8232 })
8233 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8234 weak_editor
8235 .update(cx, |this, cx| {
8236 this.add_edit_breakpoint_block(
8237 anchor,
8238 breakpoint.as_ref(),
8239 BreakpointPromptEditAction::HitCondition,
8240 window,
8241 cx,
8242 );
8243 })
8244 .log_err();
8245 })
8246 })
8247 }
8248
8249 fn render_breakpoint(
8250 &self,
8251 position: Anchor,
8252 row: DisplayRow,
8253 breakpoint: &Breakpoint,
8254 state: Option<BreakpointSessionState>,
8255 cx: &mut Context<Self>,
8256 ) -> IconButton {
8257 let is_rejected = state.is_some_and(|s| !s.verified);
8258 // Is it a breakpoint that shows up when hovering over gutter?
8259 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8260 (false, false),
8261 |PhantomBreakpointIndicator {
8262 is_active,
8263 display_row,
8264 collides_with_existing_breakpoint,
8265 }| {
8266 (
8267 is_active && display_row == row,
8268 collides_with_existing_breakpoint,
8269 )
8270 },
8271 );
8272
8273 let (color, icon) = {
8274 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8275 (false, false) => ui::IconName::DebugBreakpoint,
8276 (true, false) => ui::IconName::DebugLogBreakpoint,
8277 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8278 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8279 };
8280
8281 let color = if is_phantom {
8282 Color::Hint
8283 } else if is_rejected {
8284 Color::Disabled
8285 } else {
8286 Color::Debugger
8287 };
8288
8289 (color, icon)
8290 };
8291
8292 let breakpoint = Arc::from(breakpoint.clone());
8293
8294 let alt_as_text = gpui::Keystroke {
8295 modifiers: Modifiers::secondary_key(),
8296 ..Default::default()
8297 };
8298 let primary_action_text = if breakpoint.is_disabled() {
8299 "Enable breakpoint"
8300 } else if is_phantom && !collides_with_existing {
8301 "Set breakpoint"
8302 } else {
8303 "Unset breakpoint"
8304 };
8305 let focus_handle = self.focus_handle.clone();
8306
8307 let meta = if is_rejected {
8308 SharedString::from("No executable code is associated with this line.")
8309 } else if collides_with_existing && !breakpoint.is_disabled() {
8310 SharedString::from(format!(
8311 "{alt_as_text}-click to disable,\nright-click for more options."
8312 ))
8313 } else {
8314 SharedString::from("Right-click for more options.")
8315 };
8316 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8317 .icon_size(IconSize::XSmall)
8318 .size(ui::ButtonSize::None)
8319 .when(is_rejected, |this| {
8320 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8321 })
8322 .icon_color(color)
8323 .style(ButtonStyle::Transparent)
8324 .on_click(cx.listener({
8325 move |editor, event: &ClickEvent, window, cx| {
8326 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8327 BreakpointEditAction::InvertState
8328 } else {
8329 BreakpointEditAction::Toggle
8330 };
8331
8332 window.focus(&editor.focus_handle(cx));
8333 editor.edit_breakpoint_at_anchor(
8334 position,
8335 breakpoint.as_ref().clone(),
8336 edit_action,
8337 cx,
8338 );
8339 }
8340 }))
8341 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8342 editor.set_breakpoint_context_menu(
8343 row,
8344 Some(position),
8345 event.position(),
8346 window,
8347 cx,
8348 );
8349 }))
8350 .tooltip(move |window, cx| {
8351 Tooltip::with_meta_in(
8352 primary_action_text,
8353 Some(&ToggleBreakpoint),
8354 meta.clone(),
8355 &focus_handle,
8356 window,
8357 cx,
8358 )
8359 })
8360 }
8361
8362 fn build_tasks_context(
8363 project: &Entity<Project>,
8364 buffer: &Entity<Buffer>,
8365 buffer_row: u32,
8366 tasks: &Arc<RunnableTasks>,
8367 cx: &mut Context<Self>,
8368 ) -> Task<Option<task::TaskContext>> {
8369 let position = Point::new(buffer_row, tasks.column);
8370 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8371 let location = Location {
8372 buffer: buffer.clone(),
8373 range: range_start..range_start,
8374 };
8375 // Fill in the environmental variables from the tree-sitter captures
8376 let mut captured_task_variables = TaskVariables::default();
8377 for (capture_name, value) in tasks.extra_variables.clone() {
8378 captured_task_variables.insert(
8379 task::VariableName::Custom(capture_name.into()),
8380 value.clone(),
8381 );
8382 }
8383 project.update(cx, |project, cx| {
8384 project.task_store().update(cx, |task_store, cx| {
8385 task_store.task_context_for_location(captured_task_variables, location, cx)
8386 })
8387 })
8388 }
8389
8390 pub fn spawn_nearest_task(
8391 &mut self,
8392 action: &SpawnNearestTask,
8393 window: &mut Window,
8394 cx: &mut Context<Self>,
8395 ) {
8396 let Some((workspace, _)) = self.workspace.clone() else {
8397 return;
8398 };
8399 let Some(project) = self.project.clone() else {
8400 return;
8401 };
8402
8403 // Try to find a closest, enclosing node using tree-sitter that has a task
8404 let Some((buffer, buffer_row, tasks)) = self
8405 .find_enclosing_node_task(cx)
8406 // Or find the task that's closest in row-distance.
8407 .or_else(|| self.find_closest_task(cx))
8408 else {
8409 return;
8410 };
8411
8412 let reveal_strategy = action.reveal;
8413 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8414 cx.spawn_in(window, async move |_, cx| {
8415 let context = task_context.await?;
8416 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8417
8418 let resolved = &mut resolved_task.resolved;
8419 resolved.reveal = reveal_strategy;
8420
8421 workspace
8422 .update_in(cx, |workspace, window, cx| {
8423 workspace.schedule_resolved_task(
8424 task_source_kind,
8425 resolved_task,
8426 false,
8427 window,
8428 cx,
8429 );
8430 })
8431 .ok()
8432 })
8433 .detach();
8434 }
8435
8436 fn find_closest_task(
8437 &mut self,
8438 cx: &mut Context<Self>,
8439 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8440 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8441
8442 let ((buffer_id, row), tasks) = self
8443 .tasks
8444 .iter()
8445 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8446
8447 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8448 let tasks = Arc::new(tasks.to_owned());
8449 Some((buffer, *row, tasks))
8450 }
8451
8452 fn find_enclosing_node_task(
8453 &mut self,
8454 cx: &mut Context<Self>,
8455 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8456 let snapshot = self.buffer.read(cx).snapshot(cx);
8457 let offset = self.selections.newest::<usize>(cx).head();
8458 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8459 let buffer_id = excerpt.buffer().remote_id();
8460
8461 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8462 let mut cursor = layer.node().walk();
8463
8464 while cursor.goto_first_child_for_byte(offset).is_some() {
8465 if cursor.node().end_byte() == offset {
8466 cursor.goto_next_sibling();
8467 }
8468 }
8469
8470 // Ascend to the smallest ancestor that contains the range and has a task.
8471 loop {
8472 let node = cursor.node();
8473 let node_range = node.byte_range();
8474 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8475
8476 // Check if this node contains our offset
8477 if node_range.start <= offset && node_range.end >= offset {
8478 // If it contains offset, check for task
8479 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8480 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8481 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8482 }
8483 }
8484
8485 if !cursor.goto_parent() {
8486 break;
8487 }
8488 }
8489 None
8490 }
8491
8492 fn render_run_indicator(
8493 &self,
8494 _style: &EditorStyle,
8495 is_active: bool,
8496 row: DisplayRow,
8497 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8498 cx: &mut Context<Self>,
8499 ) -> IconButton {
8500 let color = Color::Muted;
8501 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8502
8503 IconButton::new(
8504 ("run_indicator", row.0 as usize),
8505 ui::IconName::PlayOutlined,
8506 )
8507 .shape(ui::IconButtonShape::Square)
8508 .icon_size(IconSize::XSmall)
8509 .icon_color(color)
8510 .toggle_state(is_active)
8511 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8512 let quick_launch = match e {
8513 ClickEvent::Keyboard(_) => true,
8514 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8515 };
8516
8517 window.focus(&editor.focus_handle(cx));
8518 editor.toggle_code_actions(
8519 &ToggleCodeActions {
8520 deployed_from: Some(CodeActionSource::RunMenu(row)),
8521 quick_launch,
8522 },
8523 window,
8524 cx,
8525 );
8526 }))
8527 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8528 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8529 }))
8530 }
8531
8532 pub fn context_menu_visible(&self) -> bool {
8533 !self.edit_prediction_preview_is_active()
8534 && self
8535 .context_menu
8536 .borrow()
8537 .as_ref()
8538 .is_some_and(|menu| menu.visible())
8539 }
8540
8541 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8542 self.context_menu
8543 .borrow()
8544 .as_ref()
8545 .map(|menu| menu.origin())
8546 }
8547
8548 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8549 self.context_menu_options = Some(options);
8550 }
8551
8552 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8553 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8554
8555 fn render_edit_prediction_popover(
8556 &mut self,
8557 text_bounds: &Bounds<Pixels>,
8558 content_origin: gpui::Point<Pixels>,
8559 right_margin: Pixels,
8560 editor_snapshot: &EditorSnapshot,
8561 visible_row_range: Range<DisplayRow>,
8562 scroll_top: f32,
8563 scroll_bottom: f32,
8564 line_layouts: &[LineWithInvisibles],
8565 line_height: Pixels,
8566 scroll_pixel_position: gpui::Point<Pixels>,
8567 newest_selection_head: Option<DisplayPoint>,
8568 editor_width: Pixels,
8569 style: &EditorStyle,
8570 window: &mut Window,
8571 cx: &mut App,
8572 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8573 if self.mode().is_minimap() {
8574 return None;
8575 }
8576 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8577
8578 if self.edit_prediction_visible_in_cursor_popover(true) {
8579 return None;
8580 }
8581
8582 match &active_edit_prediction.completion {
8583 EditPrediction::Move { target, .. } => {
8584 let target_display_point = target.to_display_point(editor_snapshot);
8585
8586 if self.edit_prediction_requires_modifier() {
8587 if !self.edit_prediction_preview_is_active() {
8588 return None;
8589 }
8590
8591 self.render_edit_prediction_modifier_jump_popover(
8592 text_bounds,
8593 content_origin,
8594 visible_row_range,
8595 line_layouts,
8596 line_height,
8597 scroll_pixel_position,
8598 newest_selection_head,
8599 target_display_point,
8600 window,
8601 cx,
8602 )
8603 } else {
8604 self.render_edit_prediction_eager_jump_popover(
8605 text_bounds,
8606 content_origin,
8607 editor_snapshot,
8608 visible_row_range,
8609 scroll_top,
8610 scroll_bottom,
8611 line_height,
8612 scroll_pixel_position,
8613 target_display_point,
8614 editor_width,
8615 window,
8616 cx,
8617 )
8618 }
8619 }
8620 EditPrediction::Edit {
8621 display_mode: EditDisplayMode::Inline,
8622 ..
8623 } => None,
8624 EditPrediction::Edit {
8625 display_mode: EditDisplayMode::TabAccept,
8626 edits,
8627 ..
8628 } => {
8629 let range = &edits.first()?.0;
8630 let target_display_point = range.end.to_display_point(editor_snapshot);
8631
8632 self.render_edit_prediction_end_of_line_popover(
8633 "Accept",
8634 editor_snapshot,
8635 visible_row_range,
8636 target_display_point,
8637 line_height,
8638 scroll_pixel_position,
8639 content_origin,
8640 editor_width,
8641 window,
8642 cx,
8643 )
8644 }
8645 EditPrediction::Edit {
8646 edits,
8647 edit_preview,
8648 display_mode: EditDisplayMode::DiffPopover,
8649 snapshot,
8650 } => self.render_edit_prediction_diff_popover(
8651 text_bounds,
8652 content_origin,
8653 right_margin,
8654 editor_snapshot,
8655 visible_row_range,
8656 line_layouts,
8657 line_height,
8658 scroll_pixel_position,
8659 newest_selection_head,
8660 editor_width,
8661 style,
8662 edits,
8663 edit_preview,
8664 snapshot,
8665 window,
8666 cx,
8667 ),
8668 }
8669 }
8670
8671 fn render_edit_prediction_modifier_jump_popover(
8672 &mut self,
8673 text_bounds: &Bounds<Pixels>,
8674 content_origin: gpui::Point<Pixels>,
8675 visible_row_range: Range<DisplayRow>,
8676 line_layouts: &[LineWithInvisibles],
8677 line_height: Pixels,
8678 scroll_pixel_position: gpui::Point<Pixels>,
8679 newest_selection_head: Option<DisplayPoint>,
8680 target_display_point: DisplayPoint,
8681 window: &mut Window,
8682 cx: &mut App,
8683 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8684 let scrolled_content_origin =
8685 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8686
8687 const SCROLL_PADDING_Y: Pixels = px(12.);
8688
8689 if target_display_point.row() < visible_row_range.start {
8690 return self.render_edit_prediction_scroll_popover(
8691 |_| SCROLL_PADDING_Y,
8692 IconName::ArrowUp,
8693 visible_row_range,
8694 line_layouts,
8695 newest_selection_head,
8696 scrolled_content_origin,
8697 window,
8698 cx,
8699 );
8700 } else if target_display_point.row() >= visible_row_range.end {
8701 return self.render_edit_prediction_scroll_popover(
8702 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8703 IconName::ArrowDown,
8704 visible_row_range,
8705 line_layouts,
8706 newest_selection_head,
8707 scrolled_content_origin,
8708 window,
8709 cx,
8710 );
8711 }
8712
8713 const POLE_WIDTH: Pixels = px(2.);
8714
8715 let line_layout =
8716 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8717 let target_column = target_display_point.column() as usize;
8718
8719 let target_x = line_layout.x_for_index(target_column);
8720 let target_y =
8721 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8722
8723 let flag_on_right = target_x < text_bounds.size.width / 2.;
8724
8725 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8726 border_color.l += 0.001;
8727
8728 let mut element = v_flex()
8729 .items_end()
8730 .when(flag_on_right, |el| el.items_start())
8731 .child(if flag_on_right {
8732 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8733 .rounded_bl(px(0.))
8734 .rounded_tl(px(0.))
8735 .border_l_2()
8736 .border_color(border_color)
8737 } else {
8738 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8739 .rounded_br(px(0.))
8740 .rounded_tr(px(0.))
8741 .border_r_2()
8742 .border_color(border_color)
8743 })
8744 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8745 .into_any();
8746
8747 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8748
8749 let mut origin = scrolled_content_origin + point(target_x, target_y)
8750 - point(
8751 if flag_on_right {
8752 POLE_WIDTH
8753 } else {
8754 size.width - POLE_WIDTH
8755 },
8756 size.height - line_height,
8757 );
8758
8759 origin.x = origin.x.max(content_origin.x);
8760
8761 element.prepaint_at(origin, window, cx);
8762
8763 Some((element, origin))
8764 }
8765
8766 fn render_edit_prediction_scroll_popover(
8767 &mut self,
8768 to_y: impl Fn(Size<Pixels>) -> Pixels,
8769 scroll_icon: IconName,
8770 visible_row_range: Range<DisplayRow>,
8771 line_layouts: &[LineWithInvisibles],
8772 newest_selection_head: Option<DisplayPoint>,
8773 scrolled_content_origin: gpui::Point<Pixels>,
8774 window: &mut Window,
8775 cx: &mut App,
8776 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8777 let mut element = self
8778 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8779 .into_any();
8780
8781 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8782
8783 let cursor = newest_selection_head?;
8784 let cursor_row_layout =
8785 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8786 let cursor_column = cursor.column() as usize;
8787
8788 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8789
8790 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8791
8792 element.prepaint_at(origin, window, cx);
8793 Some((element, origin))
8794 }
8795
8796 fn render_edit_prediction_eager_jump_popover(
8797 &mut self,
8798 text_bounds: &Bounds<Pixels>,
8799 content_origin: gpui::Point<Pixels>,
8800 editor_snapshot: &EditorSnapshot,
8801 visible_row_range: Range<DisplayRow>,
8802 scroll_top: f32,
8803 scroll_bottom: f32,
8804 line_height: Pixels,
8805 scroll_pixel_position: gpui::Point<Pixels>,
8806 target_display_point: DisplayPoint,
8807 editor_width: Pixels,
8808 window: &mut Window,
8809 cx: &mut App,
8810 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8811 if target_display_point.row().as_f32() < scroll_top {
8812 let mut element = self
8813 .render_edit_prediction_line_popover(
8814 "Jump to Edit",
8815 Some(IconName::ArrowUp),
8816 window,
8817 cx,
8818 )?
8819 .into_any();
8820
8821 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8822 let offset = point(
8823 (text_bounds.size.width - size.width) / 2.,
8824 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8825 );
8826
8827 let origin = text_bounds.origin + offset;
8828 element.prepaint_at(origin, window, cx);
8829 Some((element, origin))
8830 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8831 let mut element = self
8832 .render_edit_prediction_line_popover(
8833 "Jump to Edit",
8834 Some(IconName::ArrowDown),
8835 window,
8836 cx,
8837 )?
8838 .into_any();
8839
8840 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8841 let offset = point(
8842 (text_bounds.size.width - size.width) / 2.,
8843 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8844 );
8845
8846 let origin = text_bounds.origin + offset;
8847 element.prepaint_at(origin, window, cx);
8848 Some((element, origin))
8849 } else {
8850 self.render_edit_prediction_end_of_line_popover(
8851 "Jump to Edit",
8852 editor_snapshot,
8853 visible_row_range,
8854 target_display_point,
8855 line_height,
8856 scroll_pixel_position,
8857 content_origin,
8858 editor_width,
8859 window,
8860 cx,
8861 )
8862 }
8863 }
8864
8865 fn render_edit_prediction_end_of_line_popover(
8866 self: &mut Editor,
8867 label: &'static str,
8868 editor_snapshot: &EditorSnapshot,
8869 visible_row_range: Range<DisplayRow>,
8870 target_display_point: DisplayPoint,
8871 line_height: Pixels,
8872 scroll_pixel_position: gpui::Point<Pixels>,
8873 content_origin: gpui::Point<Pixels>,
8874 editor_width: Pixels,
8875 window: &mut Window,
8876 cx: &mut App,
8877 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8878 let target_line_end = DisplayPoint::new(
8879 target_display_point.row(),
8880 editor_snapshot.line_len(target_display_point.row()),
8881 );
8882
8883 let mut element = self
8884 .render_edit_prediction_line_popover(label, None, window, cx)?
8885 .into_any();
8886
8887 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8888
8889 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8890
8891 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8892 let mut origin = start_point
8893 + line_origin
8894 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8895 origin.x = origin.x.max(content_origin.x);
8896
8897 let max_x = content_origin.x + editor_width - size.width;
8898
8899 if origin.x > max_x {
8900 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8901
8902 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8903 origin.y += offset;
8904 IconName::ArrowUp
8905 } else {
8906 origin.y -= offset;
8907 IconName::ArrowDown
8908 };
8909
8910 element = self
8911 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8912 .into_any();
8913
8914 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8915
8916 origin.x = content_origin.x + editor_width - size.width - px(2.);
8917 }
8918
8919 element.prepaint_at(origin, window, cx);
8920 Some((element, origin))
8921 }
8922
8923 fn render_edit_prediction_diff_popover(
8924 self: &Editor,
8925 text_bounds: &Bounds<Pixels>,
8926 content_origin: gpui::Point<Pixels>,
8927 right_margin: Pixels,
8928 editor_snapshot: &EditorSnapshot,
8929 visible_row_range: Range<DisplayRow>,
8930 line_layouts: &[LineWithInvisibles],
8931 line_height: Pixels,
8932 scroll_pixel_position: gpui::Point<Pixels>,
8933 newest_selection_head: Option<DisplayPoint>,
8934 editor_width: Pixels,
8935 style: &EditorStyle,
8936 edits: &Vec<(Range<Anchor>, String)>,
8937 edit_preview: &Option<language::EditPreview>,
8938 snapshot: &language::BufferSnapshot,
8939 window: &mut Window,
8940 cx: &mut App,
8941 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8942 let edit_start = edits
8943 .first()
8944 .unwrap()
8945 .0
8946 .start
8947 .to_display_point(editor_snapshot);
8948 let edit_end = edits
8949 .last()
8950 .unwrap()
8951 .0
8952 .end
8953 .to_display_point(editor_snapshot);
8954
8955 let is_visible = visible_row_range.contains(&edit_start.row())
8956 || visible_row_range.contains(&edit_end.row());
8957 if !is_visible {
8958 return None;
8959 }
8960
8961 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8962 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8963 } else {
8964 // Fallback for providers without edit_preview
8965 crate::edit_prediction_fallback_text(edits, cx)
8966 };
8967
8968 let styled_text = highlighted_edits.to_styled_text(&style.text);
8969 let line_count = highlighted_edits.text.lines().count();
8970
8971 const BORDER_WIDTH: Pixels = px(1.);
8972
8973 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8974 let has_keybind = keybind.is_some();
8975
8976 let mut element = h_flex()
8977 .items_start()
8978 .child(
8979 h_flex()
8980 .bg(cx.theme().colors().editor_background)
8981 .border(BORDER_WIDTH)
8982 .shadow_xs()
8983 .border_color(cx.theme().colors().border)
8984 .rounded_l_lg()
8985 .when(line_count > 1, |el| el.rounded_br_lg())
8986 .pr_1()
8987 .child(styled_text),
8988 )
8989 .child(
8990 h_flex()
8991 .h(line_height + BORDER_WIDTH * 2.)
8992 .px_1p5()
8993 .gap_1()
8994 // Workaround: For some reason, there's a gap if we don't do this
8995 .ml(-BORDER_WIDTH)
8996 .shadow(vec![gpui::BoxShadow {
8997 color: gpui::black().opacity(0.05),
8998 offset: point(px(1.), px(1.)),
8999 blur_radius: px(2.),
9000 spread_radius: px(0.),
9001 }])
9002 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9003 .border(BORDER_WIDTH)
9004 .border_color(cx.theme().colors().border)
9005 .rounded_r_lg()
9006 .id("edit_prediction_diff_popover_keybind")
9007 .when(!has_keybind, |el| {
9008 let status_colors = cx.theme().status();
9009
9010 el.bg(status_colors.error_background)
9011 .border_color(status_colors.error.opacity(0.6))
9012 .child(Icon::new(IconName::Info).color(Color::Error))
9013 .cursor_default()
9014 .hoverable_tooltip(move |_window, cx| {
9015 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9016 })
9017 })
9018 .children(keybind),
9019 )
9020 .into_any();
9021
9022 let longest_row =
9023 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9024 let longest_line_width = if visible_row_range.contains(&longest_row) {
9025 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9026 } else {
9027 layout_line(
9028 longest_row,
9029 editor_snapshot,
9030 style,
9031 editor_width,
9032 |_| false,
9033 window,
9034 cx,
9035 )
9036 .width
9037 };
9038
9039 let viewport_bounds =
9040 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9041 right: -right_margin,
9042 ..Default::default()
9043 });
9044
9045 let x_after_longest =
9046 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9047 - scroll_pixel_position.x;
9048
9049 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9050
9051 // Fully visible if it can be displayed within the window (allow overlapping other
9052 // panes). However, this is only allowed if the popover starts within text_bounds.
9053 let can_position_to_the_right = x_after_longest < text_bounds.right()
9054 && x_after_longest + element_bounds.width < viewport_bounds.right();
9055
9056 let mut origin = if can_position_to_the_right {
9057 point(
9058 x_after_longest,
9059 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9060 - scroll_pixel_position.y,
9061 )
9062 } else {
9063 let cursor_row = newest_selection_head.map(|head| head.row());
9064 let above_edit = edit_start
9065 .row()
9066 .0
9067 .checked_sub(line_count as u32)
9068 .map(DisplayRow);
9069 let below_edit = Some(edit_end.row() + 1);
9070 let above_cursor =
9071 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9072 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9073
9074 // Place the edit popover adjacent to the edit if there is a location
9075 // available that is onscreen and does not obscure the cursor. Otherwise,
9076 // place it adjacent to the cursor.
9077 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9078 .into_iter()
9079 .flatten()
9080 .find(|&start_row| {
9081 let end_row = start_row + line_count as u32;
9082 visible_row_range.contains(&start_row)
9083 && visible_row_range.contains(&end_row)
9084 && cursor_row
9085 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9086 })?;
9087
9088 content_origin
9089 + point(
9090 -scroll_pixel_position.x,
9091 row_target.as_f32() * line_height - scroll_pixel_position.y,
9092 )
9093 };
9094
9095 origin.x -= BORDER_WIDTH;
9096
9097 window.defer_draw(element, origin, 1);
9098
9099 // Do not return an element, since it will already be drawn due to defer_draw.
9100 None
9101 }
9102
9103 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9104 px(30.)
9105 }
9106
9107 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9108 if self.read_only(cx) {
9109 cx.theme().players().read_only()
9110 } else {
9111 self.style.as_ref().unwrap().local_player
9112 }
9113 }
9114
9115 fn render_edit_prediction_accept_keybind(
9116 &self,
9117 window: &mut Window,
9118 cx: &App,
9119 ) -> Option<AnyElement> {
9120 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9121 let accept_keystroke = accept_binding.keystroke()?;
9122
9123 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9124
9125 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9126 Color::Accent
9127 } else {
9128 Color::Muted
9129 };
9130
9131 h_flex()
9132 .px_0p5()
9133 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9134 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9135 .text_size(TextSize::XSmall.rems(cx))
9136 .child(h_flex().children(ui::render_modifiers(
9137 accept_keystroke.modifiers(),
9138 PlatformStyle::platform(),
9139 Some(modifiers_color),
9140 Some(IconSize::XSmall.rems().into()),
9141 true,
9142 )))
9143 .when(is_platform_style_mac, |parent| {
9144 parent.child(accept_keystroke.key().to_string())
9145 })
9146 .when(!is_platform_style_mac, |parent| {
9147 parent.child(
9148 Key::new(
9149 util::capitalize(accept_keystroke.key()),
9150 Some(Color::Default),
9151 )
9152 .size(Some(IconSize::XSmall.rems().into())),
9153 )
9154 })
9155 .into_any()
9156 .into()
9157 }
9158
9159 fn render_edit_prediction_line_popover(
9160 &self,
9161 label: impl Into<SharedString>,
9162 icon: Option<IconName>,
9163 window: &mut Window,
9164 cx: &App,
9165 ) -> Option<Stateful<Div>> {
9166 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9167
9168 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9169 let has_keybind = keybind.is_some();
9170
9171 let result = h_flex()
9172 .id("ep-line-popover")
9173 .py_0p5()
9174 .pl_1()
9175 .pr(padding_right)
9176 .gap_1()
9177 .rounded_md()
9178 .border_1()
9179 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9180 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9181 .shadow_xs()
9182 .when(!has_keybind, |el| {
9183 let status_colors = cx.theme().status();
9184
9185 el.bg(status_colors.error_background)
9186 .border_color(status_colors.error.opacity(0.6))
9187 .pl_2()
9188 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9189 .cursor_default()
9190 .hoverable_tooltip(move |_window, cx| {
9191 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9192 })
9193 })
9194 .children(keybind)
9195 .child(
9196 Label::new(label)
9197 .size(LabelSize::Small)
9198 .when(!has_keybind, |el| {
9199 el.color(cx.theme().status().error.into()).strikethrough()
9200 }),
9201 )
9202 .when(!has_keybind, |el| {
9203 el.child(
9204 h_flex().ml_1().child(
9205 Icon::new(IconName::Info)
9206 .size(IconSize::Small)
9207 .color(cx.theme().status().error.into()),
9208 ),
9209 )
9210 })
9211 .when_some(icon, |element, icon| {
9212 element.child(
9213 div()
9214 .mt(px(1.5))
9215 .child(Icon::new(icon).size(IconSize::Small)),
9216 )
9217 });
9218
9219 Some(result)
9220 }
9221
9222 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9223 let accent_color = cx.theme().colors().text_accent;
9224 let editor_bg_color = cx.theme().colors().editor_background;
9225 editor_bg_color.blend(accent_color.opacity(0.1))
9226 }
9227
9228 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9229 let accent_color = cx.theme().colors().text_accent;
9230 let editor_bg_color = cx.theme().colors().editor_background;
9231 editor_bg_color.blend(accent_color.opacity(0.6))
9232 }
9233 fn get_prediction_provider_icon_name(
9234 provider: &Option<RegisteredEditPredictionProvider>,
9235 ) -> IconName {
9236 match provider {
9237 Some(provider) => match provider.provider.name() {
9238 "copilot" => IconName::Copilot,
9239 "supermaven" => IconName::Supermaven,
9240 _ => IconName::ZedPredict,
9241 },
9242 None => IconName::ZedPredict,
9243 }
9244 }
9245
9246 fn render_edit_prediction_cursor_popover(
9247 &self,
9248 min_width: Pixels,
9249 max_width: Pixels,
9250 cursor_point: Point,
9251 style: &EditorStyle,
9252 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9253 _window: &Window,
9254 cx: &mut Context<Editor>,
9255 ) -> Option<AnyElement> {
9256 let provider = self.edit_prediction_provider.as_ref()?;
9257 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9258
9259 let is_refreshing = provider.provider.is_refreshing(cx);
9260
9261 fn pending_completion_container(icon: IconName) -> Div {
9262 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9263 }
9264
9265 let completion = match &self.active_edit_prediction {
9266 Some(prediction) => {
9267 if !self.has_visible_completions_menu() {
9268 const RADIUS: Pixels = px(6.);
9269 const BORDER_WIDTH: Pixels = px(1.);
9270
9271 return Some(
9272 h_flex()
9273 .elevation_2(cx)
9274 .border(BORDER_WIDTH)
9275 .border_color(cx.theme().colors().border)
9276 .when(accept_keystroke.is_none(), |el| {
9277 el.border_color(cx.theme().status().error)
9278 })
9279 .rounded(RADIUS)
9280 .rounded_tl(px(0.))
9281 .overflow_hidden()
9282 .child(div().px_1p5().child(match &prediction.completion {
9283 EditPrediction::Move { target, snapshot } => {
9284 use text::ToPoint as _;
9285 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9286 {
9287 Icon::new(IconName::ZedPredictDown)
9288 } else {
9289 Icon::new(IconName::ZedPredictUp)
9290 }
9291 }
9292 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9293 }))
9294 .child(
9295 h_flex()
9296 .gap_1()
9297 .py_1()
9298 .px_2()
9299 .rounded_r(RADIUS - BORDER_WIDTH)
9300 .border_l_1()
9301 .border_color(cx.theme().colors().border)
9302 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9303 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9304 el.child(
9305 Label::new("Hold")
9306 .size(LabelSize::Small)
9307 .when(accept_keystroke.is_none(), |el| {
9308 el.strikethrough()
9309 })
9310 .line_height_style(LineHeightStyle::UiLabel),
9311 )
9312 })
9313 .id("edit_prediction_cursor_popover_keybind")
9314 .when(accept_keystroke.is_none(), |el| {
9315 let status_colors = cx.theme().status();
9316
9317 el.bg(status_colors.error_background)
9318 .border_color(status_colors.error.opacity(0.6))
9319 .child(Icon::new(IconName::Info).color(Color::Error))
9320 .cursor_default()
9321 .hoverable_tooltip(move |_window, cx| {
9322 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9323 .into()
9324 })
9325 })
9326 .when_some(
9327 accept_keystroke.as_ref(),
9328 |el, accept_keystroke| {
9329 el.child(h_flex().children(ui::render_modifiers(
9330 accept_keystroke.modifiers(),
9331 PlatformStyle::platform(),
9332 Some(Color::Default),
9333 Some(IconSize::XSmall.rems().into()),
9334 false,
9335 )))
9336 },
9337 ),
9338 )
9339 .into_any(),
9340 );
9341 }
9342
9343 self.render_edit_prediction_cursor_popover_preview(
9344 prediction,
9345 cursor_point,
9346 style,
9347 cx,
9348 )?
9349 }
9350
9351 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9352 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9353 stale_completion,
9354 cursor_point,
9355 style,
9356 cx,
9357 )?,
9358
9359 None => pending_completion_container(provider_icon)
9360 .child(Label::new("...").size(LabelSize::Small)),
9361 },
9362
9363 None => pending_completion_container(provider_icon)
9364 .child(Label::new("...").size(LabelSize::Small)),
9365 };
9366
9367 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9368 completion
9369 .with_animation(
9370 "loading-completion",
9371 Animation::new(Duration::from_secs(2))
9372 .repeat()
9373 .with_easing(pulsating_between(0.4, 0.8)),
9374 |label, delta| label.opacity(delta),
9375 )
9376 .into_any_element()
9377 } else {
9378 completion.into_any_element()
9379 };
9380
9381 let has_completion = self.active_edit_prediction.is_some();
9382
9383 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9384 Some(
9385 h_flex()
9386 .min_w(min_width)
9387 .max_w(max_width)
9388 .flex_1()
9389 .elevation_2(cx)
9390 .border_color(cx.theme().colors().border)
9391 .child(
9392 div()
9393 .flex_1()
9394 .py_1()
9395 .px_2()
9396 .overflow_hidden()
9397 .child(completion),
9398 )
9399 .when_some(accept_keystroke, |el, accept_keystroke| {
9400 if !accept_keystroke.modifiers().modified() {
9401 return el;
9402 }
9403
9404 el.child(
9405 h_flex()
9406 .h_full()
9407 .border_l_1()
9408 .rounded_r_lg()
9409 .border_color(cx.theme().colors().border)
9410 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9411 .gap_1()
9412 .py_1()
9413 .px_2()
9414 .child(
9415 h_flex()
9416 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9417 .when(is_platform_style_mac, |parent| parent.gap_1())
9418 .child(h_flex().children(ui::render_modifiers(
9419 accept_keystroke.modifiers(),
9420 PlatformStyle::platform(),
9421 Some(if !has_completion {
9422 Color::Muted
9423 } else {
9424 Color::Default
9425 }),
9426 None,
9427 false,
9428 ))),
9429 )
9430 .child(Label::new("Preview").into_any_element())
9431 .opacity(if has_completion { 1.0 } else { 0.4 }),
9432 )
9433 })
9434 .into_any(),
9435 )
9436 }
9437
9438 fn render_edit_prediction_cursor_popover_preview(
9439 &self,
9440 completion: &EditPredictionState,
9441 cursor_point: Point,
9442 style: &EditorStyle,
9443 cx: &mut Context<Editor>,
9444 ) -> Option<Div> {
9445 use text::ToPoint as _;
9446
9447 fn render_relative_row_jump(
9448 prefix: impl Into<String>,
9449 current_row: u32,
9450 target_row: u32,
9451 ) -> Div {
9452 let (row_diff, arrow) = if target_row < current_row {
9453 (current_row - target_row, IconName::ArrowUp)
9454 } else {
9455 (target_row - current_row, IconName::ArrowDown)
9456 };
9457
9458 h_flex()
9459 .child(
9460 Label::new(format!("{}{}", prefix.into(), row_diff))
9461 .color(Color::Muted)
9462 .size(LabelSize::Small),
9463 )
9464 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9465 }
9466
9467 let supports_jump = self
9468 .edit_prediction_provider
9469 .as_ref()
9470 .map(|provider| provider.provider.supports_jump_to_edit())
9471 .unwrap_or(true);
9472
9473 match &completion.completion {
9474 EditPrediction::Move {
9475 target, snapshot, ..
9476 } => {
9477 if !supports_jump {
9478 return None;
9479 }
9480
9481 Some(
9482 h_flex()
9483 .px_2()
9484 .gap_2()
9485 .flex_1()
9486 .child(
9487 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9488 Icon::new(IconName::ZedPredictDown)
9489 } else {
9490 Icon::new(IconName::ZedPredictUp)
9491 },
9492 )
9493 .child(Label::new("Jump to Edit")),
9494 )
9495 }
9496
9497 EditPrediction::Edit {
9498 edits,
9499 edit_preview,
9500 snapshot,
9501 display_mode: _,
9502 } => {
9503 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9504
9505 let (highlighted_edits, has_more_lines) =
9506 if let Some(edit_preview) = edit_preview.as_ref() {
9507 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9508 .first_line_preview()
9509 } else {
9510 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9511 };
9512
9513 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9514 .with_default_highlights(&style.text, highlighted_edits.highlights);
9515
9516 let preview = h_flex()
9517 .gap_1()
9518 .min_w_16()
9519 .child(styled_text)
9520 .when(has_more_lines, |parent| parent.child("…"));
9521
9522 let left = if supports_jump && first_edit_row != cursor_point.row {
9523 render_relative_row_jump("", cursor_point.row, first_edit_row)
9524 .into_any_element()
9525 } else {
9526 let icon_name =
9527 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9528 Icon::new(icon_name).into_any_element()
9529 };
9530
9531 Some(
9532 h_flex()
9533 .h_full()
9534 .flex_1()
9535 .gap_2()
9536 .pr_1()
9537 .overflow_x_hidden()
9538 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9539 .child(left)
9540 .child(preview),
9541 )
9542 }
9543 }
9544 }
9545
9546 pub fn render_context_menu(
9547 &self,
9548 style: &EditorStyle,
9549 max_height_in_lines: u32,
9550 window: &mut Window,
9551 cx: &mut Context<Editor>,
9552 ) -> Option<AnyElement> {
9553 let menu = self.context_menu.borrow();
9554 let menu = menu.as_ref()?;
9555 if !menu.visible() {
9556 return None;
9557 };
9558 Some(menu.render(style, max_height_in_lines, window, cx))
9559 }
9560
9561 fn render_context_menu_aside(
9562 &mut self,
9563 max_size: Size<Pixels>,
9564 window: &mut Window,
9565 cx: &mut Context<Editor>,
9566 ) -> Option<AnyElement> {
9567 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9568 if menu.visible() {
9569 menu.render_aside(max_size, window, cx)
9570 } else {
9571 None
9572 }
9573 })
9574 }
9575
9576 fn hide_context_menu(
9577 &mut self,
9578 window: &mut Window,
9579 cx: &mut Context<Self>,
9580 ) -> Option<CodeContextMenu> {
9581 cx.notify();
9582 self.completion_tasks.clear();
9583 let context_menu = self.context_menu.borrow_mut().take();
9584 self.stale_edit_prediction_in_menu.take();
9585 self.update_visible_edit_prediction(window, cx);
9586 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9587 && let Some(completion_provider) = &self.completion_provider
9588 {
9589 completion_provider.selection_changed(None, window, cx);
9590 }
9591 context_menu
9592 }
9593
9594 fn show_snippet_choices(
9595 &mut self,
9596 choices: &Vec<String>,
9597 selection: Range<Anchor>,
9598 cx: &mut Context<Self>,
9599 ) {
9600 let Some((_, buffer, _)) = self
9601 .buffer()
9602 .read(cx)
9603 .excerpt_containing(selection.start, cx)
9604 else {
9605 return;
9606 };
9607 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9608 else {
9609 return;
9610 };
9611 if buffer != end_buffer {
9612 log::error!("expected anchor range to have matching buffer IDs");
9613 return;
9614 }
9615
9616 let id = post_inc(&mut self.next_completion_id);
9617 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9618 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9619 CompletionsMenu::new_snippet_choices(
9620 id,
9621 true,
9622 choices,
9623 selection,
9624 buffer,
9625 snippet_sort_order,
9626 ),
9627 ));
9628 }
9629
9630 pub fn insert_snippet(
9631 &mut self,
9632 insertion_ranges: &[Range<usize>],
9633 snippet: Snippet,
9634 window: &mut Window,
9635 cx: &mut Context<Self>,
9636 ) -> Result<()> {
9637 struct Tabstop<T> {
9638 is_end_tabstop: bool,
9639 ranges: Vec<Range<T>>,
9640 choices: Option<Vec<String>>,
9641 }
9642
9643 let tabstops = self.buffer.update(cx, |buffer, cx| {
9644 let snippet_text: Arc<str> = snippet.text.clone().into();
9645 let edits = insertion_ranges
9646 .iter()
9647 .cloned()
9648 .map(|range| (range, snippet_text.clone()));
9649 let autoindent_mode = AutoindentMode::Block {
9650 original_indent_columns: Vec::new(),
9651 };
9652 buffer.edit(edits, Some(autoindent_mode), cx);
9653
9654 let snapshot = &*buffer.read(cx);
9655 let snippet = &snippet;
9656 snippet
9657 .tabstops
9658 .iter()
9659 .map(|tabstop| {
9660 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9661 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9662 });
9663 let mut tabstop_ranges = tabstop
9664 .ranges
9665 .iter()
9666 .flat_map(|tabstop_range| {
9667 let mut delta = 0_isize;
9668 insertion_ranges.iter().map(move |insertion_range| {
9669 let insertion_start = insertion_range.start as isize + delta;
9670 delta +=
9671 snippet.text.len() as isize - insertion_range.len() as isize;
9672
9673 let start = ((insertion_start + tabstop_range.start) as usize)
9674 .min(snapshot.len());
9675 let end = ((insertion_start + tabstop_range.end) as usize)
9676 .min(snapshot.len());
9677 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9678 })
9679 })
9680 .collect::<Vec<_>>();
9681 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9682
9683 Tabstop {
9684 is_end_tabstop,
9685 ranges: tabstop_ranges,
9686 choices: tabstop.choices.clone(),
9687 }
9688 })
9689 .collect::<Vec<_>>()
9690 });
9691 if let Some(tabstop) = tabstops.first() {
9692 self.change_selections(Default::default(), window, cx, |s| {
9693 // Reverse order so that the first range is the newest created selection.
9694 // Completions will use it and autoscroll will prioritize it.
9695 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9696 });
9697
9698 if let Some(choices) = &tabstop.choices
9699 && let Some(selection) = tabstop.ranges.first()
9700 {
9701 self.show_snippet_choices(choices, selection.clone(), cx)
9702 }
9703
9704 // If we're already at the last tabstop and it's at the end of the snippet,
9705 // we're done, we don't need to keep the state around.
9706 if !tabstop.is_end_tabstop {
9707 let choices = tabstops
9708 .iter()
9709 .map(|tabstop| tabstop.choices.clone())
9710 .collect();
9711
9712 let ranges = tabstops
9713 .into_iter()
9714 .map(|tabstop| tabstop.ranges)
9715 .collect::<Vec<_>>();
9716
9717 self.snippet_stack.push(SnippetState {
9718 active_index: 0,
9719 ranges,
9720 choices,
9721 });
9722 }
9723
9724 // Check whether the just-entered snippet ends with an auto-closable bracket.
9725 if self.autoclose_regions.is_empty() {
9726 let snapshot = self.buffer.read(cx).snapshot(cx);
9727 let mut all_selections = self.selections.all::<Point>(cx);
9728 for selection in &mut all_selections {
9729 let selection_head = selection.head();
9730 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9731 continue;
9732 };
9733
9734 let mut bracket_pair = None;
9735 let max_lookup_length = scope
9736 .brackets()
9737 .map(|(pair, _)| {
9738 pair.start
9739 .as_str()
9740 .chars()
9741 .count()
9742 .max(pair.end.as_str().chars().count())
9743 })
9744 .max();
9745 if let Some(max_lookup_length) = max_lookup_length {
9746 let next_text = snapshot
9747 .chars_at(selection_head)
9748 .take(max_lookup_length)
9749 .collect::<String>();
9750 let prev_text = snapshot
9751 .reversed_chars_at(selection_head)
9752 .take(max_lookup_length)
9753 .collect::<String>();
9754
9755 for (pair, enabled) in scope.brackets() {
9756 if enabled
9757 && pair.close
9758 && prev_text.starts_with(pair.start.as_str())
9759 && next_text.starts_with(pair.end.as_str())
9760 {
9761 bracket_pair = Some(pair.clone());
9762 break;
9763 }
9764 }
9765 }
9766
9767 if let Some(pair) = bracket_pair {
9768 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9769 let autoclose_enabled =
9770 self.use_autoclose && snapshot_settings.use_autoclose;
9771 if autoclose_enabled {
9772 let start = snapshot.anchor_after(selection_head);
9773 let end = snapshot.anchor_after(selection_head);
9774 self.autoclose_regions.push(AutocloseRegion {
9775 selection_id: selection.id,
9776 range: start..end,
9777 pair,
9778 });
9779 }
9780 }
9781 }
9782 }
9783 }
9784 Ok(())
9785 }
9786
9787 pub fn move_to_next_snippet_tabstop(
9788 &mut self,
9789 window: &mut Window,
9790 cx: &mut Context<Self>,
9791 ) -> bool {
9792 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9793 }
9794
9795 pub fn move_to_prev_snippet_tabstop(
9796 &mut self,
9797 window: &mut Window,
9798 cx: &mut Context<Self>,
9799 ) -> bool {
9800 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9801 }
9802
9803 pub fn move_to_snippet_tabstop(
9804 &mut self,
9805 bias: Bias,
9806 window: &mut Window,
9807 cx: &mut Context<Self>,
9808 ) -> bool {
9809 if let Some(mut snippet) = self.snippet_stack.pop() {
9810 match bias {
9811 Bias::Left => {
9812 if snippet.active_index > 0 {
9813 snippet.active_index -= 1;
9814 } else {
9815 self.snippet_stack.push(snippet);
9816 return false;
9817 }
9818 }
9819 Bias::Right => {
9820 if snippet.active_index + 1 < snippet.ranges.len() {
9821 snippet.active_index += 1;
9822 } else {
9823 self.snippet_stack.push(snippet);
9824 return false;
9825 }
9826 }
9827 }
9828 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9829 self.change_selections(Default::default(), window, cx, |s| {
9830 // Reverse order so that the first range is the newest created selection.
9831 // Completions will use it and autoscroll will prioritize it.
9832 s.select_ranges(current_ranges.iter().rev().cloned())
9833 });
9834
9835 if let Some(choices) = &snippet.choices[snippet.active_index]
9836 && let Some(selection) = current_ranges.first()
9837 {
9838 self.show_snippet_choices(choices, selection.clone(), cx);
9839 }
9840
9841 // If snippet state is not at the last tabstop, push it back on the stack
9842 if snippet.active_index + 1 < snippet.ranges.len() {
9843 self.snippet_stack.push(snippet);
9844 }
9845 return true;
9846 }
9847 }
9848
9849 false
9850 }
9851
9852 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9853 self.transact(window, cx, |this, window, cx| {
9854 this.select_all(&SelectAll, window, cx);
9855 this.insert("", window, cx);
9856 });
9857 }
9858
9859 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9860 if self.read_only(cx) {
9861 return;
9862 }
9863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9864 self.transact(window, cx, |this, window, cx| {
9865 this.select_autoclose_pair(window, cx);
9866 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9867 if !this.linked_edit_ranges.is_empty() {
9868 let selections = this.selections.all::<MultiBufferPoint>(cx);
9869 let snapshot = this.buffer.read(cx).snapshot(cx);
9870
9871 for selection in selections.iter() {
9872 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9873 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9874 if selection_start.buffer_id != selection_end.buffer_id {
9875 continue;
9876 }
9877 if let Some(ranges) =
9878 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9879 {
9880 for (buffer, entries) in ranges {
9881 linked_ranges.entry(buffer).or_default().extend(entries);
9882 }
9883 }
9884 }
9885 }
9886
9887 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9888 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9889 for selection in &mut selections {
9890 if selection.is_empty() {
9891 let old_head = selection.head();
9892 let mut new_head =
9893 movement::left(&display_map, old_head.to_display_point(&display_map))
9894 .to_point(&display_map);
9895 if let Some((buffer, line_buffer_range)) = display_map
9896 .buffer_snapshot
9897 .buffer_line_for_row(MultiBufferRow(old_head.row))
9898 {
9899 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9900 let indent_len = match indent_size.kind {
9901 IndentKind::Space => {
9902 buffer.settings_at(line_buffer_range.start, cx).tab_size
9903 }
9904 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9905 };
9906 if old_head.column <= indent_size.len && old_head.column > 0 {
9907 let indent_len = indent_len.get();
9908 new_head = cmp::min(
9909 new_head,
9910 MultiBufferPoint::new(
9911 old_head.row,
9912 ((old_head.column - 1) / indent_len) * indent_len,
9913 ),
9914 );
9915 }
9916 }
9917
9918 selection.set_head(new_head, SelectionGoal::None);
9919 }
9920 }
9921
9922 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9923 this.insert("", window, cx);
9924 let empty_str: Arc<str> = Arc::from("");
9925 for (buffer, edits) in linked_ranges {
9926 let snapshot = buffer.read(cx).snapshot();
9927 use text::ToPoint as TP;
9928
9929 let edits = edits
9930 .into_iter()
9931 .map(|range| {
9932 let end_point = TP::to_point(&range.end, &snapshot);
9933 let mut start_point = TP::to_point(&range.start, &snapshot);
9934
9935 if end_point == start_point {
9936 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9937 .saturating_sub(1);
9938 start_point =
9939 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9940 };
9941
9942 (start_point..end_point, empty_str.clone())
9943 })
9944 .sorted_by_key(|(range, _)| range.start)
9945 .collect::<Vec<_>>();
9946 buffer.update(cx, |this, cx| {
9947 this.edit(edits, None, cx);
9948 })
9949 }
9950 this.refresh_edit_prediction(true, false, window, cx);
9951 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9952 });
9953 }
9954
9955 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9956 if self.read_only(cx) {
9957 return;
9958 }
9959 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9960 self.transact(window, cx, |this, window, cx| {
9961 this.change_selections(Default::default(), window, cx, |s| {
9962 s.move_with(|map, selection| {
9963 if selection.is_empty() {
9964 let cursor = movement::right(map, selection.head());
9965 selection.end = cursor;
9966 selection.reversed = true;
9967 selection.goal = SelectionGoal::None;
9968 }
9969 })
9970 });
9971 this.insert("", window, cx);
9972 this.refresh_edit_prediction(true, false, window, cx);
9973 });
9974 }
9975
9976 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9977 if self.mode.is_single_line() {
9978 cx.propagate();
9979 return;
9980 }
9981
9982 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9983 if self.move_to_prev_snippet_tabstop(window, cx) {
9984 return;
9985 }
9986 self.outdent(&Outdent, window, cx);
9987 }
9988
9989 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9990 if self.mode.is_single_line() {
9991 cx.propagate();
9992 return;
9993 }
9994
9995 if self.move_to_next_snippet_tabstop(window, cx) {
9996 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9997 return;
9998 }
9999 if self.read_only(cx) {
10000 return;
10001 }
10002 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10003 let mut selections = self.selections.all_adjusted(cx);
10004 let buffer = self.buffer.read(cx);
10005 let snapshot = buffer.snapshot(cx);
10006 let rows_iter = selections.iter().map(|s| s.head().row);
10007 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10008
10009 let has_some_cursor_in_whitespace = selections
10010 .iter()
10011 .filter(|selection| selection.is_empty())
10012 .any(|selection| {
10013 let cursor = selection.head();
10014 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10015 cursor.column < current_indent.len
10016 });
10017
10018 let mut edits = Vec::new();
10019 let mut prev_edited_row = 0;
10020 let mut row_delta = 0;
10021 for selection in &mut selections {
10022 if selection.start.row != prev_edited_row {
10023 row_delta = 0;
10024 }
10025 prev_edited_row = selection.end.row;
10026
10027 // If the selection is non-empty, then increase the indentation of the selected lines.
10028 if !selection.is_empty() {
10029 row_delta =
10030 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10031 continue;
10032 }
10033
10034 let cursor = selection.head();
10035 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10036 if let Some(suggested_indent) =
10037 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10038 {
10039 // Don't do anything if already at suggested indent
10040 // and there is any other cursor which is not
10041 if has_some_cursor_in_whitespace
10042 && cursor.column == current_indent.len
10043 && current_indent.len == suggested_indent.len
10044 {
10045 continue;
10046 }
10047
10048 // Adjust line and move cursor to suggested indent
10049 // if cursor is not at suggested indent
10050 if cursor.column < suggested_indent.len
10051 && cursor.column <= current_indent.len
10052 && current_indent.len <= suggested_indent.len
10053 {
10054 selection.start = Point::new(cursor.row, suggested_indent.len);
10055 selection.end = selection.start;
10056 if row_delta == 0 {
10057 edits.extend(Buffer::edit_for_indent_size_adjustment(
10058 cursor.row,
10059 current_indent,
10060 suggested_indent,
10061 ));
10062 row_delta = suggested_indent.len - current_indent.len;
10063 }
10064 continue;
10065 }
10066
10067 // If current indent is more than suggested indent
10068 // only move cursor to current indent and skip indent
10069 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10070 selection.start = Point::new(cursor.row, current_indent.len);
10071 selection.end = selection.start;
10072 continue;
10073 }
10074 }
10075
10076 // Otherwise, insert a hard or soft tab.
10077 let settings = buffer.language_settings_at(cursor, cx);
10078 let tab_size = if settings.hard_tabs {
10079 IndentSize::tab()
10080 } else {
10081 let tab_size = settings.tab_size.get();
10082 let indent_remainder = snapshot
10083 .text_for_range(Point::new(cursor.row, 0)..cursor)
10084 .flat_map(str::chars)
10085 .fold(row_delta % tab_size, |counter: u32, c| {
10086 if c == '\t' {
10087 0
10088 } else {
10089 (counter + 1) % tab_size
10090 }
10091 });
10092
10093 let chars_to_next_tab_stop = tab_size - indent_remainder;
10094 IndentSize::spaces(chars_to_next_tab_stop)
10095 };
10096 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10097 selection.end = selection.start;
10098 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10099 row_delta += tab_size.len;
10100 }
10101
10102 self.transact(window, cx, |this, window, cx| {
10103 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10104 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10105 this.refresh_edit_prediction(true, false, window, cx);
10106 });
10107 }
10108
10109 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10110 if self.read_only(cx) {
10111 return;
10112 }
10113 if self.mode.is_single_line() {
10114 cx.propagate();
10115 return;
10116 }
10117
10118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10119 let mut selections = self.selections.all::<Point>(cx);
10120 let mut prev_edited_row = 0;
10121 let mut row_delta = 0;
10122 let mut edits = Vec::new();
10123 let buffer = self.buffer.read(cx);
10124 let snapshot = buffer.snapshot(cx);
10125 for selection in &mut selections {
10126 if selection.start.row != prev_edited_row {
10127 row_delta = 0;
10128 }
10129 prev_edited_row = selection.end.row;
10130
10131 row_delta =
10132 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10133 }
10134
10135 self.transact(window, cx, |this, window, cx| {
10136 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10137 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10138 });
10139 }
10140
10141 fn indent_selection(
10142 buffer: &MultiBuffer,
10143 snapshot: &MultiBufferSnapshot,
10144 selection: &mut Selection<Point>,
10145 edits: &mut Vec<(Range<Point>, String)>,
10146 delta_for_start_row: u32,
10147 cx: &App,
10148 ) -> u32 {
10149 let settings = buffer.language_settings_at(selection.start, cx);
10150 let tab_size = settings.tab_size.get();
10151 let indent_kind = if settings.hard_tabs {
10152 IndentKind::Tab
10153 } else {
10154 IndentKind::Space
10155 };
10156 let mut start_row = selection.start.row;
10157 let mut end_row = selection.end.row + 1;
10158
10159 // If a selection ends at the beginning of a line, don't indent
10160 // that last line.
10161 if selection.end.column == 0 && selection.end.row > selection.start.row {
10162 end_row -= 1;
10163 }
10164
10165 // Avoid re-indenting a row that has already been indented by a
10166 // previous selection, but still update this selection's column
10167 // to reflect that indentation.
10168 if delta_for_start_row > 0 {
10169 start_row += 1;
10170 selection.start.column += delta_for_start_row;
10171 if selection.end.row == selection.start.row {
10172 selection.end.column += delta_for_start_row;
10173 }
10174 }
10175
10176 let mut delta_for_end_row = 0;
10177 let has_multiple_rows = start_row + 1 != end_row;
10178 for row in start_row..end_row {
10179 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10180 let indent_delta = match (current_indent.kind, indent_kind) {
10181 (IndentKind::Space, IndentKind::Space) => {
10182 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10183 IndentSize::spaces(columns_to_next_tab_stop)
10184 }
10185 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10186 (_, IndentKind::Tab) => IndentSize::tab(),
10187 };
10188
10189 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10190 0
10191 } else {
10192 selection.start.column
10193 };
10194 let row_start = Point::new(row, start);
10195 edits.push((
10196 row_start..row_start,
10197 indent_delta.chars().collect::<String>(),
10198 ));
10199
10200 // Update this selection's endpoints to reflect the indentation.
10201 if row == selection.start.row {
10202 selection.start.column += indent_delta.len;
10203 }
10204 if row == selection.end.row {
10205 selection.end.column += indent_delta.len;
10206 delta_for_end_row = indent_delta.len;
10207 }
10208 }
10209
10210 if selection.start.row == selection.end.row {
10211 delta_for_start_row + delta_for_end_row
10212 } else {
10213 delta_for_end_row
10214 }
10215 }
10216
10217 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10218 if self.read_only(cx) {
10219 return;
10220 }
10221 if self.mode.is_single_line() {
10222 cx.propagate();
10223 return;
10224 }
10225
10226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10228 let selections = self.selections.all::<Point>(cx);
10229 let mut deletion_ranges = Vec::new();
10230 let mut last_outdent = None;
10231 {
10232 let buffer = self.buffer.read(cx);
10233 let snapshot = buffer.snapshot(cx);
10234 for selection in &selections {
10235 let settings = buffer.language_settings_at(selection.start, cx);
10236 let tab_size = settings.tab_size.get();
10237 let mut rows = selection.spanned_rows(false, &display_map);
10238
10239 // Avoid re-outdenting a row that has already been outdented by a
10240 // previous selection.
10241 if let Some(last_row) = last_outdent
10242 && last_row == rows.start
10243 {
10244 rows.start = rows.start.next_row();
10245 }
10246 let has_multiple_rows = rows.len() > 1;
10247 for row in rows.iter_rows() {
10248 let indent_size = snapshot.indent_size_for_line(row);
10249 if indent_size.len > 0 {
10250 let deletion_len = match indent_size.kind {
10251 IndentKind::Space => {
10252 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10253 if columns_to_prev_tab_stop == 0 {
10254 tab_size
10255 } else {
10256 columns_to_prev_tab_stop
10257 }
10258 }
10259 IndentKind::Tab => 1,
10260 };
10261 let start = if has_multiple_rows
10262 || deletion_len > selection.start.column
10263 || indent_size.len < selection.start.column
10264 {
10265 0
10266 } else {
10267 selection.start.column - deletion_len
10268 };
10269 deletion_ranges.push(
10270 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10271 );
10272 last_outdent = Some(row);
10273 }
10274 }
10275 }
10276 }
10277
10278 self.transact(window, cx, |this, window, cx| {
10279 this.buffer.update(cx, |buffer, cx| {
10280 let empty_str: Arc<str> = Arc::default();
10281 buffer.edit(
10282 deletion_ranges
10283 .into_iter()
10284 .map(|range| (range, empty_str.clone())),
10285 None,
10286 cx,
10287 );
10288 });
10289 let selections = this.selections.all::<usize>(cx);
10290 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10291 });
10292 }
10293
10294 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10295 if self.read_only(cx) {
10296 return;
10297 }
10298 if self.mode.is_single_line() {
10299 cx.propagate();
10300 return;
10301 }
10302
10303 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10304 let selections = self
10305 .selections
10306 .all::<usize>(cx)
10307 .into_iter()
10308 .map(|s| s.range());
10309
10310 self.transact(window, cx, |this, window, cx| {
10311 this.buffer.update(cx, |buffer, cx| {
10312 buffer.autoindent_ranges(selections, cx);
10313 });
10314 let selections = this.selections.all::<usize>(cx);
10315 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10316 });
10317 }
10318
10319 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10321 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10322 let selections = self.selections.all::<Point>(cx);
10323
10324 let mut new_cursors = Vec::new();
10325 let mut edit_ranges = Vec::new();
10326 let mut selections = selections.iter().peekable();
10327 while let Some(selection) = selections.next() {
10328 let mut rows = selection.spanned_rows(false, &display_map);
10329 let goal_display_column = selection.head().to_display_point(&display_map).column();
10330
10331 // Accumulate contiguous regions of rows that we want to delete.
10332 while let Some(next_selection) = selections.peek() {
10333 let next_rows = next_selection.spanned_rows(false, &display_map);
10334 if next_rows.start <= rows.end {
10335 rows.end = next_rows.end;
10336 selections.next().unwrap();
10337 } else {
10338 break;
10339 }
10340 }
10341
10342 let buffer = &display_map.buffer_snapshot;
10343 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10344 let edit_end;
10345 let cursor_buffer_row;
10346 if buffer.max_point().row >= rows.end.0 {
10347 // If there's a line after the range, delete the \n from the end of the row range
10348 // and position the cursor on the next line.
10349 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10350 cursor_buffer_row = rows.end;
10351 } else {
10352 // If there isn't a line after the range, delete the \n from the line before the
10353 // start of the row range and position the cursor there.
10354 edit_start = edit_start.saturating_sub(1);
10355 edit_end = buffer.len();
10356 cursor_buffer_row = rows.start.previous_row();
10357 }
10358
10359 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10360 *cursor.column_mut() =
10361 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10362
10363 new_cursors.push((
10364 selection.id,
10365 buffer.anchor_after(cursor.to_point(&display_map)),
10366 ));
10367 edit_ranges.push(edit_start..edit_end);
10368 }
10369
10370 self.transact(window, cx, |this, window, cx| {
10371 let buffer = this.buffer.update(cx, |buffer, cx| {
10372 let empty_str: Arc<str> = Arc::default();
10373 buffer.edit(
10374 edit_ranges
10375 .into_iter()
10376 .map(|range| (range, empty_str.clone())),
10377 None,
10378 cx,
10379 );
10380 buffer.snapshot(cx)
10381 });
10382 let new_selections = new_cursors
10383 .into_iter()
10384 .map(|(id, cursor)| {
10385 let cursor = cursor.to_point(&buffer);
10386 Selection {
10387 id,
10388 start: cursor,
10389 end: cursor,
10390 reversed: false,
10391 goal: SelectionGoal::None,
10392 }
10393 })
10394 .collect();
10395
10396 this.change_selections(Default::default(), window, cx, |s| {
10397 s.select(new_selections);
10398 });
10399 });
10400 }
10401
10402 pub fn join_lines_impl(
10403 &mut self,
10404 insert_whitespace: bool,
10405 window: &mut Window,
10406 cx: &mut Context<Self>,
10407 ) {
10408 if self.read_only(cx) {
10409 return;
10410 }
10411 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10412 for selection in self.selections.all::<Point>(cx) {
10413 let start = MultiBufferRow(selection.start.row);
10414 // Treat single line selections as if they include the next line. Otherwise this action
10415 // would do nothing for single line selections individual cursors.
10416 let end = if selection.start.row == selection.end.row {
10417 MultiBufferRow(selection.start.row + 1)
10418 } else {
10419 MultiBufferRow(selection.end.row)
10420 };
10421
10422 if let Some(last_row_range) = row_ranges.last_mut()
10423 && start <= last_row_range.end
10424 {
10425 last_row_range.end = end;
10426 continue;
10427 }
10428 row_ranges.push(start..end);
10429 }
10430
10431 let snapshot = self.buffer.read(cx).snapshot(cx);
10432 let mut cursor_positions = Vec::new();
10433 for row_range in &row_ranges {
10434 let anchor = snapshot.anchor_before(Point::new(
10435 row_range.end.previous_row().0,
10436 snapshot.line_len(row_range.end.previous_row()),
10437 ));
10438 cursor_positions.push(anchor..anchor);
10439 }
10440
10441 self.transact(window, cx, |this, window, cx| {
10442 for row_range in row_ranges.into_iter().rev() {
10443 for row in row_range.iter_rows().rev() {
10444 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10445 let next_line_row = row.next_row();
10446 let indent = snapshot.indent_size_for_line(next_line_row);
10447 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10448
10449 let replace =
10450 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10451 " "
10452 } else {
10453 ""
10454 };
10455
10456 this.buffer.update(cx, |buffer, cx| {
10457 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10458 });
10459 }
10460 }
10461
10462 this.change_selections(Default::default(), window, cx, |s| {
10463 s.select_anchor_ranges(cursor_positions)
10464 });
10465 });
10466 }
10467
10468 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10470 self.join_lines_impl(true, window, cx);
10471 }
10472
10473 pub fn sort_lines_case_sensitive(
10474 &mut self,
10475 _: &SortLinesCaseSensitive,
10476 window: &mut Window,
10477 cx: &mut Context<Self>,
10478 ) {
10479 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10480 }
10481
10482 pub fn sort_lines_by_length(
10483 &mut self,
10484 _: &SortLinesByLength,
10485 window: &mut Window,
10486 cx: &mut Context<Self>,
10487 ) {
10488 self.manipulate_immutable_lines(window, cx, |lines| {
10489 lines.sort_by_key(|&line| line.chars().count())
10490 })
10491 }
10492
10493 pub fn sort_lines_case_insensitive(
10494 &mut self,
10495 _: &SortLinesCaseInsensitive,
10496 window: &mut Window,
10497 cx: &mut Context<Self>,
10498 ) {
10499 self.manipulate_immutable_lines(window, cx, |lines| {
10500 lines.sort_by_key(|line| line.to_lowercase())
10501 })
10502 }
10503
10504 pub fn unique_lines_case_insensitive(
10505 &mut self,
10506 _: &UniqueLinesCaseInsensitive,
10507 window: &mut Window,
10508 cx: &mut Context<Self>,
10509 ) {
10510 self.manipulate_immutable_lines(window, cx, |lines| {
10511 let mut seen = HashSet::default();
10512 lines.retain(|line| seen.insert(line.to_lowercase()));
10513 })
10514 }
10515
10516 pub fn unique_lines_case_sensitive(
10517 &mut self,
10518 _: &UniqueLinesCaseSensitive,
10519 window: &mut Window,
10520 cx: &mut Context<Self>,
10521 ) {
10522 self.manipulate_immutable_lines(window, cx, |lines| {
10523 let mut seen = HashSet::default();
10524 lines.retain(|line| seen.insert(*line));
10525 })
10526 }
10527
10528 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10529 let snapshot = self.buffer.read(cx).snapshot(cx);
10530 for selection in self.selections.disjoint_anchors_arc().iter() {
10531 if snapshot
10532 .language_at(selection.start)
10533 .and_then(|lang| lang.config().wrap_characters.as_ref())
10534 .is_some()
10535 {
10536 return true;
10537 }
10538 }
10539 false
10540 }
10541
10542 fn wrap_selections_in_tag(
10543 &mut self,
10544 _: &WrapSelectionsInTag,
10545 window: &mut Window,
10546 cx: &mut Context<Self>,
10547 ) {
10548 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10549
10550 let snapshot = self.buffer.read(cx).snapshot(cx);
10551
10552 let mut edits = Vec::new();
10553 let mut boundaries = Vec::new();
10554
10555 for selection in self.selections.all::<Point>(cx).iter() {
10556 let Some(wrap_config) = snapshot
10557 .language_at(selection.start)
10558 .and_then(|lang| lang.config().wrap_characters.clone())
10559 else {
10560 continue;
10561 };
10562
10563 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10564 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10565
10566 let start_before = snapshot.anchor_before(selection.start);
10567 let end_after = snapshot.anchor_after(selection.end);
10568
10569 edits.push((start_before..start_before, open_tag));
10570 edits.push((end_after..end_after, close_tag));
10571
10572 boundaries.push((
10573 start_before,
10574 end_after,
10575 wrap_config.start_prefix.len(),
10576 wrap_config.end_suffix.len(),
10577 ));
10578 }
10579
10580 if edits.is_empty() {
10581 return;
10582 }
10583
10584 self.transact(window, cx, |this, window, cx| {
10585 let buffer = this.buffer.update(cx, |buffer, cx| {
10586 buffer.edit(edits, None, cx);
10587 buffer.snapshot(cx)
10588 });
10589
10590 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10591 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10592 boundaries.into_iter()
10593 {
10594 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10595 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10596 new_selections.push(open_offset..open_offset);
10597 new_selections.push(close_offset..close_offset);
10598 }
10599
10600 this.change_selections(Default::default(), window, cx, |s| {
10601 s.select_ranges(new_selections);
10602 });
10603
10604 this.request_autoscroll(Autoscroll::fit(), cx);
10605 });
10606 }
10607
10608 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10609 let Some(project) = self.project.clone() else {
10610 return;
10611 };
10612 self.reload(project, window, cx)
10613 .detach_and_notify_err(window, cx);
10614 }
10615
10616 pub fn restore_file(
10617 &mut self,
10618 _: &::git::RestoreFile,
10619 window: &mut Window,
10620 cx: &mut Context<Self>,
10621 ) {
10622 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10623 let mut buffer_ids = HashSet::default();
10624 let snapshot = self.buffer().read(cx).snapshot(cx);
10625 for selection in self.selections.all::<usize>(cx) {
10626 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10627 }
10628
10629 let buffer = self.buffer().read(cx);
10630 let ranges = buffer_ids
10631 .into_iter()
10632 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10633 .collect::<Vec<_>>();
10634
10635 self.restore_hunks_in_ranges(ranges, window, cx);
10636 }
10637
10638 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10639 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10640 let selections = self
10641 .selections
10642 .all(cx)
10643 .into_iter()
10644 .map(|s| s.range())
10645 .collect();
10646 self.restore_hunks_in_ranges(selections, window, cx);
10647 }
10648
10649 pub fn restore_hunks_in_ranges(
10650 &mut self,
10651 ranges: Vec<Range<Point>>,
10652 window: &mut Window,
10653 cx: &mut Context<Editor>,
10654 ) {
10655 let mut revert_changes = HashMap::default();
10656 let chunk_by = self
10657 .snapshot(window, cx)
10658 .hunks_for_ranges(ranges)
10659 .into_iter()
10660 .chunk_by(|hunk| hunk.buffer_id);
10661 for (buffer_id, hunks) in &chunk_by {
10662 let hunks = hunks.collect::<Vec<_>>();
10663 for hunk in &hunks {
10664 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10665 }
10666 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10667 }
10668 drop(chunk_by);
10669 if !revert_changes.is_empty() {
10670 self.transact(window, cx, |editor, window, cx| {
10671 editor.restore(revert_changes, window, cx);
10672 });
10673 }
10674 }
10675
10676 pub fn open_active_item_in_terminal(
10677 &mut self,
10678 _: &OpenInTerminal,
10679 window: &mut Window,
10680 cx: &mut Context<Self>,
10681 ) {
10682 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10683 let project_path = buffer.read(cx).project_path(cx)?;
10684 let project = self.project()?.read(cx);
10685 let entry = project.entry_for_path(&project_path, cx)?;
10686 let parent = match &entry.canonical_path {
10687 Some(canonical_path) => canonical_path.to_path_buf(),
10688 None => project.absolute_path(&project_path, cx)?,
10689 }
10690 .parent()?
10691 .to_path_buf();
10692 Some(parent)
10693 }) {
10694 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10695 }
10696 }
10697
10698 fn set_breakpoint_context_menu(
10699 &mut self,
10700 display_row: DisplayRow,
10701 position: Option<Anchor>,
10702 clicked_point: gpui::Point<Pixels>,
10703 window: &mut Window,
10704 cx: &mut Context<Self>,
10705 ) {
10706 let source = self
10707 .buffer
10708 .read(cx)
10709 .snapshot(cx)
10710 .anchor_before(Point::new(display_row.0, 0u32));
10711
10712 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10713
10714 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10715 self,
10716 source,
10717 clicked_point,
10718 context_menu,
10719 window,
10720 cx,
10721 );
10722 }
10723
10724 fn add_edit_breakpoint_block(
10725 &mut self,
10726 anchor: Anchor,
10727 breakpoint: &Breakpoint,
10728 edit_action: BreakpointPromptEditAction,
10729 window: &mut Window,
10730 cx: &mut Context<Self>,
10731 ) {
10732 let weak_editor = cx.weak_entity();
10733 let bp_prompt = cx.new(|cx| {
10734 BreakpointPromptEditor::new(
10735 weak_editor,
10736 anchor,
10737 breakpoint.clone(),
10738 edit_action,
10739 window,
10740 cx,
10741 )
10742 });
10743
10744 let height = bp_prompt.update(cx, |this, cx| {
10745 this.prompt
10746 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10747 });
10748 let cloned_prompt = bp_prompt.clone();
10749 let blocks = vec![BlockProperties {
10750 style: BlockStyle::Sticky,
10751 placement: BlockPlacement::Above(anchor),
10752 height: Some(height),
10753 render: Arc::new(move |cx| {
10754 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10755 cloned_prompt.clone().into_any_element()
10756 }),
10757 priority: 0,
10758 }];
10759
10760 let focus_handle = bp_prompt.focus_handle(cx);
10761 window.focus(&focus_handle);
10762
10763 let block_ids = self.insert_blocks(blocks, None, cx);
10764 bp_prompt.update(cx, |prompt, _| {
10765 prompt.add_block_ids(block_ids);
10766 });
10767 }
10768
10769 pub(crate) fn breakpoint_at_row(
10770 &self,
10771 row: u32,
10772 window: &mut Window,
10773 cx: &mut Context<Self>,
10774 ) -> Option<(Anchor, Breakpoint)> {
10775 let snapshot = self.snapshot(window, cx);
10776 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10777
10778 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10779 }
10780
10781 pub(crate) fn breakpoint_at_anchor(
10782 &self,
10783 breakpoint_position: Anchor,
10784 snapshot: &EditorSnapshot,
10785 cx: &mut Context<Self>,
10786 ) -> Option<(Anchor, Breakpoint)> {
10787 let buffer = self
10788 .buffer
10789 .read(cx)
10790 .buffer_for_anchor(breakpoint_position, cx)?;
10791
10792 let enclosing_excerpt = breakpoint_position.excerpt_id;
10793 let buffer_snapshot = buffer.read(cx).snapshot();
10794
10795 let row = buffer_snapshot
10796 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10797 .row;
10798
10799 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10800 let anchor_end = snapshot
10801 .buffer_snapshot
10802 .anchor_after(Point::new(row, line_len));
10803
10804 self.breakpoint_store
10805 .as_ref()?
10806 .read_with(cx, |breakpoint_store, cx| {
10807 breakpoint_store
10808 .breakpoints(
10809 &buffer,
10810 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10811 &buffer_snapshot,
10812 cx,
10813 )
10814 .next()
10815 .and_then(|(bp, _)| {
10816 let breakpoint_row = buffer_snapshot
10817 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10818 .row;
10819
10820 if breakpoint_row == row {
10821 snapshot
10822 .buffer_snapshot
10823 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10824 .map(|position| (position, bp.bp.clone()))
10825 } else {
10826 None
10827 }
10828 })
10829 })
10830 }
10831
10832 pub fn edit_log_breakpoint(
10833 &mut self,
10834 _: &EditLogBreakpoint,
10835 window: &mut Window,
10836 cx: &mut Context<Self>,
10837 ) {
10838 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10839 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10840 message: None,
10841 state: BreakpointState::Enabled,
10842 condition: None,
10843 hit_condition: None,
10844 });
10845
10846 self.add_edit_breakpoint_block(
10847 anchor,
10848 &breakpoint,
10849 BreakpointPromptEditAction::Log,
10850 window,
10851 cx,
10852 );
10853 }
10854 }
10855
10856 fn breakpoints_at_cursors(
10857 &self,
10858 window: &mut Window,
10859 cx: &mut Context<Self>,
10860 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10861 let snapshot = self.snapshot(window, cx);
10862 let cursors = self
10863 .selections
10864 .disjoint_anchors_arc()
10865 .iter()
10866 .map(|selection| {
10867 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10868
10869 let breakpoint_position = self
10870 .breakpoint_at_row(cursor_position.row, window, cx)
10871 .map(|bp| bp.0)
10872 .unwrap_or_else(|| {
10873 snapshot
10874 .display_snapshot
10875 .buffer_snapshot
10876 .anchor_after(Point::new(cursor_position.row, 0))
10877 });
10878
10879 let breakpoint = self
10880 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10881 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10882
10883 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10884 })
10885 // 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.
10886 .collect::<HashMap<Anchor, _>>();
10887
10888 cursors.into_iter().collect()
10889 }
10890
10891 pub fn enable_breakpoint(
10892 &mut self,
10893 _: &crate::actions::EnableBreakpoint,
10894 window: &mut Window,
10895 cx: &mut Context<Self>,
10896 ) {
10897 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10898 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10899 continue;
10900 };
10901 self.edit_breakpoint_at_anchor(
10902 anchor,
10903 breakpoint,
10904 BreakpointEditAction::InvertState,
10905 cx,
10906 );
10907 }
10908 }
10909
10910 pub fn disable_breakpoint(
10911 &mut self,
10912 _: &crate::actions::DisableBreakpoint,
10913 window: &mut Window,
10914 cx: &mut Context<Self>,
10915 ) {
10916 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10917 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10918 continue;
10919 };
10920 self.edit_breakpoint_at_anchor(
10921 anchor,
10922 breakpoint,
10923 BreakpointEditAction::InvertState,
10924 cx,
10925 );
10926 }
10927 }
10928
10929 pub fn toggle_breakpoint(
10930 &mut self,
10931 _: &crate::actions::ToggleBreakpoint,
10932 window: &mut Window,
10933 cx: &mut Context<Self>,
10934 ) {
10935 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10936 if let Some(breakpoint) = breakpoint {
10937 self.edit_breakpoint_at_anchor(
10938 anchor,
10939 breakpoint,
10940 BreakpointEditAction::Toggle,
10941 cx,
10942 );
10943 } else {
10944 self.edit_breakpoint_at_anchor(
10945 anchor,
10946 Breakpoint::new_standard(),
10947 BreakpointEditAction::Toggle,
10948 cx,
10949 );
10950 }
10951 }
10952 }
10953
10954 pub fn edit_breakpoint_at_anchor(
10955 &mut self,
10956 breakpoint_position: Anchor,
10957 breakpoint: Breakpoint,
10958 edit_action: BreakpointEditAction,
10959 cx: &mut Context<Self>,
10960 ) {
10961 let Some(breakpoint_store) = &self.breakpoint_store else {
10962 return;
10963 };
10964
10965 let Some(buffer) = self
10966 .buffer
10967 .read(cx)
10968 .buffer_for_anchor(breakpoint_position, cx)
10969 else {
10970 return;
10971 };
10972
10973 breakpoint_store.update(cx, |breakpoint_store, cx| {
10974 breakpoint_store.toggle_breakpoint(
10975 buffer,
10976 BreakpointWithPosition {
10977 position: breakpoint_position.text_anchor,
10978 bp: breakpoint,
10979 },
10980 edit_action,
10981 cx,
10982 );
10983 });
10984
10985 cx.notify();
10986 }
10987
10988 #[cfg(any(test, feature = "test-support"))]
10989 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10990 self.breakpoint_store.clone()
10991 }
10992
10993 pub fn prepare_restore_change(
10994 &self,
10995 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10996 hunk: &MultiBufferDiffHunk,
10997 cx: &mut App,
10998 ) -> Option<()> {
10999 if hunk.is_created_file() {
11000 return None;
11001 }
11002 let buffer = self.buffer.read(cx);
11003 let diff = buffer.diff_for(hunk.buffer_id)?;
11004 let buffer = buffer.buffer(hunk.buffer_id)?;
11005 let buffer = buffer.read(cx);
11006 let original_text = diff
11007 .read(cx)
11008 .base_text()
11009 .as_rope()
11010 .slice(hunk.diff_base_byte_range.clone());
11011 let buffer_snapshot = buffer.snapshot();
11012 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11013 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11014 probe
11015 .0
11016 .start
11017 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11018 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11019 }) {
11020 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11021 Some(())
11022 } else {
11023 None
11024 }
11025 }
11026
11027 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11028 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11029 }
11030
11031 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11032 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11033 }
11034
11035 fn manipulate_lines<M>(
11036 &mut self,
11037 window: &mut Window,
11038 cx: &mut Context<Self>,
11039 mut manipulate: M,
11040 ) where
11041 M: FnMut(&str) -> LineManipulationResult,
11042 {
11043 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11044
11045 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11046 let buffer = self.buffer.read(cx).snapshot(cx);
11047
11048 let mut edits = Vec::new();
11049
11050 let selections = self.selections.all::<Point>(cx);
11051 let mut selections = selections.iter().peekable();
11052 let mut contiguous_row_selections = Vec::new();
11053 let mut new_selections = Vec::new();
11054 let mut added_lines = 0;
11055 let mut removed_lines = 0;
11056
11057 while let Some(selection) = selections.next() {
11058 let (start_row, end_row) = consume_contiguous_rows(
11059 &mut contiguous_row_selections,
11060 selection,
11061 &display_map,
11062 &mut selections,
11063 );
11064
11065 let start_point = Point::new(start_row.0, 0);
11066 let end_point = Point::new(
11067 end_row.previous_row().0,
11068 buffer.line_len(end_row.previous_row()),
11069 );
11070 let text = buffer
11071 .text_for_range(start_point..end_point)
11072 .collect::<String>();
11073
11074 let LineManipulationResult {
11075 new_text,
11076 line_count_before,
11077 line_count_after,
11078 } = manipulate(&text);
11079
11080 edits.push((start_point..end_point, new_text));
11081
11082 // Selections must change based on added and removed line count
11083 let start_row =
11084 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11085 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11086 new_selections.push(Selection {
11087 id: selection.id,
11088 start: start_row,
11089 end: end_row,
11090 goal: SelectionGoal::None,
11091 reversed: selection.reversed,
11092 });
11093
11094 if line_count_after > line_count_before {
11095 added_lines += line_count_after - line_count_before;
11096 } else if line_count_before > line_count_after {
11097 removed_lines += line_count_before - line_count_after;
11098 }
11099 }
11100
11101 self.transact(window, cx, |this, window, cx| {
11102 let buffer = this.buffer.update(cx, |buffer, cx| {
11103 buffer.edit(edits, None, cx);
11104 buffer.snapshot(cx)
11105 });
11106
11107 // Recalculate offsets on newly edited buffer
11108 let new_selections = new_selections
11109 .iter()
11110 .map(|s| {
11111 let start_point = Point::new(s.start.0, 0);
11112 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11113 Selection {
11114 id: s.id,
11115 start: buffer.point_to_offset(start_point),
11116 end: buffer.point_to_offset(end_point),
11117 goal: s.goal,
11118 reversed: s.reversed,
11119 }
11120 })
11121 .collect();
11122
11123 this.change_selections(Default::default(), window, cx, |s| {
11124 s.select(new_selections);
11125 });
11126
11127 this.request_autoscroll(Autoscroll::fit(), cx);
11128 });
11129 }
11130
11131 fn manipulate_immutable_lines<Fn>(
11132 &mut self,
11133 window: &mut Window,
11134 cx: &mut Context<Self>,
11135 mut callback: Fn,
11136 ) where
11137 Fn: FnMut(&mut Vec<&str>),
11138 {
11139 self.manipulate_lines(window, cx, |text| {
11140 let mut lines: Vec<&str> = text.split('\n').collect();
11141 let line_count_before = lines.len();
11142
11143 callback(&mut lines);
11144
11145 LineManipulationResult {
11146 new_text: lines.join("\n"),
11147 line_count_before,
11148 line_count_after: lines.len(),
11149 }
11150 });
11151 }
11152
11153 fn manipulate_mutable_lines<Fn>(
11154 &mut self,
11155 window: &mut Window,
11156 cx: &mut Context<Self>,
11157 mut callback: Fn,
11158 ) where
11159 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11160 {
11161 self.manipulate_lines(window, cx, |text| {
11162 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11163 let line_count_before = lines.len();
11164
11165 callback(&mut lines);
11166
11167 LineManipulationResult {
11168 new_text: lines.join("\n"),
11169 line_count_before,
11170 line_count_after: lines.len(),
11171 }
11172 });
11173 }
11174
11175 pub fn convert_indentation_to_spaces(
11176 &mut self,
11177 _: &ConvertIndentationToSpaces,
11178 window: &mut Window,
11179 cx: &mut Context<Self>,
11180 ) {
11181 let settings = self.buffer.read(cx).language_settings(cx);
11182 let tab_size = settings.tab_size.get() as usize;
11183
11184 self.manipulate_mutable_lines(window, cx, |lines| {
11185 // Allocates a reasonably sized scratch buffer once for the whole loop
11186 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11187 // Avoids recomputing spaces that could be inserted many times
11188 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11189 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11190 .collect();
11191
11192 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11193 let mut chars = line.as_ref().chars();
11194 let mut col = 0;
11195 let mut changed = false;
11196
11197 for ch in chars.by_ref() {
11198 match ch {
11199 ' ' => {
11200 reindented_line.push(' ');
11201 col += 1;
11202 }
11203 '\t' => {
11204 // \t are converted to spaces depending on the current column
11205 let spaces_len = tab_size - (col % tab_size);
11206 reindented_line.extend(&space_cache[spaces_len - 1]);
11207 col += spaces_len;
11208 changed = true;
11209 }
11210 _ => {
11211 // If we dont append before break, the character is consumed
11212 reindented_line.push(ch);
11213 break;
11214 }
11215 }
11216 }
11217
11218 if !changed {
11219 reindented_line.clear();
11220 continue;
11221 }
11222 // Append the rest of the line and replace old reference with new one
11223 reindented_line.extend(chars);
11224 *line = Cow::Owned(reindented_line.clone());
11225 reindented_line.clear();
11226 }
11227 });
11228 }
11229
11230 pub fn convert_indentation_to_tabs(
11231 &mut self,
11232 _: &ConvertIndentationToTabs,
11233 window: &mut Window,
11234 cx: &mut Context<Self>,
11235 ) {
11236 let settings = self.buffer.read(cx).language_settings(cx);
11237 let tab_size = settings.tab_size.get() as usize;
11238
11239 self.manipulate_mutable_lines(window, cx, |lines| {
11240 // Allocates a reasonably sized buffer once for the whole loop
11241 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11242 // Avoids recomputing spaces that could be inserted many times
11243 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11244 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11245 .collect();
11246
11247 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11248 let mut chars = line.chars();
11249 let mut spaces_count = 0;
11250 let mut first_non_indent_char = None;
11251 let mut changed = false;
11252
11253 for ch in chars.by_ref() {
11254 match ch {
11255 ' ' => {
11256 // Keep track of spaces. Append \t when we reach tab_size
11257 spaces_count += 1;
11258 changed = true;
11259 if spaces_count == tab_size {
11260 reindented_line.push('\t');
11261 spaces_count = 0;
11262 }
11263 }
11264 '\t' => {
11265 reindented_line.push('\t');
11266 spaces_count = 0;
11267 }
11268 _ => {
11269 // Dont append it yet, we might have remaining spaces
11270 first_non_indent_char = Some(ch);
11271 break;
11272 }
11273 }
11274 }
11275
11276 if !changed {
11277 reindented_line.clear();
11278 continue;
11279 }
11280 // Remaining spaces that didn't make a full tab stop
11281 if spaces_count > 0 {
11282 reindented_line.extend(&space_cache[spaces_count - 1]);
11283 }
11284 // If we consume an extra character that was not indentation, add it back
11285 if let Some(extra_char) = first_non_indent_char {
11286 reindented_line.push(extra_char);
11287 }
11288 // Append the rest of the line and replace old reference with new one
11289 reindented_line.extend(chars);
11290 *line = Cow::Owned(reindented_line.clone());
11291 reindented_line.clear();
11292 }
11293 });
11294 }
11295
11296 pub fn convert_to_upper_case(
11297 &mut self,
11298 _: &ConvertToUpperCase,
11299 window: &mut Window,
11300 cx: &mut Context<Self>,
11301 ) {
11302 self.manipulate_text(window, cx, |text| text.to_uppercase())
11303 }
11304
11305 pub fn convert_to_lower_case(
11306 &mut self,
11307 _: &ConvertToLowerCase,
11308 window: &mut Window,
11309 cx: &mut Context<Self>,
11310 ) {
11311 self.manipulate_text(window, cx, |text| text.to_lowercase())
11312 }
11313
11314 pub fn convert_to_title_case(
11315 &mut self,
11316 _: &ConvertToTitleCase,
11317 window: &mut Window,
11318 cx: &mut Context<Self>,
11319 ) {
11320 self.manipulate_text(window, cx, |text| {
11321 text.split('\n')
11322 .map(|line| line.to_case(Case::Title))
11323 .join("\n")
11324 })
11325 }
11326
11327 pub fn convert_to_snake_case(
11328 &mut self,
11329 _: &ConvertToSnakeCase,
11330 window: &mut Window,
11331 cx: &mut Context<Self>,
11332 ) {
11333 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11334 }
11335
11336 pub fn convert_to_kebab_case(
11337 &mut self,
11338 _: &ConvertToKebabCase,
11339 window: &mut Window,
11340 cx: &mut Context<Self>,
11341 ) {
11342 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11343 }
11344
11345 pub fn convert_to_upper_camel_case(
11346 &mut self,
11347 _: &ConvertToUpperCamelCase,
11348 window: &mut Window,
11349 cx: &mut Context<Self>,
11350 ) {
11351 self.manipulate_text(window, cx, |text| {
11352 text.split('\n')
11353 .map(|line| line.to_case(Case::UpperCamel))
11354 .join("\n")
11355 })
11356 }
11357
11358 pub fn convert_to_lower_camel_case(
11359 &mut self,
11360 _: &ConvertToLowerCamelCase,
11361 window: &mut Window,
11362 cx: &mut Context<Self>,
11363 ) {
11364 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11365 }
11366
11367 pub fn convert_to_opposite_case(
11368 &mut self,
11369 _: &ConvertToOppositeCase,
11370 window: &mut Window,
11371 cx: &mut Context<Self>,
11372 ) {
11373 self.manipulate_text(window, cx, |text| {
11374 text.chars()
11375 .fold(String::with_capacity(text.len()), |mut t, c| {
11376 if c.is_uppercase() {
11377 t.extend(c.to_lowercase());
11378 } else {
11379 t.extend(c.to_uppercase());
11380 }
11381 t
11382 })
11383 })
11384 }
11385
11386 pub fn convert_to_sentence_case(
11387 &mut self,
11388 _: &ConvertToSentenceCase,
11389 window: &mut Window,
11390 cx: &mut Context<Self>,
11391 ) {
11392 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11393 }
11394
11395 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11396 self.manipulate_text(window, cx, |text| {
11397 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11398 if has_upper_case_characters {
11399 text.to_lowercase()
11400 } else {
11401 text.to_uppercase()
11402 }
11403 })
11404 }
11405
11406 pub fn convert_to_rot13(
11407 &mut self,
11408 _: &ConvertToRot13,
11409 window: &mut Window,
11410 cx: &mut Context<Self>,
11411 ) {
11412 self.manipulate_text(window, cx, |text| {
11413 text.chars()
11414 .map(|c| match c {
11415 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11416 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11417 _ => c,
11418 })
11419 .collect()
11420 })
11421 }
11422
11423 pub fn convert_to_rot47(
11424 &mut self,
11425 _: &ConvertToRot47,
11426 window: &mut Window,
11427 cx: &mut Context<Self>,
11428 ) {
11429 self.manipulate_text(window, cx, |text| {
11430 text.chars()
11431 .map(|c| {
11432 let code_point = c as u32;
11433 if code_point >= 33 && code_point <= 126 {
11434 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11435 }
11436 c
11437 })
11438 .collect()
11439 })
11440 }
11441
11442 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11443 where
11444 Fn: FnMut(&str) -> String,
11445 {
11446 let buffer = self.buffer.read(cx).snapshot(cx);
11447
11448 let mut new_selections = Vec::new();
11449 let mut edits = Vec::new();
11450 let mut selection_adjustment = 0i32;
11451
11452 for selection in self.selections.all_adjusted(cx) {
11453 let selection_is_empty = selection.is_empty();
11454
11455 let (start, end) = if selection_is_empty {
11456 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11457 (word_range.start, word_range.end)
11458 } else {
11459 (
11460 buffer.point_to_offset(selection.start),
11461 buffer.point_to_offset(selection.end),
11462 )
11463 };
11464
11465 let text = buffer.text_for_range(start..end).collect::<String>();
11466 let old_length = text.len() as i32;
11467 let text = callback(&text);
11468
11469 new_selections.push(Selection {
11470 start: (start as i32 - selection_adjustment) as usize,
11471 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11472 goal: SelectionGoal::None,
11473 id: selection.id,
11474 reversed: selection.reversed,
11475 });
11476
11477 selection_adjustment += old_length - text.len() as i32;
11478
11479 edits.push((start..end, text));
11480 }
11481
11482 self.transact(window, cx, |this, window, cx| {
11483 this.buffer.update(cx, |buffer, cx| {
11484 buffer.edit(edits, None, cx);
11485 });
11486
11487 this.change_selections(Default::default(), window, cx, |s| {
11488 s.select(new_selections);
11489 });
11490
11491 this.request_autoscroll(Autoscroll::fit(), cx);
11492 });
11493 }
11494
11495 pub fn move_selection_on_drop(
11496 &mut self,
11497 selection: &Selection<Anchor>,
11498 target: DisplayPoint,
11499 is_cut: bool,
11500 window: &mut Window,
11501 cx: &mut Context<Self>,
11502 ) {
11503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11504 let buffer = &display_map.buffer_snapshot;
11505 let mut edits = Vec::new();
11506 let insert_point = display_map
11507 .clip_point(target, Bias::Left)
11508 .to_point(&display_map);
11509 let text = buffer
11510 .text_for_range(selection.start..selection.end)
11511 .collect::<String>();
11512 if is_cut {
11513 edits.push(((selection.start..selection.end), String::new()));
11514 }
11515 let insert_anchor = buffer.anchor_before(insert_point);
11516 edits.push(((insert_anchor..insert_anchor), text));
11517 let last_edit_start = insert_anchor.bias_left(buffer);
11518 let last_edit_end = insert_anchor.bias_right(buffer);
11519 self.transact(window, cx, |this, window, cx| {
11520 this.buffer.update(cx, |buffer, cx| {
11521 buffer.edit(edits, None, cx);
11522 });
11523 this.change_selections(Default::default(), window, cx, |s| {
11524 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11525 });
11526 });
11527 }
11528
11529 pub fn clear_selection_drag_state(&mut self) {
11530 self.selection_drag_state = SelectionDragState::None;
11531 }
11532
11533 pub fn duplicate(
11534 &mut self,
11535 upwards: bool,
11536 whole_lines: bool,
11537 window: &mut Window,
11538 cx: &mut Context<Self>,
11539 ) {
11540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11541
11542 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11543 let buffer = &display_map.buffer_snapshot;
11544 let selections = self.selections.all::<Point>(cx);
11545
11546 let mut edits = Vec::new();
11547 let mut selections_iter = selections.iter().peekable();
11548 while let Some(selection) = selections_iter.next() {
11549 let mut rows = selection.spanned_rows(false, &display_map);
11550 // duplicate line-wise
11551 if whole_lines || selection.start == selection.end {
11552 // Avoid duplicating the same lines twice.
11553 while let Some(next_selection) = selections_iter.peek() {
11554 let next_rows = next_selection.spanned_rows(false, &display_map);
11555 if next_rows.start < rows.end {
11556 rows.end = next_rows.end;
11557 selections_iter.next().unwrap();
11558 } else {
11559 break;
11560 }
11561 }
11562
11563 // Copy the text from the selected row region and splice it either at the start
11564 // or end of the region.
11565 let start = Point::new(rows.start.0, 0);
11566 let end = Point::new(
11567 rows.end.previous_row().0,
11568 buffer.line_len(rows.end.previous_row()),
11569 );
11570 let text = buffer
11571 .text_for_range(start..end)
11572 .chain(Some("\n"))
11573 .collect::<String>();
11574 let insert_location = if upwards {
11575 Point::new(rows.end.0, 0)
11576 } else {
11577 start
11578 };
11579 edits.push((insert_location..insert_location, text));
11580 } else {
11581 // duplicate character-wise
11582 let start = selection.start;
11583 let end = selection.end;
11584 let text = buffer.text_for_range(start..end).collect::<String>();
11585 edits.push((selection.end..selection.end, text));
11586 }
11587 }
11588
11589 self.transact(window, cx, |this, _, cx| {
11590 this.buffer.update(cx, |buffer, cx| {
11591 buffer.edit(edits, None, cx);
11592 });
11593
11594 this.request_autoscroll(Autoscroll::fit(), cx);
11595 });
11596 }
11597
11598 pub fn duplicate_line_up(
11599 &mut self,
11600 _: &DuplicateLineUp,
11601 window: &mut Window,
11602 cx: &mut Context<Self>,
11603 ) {
11604 self.duplicate(true, true, window, cx);
11605 }
11606
11607 pub fn duplicate_line_down(
11608 &mut self,
11609 _: &DuplicateLineDown,
11610 window: &mut Window,
11611 cx: &mut Context<Self>,
11612 ) {
11613 self.duplicate(false, true, window, cx);
11614 }
11615
11616 pub fn duplicate_selection(
11617 &mut self,
11618 _: &DuplicateSelection,
11619 window: &mut Window,
11620 cx: &mut Context<Self>,
11621 ) {
11622 self.duplicate(false, false, window, cx);
11623 }
11624
11625 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11627 if self.mode.is_single_line() {
11628 cx.propagate();
11629 return;
11630 }
11631
11632 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11633 let buffer = self.buffer.read(cx).snapshot(cx);
11634
11635 let mut edits = Vec::new();
11636 let mut unfold_ranges = Vec::new();
11637 let mut refold_creases = Vec::new();
11638
11639 let selections = self.selections.all::<Point>(cx);
11640 let mut selections = selections.iter().peekable();
11641 let mut contiguous_row_selections = Vec::new();
11642 let mut new_selections = Vec::new();
11643
11644 while let Some(selection) = selections.next() {
11645 // Find all the selections that span a contiguous row range
11646 let (start_row, end_row) = consume_contiguous_rows(
11647 &mut contiguous_row_selections,
11648 selection,
11649 &display_map,
11650 &mut selections,
11651 );
11652
11653 // Move the text spanned by the row range to be before the line preceding the row range
11654 if start_row.0 > 0 {
11655 let range_to_move = Point::new(
11656 start_row.previous_row().0,
11657 buffer.line_len(start_row.previous_row()),
11658 )
11659 ..Point::new(
11660 end_row.previous_row().0,
11661 buffer.line_len(end_row.previous_row()),
11662 );
11663 let insertion_point = display_map
11664 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11665 .0;
11666
11667 // Don't move lines across excerpts
11668 if buffer
11669 .excerpt_containing(insertion_point..range_to_move.end)
11670 .is_some()
11671 {
11672 let text = buffer
11673 .text_for_range(range_to_move.clone())
11674 .flat_map(|s| s.chars())
11675 .skip(1)
11676 .chain(['\n'])
11677 .collect::<String>();
11678
11679 edits.push((
11680 buffer.anchor_after(range_to_move.start)
11681 ..buffer.anchor_before(range_to_move.end),
11682 String::new(),
11683 ));
11684 let insertion_anchor = buffer.anchor_after(insertion_point);
11685 edits.push((insertion_anchor..insertion_anchor, text));
11686
11687 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11688
11689 // Move selections up
11690 new_selections.extend(contiguous_row_selections.drain(..).map(
11691 |mut selection| {
11692 selection.start.row -= row_delta;
11693 selection.end.row -= row_delta;
11694 selection
11695 },
11696 ));
11697
11698 // Move folds up
11699 unfold_ranges.push(range_to_move.clone());
11700 for fold in display_map.folds_in_range(
11701 buffer.anchor_before(range_to_move.start)
11702 ..buffer.anchor_after(range_to_move.end),
11703 ) {
11704 let mut start = fold.range.start.to_point(&buffer);
11705 let mut end = fold.range.end.to_point(&buffer);
11706 start.row -= row_delta;
11707 end.row -= row_delta;
11708 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11709 }
11710 }
11711 }
11712
11713 // If we didn't move line(s), preserve the existing selections
11714 new_selections.append(&mut contiguous_row_selections);
11715 }
11716
11717 self.transact(window, cx, |this, window, cx| {
11718 this.unfold_ranges(&unfold_ranges, true, true, cx);
11719 this.buffer.update(cx, |buffer, cx| {
11720 for (range, text) in edits {
11721 buffer.edit([(range, text)], None, cx);
11722 }
11723 });
11724 this.fold_creases(refold_creases, true, window, cx);
11725 this.change_selections(Default::default(), window, cx, |s| {
11726 s.select(new_selections);
11727 })
11728 });
11729 }
11730
11731 pub fn move_line_down(
11732 &mut self,
11733 _: &MoveLineDown,
11734 window: &mut Window,
11735 cx: &mut Context<Self>,
11736 ) {
11737 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11738 if self.mode.is_single_line() {
11739 cx.propagate();
11740 return;
11741 }
11742
11743 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11744 let buffer = self.buffer.read(cx).snapshot(cx);
11745
11746 let mut edits = Vec::new();
11747 let mut unfold_ranges = Vec::new();
11748 let mut refold_creases = Vec::new();
11749
11750 let selections = self.selections.all::<Point>(cx);
11751 let mut selections = selections.iter().peekable();
11752 let mut contiguous_row_selections = Vec::new();
11753 let mut new_selections = Vec::new();
11754
11755 while let Some(selection) = selections.next() {
11756 // Find all the selections that span a contiguous row range
11757 let (start_row, end_row) = consume_contiguous_rows(
11758 &mut contiguous_row_selections,
11759 selection,
11760 &display_map,
11761 &mut selections,
11762 );
11763
11764 // Move the text spanned by the row range to be after the last line of the row range
11765 if end_row.0 <= buffer.max_point().row {
11766 let range_to_move =
11767 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11768 let insertion_point = display_map
11769 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11770 .0;
11771
11772 // Don't move lines across excerpt boundaries
11773 if buffer
11774 .excerpt_containing(range_to_move.start..insertion_point)
11775 .is_some()
11776 {
11777 let mut text = String::from("\n");
11778 text.extend(buffer.text_for_range(range_to_move.clone()));
11779 text.pop(); // Drop trailing newline
11780 edits.push((
11781 buffer.anchor_after(range_to_move.start)
11782 ..buffer.anchor_before(range_to_move.end),
11783 String::new(),
11784 ));
11785 let insertion_anchor = buffer.anchor_after(insertion_point);
11786 edits.push((insertion_anchor..insertion_anchor, text));
11787
11788 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11789
11790 // Move selections down
11791 new_selections.extend(contiguous_row_selections.drain(..).map(
11792 |mut selection| {
11793 selection.start.row += row_delta;
11794 selection.end.row += row_delta;
11795 selection
11796 },
11797 ));
11798
11799 // Move folds down
11800 unfold_ranges.push(range_to_move.clone());
11801 for fold in display_map.folds_in_range(
11802 buffer.anchor_before(range_to_move.start)
11803 ..buffer.anchor_after(range_to_move.end),
11804 ) {
11805 let mut start = fold.range.start.to_point(&buffer);
11806 let mut end = fold.range.end.to_point(&buffer);
11807 start.row += row_delta;
11808 end.row += row_delta;
11809 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11810 }
11811 }
11812 }
11813
11814 // If we didn't move line(s), preserve the existing selections
11815 new_selections.append(&mut contiguous_row_selections);
11816 }
11817
11818 self.transact(window, cx, |this, window, cx| {
11819 this.unfold_ranges(&unfold_ranges, true, true, cx);
11820 this.buffer.update(cx, |buffer, cx| {
11821 for (range, text) in edits {
11822 buffer.edit([(range, text)], None, cx);
11823 }
11824 });
11825 this.fold_creases(refold_creases, true, window, cx);
11826 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11827 });
11828 }
11829
11830 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11831 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11832 let text_layout_details = &self.text_layout_details(window);
11833 self.transact(window, cx, |this, window, cx| {
11834 let edits = this.change_selections(Default::default(), window, cx, |s| {
11835 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11836 s.move_with(|display_map, selection| {
11837 if !selection.is_empty() {
11838 return;
11839 }
11840
11841 let mut head = selection.head();
11842 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11843 if head.column() == display_map.line_len(head.row()) {
11844 transpose_offset = display_map
11845 .buffer_snapshot
11846 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11847 }
11848
11849 if transpose_offset == 0 {
11850 return;
11851 }
11852
11853 *head.column_mut() += 1;
11854 head = display_map.clip_point(head, Bias::Right);
11855 let goal = SelectionGoal::HorizontalPosition(
11856 display_map
11857 .x_for_display_point(head, text_layout_details)
11858 .into(),
11859 );
11860 selection.collapse_to(head, goal);
11861
11862 let transpose_start = display_map
11863 .buffer_snapshot
11864 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11865 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11866 let transpose_end = display_map
11867 .buffer_snapshot
11868 .clip_offset(transpose_offset + 1, Bias::Right);
11869 if let Some(ch) =
11870 display_map.buffer_snapshot.chars_at(transpose_start).next()
11871 {
11872 edits.push((transpose_start..transpose_offset, String::new()));
11873 edits.push((transpose_end..transpose_end, ch.to_string()));
11874 }
11875 }
11876 });
11877 edits
11878 });
11879 this.buffer
11880 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11881 let selections = this.selections.all::<usize>(cx);
11882 this.change_selections(Default::default(), window, cx, |s| {
11883 s.select(selections);
11884 });
11885 });
11886 }
11887
11888 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11889 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11890 if self.mode.is_single_line() {
11891 cx.propagate();
11892 return;
11893 }
11894
11895 self.rewrap_impl(RewrapOptions::default(), cx)
11896 }
11897
11898 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11899 let buffer = self.buffer.read(cx).snapshot(cx);
11900 let selections = self.selections.all::<Point>(cx);
11901
11902 #[derive(Clone, Debug, PartialEq)]
11903 enum CommentFormat {
11904 /// single line comment, with prefix for line
11905 Line(String),
11906 /// single line within a block comment, with prefix for line
11907 BlockLine(String),
11908 /// a single line of a block comment that includes the initial delimiter
11909 BlockCommentWithStart(BlockCommentConfig),
11910 /// a single line of a block comment that includes the ending delimiter
11911 BlockCommentWithEnd(BlockCommentConfig),
11912 }
11913
11914 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11915 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11916 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11917 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11918 .peekable();
11919
11920 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11921 row
11922 } else {
11923 return Vec::new();
11924 };
11925
11926 let language_settings = buffer.language_settings_at(selection.head(), cx);
11927 let language_scope = buffer.language_scope_at(selection.head());
11928
11929 let indent_and_prefix_for_row =
11930 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11931 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11932 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11933 &language_scope
11934 {
11935 let indent_end = Point::new(row, indent.len);
11936 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11937 let line_text_after_indent = buffer
11938 .text_for_range(indent_end..line_end)
11939 .collect::<String>();
11940
11941 let is_within_comment_override = buffer
11942 .language_scope_at(indent_end)
11943 .is_some_and(|scope| scope.override_name() == Some("comment"));
11944 let comment_delimiters = if is_within_comment_override {
11945 // we are within a comment syntax node, but we don't
11946 // yet know what kind of comment: block, doc or line
11947 match (
11948 language_scope.documentation_comment(),
11949 language_scope.block_comment(),
11950 ) {
11951 (Some(config), _) | (_, Some(config))
11952 if buffer.contains_str_at(indent_end, &config.start) =>
11953 {
11954 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11955 }
11956 (Some(config), _) | (_, Some(config))
11957 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11958 {
11959 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11960 }
11961 (Some(config), _) | (_, Some(config))
11962 if buffer.contains_str_at(indent_end, &config.prefix) =>
11963 {
11964 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11965 }
11966 (_, _) => language_scope
11967 .line_comment_prefixes()
11968 .iter()
11969 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11970 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11971 }
11972 } else {
11973 // we not in an overridden comment node, but we may
11974 // be within a non-overridden line comment node
11975 language_scope
11976 .line_comment_prefixes()
11977 .iter()
11978 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11979 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11980 };
11981
11982 let rewrap_prefix = language_scope
11983 .rewrap_prefixes()
11984 .iter()
11985 .find_map(|prefix_regex| {
11986 prefix_regex.find(&line_text_after_indent).map(|mat| {
11987 if mat.start() == 0 {
11988 Some(mat.as_str().to_string())
11989 } else {
11990 None
11991 }
11992 })
11993 })
11994 .flatten();
11995 (comment_delimiters, rewrap_prefix)
11996 } else {
11997 (None, None)
11998 };
11999 (indent, comment_prefix, rewrap_prefix)
12000 };
12001
12002 let mut ranges = Vec::new();
12003 let from_empty_selection = selection.is_empty();
12004
12005 let mut current_range_start = first_row;
12006 let mut prev_row = first_row;
12007 let (
12008 mut current_range_indent,
12009 mut current_range_comment_delimiters,
12010 mut current_range_rewrap_prefix,
12011 ) = indent_and_prefix_for_row(first_row);
12012
12013 for row in non_blank_rows_iter.skip(1) {
12014 let has_paragraph_break = row > prev_row + 1;
12015
12016 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12017 indent_and_prefix_for_row(row);
12018
12019 let has_indent_change = row_indent != current_range_indent;
12020 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12021
12022 let has_boundary_change = has_comment_change
12023 || row_rewrap_prefix.is_some()
12024 || (has_indent_change && current_range_comment_delimiters.is_some());
12025
12026 if has_paragraph_break || has_boundary_change {
12027 ranges.push((
12028 language_settings.clone(),
12029 Point::new(current_range_start, 0)
12030 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12031 current_range_indent,
12032 current_range_comment_delimiters.clone(),
12033 current_range_rewrap_prefix.clone(),
12034 from_empty_selection,
12035 ));
12036 current_range_start = row;
12037 current_range_indent = row_indent;
12038 current_range_comment_delimiters = row_comment_delimiters;
12039 current_range_rewrap_prefix = row_rewrap_prefix;
12040 }
12041 prev_row = row;
12042 }
12043
12044 ranges.push((
12045 language_settings.clone(),
12046 Point::new(current_range_start, 0)
12047 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12048 current_range_indent,
12049 current_range_comment_delimiters,
12050 current_range_rewrap_prefix,
12051 from_empty_selection,
12052 ));
12053
12054 ranges
12055 });
12056
12057 let mut edits = Vec::new();
12058 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12059
12060 for (
12061 language_settings,
12062 wrap_range,
12063 mut indent_size,
12064 comment_prefix,
12065 rewrap_prefix,
12066 from_empty_selection,
12067 ) in wrap_ranges
12068 {
12069 let mut start_row = wrap_range.start.row;
12070 let mut end_row = wrap_range.end.row;
12071
12072 // Skip selections that overlap with a range that has already been rewrapped.
12073 let selection_range = start_row..end_row;
12074 if rewrapped_row_ranges
12075 .iter()
12076 .any(|range| range.overlaps(&selection_range))
12077 {
12078 continue;
12079 }
12080
12081 let tab_size = language_settings.tab_size;
12082
12083 let (line_prefix, inside_comment) = match &comment_prefix {
12084 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12085 (Some(prefix.as_str()), true)
12086 }
12087 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12088 (Some(prefix.as_ref()), true)
12089 }
12090 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12091 start: _,
12092 end: _,
12093 prefix,
12094 tab_size,
12095 })) => {
12096 indent_size.len += tab_size;
12097 (Some(prefix.as_ref()), true)
12098 }
12099 None => (None, false),
12100 };
12101 let indent_prefix = indent_size.chars().collect::<String>();
12102 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12103
12104 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12105 RewrapBehavior::InComments => inside_comment,
12106 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12107 RewrapBehavior::Anywhere => true,
12108 };
12109
12110 let should_rewrap = options.override_language_settings
12111 || allow_rewrap_based_on_language
12112 || self.hard_wrap.is_some();
12113 if !should_rewrap {
12114 continue;
12115 }
12116
12117 if from_empty_selection {
12118 'expand_upwards: while start_row > 0 {
12119 let prev_row = start_row - 1;
12120 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12121 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12122 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12123 {
12124 start_row = prev_row;
12125 } else {
12126 break 'expand_upwards;
12127 }
12128 }
12129
12130 'expand_downwards: while end_row < buffer.max_point().row {
12131 let next_row = end_row + 1;
12132 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12133 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12134 && !buffer.is_line_blank(MultiBufferRow(next_row))
12135 {
12136 end_row = next_row;
12137 } else {
12138 break 'expand_downwards;
12139 }
12140 }
12141 }
12142
12143 let start = Point::new(start_row, 0);
12144 let start_offset = start.to_offset(&buffer);
12145 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12146 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12147 let mut first_line_delimiter = None;
12148 let mut last_line_delimiter = None;
12149 let Some(lines_without_prefixes) = selection_text
12150 .lines()
12151 .enumerate()
12152 .map(|(ix, line)| {
12153 let line_trimmed = line.trim_start();
12154 if rewrap_prefix.is_some() && ix > 0 {
12155 Ok(line_trimmed)
12156 } else if let Some(
12157 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12158 start,
12159 prefix,
12160 end,
12161 tab_size,
12162 })
12163 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12164 start,
12165 prefix,
12166 end,
12167 tab_size,
12168 }),
12169 ) = &comment_prefix
12170 {
12171 let line_trimmed = line_trimmed
12172 .strip_prefix(start.as_ref())
12173 .map(|s| {
12174 let mut indent_size = indent_size;
12175 indent_size.len -= tab_size;
12176 let indent_prefix: String = indent_size.chars().collect();
12177 first_line_delimiter = Some((indent_prefix, start));
12178 s.trim_start()
12179 })
12180 .unwrap_or(line_trimmed);
12181 let line_trimmed = line_trimmed
12182 .strip_suffix(end.as_ref())
12183 .map(|s| {
12184 last_line_delimiter = Some(end);
12185 s.trim_end()
12186 })
12187 .unwrap_or(line_trimmed);
12188 let line_trimmed = line_trimmed
12189 .strip_prefix(prefix.as_ref())
12190 .unwrap_or(line_trimmed);
12191 Ok(line_trimmed)
12192 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12193 line_trimmed.strip_prefix(prefix).with_context(|| {
12194 format!("line did not start with prefix {prefix:?}: {line:?}")
12195 })
12196 } else {
12197 line_trimmed
12198 .strip_prefix(&line_prefix.trim_start())
12199 .with_context(|| {
12200 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12201 })
12202 }
12203 })
12204 .collect::<Result<Vec<_>, _>>()
12205 .log_err()
12206 else {
12207 continue;
12208 };
12209
12210 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12211 buffer
12212 .language_settings_at(Point::new(start_row, 0), cx)
12213 .preferred_line_length as usize
12214 });
12215
12216 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12217 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12218 } else {
12219 line_prefix.clone()
12220 };
12221
12222 let wrapped_text = {
12223 let mut wrapped_text = wrap_with_prefix(
12224 line_prefix,
12225 subsequent_lines_prefix,
12226 lines_without_prefixes.join("\n"),
12227 wrap_column,
12228 tab_size,
12229 options.preserve_existing_whitespace,
12230 );
12231
12232 if let Some((indent, delimiter)) = first_line_delimiter {
12233 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12234 }
12235 if let Some(last_line) = last_line_delimiter {
12236 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12237 }
12238
12239 wrapped_text
12240 };
12241
12242 // TODO: should always use char-based diff while still supporting cursor behavior that
12243 // matches vim.
12244 let mut diff_options = DiffOptions::default();
12245 if options.override_language_settings {
12246 diff_options.max_word_diff_len = 0;
12247 diff_options.max_word_diff_line_count = 0;
12248 } else {
12249 diff_options.max_word_diff_len = usize::MAX;
12250 diff_options.max_word_diff_line_count = usize::MAX;
12251 }
12252
12253 for (old_range, new_text) in
12254 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12255 {
12256 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12257 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12258 edits.push((edit_start..edit_end, new_text));
12259 }
12260
12261 rewrapped_row_ranges.push(start_row..=end_row);
12262 }
12263
12264 self.buffer
12265 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12266 }
12267
12268 pub fn cut_common(
12269 &mut self,
12270 cut_no_selection_line: bool,
12271 window: &mut Window,
12272 cx: &mut Context<Self>,
12273 ) -> ClipboardItem {
12274 let mut text = String::new();
12275 let buffer = self.buffer.read(cx).snapshot(cx);
12276 let mut selections = self.selections.all::<Point>(cx);
12277 let mut clipboard_selections = Vec::with_capacity(selections.len());
12278 {
12279 let max_point = buffer.max_point();
12280 let mut is_first = true;
12281 for selection in &mut selections {
12282 let is_entire_line =
12283 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12284 if is_entire_line {
12285 selection.start = Point::new(selection.start.row, 0);
12286 if !selection.is_empty() && selection.end.column == 0 {
12287 selection.end = cmp::min(max_point, selection.end);
12288 } else {
12289 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12290 }
12291 selection.goal = SelectionGoal::None;
12292 }
12293 if is_first {
12294 is_first = false;
12295 } else {
12296 text += "\n";
12297 }
12298 let mut len = 0;
12299 for chunk in buffer.text_for_range(selection.start..selection.end) {
12300 text.push_str(chunk);
12301 len += chunk.len();
12302 }
12303 clipboard_selections.push(ClipboardSelection {
12304 len,
12305 is_entire_line,
12306 first_line_indent: buffer
12307 .indent_size_for_line(MultiBufferRow(selection.start.row))
12308 .len,
12309 });
12310 }
12311 }
12312
12313 self.transact(window, cx, |this, window, cx| {
12314 this.change_selections(Default::default(), window, cx, |s| {
12315 s.select(selections);
12316 });
12317 this.insert("", window, cx);
12318 });
12319 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12320 }
12321
12322 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12323 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12324 let item = self.cut_common(true, window, cx);
12325 cx.write_to_clipboard(item);
12326 }
12327
12328 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12329 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12330 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12331 s.move_with(|snapshot, sel| {
12332 if sel.is_empty() {
12333 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12334 }
12335 if sel.is_empty() {
12336 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12337 }
12338 });
12339 });
12340 let item = self.cut_common(true, window, cx);
12341 cx.set_global(KillRing(item))
12342 }
12343
12344 pub fn kill_ring_yank(
12345 &mut self,
12346 _: &KillRingYank,
12347 window: &mut Window,
12348 cx: &mut Context<Self>,
12349 ) {
12350 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12351 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12352 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12353 (kill_ring.text().to_string(), kill_ring.metadata_json())
12354 } else {
12355 return;
12356 }
12357 } else {
12358 return;
12359 };
12360 self.do_paste(&text, metadata, false, window, cx);
12361 }
12362
12363 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12364 self.do_copy(true, cx);
12365 }
12366
12367 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12368 self.do_copy(false, cx);
12369 }
12370
12371 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12372 let selections = self.selections.all::<Point>(cx);
12373 let buffer = self.buffer.read(cx).read(cx);
12374 let mut text = String::new();
12375
12376 let mut clipboard_selections = Vec::with_capacity(selections.len());
12377 {
12378 let max_point = buffer.max_point();
12379 let mut is_first = true;
12380 for selection in &selections {
12381 let mut start = selection.start;
12382 let mut end = selection.end;
12383 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12384 if is_entire_line {
12385 start = Point::new(start.row, 0);
12386 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12387 }
12388
12389 let mut trimmed_selections = Vec::new();
12390 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12391 let row = MultiBufferRow(start.row);
12392 let first_indent = buffer.indent_size_for_line(row);
12393 if first_indent.len == 0 || start.column > first_indent.len {
12394 trimmed_selections.push(start..end);
12395 } else {
12396 trimmed_selections.push(
12397 Point::new(row.0, first_indent.len)
12398 ..Point::new(row.0, buffer.line_len(row)),
12399 );
12400 for row in start.row + 1..=end.row {
12401 let mut line_len = buffer.line_len(MultiBufferRow(row));
12402 if row == end.row {
12403 line_len = end.column;
12404 }
12405 if line_len == 0 {
12406 trimmed_selections
12407 .push(Point::new(row, 0)..Point::new(row, line_len));
12408 continue;
12409 }
12410 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12411 if row_indent_size.len >= first_indent.len {
12412 trimmed_selections.push(
12413 Point::new(row, first_indent.len)..Point::new(row, line_len),
12414 );
12415 } else {
12416 trimmed_selections.clear();
12417 trimmed_selections.push(start..end);
12418 break;
12419 }
12420 }
12421 }
12422 } else {
12423 trimmed_selections.push(start..end);
12424 }
12425
12426 for trimmed_range in trimmed_selections {
12427 if is_first {
12428 is_first = false;
12429 } else {
12430 text += "\n";
12431 }
12432 let mut len = 0;
12433 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12434 text.push_str(chunk);
12435 len += chunk.len();
12436 }
12437 clipboard_selections.push(ClipboardSelection {
12438 len,
12439 is_entire_line,
12440 first_line_indent: buffer
12441 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12442 .len,
12443 });
12444 }
12445 }
12446 }
12447
12448 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12449 text,
12450 clipboard_selections,
12451 ));
12452 }
12453
12454 pub fn do_paste(
12455 &mut self,
12456 text: &String,
12457 clipboard_selections: Option<Vec<ClipboardSelection>>,
12458 handle_entire_lines: bool,
12459 window: &mut Window,
12460 cx: &mut Context<Self>,
12461 ) {
12462 if self.read_only(cx) {
12463 return;
12464 }
12465
12466 let clipboard_text = Cow::Borrowed(text.as_str());
12467
12468 self.transact(window, cx, |this, window, cx| {
12469 let had_active_edit_prediction = this.has_active_edit_prediction();
12470 let old_selections = this.selections.all::<usize>(cx);
12471 let cursor_offset = this.selections.last::<usize>(cx).head();
12472
12473 if let Some(mut clipboard_selections) = clipboard_selections {
12474 let all_selections_were_entire_line =
12475 clipboard_selections.iter().all(|s| s.is_entire_line);
12476 let first_selection_indent_column =
12477 clipboard_selections.first().map(|s| s.first_line_indent);
12478 if clipboard_selections.len() != old_selections.len() {
12479 clipboard_selections.drain(..);
12480 }
12481 let mut auto_indent_on_paste = true;
12482
12483 this.buffer.update(cx, |buffer, cx| {
12484 let snapshot = buffer.read(cx);
12485 auto_indent_on_paste = snapshot
12486 .language_settings_at(cursor_offset, cx)
12487 .auto_indent_on_paste;
12488
12489 let mut start_offset = 0;
12490 let mut edits = Vec::new();
12491 let mut original_indent_columns = Vec::new();
12492 for (ix, selection) in old_selections.iter().enumerate() {
12493 let to_insert;
12494 let entire_line;
12495 let original_indent_column;
12496 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12497 let end_offset = start_offset + clipboard_selection.len;
12498 to_insert = &clipboard_text[start_offset..end_offset];
12499 entire_line = clipboard_selection.is_entire_line;
12500 start_offset = end_offset + 1;
12501 original_indent_column = Some(clipboard_selection.first_line_indent);
12502 } else {
12503 to_insert = &*clipboard_text;
12504 entire_line = all_selections_were_entire_line;
12505 original_indent_column = first_selection_indent_column
12506 }
12507
12508 let (range, to_insert) =
12509 if selection.is_empty() && handle_entire_lines && entire_line {
12510 // If the corresponding selection was empty when this slice of the
12511 // clipboard text was written, then the entire line containing the
12512 // selection was copied. If this selection is also currently empty,
12513 // then paste the line before the current line of the buffer.
12514 let column = selection.start.to_point(&snapshot).column as usize;
12515 let line_start = selection.start - column;
12516 (line_start..line_start, Cow::Borrowed(to_insert))
12517 } else {
12518 let language = snapshot.language_at(selection.head());
12519 let range = selection.range();
12520 if let Some(language) = language
12521 && language.name() == "Markdown".into()
12522 {
12523 edit_for_markdown_paste(
12524 &snapshot,
12525 range,
12526 to_insert,
12527 url::Url::parse(to_insert).ok(),
12528 )
12529 } else {
12530 (range, Cow::Borrowed(to_insert))
12531 }
12532 };
12533
12534 edits.push((range, to_insert));
12535 original_indent_columns.push(original_indent_column);
12536 }
12537 drop(snapshot);
12538
12539 buffer.edit(
12540 edits,
12541 if auto_indent_on_paste {
12542 Some(AutoindentMode::Block {
12543 original_indent_columns,
12544 })
12545 } else {
12546 None
12547 },
12548 cx,
12549 );
12550 });
12551
12552 let selections = this.selections.all::<usize>(cx);
12553 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12554 } else {
12555 let url = url::Url::parse(&clipboard_text).ok();
12556
12557 let auto_indent_mode = if !clipboard_text.is_empty() {
12558 Some(AutoindentMode::Block {
12559 original_indent_columns: Vec::new(),
12560 })
12561 } else {
12562 None
12563 };
12564
12565 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12566 let snapshot = buffer.snapshot(cx);
12567
12568 let anchors = old_selections
12569 .iter()
12570 .map(|s| {
12571 let anchor = snapshot.anchor_after(s.head());
12572 s.map(|_| anchor)
12573 })
12574 .collect::<Vec<_>>();
12575
12576 let mut edits = Vec::new();
12577
12578 for selection in old_selections.iter() {
12579 let language = snapshot.language_at(selection.head());
12580 let range = selection.range();
12581
12582 let (edit_range, edit_text) = if let Some(language) = language
12583 && language.name() == "Markdown".into()
12584 {
12585 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12586 } else {
12587 (range, clipboard_text.clone())
12588 };
12589
12590 edits.push((edit_range, edit_text));
12591 }
12592
12593 drop(snapshot);
12594 buffer.edit(edits, auto_indent_mode, cx);
12595
12596 anchors
12597 });
12598
12599 this.change_selections(Default::default(), window, cx, |s| {
12600 s.select_anchors(selection_anchors);
12601 });
12602 }
12603
12604 let trigger_in_words =
12605 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12606
12607 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12608 });
12609 }
12610
12611 pub fn diff_clipboard_with_selection(
12612 &mut self,
12613 _: &DiffClipboardWithSelection,
12614 window: &mut Window,
12615 cx: &mut Context<Self>,
12616 ) {
12617 let selections = self.selections.all::<usize>(cx);
12618
12619 if selections.is_empty() {
12620 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12621 return;
12622 };
12623
12624 let clipboard_text = match cx.read_from_clipboard() {
12625 Some(item) => match item.entries().first() {
12626 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12627 _ => None,
12628 },
12629 None => None,
12630 };
12631
12632 let Some(clipboard_text) = clipboard_text else {
12633 log::warn!("Clipboard doesn't contain text.");
12634 return;
12635 };
12636
12637 window.dispatch_action(
12638 Box::new(DiffClipboardWithSelectionData {
12639 clipboard_text,
12640 editor: cx.entity(),
12641 }),
12642 cx,
12643 );
12644 }
12645
12646 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12647 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12648 if let Some(item) = cx.read_from_clipboard() {
12649 let entries = item.entries();
12650
12651 match entries.first() {
12652 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12653 // of all the pasted entries.
12654 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12655 .do_paste(
12656 clipboard_string.text(),
12657 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12658 true,
12659 window,
12660 cx,
12661 ),
12662 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12663 }
12664 }
12665 }
12666
12667 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12668 if self.read_only(cx) {
12669 return;
12670 }
12671
12672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12673
12674 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12675 if let Some((selections, _)) =
12676 self.selection_history.transaction(transaction_id).cloned()
12677 {
12678 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12679 s.select_anchors(selections.to_vec());
12680 });
12681 } else {
12682 log::error!(
12683 "No entry in selection_history found for undo. \
12684 This may correspond to a bug where undo does not update the selection. \
12685 If this is occurring, please add details to \
12686 https://github.com/zed-industries/zed/issues/22692"
12687 );
12688 }
12689 self.request_autoscroll(Autoscroll::fit(), cx);
12690 self.unmark_text(window, cx);
12691 self.refresh_edit_prediction(true, false, window, cx);
12692 cx.emit(EditorEvent::Edited { transaction_id });
12693 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12694 }
12695 }
12696
12697 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12698 if self.read_only(cx) {
12699 return;
12700 }
12701
12702 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12703
12704 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12705 if let Some((_, Some(selections))) =
12706 self.selection_history.transaction(transaction_id).cloned()
12707 {
12708 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12709 s.select_anchors(selections.to_vec());
12710 });
12711 } else {
12712 log::error!(
12713 "No entry in selection_history found for redo. \
12714 This may correspond to a bug where undo does not update the selection. \
12715 If this is occurring, please add details to \
12716 https://github.com/zed-industries/zed/issues/22692"
12717 );
12718 }
12719 self.request_autoscroll(Autoscroll::fit(), cx);
12720 self.unmark_text(window, cx);
12721 self.refresh_edit_prediction(true, false, window, cx);
12722 cx.emit(EditorEvent::Edited { transaction_id });
12723 }
12724 }
12725
12726 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12727 self.buffer
12728 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12729 }
12730
12731 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12732 self.buffer
12733 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12734 }
12735
12736 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12738 self.change_selections(Default::default(), window, cx, |s| {
12739 s.move_with(|map, selection| {
12740 let cursor = if selection.is_empty() {
12741 movement::left(map, selection.start)
12742 } else {
12743 selection.start
12744 };
12745 selection.collapse_to(cursor, SelectionGoal::None);
12746 });
12747 })
12748 }
12749
12750 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12752 self.change_selections(Default::default(), window, cx, |s| {
12753 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12754 })
12755 }
12756
12757 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12758 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12759 self.change_selections(Default::default(), window, cx, |s| {
12760 s.move_with(|map, selection| {
12761 let cursor = if selection.is_empty() {
12762 movement::right(map, selection.end)
12763 } else {
12764 selection.end
12765 };
12766 selection.collapse_to(cursor, SelectionGoal::None)
12767 });
12768 })
12769 }
12770
12771 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12773 self.change_selections(Default::default(), window, cx, |s| {
12774 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12775 })
12776 }
12777
12778 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12779 if self.take_rename(true, window, cx).is_some() {
12780 return;
12781 }
12782
12783 if self.mode.is_single_line() {
12784 cx.propagate();
12785 return;
12786 }
12787
12788 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12789
12790 let text_layout_details = &self.text_layout_details(window);
12791 let selection_count = self.selections.count();
12792 let first_selection = self.selections.first_anchor();
12793
12794 self.change_selections(Default::default(), window, cx, |s| {
12795 s.move_with(|map, selection| {
12796 if !selection.is_empty() {
12797 selection.goal = SelectionGoal::None;
12798 }
12799 let (cursor, goal) = movement::up(
12800 map,
12801 selection.start,
12802 selection.goal,
12803 false,
12804 text_layout_details,
12805 );
12806 selection.collapse_to(cursor, goal);
12807 });
12808 });
12809
12810 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12811 {
12812 cx.propagate();
12813 }
12814 }
12815
12816 pub fn move_up_by_lines(
12817 &mut self,
12818 action: &MoveUpByLines,
12819 window: &mut Window,
12820 cx: &mut Context<Self>,
12821 ) {
12822 if self.take_rename(true, window, cx).is_some() {
12823 return;
12824 }
12825
12826 if self.mode.is_single_line() {
12827 cx.propagate();
12828 return;
12829 }
12830
12831 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12832
12833 let text_layout_details = &self.text_layout_details(window);
12834
12835 self.change_selections(Default::default(), window, cx, |s| {
12836 s.move_with(|map, selection| {
12837 if !selection.is_empty() {
12838 selection.goal = SelectionGoal::None;
12839 }
12840 let (cursor, goal) = movement::up_by_rows(
12841 map,
12842 selection.start,
12843 action.lines,
12844 selection.goal,
12845 false,
12846 text_layout_details,
12847 );
12848 selection.collapse_to(cursor, goal);
12849 });
12850 })
12851 }
12852
12853 pub fn move_down_by_lines(
12854 &mut self,
12855 action: &MoveDownByLines,
12856 window: &mut Window,
12857 cx: &mut Context<Self>,
12858 ) {
12859 if self.take_rename(true, window, cx).is_some() {
12860 return;
12861 }
12862
12863 if self.mode.is_single_line() {
12864 cx.propagate();
12865 return;
12866 }
12867
12868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12869
12870 let text_layout_details = &self.text_layout_details(window);
12871
12872 self.change_selections(Default::default(), window, cx, |s| {
12873 s.move_with(|map, selection| {
12874 if !selection.is_empty() {
12875 selection.goal = SelectionGoal::None;
12876 }
12877 let (cursor, goal) = movement::down_by_rows(
12878 map,
12879 selection.start,
12880 action.lines,
12881 selection.goal,
12882 false,
12883 text_layout_details,
12884 );
12885 selection.collapse_to(cursor, goal);
12886 });
12887 })
12888 }
12889
12890 pub fn select_down_by_lines(
12891 &mut self,
12892 action: &SelectDownByLines,
12893 window: &mut Window,
12894 cx: &mut Context<Self>,
12895 ) {
12896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12897 let text_layout_details = &self.text_layout_details(window);
12898 self.change_selections(Default::default(), window, cx, |s| {
12899 s.move_heads_with(|map, head, goal| {
12900 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12901 })
12902 })
12903 }
12904
12905 pub fn select_up_by_lines(
12906 &mut self,
12907 action: &SelectUpByLines,
12908 window: &mut Window,
12909 cx: &mut Context<Self>,
12910 ) {
12911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12912 let text_layout_details = &self.text_layout_details(window);
12913 self.change_selections(Default::default(), window, cx, |s| {
12914 s.move_heads_with(|map, head, goal| {
12915 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12916 })
12917 })
12918 }
12919
12920 pub fn select_page_up(
12921 &mut self,
12922 _: &SelectPageUp,
12923 window: &mut Window,
12924 cx: &mut Context<Self>,
12925 ) {
12926 let Some(row_count) = self.visible_row_count() else {
12927 return;
12928 };
12929
12930 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12931
12932 let text_layout_details = &self.text_layout_details(window);
12933
12934 self.change_selections(Default::default(), window, cx, |s| {
12935 s.move_heads_with(|map, head, goal| {
12936 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12937 })
12938 })
12939 }
12940
12941 pub fn move_page_up(
12942 &mut self,
12943 action: &MovePageUp,
12944 window: &mut Window,
12945 cx: &mut Context<Self>,
12946 ) {
12947 if self.take_rename(true, window, cx).is_some() {
12948 return;
12949 }
12950
12951 if self
12952 .context_menu
12953 .borrow_mut()
12954 .as_mut()
12955 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12956 .unwrap_or(false)
12957 {
12958 return;
12959 }
12960
12961 if matches!(self.mode, EditorMode::SingleLine) {
12962 cx.propagate();
12963 return;
12964 }
12965
12966 let Some(row_count) = self.visible_row_count() else {
12967 return;
12968 };
12969
12970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12971
12972 let effects = if action.center_cursor {
12973 SelectionEffects::scroll(Autoscroll::center())
12974 } else {
12975 SelectionEffects::default()
12976 };
12977
12978 let text_layout_details = &self.text_layout_details(window);
12979
12980 self.change_selections(effects, window, cx, |s| {
12981 s.move_with(|map, selection| {
12982 if !selection.is_empty() {
12983 selection.goal = SelectionGoal::None;
12984 }
12985 let (cursor, goal) = movement::up_by_rows(
12986 map,
12987 selection.end,
12988 row_count,
12989 selection.goal,
12990 false,
12991 text_layout_details,
12992 );
12993 selection.collapse_to(cursor, goal);
12994 });
12995 });
12996 }
12997
12998 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13000 let text_layout_details = &self.text_layout_details(window);
13001 self.change_selections(Default::default(), window, cx, |s| {
13002 s.move_heads_with(|map, head, goal| {
13003 movement::up(map, head, goal, false, text_layout_details)
13004 })
13005 })
13006 }
13007
13008 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13009 self.take_rename(true, window, cx);
13010
13011 if self.mode.is_single_line() {
13012 cx.propagate();
13013 return;
13014 }
13015
13016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13017
13018 let text_layout_details = &self.text_layout_details(window);
13019 let selection_count = self.selections.count();
13020 let first_selection = self.selections.first_anchor();
13021
13022 self.change_selections(Default::default(), window, cx, |s| {
13023 s.move_with(|map, selection| {
13024 if !selection.is_empty() {
13025 selection.goal = SelectionGoal::None;
13026 }
13027 let (cursor, goal) = movement::down(
13028 map,
13029 selection.end,
13030 selection.goal,
13031 false,
13032 text_layout_details,
13033 );
13034 selection.collapse_to(cursor, goal);
13035 });
13036 });
13037
13038 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13039 {
13040 cx.propagate();
13041 }
13042 }
13043
13044 pub fn select_page_down(
13045 &mut self,
13046 _: &SelectPageDown,
13047 window: &mut Window,
13048 cx: &mut Context<Self>,
13049 ) {
13050 let Some(row_count) = self.visible_row_count() else {
13051 return;
13052 };
13053
13054 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13055
13056 let text_layout_details = &self.text_layout_details(window);
13057
13058 self.change_selections(Default::default(), window, cx, |s| {
13059 s.move_heads_with(|map, head, goal| {
13060 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13061 })
13062 })
13063 }
13064
13065 pub fn move_page_down(
13066 &mut self,
13067 action: &MovePageDown,
13068 window: &mut Window,
13069 cx: &mut Context<Self>,
13070 ) {
13071 if self.take_rename(true, window, cx).is_some() {
13072 return;
13073 }
13074
13075 if self
13076 .context_menu
13077 .borrow_mut()
13078 .as_mut()
13079 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13080 .unwrap_or(false)
13081 {
13082 return;
13083 }
13084
13085 if matches!(self.mode, EditorMode::SingleLine) {
13086 cx.propagate();
13087 return;
13088 }
13089
13090 let Some(row_count) = self.visible_row_count() else {
13091 return;
13092 };
13093
13094 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13095
13096 let effects = if action.center_cursor {
13097 SelectionEffects::scroll(Autoscroll::center())
13098 } else {
13099 SelectionEffects::default()
13100 };
13101
13102 let text_layout_details = &self.text_layout_details(window);
13103 self.change_selections(effects, window, cx, |s| {
13104 s.move_with(|map, selection| {
13105 if !selection.is_empty() {
13106 selection.goal = SelectionGoal::None;
13107 }
13108 let (cursor, goal) = movement::down_by_rows(
13109 map,
13110 selection.end,
13111 row_count,
13112 selection.goal,
13113 false,
13114 text_layout_details,
13115 );
13116 selection.collapse_to(cursor, goal);
13117 });
13118 });
13119 }
13120
13121 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13123 let text_layout_details = &self.text_layout_details(window);
13124 self.change_selections(Default::default(), window, cx, |s| {
13125 s.move_heads_with(|map, head, goal| {
13126 movement::down(map, head, goal, false, text_layout_details)
13127 })
13128 });
13129 }
13130
13131 pub fn context_menu_first(
13132 &mut self,
13133 _: &ContextMenuFirst,
13134 window: &mut Window,
13135 cx: &mut Context<Self>,
13136 ) {
13137 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13138 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13139 }
13140 }
13141
13142 pub fn context_menu_prev(
13143 &mut self,
13144 _: &ContextMenuPrevious,
13145 window: &mut Window,
13146 cx: &mut Context<Self>,
13147 ) {
13148 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13149 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13150 }
13151 }
13152
13153 pub fn context_menu_next(
13154 &mut self,
13155 _: &ContextMenuNext,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13160 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13161 }
13162 }
13163
13164 pub fn context_menu_last(
13165 &mut self,
13166 _: &ContextMenuLast,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13171 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13172 }
13173 }
13174
13175 pub fn signature_help_prev(
13176 &mut self,
13177 _: &SignatureHelpPrevious,
13178 _: &mut Window,
13179 cx: &mut Context<Self>,
13180 ) {
13181 if let Some(popover) = self.signature_help_state.popover_mut() {
13182 if popover.current_signature == 0 {
13183 popover.current_signature = popover.signatures.len() - 1;
13184 } else {
13185 popover.current_signature -= 1;
13186 }
13187 cx.notify();
13188 }
13189 }
13190
13191 pub fn signature_help_next(
13192 &mut self,
13193 _: &SignatureHelpNext,
13194 _: &mut Window,
13195 cx: &mut Context<Self>,
13196 ) {
13197 if let Some(popover) = self.signature_help_state.popover_mut() {
13198 if popover.current_signature + 1 == popover.signatures.len() {
13199 popover.current_signature = 0;
13200 } else {
13201 popover.current_signature += 1;
13202 }
13203 cx.notify();
13204 }
13205 }
13206
13207 pub fn move_to_previous_word_start(
13208 &mut self,
13209 _: &MoveToPreviousWordStart,
13210 window: &mut Window,
13211 cx: &mut Context<Self>,
13212 ) {
13213 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13214 self.change_selections(Default::default(), window, cx, |s| {
13215 s.move_cursors_with(|map, head, _| {
13216 (
13217 movement::previous_word_start(map, head),
13218 SelectionGoal::None,
13219 )
13220 });
13221 })
13222 }
13223
13224 pub fn move_to_previous_subword_start(
13225 &mut self,
13226 _: &MoveToPreviousSubwordStart,
13227 window: &mut Window,
13228 cx: &mut Context<Self>,
13229 ) {
13230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13231 self.change_selections(Default::default(), window, cx, |s| {
13232 s.move_cursors_with(|map, head, _| {
13233 (
13234 movement::previous_subword_start(map, head),
13235 SelectionGoal::None,
13236 )
13237 });
13238 })
13239 }
13240
13241 pub fn select_to_previous_word_start(
13242 &mut self,
13243 _: &SelectToPreviousWordStart,
13244 window: &mut Window,
13245 cx: &mut Context<Self>,
13246 ) {
13247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13248 self.change_selections(Default::default(), window, cx, |s| {
13249 s.move_heads_with(|map, head, _| {
13250 (
13251 movement::previous_word_start(map, head),
13252 SelectionGoal::None,
13253 )
13254 });
13255 })
13256 }
13257
13258 pub fn select_to_previous_subword_start(
13259 &mut self,
13260 _: &SelectToPreviousSubwordStart,
13261 window: &mut Window,
13262 cx: &mut Context<Self>,
13263 ) {
13264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13265 self.change_selections(Default::default(), window, cx, |s| {
13266 s.move_heads_with(|map, head, _| {
13267 (
13268 movement::previous_subword_start(map, head),
13269 SelectionGoal::None,
13270 )
13271 });
13272 })
13273 }
13274
13275 pub fn delete_to_previous_word_start(
13276 &mut self,
13277 action: &DeleteToPreviousWordStart,
13278 window: &mut Window,
13279 cx: &mut Context<Self>,
13280 ) {
13281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13282 self.transact(window, cx, |this, window, cx| {
13283 this.select_autoclose_pair(window, cx);
13284 this.change_selections(Default::default(), window, cx, |s| {
13285 s.move_with(|map, selection| {
13286 if selection.is_empty() {
13287 let mut cursor = if action.ignore_newlines {
13288 movement::previous_word_start(map, selection.head())
13289 } else {
13290 movement::previous_word_start_or_newline(map, selection.head())
13291 };
13292 cursor = movement::adjust_greedy_deletion(
13293 map,
13294 selection.head(),
13295 cursor,
13296 action.ignore_brackets,
13297 );
13298 selection.set_head(cursor, SelectionGoal::None);
13299 }
13300 });
13301 });
13302 this.insert("", window, cx);
13303 });
13304 }
13305
13306 pub fn delete_to_previous_subword_start(
13307 &mut self,
13308 _: &DeleteToPreviousSubwordStart,
13309 window: &mut Window,
13310 cx: &mut Context<Self>,
13311 ) {
13312 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13313 self.transact(window, cx, |this, window, cx| {
13314 this.select_autoclose_pair(window, cx);
13315 this.change_selections(Default::default(), window, cx, |s| {
13316 s.move_with(|map, selection| {
13317 if selection.is_empty() {
13318 let mut cursor = movement::previous_subword_start(map, selection.head());
13319 cursor =
13320 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13321 selection.set_head(cursor, SelectionGoal::None);
13322 }
13323 });
13324 });
13325 this.insert("", window, cx);
13326 });
13327 }
13328
13329 pub fn move_to_next_word_end(
13330 &mut self,
13331 _: &MoveToNextWordEnd,
13332 window: &mut Window,
13333 cx: &mut Context<Self>,
13334 ) {
13335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13336 self.change_selections(Default::default(), window, cx, |s| {
13337 s.move_cursors_with(|map, head, _| {
13338 (movement::next_word_end(map, head), SelectionGoal::None)
13339 });
13340 })
13341 }
13342
13343 pub fn move_to_next_subword_end(
13344 &mut self,
13345 _: &MoveToNextSubwordEnd,
13346 window: &mut Window,
13347 cx: &mut Context<Self>,
13348 ) {
13349 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13350 self.change_selections(Default::default(), window, cx, |s| {
13351 s.move_cursors_with(|map, head, _| {
13352 (movement::next_subword_end(map, head), SelectionGoal::None)
13353 });
13354 })
13355 }
13356
13357 pub fn select_to_next_word_end(
13358 &mut self,
13359 _: &SelectToNextWordEnd,
13360 window: &mut Window,
13361 cx: &mut Context<Self>,
13362 ) {
13363 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13364 self.change_selections(Default::default(), window, cx, |s| {
13365 s.move_heads_with(|map, head, _| {
13366 (movement::next_word_end(map, head), SelectionGoal::None)
13367 });
13368 })
13369 }
13370
13371 pub fn select_to_next_subword_end(
13372 &mut self,
13373 _: &SelectToNextSubwordEnd,
13374 window: &mut Window,
13375 cx: &mut Context<Self>,
13376 ) {
13377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13378 self.change_selections(Default::default(), window, cx, |s| {
13379 s.move_heads_with(|map, head, _| {
13380 (movement::next_subword_end(map, head), SelectionGoal::None)
13381 });
13382 })
13383 }
13384
13385 pub fn delete_to_next_word_end(
13386 &mut self,
13387 action: &DeleteToNextWordEnd,
13388 window: &mut Window,
13389 cx: &mut Context<Self>,
13390 ) {
13391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13392 self.transact(window, cx, |this, window, cx| {
13393 this.change_selections(Default::default(), window, cx, |s| {
13394 s.move_with(|map, selection| {
13395 if selection.is_empty() {
13396 let mut cursor = if action.ignore_newlines {
13397 movement::next_word_end(map, selection.head())
13398 } else {
13399 movement::next_word_end_or_newline(map, selection.head())
13400 };
13401 cursor = movement::adjust_greedy_deletion(
13402 map,
13403 selection.head(),
13404 cursor,
13405 action.ignore_brackets,
13406 );
13407 selection.set_head(cursor, SelectionGoal::None);
13408 }
13409 });
13410 });
13411 this.insert("", window, cx);
13412 });
13413 }
13414
13415 pub fn delete_to_next_subword_end(
13416 &mut self,
13417 _: &DeleteToNextSubwordEnd,
13418 window: &mut Window,
13419 cx: &mut Context<Self>,
13420 ) {
13421 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13422 self.transact(window, cx, |this, window, cx| {
13423 this.change_selections(Default::default(), window, cx, |s| {
13424 s.move_with(|map, selection| {
13425 if selection.is_empty() {
13426 let mut cursor = movement::next_subword_end(map, selection.head());
13427 cursor =
13428 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13429 selection.set_head(cursor, SelectionGoal::None);
13430 }
13431 });
13432 });
13433 this.insert("", window, cx);
13434 });
13435 }
13436
13437 pub fn move_to_beginning_of_line(
13438 &mut self,
13439 action: &MoveToBeginningOfLine,
13440 window: &mut Window,
13441 cx: &mut Context<Self>,
13442 ) {
13443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13444 self.change_selections(Default::default(), window, cx, |s| {
13445 s.move_cursors_with(|map, head, _| {
13446 (
13447 movement::indented_line_beginning(
13448 map,
13449 head,
13450 action.stop_at_soft_wraps,
13451 action.stop_at_indent,
13452 ),
13453 SelectionGoal::None,
13454 )
13455 });
13456 })
13457 }
13458
13459 pub fn select_to_beginning_of_line(
13460 &mut self,
13461 action: &SelectToBeginningOfLine,
13462 window: &mut Window,
13463 cx: &mut Context<Self>,
13464 ) {
13465 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13466 self.change_selections(Default::default(), window, cx, |s| {
13467 s.move_heads_with(|map, head, _| {
13468 (
13469 movement::indented_line_beginning(
13470 map,
13471 head,
13472 action.stop_at_soft_wraps,
13473 action.stop_at_indent,
13474 ),
13475 SelectionGoal::None,
13476 )
13477 });
13478 });
13479 }
13480
13481 pub fn delete_to_beginning_of_line(
13482 &mut self,
13483 action: &DeleteToBeginningOfLine,
13484 window: &mut Window,
13485 cx: &mut Context<Self>,
13486 ) {
13487 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13488 self.transact(window, cx, |this, window, cx| {
13489 this.change_selections(Default::default(), window, cx, |s| {
13490 s.move_with(|_, selection| {
13491 selection.reversed = true;
13492 });
13493 });
13494
13495 this.select_to_beginning_of_line(
13496 &SelectToBeginningOfLine {
13497 stop_at_soft_wraps: false,
13498 stop_at_indent: action.stop_at_indent,
13499 },
13500 window,
13501 cx,
13502 );
13503 this.backspace(&Backspace, window, cx);
13504 });
13505 }
13506
13507 pub fn move_to_end_of_line(
13508 &mut self,
13509 action: &MoveToEndOfLine,
13510 window: &mut Window,
13511 cx: &mut Context<Self>,
13512 ) {
13513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13514 self.change_selections(Default::default(), window, cx, |s| {
13515 s.move_cursors_with(|map, head, _| {
13516 (
13517 movement::line_end(map, head, action.stop_at_soft_wraps),
13518 SelectionGoal::None,
13519 )
13520 });
13521 })
13522 }
13523
13524 pub fn select_to_end_of_line(
13525 &mut self,
13526 action: &SelectToEndOfLine,
13527 window: &mut Window,
13528 cx: &mut Context<Self>,
13529 ) {
13530 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13531 self.change_selections(Default::default(), window, cx, |s| {
13532 s.move_heads_with(|map, head, _| {
13533 (
13534 movement::line_end(map, head, action.stop_at_soft_wraps),
13535 SelectionGoal::None,
13536 )
13537 });
13538 })
13539 }
13540
13541 pub fn delete_to_end_of_line(
13542 &mut self,
13543 _: &DeleteToEndOfLine,
13544 window: &mut Window,
13545 cx: &mut Context<Self>,
13546 ) {
13547 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13548 self.transact(window, cx, |this, window, cx| {
13549 this.select_to_end_of_line(
13550 &SelectToEndOfLine {
13551 stop_at_soft_wraps: false,
13552 },
13553 window,
13554 cx,
13555 );
13556 this.delete(&Delete, window, cx);
13557 });
13558 }
13559
13560 pub fn cut_to_end_of_line(
13561 &mut self,
13562 action: &CutToEndOfLine,
13563 window: &mut Window,
13564 cx: &mut Context<Self>,
13565 ) {
13566 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13567 self.transact(window, cx, |this, window, cx| {
13568 this.select_to_end_of_line(
13569 &SelectToEndOfLine {
13570 stop_at_soft_wraps: false,
13571 },
13572 window,
13573 cx,
13574 );
13575 if !action.stop_at_newlines {
13576 this.change_selections(Default::default(), window, cx, |s| {
13577 s.move_with(|_, sel| {
13578 if sel.is_empty() {
13579 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13580 }
13581 });
13582 });
13583 }
13584 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13585 let item = this.cut_common(false, window, cx);
13586 cx.write_to_clipboard(item);
13587 });
13588 }
13589
13590 pub fn move_to_start_of_paragraph(
13591 &mut self,
13592 _: &MoveToStartOfParagraph,
13593 window: &mut Window,
13594 cx: &mut Context<Self>,
13595 ) {
13596 if matches!(self.mode, EditorMode::SingleLine) {
13597 cx.propagate();
13598 return;
13599 }
13600 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13601 self.change_selections(Default::default(), window, cx, |s| {
13602 s.move_with(|map, selection| {
13603 selection.collapse_to(
13604 movement::start_of_paragraph(map, selection.head(), 1),
13605 SelectionGoal::None,
13606 )
13607 });
13608 })
13609 }
13610
13611 pub fn move_to_end_of_paragraph(
13612 &mut self,
13613 _: &MoveToEndOfParagraph,
13614 window: &mut Window,
13615 cx: &mut Context<Self>,
13616 ) {
13617 if matches!(self.mode, EditorMode::SingleLine) {
13618 cx.propagate();
13619 return;
13620 }
13621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13622 self.change_selections(Default::default(), window, cx, |s| {
13623 s.move_with(|map, selection| {
13624 selection.collapse_to(
13625 movement::end_of_paragraph(map, selection.head(), 1),
13626 SelectionGoal::None,
13627 )
13628 });
13629 })
13630 }
13631
13632 pub fn select_to_start_of_paragraph(
13633 &mut self,
13634 _: &SelectToStartOfParagraph,
13635 window: &mut Window,
13636 cx: &mut Context<Self>,
13637 ) {
13638 if matches!(self.mode, EditorMode::SingleLine) {
13639 cx.propagate();
13640 return;
13641 }
13642 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13643 self.change_selections(Default::default(), window, cx, |s| {
13644 s.move_heads_with(|map, head, _| {
13645 (
13646 movement::start_of_paragraph(map, head, 1),
13647 SelectionGoal::None,
13648 )
13649 });
13650 })
13651 }
13652
13653 pub fn select_to_end_of_paragraph(
13654 &mut self,
13655 _: &SelectToEndOfParagraph,
13656 window: &mut Window,
13657 cx: &mut Context<Self>,
13658 ) {
13659 if matches!(self.mode, EditorMode::SingleLine) {
13660 cx.propagate();
13661 return;
13662 }
13663 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13664 self.change_selections(Default::default(), window, cx, |s| {
13665 s.move_heads_with(|map, head, _| {
13666 (
13667 movement::end_of_paragraph(map, head, 1),
13668 SelectionGoal::None,
13669 )
13670 });
13671 })
13672 }
13673
13674 pub fn move_to_start_of_excerpt(
13675 &mut self,
13676 _: &MoveToStartOfExcerpt,
13677 window: &mut Window,
13678 cx: &mut Context<Self>,
13679 ) {
13680 if matches!(self.mode, EditorMode::SingleLine) {
13681 cx.propagate();
13682 return;
13683 }
13684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13685 self.change_selections(Default::default(), window, cx, |s| {
13686 s.move_with(|map, selection| {
13687 selection.collapse_to(
13688 movement::start_of_excerpt(
13689 map,
13690 selection.head(),
13691 workspace::searchable::Direction::Prev,
13692 ),
13693 SelectionGoal::None,
13694 )
13695 });
13696 })
13697 }
13698
13699 pub fn move_to_start_of_next_excerpt(
13700 &mut self,
13701 _: &MoveToStartOfNextExcerpt,
13702 window: &mut Window,
13703 cx: &mut Context<Self>,
13704 ) {
13705 if matches!(self.mode, EditorMode::SingleLine) {
13706 cx.propagate();
13707 return;
13708 }
13709
13710 self.change_selections(Default::default(), window, cx, |s| {
13711 s.move_with(|map, selection| {
13712 selection.collapse_to(
13713 movement::start_of_excerpt(
13714 map,
13715 selection.head(),
13716 workspace::searchable::Direction::Next,
13717 ),
13718 SelectionGoal::None,
13719 )
13720 });
13721 })
13722 }
13723
13724 pub fn move_to_end_of_excerpt(
13725 &mut self,
13726 _: &MoveToEndOfExcerpt,
13727 window: &mut Window,
13728 cx: &mut Context<Self>,
13729 ) {
13730 if matches!(self.mode, EditorMode::SingleLine) {
13731 cx.propagate();
13732 return;
13733 }
13734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13735 self.change_selections(Default::default(), window, cx, |s| {
13736 s.move_with(|map, selection| {
13737 selection.collapse_to(
13738 movement::end_of_excerpt(
13739 map,
13740 selection.head(),
13741 workspace::searchable::Direction::Next,
13742 ),
13743 SelectionGoal::None,
13744 )
13745 });
13746 })
13747 }
13748
13749 pub fn move_to_end_of_previous_excerpt(
13750 &mut self,
13751 _: &MoveToEndOfPreviousExcerpt,
13752 window: &mut Window,
13753 cx: &mut Context<Self>,
13754 ) {
13755 if matches!(self.mode, EditorMode::SingleLine) {
13756 cx.propagate();
13757 return;
13758 }
13759 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13760 self.change_selections(Default::default(), window, cx, |s| {
13761 s.move_with(|map, selection| {
13762 selection.collapse_to(
13763 movement::end_of_excerpt(
13764 map,
13765 selection.head(),
13766 workspace::searchable::Direction::Prev,
13767 ),
13768 SelectionGoal::None,
13769 )
13770 });
13771 })
13772 }
13773
13774 pub fn select_to_start_of_excerpt(
13775 &mut self,
13776 _: &SelectToStartOfExcerpt,
13777 window: &mut Window,
13778 cx: &mut Context<Self>,
13779 ) {
13780 if matches!(self.mode, EditorMode::SingleLine) {
13781 cx.propagate();
13782 return;
13783 }
13784 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13785 self.change_selections(Default::default(), window, cx, |s| {
13786 s.move_heads_with(|map, head, _| {
13787 (
13788 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13789 SelectionGoal::None,
13790 )
13791 });
13792 })
13793 }
13794
13795 pub fn select_to_start_of_next_excerpt(
13796 &mut self,
13797 _: &SelectToStartOfNextExcerpt,
13798 window: &mut Window,
13799 cx: &mut Context<Self>,
13800 ) {
13801 if matches!(self.mode, EditorMode::SingleLine) {
13802 cx.propagate();
13803 return;
13804 }
13805 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13806 self.change_selections(Default::default(), window, cx, |s| {
13807 s.move_heads_with(|map, head, _| {
13808 (
13809 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13810 SelectionGoal::None,
13811 )
13812 });
13813 })
13814 }
13815
13816 pub fn select_to_end_of_excerpt(
13817 &mut self,
13818 _: &SelectToEndOfExcerpt,
13819 window: &mut Window,
13820 cx: &mut Context<Self>,
13821 ) {
13822 if matches!(self.mode, EditorMode::SingleLine) {
13823 cx.propagate();
13824 return;
13825 }
13826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13827 self.change_selections(Default::default(), window, cx, |s| {
13828 s.move_heads_with(|map, head, _| {
13829 (
13830 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13831 SelectionGoal::None,
13832 )
13833 });
13834 })
13835 }
13836
13837 pub fn select_to_end_of_previous_excerpt(
13838 &mut self,
13839 _: &SelectToEndOfPreviousExcerpt,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) {
13843 if matches!(self.mode, EditorMode::SingleLine) {
13844 cx.propagate();
13845 return;
13846 }
13847 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13848 self.change_selections(Default::default(), window, cx, |s| {
13849 s.move_heads_with(|map, head, _| {
13850 (
13851 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13852 SelectionGoal::None,
13853 )
13854 });
13855 })
13856 }
13857
13858 pub fn move_to_beginning(
13859 &mut self,
13860 _: &MoveToBeginning,
13861 window: &mut Window,
13862 cx: &mut Context<Self>,
13863 ) {
13864 if matches!(self.mode, EditorMode::SingleLine) {
13865 cx.propagate();
13866 return;
13867 }
13868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13869 self.change_selections(Default::default(), window, cx, |s| {
13870 s.select_ranges(vec![0..0]);
13871 });
13872 }
13873
13874 pub fn select_to_beginning(
13875 &mut self,
13876 _: &SelectToBeginning,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 let mut selection = self.selections.last::<Point>(cx);
13881 selection.set_head(Point::zero(), SelectionGoal::None);
13882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13883 self.change_selections(Default::default(), window, cx, |s| {
13884 s.select(vec![selection]);
13885 });
13886 }
13887
13888 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13889 if matches!(self.mode, EditorMode::SingleLine) {
13890 cx.propagate();
13891 return;
13892 }
13893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13894 let cursor = self.buffer.read(cx).read(cx).len();
13895 self.change_selections(Default::default(), window, cx, |s| {
13896 s.select_ranges(vec![cursor..cursor])
13897 });
13898 }
13899
13900 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13901 self.nav_history = nav_history;
13902 }
13903
13904 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13905 self.nav_history.as_ref()
13906 }
13907
13908 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13909 self.push_to_nav_history(
13910 self.selections.newest_anchor().head(),
13911 None,
13912 false,
13913 true,
13914 cx,
13915 );
13916 }
13917
13918 fn push_to_nav_history(
13919 &mut self,
13920 cursor_anchor: Anchor,
13921 new_position: Option<Point>,
13922 is_deactivate: bool,
13923 always: bool,
13924 cx: &mut Context<Self>,
13925 ) {
13926 if let Some(nav_history) = self.nav_history.as_mut() {
13927 let buffer = self.buffer.read(cx).read(cx);
13928 let cursor_position = cursor_anchor.to_point(&buffer);
13929 let scroll_state = self.scroll_manager.anchor();
13930 let scroll_top_row = scroll_state.top_row(&buffer);
13931 drop(buffer);
13932
13933 if let Some(new_position) = new_position {
13934 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13935 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13936 return;
13937 }
13938 }
13939
13940 nav_history.push(
13941 Some(NavigationData {
13942 cursor_anchor,
13943 cursor_position,
13944 scroll_anchor: scroll_state,
13945 scroll_top_row,
13946 }),
13947 cx,
13948 );
13949 cx.emit(EditorEvent::PushedToNavHistory {
13950 anchor: cursor_anchor,
13951 is_deactivate,
13952 })
13953 }
13954 }
13955
13956 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13958 let buffer = self.buffer.read(cx).snapshot(cx);
13959 let mut selection = self.selections.first::<usize>(cx);
13960 selection.set_head(buffer.len(), SelectionGoal::None);
13961 self.change_selections(Default::default(), window, cx, |s| {
13962 s.select(vec![selection]);
13963 });
13964 }
13965
13966 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13968 let end = self.buffer.read(cx).read(cx).len();
13969 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13970 s.select_ranges(vec![0..end]);
13971 });
13972 }
13973
13974 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13976 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13977 let mut selections = self.selections.all::<Point>(cx);
13978 let max_point = display_map.buffer_snapshot.max_point();
13979 for selection in &mut selections {
13980 let rows = selection.spanned_rows(true, &display_map);
13981 selection.start = Point::new(rows.start.0, 0);
13982 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13983 selection.reversed = false;
13984 }
13985 self.change_selections(Default::default(), window, cx, |s| {
13986 s.select(selections);
13987 });
13988 }
13989
13990 pub fn split_selection_into_lines(
13991 &mut self,
13992 action: &SplitSelectionIntoLines,
13993 window: &mut Window,
13994 cx: &mut Context<Self>,
13995 ) {
13996 let selections = self
13997 .selections
13998 .all::<Point>(cx)
13999 .into_iter()
14000 .map(|selection| selection.start..selection.end)
14001 .collect::<Vec<_>>();
14002 self.unfold_ranges(&selections, true, true, cx);
14003
14004 let mut new_selection_ranges = Vec::new();
14005 {
14006 let buffer = self.buffer.read(cx).read(cx);
14007 for selection in selections {
14008 for row in selection.start.row..selection.end.row {
14009 let line_start = Point::new(row, 0);
14010 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14011
14012 if action.keep_selections {
14013 // Keep the selection range for each line
14014 let selection_start = if row == selection.start.row {
14015 selection.start
14016 } else {
14017 line_start
14018 };
14019 new_selection_ranges.push(selection_start..line_end);
14020 } else {
14021 // Collapse to cursor at end of line
14022 new_selection_ranges.push(line_end..line_end);
14023 }
14024 }
14025
14026 let is_multiline_selection = selection.start.row != selection.end.row;
14027 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14028 // so this action feels more ergonomic when paired with other selection operations
14029 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14030 if !should_skip_last {
14031 if action.keep_selections {
14032 if is_multiline_selection {
14033 let line_start = Point::new(selection.end.row, 0);
14034 new_selection_ranges.push(line_start..selection.end);
14035 } else {
14036 new_selection_ranges.push(selection.start..selection.end);
14037 }
14038 } else {
14039 new_selection_ranges.push(selection.end..selection.end);
14040 }
14041 }
14042 }
14043 }
14044 self.change_selections(Default::default(), window, cx, |s| {
14045 s.select_ranges(new_selection_ranges);
14046 });
14047 }
14048
14049 pub fn add_selection_above(
14050 &mut self,
14051 _: &AddSelectionAbove,
14052 window: &mut Window,
14053 cx: &mut Context<Self>,
14054 ) {
14055 self.add_selection(true, window, cx);
14056 }
14057
14058 pub fn add_selection_below(
14059 &mut self,
14060 _: &AddSelectionBelow,
14061 window: &mut Window,
14062 cx: &mut Context<Self>,
14063 ) {
14064 self.add_selection(false, window, cx);
14065 }
14066
14067 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14068 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14069
14070 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14071 let all_selections = self.selections.all::<Point>(cx);
14072 let text_layout_details = self.text_layout_details(window);
14073
14074 let (mut columnar_selections, new_selections_to_columnarize) = {
14075 if let Some(state) = self.add_selections_state.as_ref() {
14076 let columnar_selection_ids: HashSet<_> = state
14077 .groups
14078 .iter()
14079 .flat_map(|group| group.stack.iter())
14080 .copied()
14081 .collect();
14082
14083 all_selections
14084 .into_iter()
14085 .partition(|s| columnar_selection_ids.contains(&s.id))
14086 } else {
14087 (Vec::new(), all_selections)
14088 }
14089 };
14090
14091 let mut state = self
14092 .add_selections_state
14093 .take()
14094 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14095
14096 for selection in new_selections_to_columnarize {
14097 let range = selection.display_range(&display_map).sorted();
14098 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14099 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14100 let positions = start_x.min(end_x)..start_x.max(end_x);
14101 let mut stack = Vec::new();
14102 for row in range.start.row().0..=range.end.row().0 {
14103 if let Some(selection) = self.selections.build_columnar_selection(
14104 &display_map,
14105 DisplayRow(row),
14106 &positions,
14107 selection.reversed,
14108 &text_layout_details,
14109 ) {
14110 stack.push(selection.id);
14111 columnar_selections.push(selection);
14112 }
14113 }
14114 if !stack.is_empty() {
14115 if above {
14116 stack.reverse();
14117 }
14118 state.groups.push(AddSelectionsGroup { above, stack });
14119 }
14120 }
14121
14122 let mut final_selections = Vec::new();
14123 let end_row = if above {
14124 DisplayRow(0)
14125 } else {
14126 display_map.max_point().row()
14127 };
14128
14129 let mut last_added_item_per_group = HashMap::default();
14130 for group in state.groups.iter_mut() {
14131 if let Some(last_id) = group.stack.last() {
14132 last_added_item_per_group.insert(*last_id, group);
14133 }
14134 }
14135
14136 for selection in columnar_selections {
14137 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14138 if above == group.above {
14139 let range = selection.display_range(&display_map).sorted();
14140 debug_assert_eq!(range.start.row(), range.end.row());
14141 let mut row = range.start.row();
14142 let positions =
14143 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14144 px(start)..px(end)
14145 } else {
14146 let start_x =
14147 display_map.x_for_display_point(range.start, &text_layout_details);
14148 let end_x =
14149 display_map.x_for_display_point(range.end, &text_layout_details);
14150 start_x.min(end_x)..start_x.max(end_x)
14151 };
14152
14153 let mut maybe_new_selection = None;
14154 while row != end_row {
14155 if above {
14156 row.0 -= 1;
14157 } else {
14158 row.0 += 1;
14159 }
14160 if let Some(new_selection) = self.selections.build_columnar_selection(
14161 &display_map,
14162 row,
14163 &positions,
14164 selection.reversed,
14165 &text_layout_details,
14166 ) {
14167 maybe_new_selection = Some(new_selection);
14168 break;
14169 }
14170 }
14171
14172 if let Some(new_selection) = maybe_new_selection {
14173 group.stack.push(new_selection.id);
14174 if above {
14175 final_selections.push(new_selection);
14176 final_selections.push(selection);
14177 } else {
14178 final_selections.push(selection);
14179 final_selections.push(new_selection);
14180 }
14181 } else {
14182 final_selections.push(selection);
14183 }
14184 } else {
14185 group.stack.pop();
14186 }
14187 } else {
14188 final_selections.push(selection);
14189 }
14190 }
14191
14192 self.change_selections(Default::default(), window, cx, |s| {
14193 s.select(final_selections);
14194 });
14195
14196 let final_selection_ids: HashSet<_> = self
14197 .selections
14198 .all::<Point>(cx)
14199 .iter()
14200 .map(|s| s.id)
14201 .collect();
14202 state.groups.retain_mut(|group| {
14203 // selections might get merged above so we remove invalid items from stacks
14204 group.stack.retain(|id| final_selection_ids.contains(id));
14205
14206 // single selection in stack can be treated as initial state
14207 group.stack.len() > 1
14208 });
14209
14210 if !state.groups.is_empty() {
14211 self.add_selections_state = Some(state);
14212 }
14213 }
14214
14215 fn select_match_ranges(
14216 &mut self,
14217 range: Range<usize>,
14218 reversed: bool,
14219 replace_newest: bool,
14220 auto_scroll: Option<Autoscroll>,
14221 window: &mut Window,
14222 cx: &mut Context<Editor>,
14223 ) {
14224 self.unfold_ranges(
14225 std::slice::from_ref(&range),
14226 false,
14227 auto_scroll.is_some(),
14228 cx,
14229 );
14230 let effects = if let Some(scroll) = auto_scroll {
14231 SelectionEffects::scroll(scroll)
14232 } else {
14233 SelectionEffects::no_scroll()
14234 };
14235 self.change_selections(effects, window, cx, |s| {
14236 if replace_newest {
14237 s.delete(s.newest_anchor().id);
14238 }
14239 if reversed {
14240 s.insert_range(range.end..range.start);
14241 } else {
14242 s.insert_range(range);
14243 }
14244 });
14245 }
14246
14247 pub fn select_next_match_internal(
14248 &mut self,
14249 display_map: &DisplaySnapshot,
14250 replace_newest: bool,
14251 autoscroll: Option<Autoscroll>,
14252 window: &mut Window,
14253 cx: &mut Context<Self>,
14254 ) -> Result<()> {
14255 let buffer = &display_map.buffer_snapshot;
14256 let mut selections = self.selections.all::<usize>(cx);
14257 if let Some(mut select_next_state) = self.select_next_state.take() {
14258 let query = &select_next_state.query;
14259 if !select_next_state.done {
14260 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14261 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14262 let mut next_selected_range = None;
14263
14264 let bytes_after_last_selection =
14265 buffer.bytes_in_range(last_selection.end..buffer.len());
14266 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14267 let query_matches = query
14268 .stream_find_iter(bytes_after_last_selection)
14269 .map(|result| (last_selection.end, result))
14270 .chain(
14271 query
14272 .stream_find_iter(bytes_before_first_selection)
14273 .map(|result| (0, result)),
14274 );
14275
14276 for (start_offset, query_match) in query_matches {
14277 let query_match = query_match.unwrap(); // can only fail due to I/O
14278 let offset_range =
14279 start_offset + query_match.start()..start_offset + query_match.end();
14280
14281 if !select_next_state.wordwise
14282 || (!buffer.is_inside_word(offset_range.start, None)
14283 && !buffer.is_inside_word(offset_range.end, None))
14284 {
14285 // TODO: This is n^2, because we might check all the selections
14286 if !selections
14287 .iter()
14288 .any(|selection| selection.range().overlaps(&offset_range))
14289 {
14290 next_selected_range = Some(offset_range);
14291 break;
14292 }
14293 }
14294 }
14295
14296 if let Some(next_selected_range) = next_selected_range {
14297 self.select_match_ranges(
14298 next_selected_range,
14299 last_selection.reversed,
14300 replace_newest,
14301 autoscroll,
14302 window,
14303 cx,
14304 );
14305 } else {
14306 select_next_state.done = true;
14307 }
14308 }
14309
14310 self.select_next_state = Some(select_next_state);
14311 } else {
14312 let mut only_carets = true;
14313 let mut same_text_selected = true;
14314 let mut selected_text = None;
14315
14316 let mut selections_iter = selections.iter().peekable();
14317 while let Some(selection) = selections_iter.next() {
14318 if selection.start != selection.end {
14319 only_carets = false;
14320 }
14321
14322 if same_text_selected {
14323 if selected_text.is_none() {
14324 selected_text =
14325 Some(buffer.text_for_range(selection.range()).collect::<String>());
14326 }
14327
14328 if let Some(next_selection) = selections_iter.peek() {
14329 if next_selection.range().len() == selection.range().len() {
14330 let next_selected_text = buffer
14331 .text_for_range(next_selection.range())
14332 .collect::<String>();
14333 if Some(next_selected_text) != selected_text {
14334 same_text_selected = false;
14335 selected_text = None;
14336 }
14337 } else {
14338 same_text_selected = false;
14339 selected_text = None;
14340 }
14341 }
14342 }
14343 }
14344
14345 if only_carets {
14346 for selection in &mut selections {
14347 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14348 selection.start = word_range.start;
14349 selection.end = word_range.end;
14350 selection.goal = SelectionGoal::None;
14351 selection.reversed = false;
14352 self.select_match_ranges(
14353 selection.start..selection.end,
14354 selection.reversed,
14355 replace_newest,
14356 autoscroll,
14357 window,
14358 cx,
14359 );
14360 }
14361
14362 if selections.len() == 1 {
14363 let selection = selections
14364 .last()
14365 .expect("ensured that there's only one selection");
14366 let query = buffer
14367 .text_for_range(selection.start..selection.end)
14368 .collect::<String>();
14369 let is_empty = query.is_empty();
14370 let select_state = SelectNextState {
14371 query: AhoCorasick::new(&[query])?,
14372 wordwise: true,
14373 done: is_empty,
14374 };
14375 self.select_next_state = Some(select_state);
14376 } else {
14377 self.select_next_state = None;
14378 }
14379 } else if let Some(selected_text) = selected_text {
14380 self.select_next_state = Some(SelectNextState {
14381 query: AhoCorasick::new(&[selected_text])?,
14382 wordwise: false,
14383 done: false,
14384 });
14385 self.select_next_match_internal(
14386 display_map,
14387 replace_newest,
14388 autoscroll,
14389 window,
14390 cx,
14391 )?;
14392 }
14393 }
14394 Ok(())
14395 }
14396
14397 pub fn select_all_matches(
14398 &mut self,
14399 _action: &SelectAllMatches,
14400 window: &mut Window,
14401 cx: &mut Context<Self>,
14402 ) -> Result<()> {
14403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14404
14405 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14406
14407 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14408 let Some(select_next_state) = self.select_next_state.as_mut() else {
14409 return Ok(());
14410 };
14411 if select_next_state.done {
14412 return Ok(());
14413 }
14414
14415 let mut new_selections = Vec::new();
14416
14417 let reversed = self.selections.oldest::<usize>(cx).reversed;
14418 let buffer = &display_map.buffer_snapshot;
14419 let query_matches = select_next_state
14420 .query
14421 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14422
14423 for query_match in query_matches.into_iter() {
14424 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14425 let offset_range = if reversed {
14426 query_match.end()..query_match.start()
14427 } else {
14428 query_match.start()..query_match.end()
14429 };
14430
14431 if !select_next_state.wordwise
14432 || (!buffer.is_inside_word(offset_range.start, None)
14433 && !buffer.is_inside_word(offset_range.end, None))
14434 {
14435 new_selections.push(offset_range.start..offset_range.end);
14436 }
14437 }
14438
14439 select_next_state.done = true;
14440
14441 if new_selections.is_empty() {
14442 log::error!("bug: new_selections is empty in select_all_matches");
14443 return Ok(());
14444 }
14445
14446 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14447 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14448 selections.select_ranges(new_selections)
14449 });
14450
14451 Ok(())
14452 }
14453
14454 pub fn select_next(
14455 &mut self,
14456 action: &SelectNext,
14457 window: &mut Window,
14458 cx: &mut Context<Self>,
14459 ) -> Result<()> {
14460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14461 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14462 self.select_next_match_internal(
14463 &display_map,
14464 action.replace_newest,
14465 Some(Autoscroll::newest()),
14466 window,
14467 cx,
14468 )?;
14469 Ok(())
14470 }
14471
14472 pub fn select_previous(
14473 &mut self,
14474 action: &SelectPrevious,
14475 window: &mut Window,
14476 cx: &mut Context<Self>,
14477 ) -> Result<()> {
14478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14479 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14480 let buffer = &display_map.buffer_snapshot;
14481 let mut selections = self.selections.all::<usize>(cx);
14482 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14483 let query = &select_prev_state.query;
14484 if !select_prev_state.done {
14485 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14486 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14487 let mut next_selected_range = None;
14488 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14489 let bytes_before_last_selection =
14490 buffer.reversed_bytes_in_range(0..last_selection.start);
14491 let bytes_after_first_selection =
14492 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14493 let query_matches = query
14494 .stream_find_iter(bytes_before_last_selection)
14495 .map(|result| (last_selection.start, result))
14496 .chain(
14497 query
14498 .stream_find_iter(bytes_after_first_selection)
14499 .map(|result| (buffer.len(), result)),
14500 );
14501 for (end_offset, query_match) in query_matches {
14502 let query_match = query_match.unwrap(); // can only fail due to I/O
14503 let offset_range =
14504 end_offset - query_match.end()..end_offset - query_match.start();
14505
14506 if !select_prev_state.wordwise
14507 || (!buffer.is_inside_word(offset_range.start, None)
14508 && !buffer.is_inside_word(offset_range.end, None))
14509 {
14510 next_selected_range = Some(offset_range);
14511 break;
14512 }
14513 }
14514
14515 if let Some(next_selected_range) = next_selected_range {
14516 self.select_match_ranges(
14517 next_selected_range,
14518 last_selection.reversed,
14519 action.replace_newest,
14520 Some(Autoscroll::newest()),
14521 window,
14522 cx,
14523 );
14524 } else {
14525 select_prev_state.done = true;
14526 }
14527 }
14528
14529 self.select_prev_state = Some(select_prev_state);
14530 } else {
14531 let mut only_carets = true;
14532 let mut same_text_selected = true;
14533 let mut selected_text = None;
14534
14535 let mut selections_iter = selections.iter().peekable();
14536 while let Some(selection) = selections_iter.next() {
14537 if selection.start != selection.end {
14538 only_carets = false;
14539 }
14540
14541 if same_text_selected {
14542 if selected_text.is_none() {
14543 selected_text =
14544 Some(buffer.text_for_range(selection.range()).collect::<String>());
14545 }
14546
14547 if let Some(next_selection) = selections_iter.peek() {
14548 if next_selection.range().len() == selection.range().len() {
14549 let next_selected_text = buffer
14550 .text_for_range(next_selection.range())
14551 .collect::<String>();
14552 if Some(next_selected_text) != selected_text {
14553 same_text_selected = false;
14554 selected_text = None;
14555 }
14556 } else {
14557 same_text_selected = false;
14558 selected_text = None;
14559 }
14560 }
14561 }
14562 }
14563
14564 if only_carets {
14565 for selection in &mut selections {
14566 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14567 selection.start = word_range.start;
14568 selection.end = word_range.end;
14569 selection.goal = SelectionGoal::None;
14570 selection.reversed = false;
14571 self.select_match_ranges(
14572 selection.start..selection.end,
14573 selection.reversed,
14574 action.replace_newest,
14575 Some(Autoscroll::newest()),
14576 window,
14577 cx,
14578 );
14579 }
14580 if selections.len() == 1 {
14581 let selection = selections
14582 .last()
14583 .expect("ensured that there's only one selection");
14584 let query = buffer
14585 .text_for_range(selection.start..selection.end)
14586 .collect::<String>();
14587 let is_empty = query.is_empty();
14588 let select_state = SelectNextState {
14589 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14590 wordwise: true,
14591 done: is_empty,
14592 };
14593 self.select_prev_state = Some(select_state);
14594 } else {
14595 self.select_prev_state = None;
14596 }
14597 } else if let Some(selected_text) = selected_text {
14598 self.select_prev_state = Some(SelectNextState {
14599 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14600 wordwise: false,
14601 done: false,
14602 });
14603 self.select_previous(action, window, cx)?;
14604 }
14605 }
14606 Ok(())
14607 }
14608
14609 pub fn find_next_match(
14610 &mut self,
14611 _: &FindNextMatch,
14612 window: &mut Window,
14613 cx: &mut Context<Self>,
14614 ) -> Result<()> {
14615 let selections = self.selections.disjoint_anchors_arc();
14616 match selections.first() {
14617 Some(first) if selections.len() >= 2 => {
14618 self.change_selections(Default::default(), window, cx, |s| {
14619 s.select_ranges([first.range()]);
14620 });
14621 }
14622 _ => self.select_next(
14623 &SelectNext {
14624 replace_newest: true,
14625 },
14626 window,
14627 cx,
14628 )?,
14629 }
14630 Ok(())
14631 }
14632
14633 pub fn find_previous_match(
14634 &mut self,
14635 _: &FindPreviousMatch,
14636 window: &mut Window,
14637 cx: &mut Context<Self>,
14638 ) -> Result<()> {
14639 let selections = self.selections.disjoint_anchors_arc();
14640 match selections.last() {
14641 Some(last) if selections.len() >= 2 => {
14642 self.change_selections(Default::default(), window, cx, |s| {
14643 s.select_ranges([last.range()]);
14644 });
14645 }
14646 _ => self.select_previous(
14647 &SelectPrevious {
14648 replace_newest: true,
14649 },
14650 window,
14651 cx,
14652 )?,
14653 }
14654 Ok(())
14655 }
14656
14657 pub fn toggle_comments(
14658 &mut self,
14659 action: &ToggleComments,
14660 window: &mut Window,
14661 cx: &mut Context<Self>,
14662 ) {
14663 if self.read_only(cx) {
14664 return;
14665 }
14666 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14667 let text_layout_details = &self.text_layout_details(window);
14668 self.transact(window, cx, |this, window, cx| {
14669 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14670 let mut edits = Vec::new();
14671 let mut selection_edit_ranges = Vec::new();
14672 let mut last_toggled_row = None;
14673 let snapshot = this.buffer.read(cx).read(cx);
14674 let empty_str: Arc<str> = Arc::default();
14675 let mut suffixes_inserted = Vec::new();
14676 let ignore_indent = action.ignore_indent;
14677
14678 fn comment_prefix_range(
14679 snapshot: &MultiBufferSnapshot,
14680 row: MultiBufferRow,
14681 comment_prefix: &str,
14682 comment_prefix_whitespace: &str,
14683 ignore_indent: bool,
14684 ) -> Range<Point> {
14685 let indent_size = if ignore_indent {
14686 0
14687 } else {
14688 snapshot.indent_size_for_line(row).len
14689 };
14690
14691 let start = Point::new(row.0, indent_size);
14692
14693 let mut line_bytes = snapshot
14694 .bytes_in_range(start..snapshot.max_point())
14695 .flatten()
14696 .copied();
14697
14698 // If this line currently begins with the line comment prefix, then record
14699 // the range containing the prefix.
14700 if line_bytes
14701 .by_ref()
14702 .take(comment_prefix.len())
14703 .eq(comment_prefix.bytes())
14704 {
14705 // Include any whitespace that matches the comment prefix.
14706 let matching_whitespace_len = line_bytes
14707 .zip(comment_prefix_whitespace.bytes())
14708 .take_while(|(a, b)| a == b)
14709 .count() as u32;
14710 let end = Point::new(
14711 start.row,
14712 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14713 );
14714 start..end
14715 } else {
14716 start..start
14717 }
14718 }
14719
14720 fn comment_suffix_range(
14721 snapshot: &MultiBufferSnapshot,
14722 row: MultiBufferRow,
14723 comment_suffix: &str,
14724 comment_suffix_has_leading_space: bool,
14725 ) -> Range<Point> {
14726 let end = Point::new(row.0, snapshot.line_len(row));
14727 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14728
14729 let mut line_end_bytes = snapshot
14730 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14731 .flatten()
14732 .copied();
14733
14734 let leading_space_len = if suffix_start_column > 0
14735 && line_end_bytes.next() == Some(b' ')
14736 && comment_suffix_has_leading_space
14737 {
14738 1
14739 } else {
14740 0
14741 };
14742
14743 // If this line currently begins with the line comment prefix, then record
14744 // the range containing the prefix.
14745 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14746 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14747 start..end
14748 } else {
14749 end..end
14750 }
14751 }
14752
14753 // TODO: Handle selections that cross excerpts
14754 for selection in &mut selections {
14755 let start_column = snapshot
14756 .indent_size_for_line(MultiBufferRow(selection.start.row))
14757 .len;
14758 let language = if let Some(language) =
14759 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14760 {
14761 language
14762 } else {
14763 continue;
14764 };
14765
14766 selection_edit_ranges.clear();
14767
14768 // If multiple selections contain a given row, avoid processing that
14769 // row more than once.
14770 let mut start_row = MultiBufferRow(selection.start.row);
14771 if last_toggled_row == Some(start_row) {
14772 start_row = start_row.next_row();
14773 }
14774 let end_row =
14775 if selection.end.row > selection.start.row && selection.end.column == 0 {
14776 MultiBufferRow(selection.end.row - 1)
14777 } else {
14778 MultiBufferRow(selection.end.row)
14779 };
14780 last_toggled_row = Some(end_row);
14781
14782 if start_row > end_row {
14783 continue;
14784 }
14785
14786 // If the language has line comments, toggle those.
14787 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14788
14789 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14790 if ignore_indent {
14791 full_comment_prefixes = full_comment_prefixes
14792 .into_iter()
14793 .map(|s| Arc::from(s.trim_end()))
14794 .collect();
14795 }
14796
14797 if !full_comment_prefixes.is_empty() {
14798 let first_prefix = full_comment_prefixes
14799 .first()
14800 .expect("prefixes is non-empty");
14801 let prefix_trimmed_lengths = full_comment_prefixes
14802 .iter()
14803 .map(|p| p.trim_end_matches(' ').len())
14804 .collect::<SmallVec<[usize; 4]>>();
14805
14806 let mut all_selection_lines_are_comments = true;
14807
14808 for row in start_row.0..=end_row.0 {
14809 let row = MultiBufferRow(row);
14810 if start_row < end_row && snapshot.is_line_blank(row) {
14811 continue;
14812 }
14813
14814 let prefix_range = full_comment_prefixes
14815 .iter()
14816 .zip(prefix_trimmed_lengths.iter().copied())
14817 .map(|(prefix, trimmed_prefix_len)| {
14818 comment_prefix_range(
14819 snapshot.deref(),
14820 row,
14821 &prefix[..trimmed_prefix_len],
14822 &prefix[trimmed_prefix_len..],
14823 ignore_indent,
14824 )
14825 })
14826 .max_by_key(|range| range.end.column - range.start.column)
14827 .expect("prefixes is non-empty");
14828
14829 if prefix_range.is_empty() {
14830 all_selection_lines_are_comments = false;
14831 }
14832
14833 selection_edit_ranges.push(prefix_range);
14834 }
14835
14836 if all_selection_lines_are_comments {
14837 edits.extend(
14838 selection_edit_ranges
14839 .iter()
14840 .cloned()
14841 .map(|range| (range, empty_str.clone())),
14842 );
14843 } else {
14844 let min_column = selection_edit_ranges
14845 .iter()
14846 .map(|range| range.start.column)
14847 .min()
14848 .unwrap_or(0);
14849 edits.extend(selection_edit_ranges.iter().map(|range| {
14850 let position = Point::new(range.start.row, min_column);
14851 (position..position, first_prefix.clone())
14852 }));
14853 }
14854 } else if let Some(BlockCommentConfig {
14855 start: full_comment_prefix,
14856 end: comment_suffix,
14857 ..
14858 }) = language.block_comment()
14859 {
14860 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14861 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14862 let prefix_range = comment_prefix_range(
14863 snapshot.deref(),
14864 start_row,
14865 comment_prefix,
14866 comment_prefix_whitespace,
14867 ignore_indent,
14868 );
14869 let suffix_range = comment_suffix_range(
14870 snapshot.deref(),
14871 end_row,
14872 comment_suffix.trim_start_matches(' '),
14873 comment_suffix.starts_with(' '),
14874 );
14875
14876 if prefix_range.is_empty() || suffix_range.is_empty() {
14877 edits.push((
14878 prefix_range.start..prefix_range.start,
14879 full_comment_prefix.clone(),
14880 ));
14881 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14882 suffixes_inserted.push((end_row, comment_suffix.len()));
14883 } else {
14884 edits.push((prefix_range, empty_str.clone()));
14885 edits.push((suffix_range, empty_str.clone()));
14886 }
14887 } else {
14888 continue;
14889 }
14890 }
14891
14892 drop(snapshot);
14893 this.buffer.update(cx, |buffer, cx| {
14894 buffer.edit(edits, None, cx);
14895 });
14896
14897 // Adjust selections so that they end before any comment suffixes that
14898 // were inserted.
14899 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14900 let mut selections = this.selections.all::<Point>(cx);
14901 let snapshot = this.buffer.read(cx).read(cx);
14902 for selection in &mut selections {
14903 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14904 match row.cmp(&MultiBufferRow(selection.end.row)) {
14905 Ordering::Less => {
14906 suffixes_inserted.next();
14907 continue;
14908 }
14909 Ordering::Greater => break,
14910 Ordering::Equal => {
14911 if selection.end.column == snapshot.line_len(row) {
14912 if selection.is_empty() {
14913 selection.start.column -= suffix_len as u32;
14914 }
14915 selection.end.column -= suffix_len as u32;
14916 }
14917 break;
14918 }
14919 }
14920 }
14921 }
14922
14923 drop(snapshot);
14924 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14925
14926 let selections = this.selections.all::<Point>(cx);
14927 let selections_on_single_row = selections.windows(2).all(|selections| {
14928 selections[0].start.row == selections[1].start.row
14929 && selections[0].end.row == selections[1].end.row
14930 && selections[0].start.row == selections[0].end.row
14931 });
14932 let selections_selecting = selections
14933 .iter()
14934 .any(|selection| selection.start != selection.end);
14935 let advance_downwards = action.advance_downwards
14936 && selections_on_single_row
14937 && !selections_selecting
14938 && !matches!(this.mode, EditorMode::SingleLine);
14939
14940 if advance_downwards {
14941 let snapshot = this.buffer.read(cx).snapshot(cx);
14942
14943 this.change_selections(Default::default(), window, cx, |s| {
14944 s.move_cursors_with(|display_snapshot, display_point, _| {
14945 let mut point = display_point.to_point(display_snapshot);
14946 point.row += 1;
14947 point = snapshot.clip_point(point, Bias::Left);
14948 let display_point = point.to_display_point(display_snapshot);
14949 let goal = SelectionGoal::HorizontalPosition(
14950 display_snapshot
14951 .x_for_display_point(display_point, text_layout_details)
14952 .into(),
14953 );
14954 (display_point, goal)
14955 })
14956 });
14957 }
14958 });
14959 }
14960
14961 pub fn select_enclosing_symbol(
14962 &mut self,
14963 _: &SelectEnclosingSymbol,
14964 window: &mut Window,
14965 cx: &mut Context<Self>,
14966 ) {
14967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14968
14969 let buffer = self.buffer.read(cx).snapshot(cx);
14970 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14971
14972 fn update_selection(
14973 selection: &Selection<usize>,
14974 buffer_snap: &MultiBufferSnapshot,
14975 ) -> Option<Selection<usize>> {
14976 let cursor = selection.head();
14977 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14978 for symbol in symbols.iter().rev() {
14979 let start = symbol.range.start.to_offset(buffer_snap);
14980 let end = symbol.range.end.to_offset(buffer_snap);
14981 let new_range = start..end;
14982 if start < selection.start || end > selection.end {
14983 return Some(Selection {
14984 id: selection.id,
14985 start: new_range.start,
14986 end: new_range.end,
14987 goal: SelectionGoal::None,
14988 reversed: selection.reversed,
14989 });
14990 }
14991 }
14992 None
14993 }
14994
14995 let mut selected_larger_symbol = false;
14996 let new_selections = old_selections
14997 .iter()
14998 .map(|selection| match update_selection(selection, &buffer) {
14999 Some(new_selection) => {
15000 if new_selection.range() != selection.range() {
15001 selected_larger_symbol = true;
15002 }
15003 new_selection
15004 }
15005 None => selection.clone(),
15006 })
15007 .collect::<Vec<_>>();
15008
15009 if selected_larger_symbol {
15010 self.change_selections(Default::default(), window, cx, |s| {
15011 s.select(new_selections);
15012 });
15013 }
15014 }
15015
15016 pub fn select_larger_syntax_node(
15017 &mut self,
15018 _: &SelectLargerSyntaxNode,
15019 window: &mut Window,
15020 cx: &mut Context<Self>,
15021 ) {
15022 let Some(visible_row_count) = self.visible_row_count() else {
15023 return;
15024 };
15025 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15026 if old_selections.is_empty() {
15027 return;
15028 }
15029
15030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15031
15032 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15033 let buffer = self.buffer.read(cx).snapshot(cx);
15034
15035 let mut selected_larger_node = false;
15036 let mut new_selections = old_selections
15037 .iter()
15038 .map(|selection| {
15039 let old_range = selection.start..selection.end;
15040
15041 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15042 // manually select word at selection
15043 if ["string_content", "inline"].contains(&node.kind()) {
15044 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15045 // ignore if word is already selected
15046 if !word_range.is_empty() && old_range != word_range {
15047 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15048 // only select word if start and end point belongs to same word
15049 if word_range == last_word_range {
15050 selected_larger_node = true;
15051 return Selection {
15052 id: selection.id,
15053 start: word_range.start,
15054 end: word_range.end,
15055 goal: SelectionGoal::None,
15056 reversed: selection.reversed,
15057 };
15058 }
15059 }
15060 }
15061 }
15062
15063 let mut new_range = old_range.clone();
15064 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
15065 {
15066 new_range = match containing_range {
15067 MultiOrSingleBufferOffsetRange::Single(_) => break,
15068 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15069 };
15070 if !node.is_named() {
15071 continue;
15072 }
15073 if !display_map.intersects_fold(new_range.start)
15074 && !display_map.intersects_fold(new_range.end)
15075 {
15076 break;
15077 }
15078 }
15079
15080 selected_larger_node |= new_range != old_range;
15081 Selection {
15082 id: selection.id,
15083 start: new_range.start,
15084 end: new_range.end,
15085 goal: SelectionGoal::None,
15086 reversed: selection.reversed,
15087 }
15088 })
15089 .collect::<Vec<_>>();
15090
15091 if !selected_larger_node {
15092 return; // don't put this call in the history
15093 }
15094
15095 // scroll based on transformation done to the last selection created by the user
15096 let (last_old, last_new) = old_selections
15097 .last()
15098 .zip(new_selections.last().cloned())
15099 .expect("old_selections isn't empty");
15100
15101 // revert selection
15102 let is_selection_reversed = {
15103 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15104 new_selections.last_mut().expect("checked above").reversed =
15105 should_newest_selection_be_reversed;
15106 should_newest_selection_be_reversed
15107 };
15108
15109 if selected_larger_node {
15110 self.select_syntax_node_history.disable_clearing = true;
15111 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15112 s.select(new_selections.clone());
15113 });
15114 self.select_syntax_node_history.disable_clearing = false;
15115 }
15116
15117 let start_row = last_new.start.to_display_point(&display_map).row().0;
15118 let end_row = last_new.end.to_display_point(&display_map).row().0;
15119 let selection_height = end_row - start_row + 1;
15120 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15121
15122 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15123 let scroll_behavior = if fits_on_the_screen {
15124 self.request_autoscroll(Autoscroll::fit(), cx);
15125 SelectSyntaxNodeScrollBehavior::FitSelection
15126 } else if is_selection_reversed {
15127 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15128 SelectSyntaxNodeScrollBehavior::CursorTop
15129 } else {
15130 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15131 SelectSyntaxNodeScrollBehavior::CursorBottom
15132 };
15133
15134 self.select_syntax_node_history.push((
15135 old_selections,
15136 scroll_behavior,
15137 is_selection_reversed,
15138 ));
15139 }
15140
15141 pub fn select_smaller_syntax_node(
15142 &mut self,
15143 _: &SelectSmallerSyntaxNode,
15144 window: &mut Window,
15145 cx: &mut Context<Self>,
15146 ) {
15147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15148
15149 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15150 self.select_syntax_node_history.pop()
15151 {
15152 if let Some(selection) = selections.last_mut() {
15153 selection.reversed = is_selection_reversed;
15154 }
15155
15156 self.select_syntax_node_history.disable_clearing = true;
15157 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15158 s.select(selections.to_vec());
15159 });
15160 self.select_syntax_node_history.disable_clearing = false;
15161
15162 match scroll_behavior {
15163 SelectSyntaxNodeScrollBehavior::CursorTop => {
15164 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15165 }
15166 SelectSyntaxNodeScrollBehavior::FitSelection => {
15167 self.request_autoscroll(Autoscroll::fit(), cx);
15168 }
15169 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15170 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15171 }
15172 }
15173 }
15174 }
15175
15176 pub fn unwrap_syntax_node(
15177 &mut self,
15178 _: &UnwrapSyntaxNode,
15179 window: &mut Window,
15180 cx: &mut Context<Self>,
15181 ) {
15182 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15183
15184 let buffer = self.buffer.read(cx).snapshot(cx);
15185 let selections = self
15186 .selections
15187 .all::<usize>(cx)
15188 .into_iter()
15189 // subtracting the offset requires sorting
15190 .sorted_by_key(|i| i.start);
15191
15192 let full_edits = selections
15193 .into_iter()
15194 .filter_map(|selection| {
15195 let child = if selection.is_empty()
15196 && let Some((_, ancestor_range)) =
15197 buffer.syntax_ancestor(selection.start..selection.end)
15198 {
15199 match ancestor_range {
15200 MultiOrSingleBufferOffsetRange::Single(range) => range,
15201 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15202 }
15203 } else {
15204 selection.range()
15205 };
15206
15207 let mut parent = child.clone();
15208 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15209 parent = match ancestor_range {
15210 MultiOrSingleBufferOffsetRange::Single(range) => range,
15211 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15212 };
15213 if parent.start < child.start || parent.end > child.end {
15214 break;
15215 }
15216 }
15217
15218 if parent == child {
15219 return None;
15220 }
15221 let text = buffer.text_for_range(child).collect::<String>();
15222 Some((selection.id, parent, text))
15223 })
15224 .collect::<Vec<_>>();
15225 if full_edits.is_empty() {
15226 return;
15227 }
15228
15229 self.transact(window, cx, |this, window, cx| {
15230 this.buffer.update(cx, |buffer, cx| {
15231 buffer.edit(
15232 full_edits
15233 .iter()
15234 .map(|(_, p, t)| (p.clone(), t.clone()))
15235 .collect::<Vec<_>>(),
15236 None,
15237 cx,
15238 );
15239 });
15240 this.change_selections(Default::default(), window, cx, |s| {
15241 let mut offset = 0;
15242 let mut selections = vec![];
15243 for (id, parent, text) in full_edits {
15244 let start = parent.start - offset;
15245 offset += parent.len() - text.len();
15246 selections.push(Selection {
15247 id,
15248 start,
15249 end: start + text.len(),
15250 reversed: false,
15251 goal: Default::default(),
15252 });
15253 }
15254 s.select(selections);
15255 });
15256 });
15257 }
15258
15259 pub fn select_next_syntax_node(
15260 &mut self,
15261 _: &SelectNextSyntaxNode,
15262 window: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) {
15265 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15266 if old_selections.is_empty() {
15267 return;
15268 }
15269
15270 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15271
15272 let buffer = self.buffer.read(cx).snapshot(cx);
15273 let mut selected_sibling = false;
15274
15275 let new_selections = old_selections
15276 .iter()
15277 .map(|selection| {
15278 let old_range = selection.start..selection.end;
15279
15280 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15281 let new_range = node.byte_range();
15282 selected_sibling = true;
15283 Selection {
15284 id: selection.id,
15285 start: new_range.start,
15286 end: new_range.end,
15287 goal: SelectionGoal::None,
15288 reversed: selection.reversed,
15289 }
15290 } else {
15291 selection.clone()
15292 }
15293 })
15294 .collect::<Vec<_>>();
15295
15296 if selected_sibling {
15297 self.change_selections(
15298 SelectionEffects::scroll(Autoscroll::fit()),
15299 window,
15300 cx,
15301 |s| {
15302 s.select(new_selections);
15303 },
15304 );
15305 }
15306 }
15307
15308 pub fn select_prev_syntax_node(
15309 &mut self,
15310 _: &SelectPreviousSyntaxNode,
15311 window: &mut Window,
15312 cx: &mut Context<Self>,
15313 ) {
15314 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15315 if old_selections.is_empty() {
15316 return;
15317 }
15318
15319 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15320
15321 let buffer = self.buffer.read(cx).snapshot(cx);
15322 let mut selected_sibling = false;
15323
15324 let new_selections = old_selections
15325 .iter()
15326 .map(|selection| {
15327 let old_range = selection.start..selection.end;
15328
15329 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15330 let new_range = node.byte_range();
15331 selected_sibling = true;
15332 Selection {
15333 id: selection.id,
15334 start: new_range.start,
15335 end: new_range.end,
15336 goal: SelectionGoal::None,
15337 reversed: selection.reversed,
15338 }
15339 } else {
15340 selection.clone()
15341 }
15342 })
15343 .collect::<Vec<_>>();
15344
15345 if selected_sibling {
15346 self.change_selections(
15347 SelectionEffects::scroll(Autoscroll::fit()),
15348 window,
15349 cx,
15350 |s| {
15351 s.select(new_selections);
15352 },
15353 );
15354 }
15355 }
15356
15357 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15358 if !EditorSettings::get_global(cx).gutter.runnables {
15359 self.clear_tasks();
15360 return Task::ready(());
15361 }
15362 let project = self.project().map(Entity::downgrade);
15363 let task_sources = self.lsp_task_sources(cx);
15364 let multi_buffer = self.buffer.downgrade();
15365 cx.spawn_in(window, async move |editor, cx| {
15366 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15367 let Some(project) = project.and_then(|p| p.upgrade()) else {
15368 return;
15369 };
15370 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15371 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15372 }) else {
15373 return;
15374 };
15375
15376 let hide_runnables = project
15377 .update(cx, |project, _| project.is_via_collab())
15378 .unwrap_or(true);
15379 if hide_runnables {
15380 return;
15381 }
15382 let new_rows =
15383 cx.background_spawn({
15384 let snapshot = display_snapshot.clone();
15385 async move {
15386 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15387 }
15388 })
15389 .await;
15390 let Ok(lsp_tasks) =
15391 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15392 else {
15393 return;
15394 };
15395 let lsp_tasks = lsp_tasks.await;
15396
15397 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15398 lsp_tasks
15399 .into_iter()
15400 .flat_map(|(kind, tasks)| {
15401 tasks.into_iter().filter_map(move |(location, task)| {
15402 Some((kind.clone(), location?, task))
15403 })
15404 })
15405 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15406 let buffer = location.target.buffer;
15407 let buffer_snapshot = buffer.read(cx).snapshot();
15408 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15409 |(excerpt_id, snapshot, _)| {
15410 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15411 display_snapshot
15412 .buffer_snapshot
15413 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15414 } else {
15415 None
15416 }
15417 },
15418 );
15419 if let Some(offset) = offset {
15420 let task_buffer_range =
15421 location.target.range.to_point(&buffer_snapshot);
15422 let context_buffer_range =
15423 task_buffer_range.to_offset(&buffer_snapshot);
15424 let context_range = BufferOffset(context_buffer_range.start)
15425 ..BufferOffset(context_buffer_range.end);
15426
15427 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15428 .or_insert_with(|| RunnableTasks {
15429 templates: Vec::new(),
15430 offset,
15431 column: task_buffer_range.start.column,
15432 extra_variables: HashMap::default(),
15433 context_range,
15434 })
15435 .templates
15436 .push((kind, task.original_task().clone()));
15437 }
15438
15439 acc
15440 })
15441 }) else {
15442 return;
15443 };
15444
15445 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15446 buffer.language_settings(cx).tasks.prefer_lsp
15447 }) else {
15448 return;
15449 };
15450
15451 let rows = Self::runnable_rows(
15452 project,
15453 display_snapshot,
15454 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15455 new_rows,
15456 cx.clone(),
15457 )
15458 .await;
15459 editor
15460 .update(cx, |editor, _| {
15461 editor.clear_tasks();
15462 for (key, mut value) in rows {
15463 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15464 value.templates.extend(lsp_tasks.templates);
15465 }
15466
15467 editor.insert_tasks(key, value);
15468 }
15469 for (key, value) in lsp_tasks_by_rows {
15470 editor.insert_tasks(key, value);
15471 }
15472 })
15473 .ok();
15474 })
15475 }
15476 fn fetch_runnable_ranges(
15477 snapshot: &DisplaySnapshot,
15478 range: Range<Anchor>,
15479 ) -> Vec<language::RunnableRange> {
15480 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15481 }
15482
15483 fn runnable_rows(
15484 project: Entity<Project>,
15485 snapshot: DisplaySnapshot,
15486 prefer_lsp: bool,
15487 runnable_ranges: Vec<RunnableRange>,
15488 cx: AsyncWindowContext,
15489 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15490 cx.spawn(async move |cx| {
15491 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15492 for mut runnable in runnable_ranges {
15493 let Some(tasks) = cx
15494 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15495 .ok()
15496 else {
15497 continue;
15498 };
15499 let mut tasks = tasks.await;
15500
15501 if prefer_lsp {
15502 tasks.retain(|(task_kind, _)| {
15503 !matches!(task_kind, TaskSourceKind::Language { .. })
15504 });
15505 }
15506 if tasks.is_empty() {
15507 continue;
15508 }
15509
15510 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15511 let Some(row) = snapshot
15512 .buffer_snapshot
15513 .buffer_line_for_row(MultiBufferRow(point.row))
15514 .map(|(_, range)| range.start.row)
15515 else {
15516 continue;
15517 };
15518
15519 let context_range =
15520 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15521 runnable_rows.push((
15522 (runnable.buffer_id, row),
15523 RunnableTasks {
15524 templates: tasks,
15525 offset: snapshot
15526 .buffer_snapshot
15527 .anchor_before(runnable.run_range.start),
15528 context_range,
15529 column: point.column,
15530 extra_variables: runnable.extra_captures,
15531 },
15532 ));
15533 }
15534 runnable_rows
15535 })
15536 }
15537
15538 fn templates_with_tags(
15539 project: &Entity<Project>,
15540 runnable: &mut Runnable,
15541 cx: &mut App,
15542 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15543 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15544 let (worktree_id, file) = project
15545 .buffer_for_id(runnable.buffer, cx)
15546 .and_then(|buffer| buffer.read(cx).file())
15547 .map(|file| (file.worktree_id(cx), file.clone()))
15548 .unzip();
15549
15550 (
15551 project.task_store().read(cx).task_inventory().cloned(),
15552 worktree_id,
15553 file,
15554 )
15555 });
15556
15557 let tags = mem::take(&mut runnable.tags);
15558 let language = runnable.language.clone();
15559 cx.spawn(async move |cx| {
15560 let mut templates_with_tags = Vec::new();
15561 if let Some(inventory) = inventory {
15562 for RunnableTag(tag) in tags {
15563 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15564 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15565 }) else {
15566 return templates_with_tags;
15567 };
15568 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15569 move |(_, template)| {
15570 template.tags.iter().any(|source_tag| source_tag == &tag)
15571 },
15572 ));
15573 }
15574 }
15575 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15576
15577 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15578 // Strongest source wins; if we have worktree tag binding, prefer that to
15579 // global and language bindings;
15580 // if we have a global binding, prefer that to language binding.
15581 let first_mismatch = templates_with_tags
15582 .iter()
15583 .position(|(tag_source, _)| tag_source != leading_tag_source);
15584 if let Some(index) = first_mismatch {
15585 templates_with_tags.truncate(index);
15586 }
15587 }
15588
15589 templates_with_tags
15590 })
15591 }
15592
15593 pub fn move_to_enclosing_bracket(
15594 &mut self,
15595 _: &MoveToEnclosingBracket,
15596 window: &mut Window,
15597 cx: &mut Context<Self>,
15598 ) {
15599 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15600 self.change_selections(Default::default(), window, cx, |s| {
15601 s.move_offsets_with(|snapshot, selection| {
15602 let Some(enclosing_bracket_ranges) =
15603 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15604 else {
15605 return;
15606 };
15607
15608 let mut best_length = usize::MAX;
15609 let mut best_inside = false;
15610 let mut best_in_bracket_range = false;
15611 let mut best_destination = None;
15612 for (open, close) in enclosing_bracket_ranges {
15613 let close = close.to_inclusive();
15614 let length = close.end() - open.start;
15615 let inside = selection.start >= open.end && selection.end <= *close.start();
15616 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15617 || close.contains(&selection.head());
15618
15619 // If best is next to a bracket and current isn't, skip
15620 if !in_bracket_range && best_in_bracket_range {
15621 continue;
15622 }
15623
15624 // Prefer smaller lengths unless best is inside and current isn't
15625 if length > best_length && (best_inside || !inside) {
15626 continue;
15627 }
15628
15629 best_length = length;
15630 best_inside = inside;
15631 best_in_bracket_range = in_bracket_range;
15632 best_destination = Some(
15633 if close.contains(&selection.start) && close.contains(&selection.end) {
15634 if inside { open.end } else { open.start }
15635 } else if inside {
15636 *close.start()
15637 } else {
15638 *close.end()
15639 },
15640 );
15641 }
15642
15643 if let Some(destination) = best_destination {
15644 selection.collapse_to(destination, SelectionGoal::None);
15645 }
15646 })
15647 });
15648 }
15649
15650 pub fn undo_selection(
15651 &mut self,
15652 _: &UndoSelection,
15653 window: &mut Window,
15654 cx: &mut Context<Self>,
15655 ) {
15656 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15657 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15658 self.selection_history.mode = SelectionHistoryMode::Undoing;
15659 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15660 this.end_selection(window, cx);
15661 this.change_selections(
15662 SelectionEffects::scroll(Autoscroll::newest()),
15663 window,
15664 cx,
15665 |s| s.select_anchors(entry.selections.to_vec()),
15666 );
15667 });
15668 self.selection_history.mode = SelectionHistoryMode::Normal;
15669
15670 self.select_next_state = entry.select_next_state;
15671 self.select_prev_state = entry.select_prev_state;
15672 self.add_selections_state = entry.add_selections_state;
15673 }
15674 }
15675
15676 pub fn redo_selection(
15677 &mut self,
15678 _: &RedoSelection,
15679 window: &mut Window,
15680 cx: &mut Context<Self>,
15681 ) {
15682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15683 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15684 self.selection_history.mode = SelectionHistoryMode::Redoing;
15685 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15686 this.end_selection(window, cx);
15687 this.change_selections(
15688 SelectionEffects::scroll(Autoscroll::newest()),
15689 window,
15690 cx,
15691 |s| s.select_anchors(entry.selections.to_vec()),
15692 );
15693 });
15694 self.selection_history.mode = SelectionHistoryMode::Normal;
15695
15696 self.select_next_state = entry.select_next_state;
15697 self.select_prev_state = entry.select_prev_state;
15698 self.add_selections_state = entry.add_selections_state;
15699 }
15700 }
15701
15702 pub fn expand_excerpts(
15703 &mut self,
15704 action: &ExpandExcerpts,
15705 _: &mut Window,
15706 cx: &mut Context<Self>,
15707 ) {
15708 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15709 }
15710
15711 pub fn expand_excerpts_down(
15712 &mut self,
15713 action: &ExpandExcerptsDown,
15714 _: &mut Window,
15715 cx: &mut Context<Self>,
15716 ) {
15717 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15718 }
15719
15720 pub fn expand_excerpts_up(
15721 &mut self,
15722 action: &ExpandExcerptsUp,
15723 _: &mut Window,
15724 cx: &mut Context<Self>,
15725 ) {
15726 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15727 }
15728
15729 pub fn expand_excerpts_for_direction(
15730 &mut self,
15731 lines: u32,
15732 direction: ExpandExcerptDirection,
15733
15734 cx: &mut Context<Self>,
15735 ) {
15736 let selections = self.selections.disjoint_anchors_arc();
15737
15738 let lines = if lines == 0 {
15739 EditorSettings::get_global(cx).expand_excerpt_lines
15740 } else {
15741 lines
15742 };
15743
15744 self.buffer.update(cx, |buffer, cx| {
15745 let snapshot = buffer.snapshot(cx);
15746 let mut excerpt_ids = selections
15747 .iter()
15748 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15749 .collect::<Vec<_>>();
15750 excerpt_ids.sort();
15751 excerpt_ids.dedup();
15752 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15753 })
15754 }
15755
15756 pub fn expand_excerpt(
15757 &mut self,
15758 excerpt: ExcerptId,
15759 direction: ExpandExcerptDirection,
15760 window: &mut Window,
15761 cx: &mut Context<Self>,
15762 ) {
15763 let current_scroll_position = self.scroll_position(cx);
15764 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15765 let mut should_scroll_up = false;
15766
15767 if direction == ExpandExcerptDirection::Down {
15768 let multi_buffer = self.buffer.read(cx);
15769 let snapshot = multi_buffer.snapshot(cx);
15770 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15771 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15772 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15773 {
15774 let buffer_snapshot = buffer.read(cx).snapshot();
15775 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15776 let last_row = buffer_snapshot.max_point().row;
15777 let lines_below = last_row.saturating_sub(excerpt_end_row);
15778 should_scroll_up = lines_below >= lines_to_expand;
15779 }
15780 }
15781
15782 self.buffer.update(cx, |buffer, cx| {
15783 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15784 });
15785
15786 if should_scroll_up {
15787 let new_scroll_position =
15788 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15789 self.set_scroll_position(new_scroll_position, window, cx);
15790 }
15791 }
15792
15793 pub fn go_to_singleton_buffer_point(
15794 &mut self,
15795 point: Point,
15796 window: &mut Window,
15797 cx: &mut Context<Self>,
15798 ) {
15799 self.go_to_singleton_buffer_range(point..point, window, cx);
15800 }
15801
15802 pub fn go_to_singleton_buffer_range(
15803 &mut self,
15804 range: Range<Point>,
15805 window: &mut Window,
15806 cx: &mut Context<Self>,
15807 ) {
15808 let multibuffer = self.buffer().read(cx);
15809 let Some(buffer) = multibuffer.as_singleton() else {
15810 return;
15811 };
15812 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15813 return;
15814 };
15815 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15816 return;
15817 };
15818 self.change_selections(
15819 SelectionEffects::default().nav_history(true),
15820 window,
15821 cx,
15822 |s| s.select_anchor_ranges([start..end]),
15823 );
15824 }
15825
15826 pub fn go_to_diagnostic(
15827 &mut self,
15828 action: &GoToDiagnostic,
15829 window: &mut Window,
15830 cx: &mut Context<Self>,
15831 ) {
15832 if !self.diagnostics_enabled() {
15833 return;
15834 }
15835 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15836 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15837 }
15838
15839 pub fn go_to_prev_diagnostic(
15840 &mut self,
15841 action: &GoToPreviousDiagnostic,
15842 window: &mut Window,
15843 cx: &mut Context<Self>,
15844 ) {
15845 if !self.diagnostics_enabled() {
15846 return;
15847 }
15848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15849 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15850 }
15851
15852 pub fn go_to_diagnostic_impl(
15853 &mut self,
15854 direction: Direction,
15855 severity: GoToDiagnosticSeverityFilter,
15856 window: &mut Window,
15857 cx: &mut Context<Self>,
15858 ) {
15859 let buffer = self.buffer.read(cx).snapshot(cx);
15860 let selection = self.selections.newest::<usize>(cx);
15861
15862 let mut active_group_id = None;
15863 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15864 && active_group.active_range.start.to_offset(&buffer) == selection.start
15865 {
15866 active_group_id = Some(active_group.group_id);
15867 }
15868
15869 fn filtered(
15870 snapshot: EditorSnapshot,
15871 severity: GoToDiagnosticSeverityFilter,
15872 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15873 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15874 diagnostics
15875 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15876 .filter(|entry| entry.range.start != entry.range.end)
15877 .filter(|entry| !entry.diagnostic.is_unnecessary)
15878 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15879 }
15880
15881 let snapshot = self.snapshot(window, cx);
15882 let before = filtered(
15883 snapshot.clone(),
15884 severity,
15885 buffer
15886 .diagnostics_in_range(0..selection.start)
15887 .filter(|entry| entry.range.start <= selection.start),
15888 );
15889 let after = filtered(
15890 snapshot,
15891 severity,
15892 buffer
15893 .diagnostics_in_range(selection.start..buffer.len())
15894 .filter(|entry| entry.range.start >= selection.start),
15895 );
15896
15897 let mut found: Option<DiagnosticEntry<usize>> = None;
15898 if direction == Direction::Prev {
15899 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15900 {
15901 for diagnostic in prev_diagnostics.into_iter().rev() {
15902 if diagnostic.range.start != selection.start
15903 || active_group_id
15904 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15905 {
15906 found = Some(diagnostic);
15907 break 'outer;
15908 }
15909 }
15910 }
15911 } else {
15912 for diagnostic in after.chain(before) {
15913 if diagnostic.range.start != selection.start
15914 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15915 {
15916 found = Some(diagnostic);
15917 break;
15918 }
15919 }
15920 }
15921 let Some(next_diagnostic) = found else {
15922 return;
15923 };
15924
15925 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15926 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15927 return;
15928 };
15929 self.change_selections(Default::default(), window, cx, |s| {
15930 s.select_ranges(vec![
15931 next_diagnostic.range.start..next_diagnostic.range.start,
15932 ])
15933 });
15934 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15935 self.refresh_edit_prediction(false, true, window, cx);
15936 }
15937
15938 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15940 let snapshot = self.snapshot(window, cx);
15941 let selection = self.selections.newest::<Point>(cx);
15942 self.go_to_hunk_before_or_after_position(
15943 &snapshot,
15944 selection.head(),
15945 Direction::Next,
15946 window,
15947 cx,
15948 );
15949 }
15950
15951 pub fn go_to_hunk_before_or_after_position(
15952 &mut self,
15953 snapshot: &EditorSnapshot,
15954 position: Point,
15955 direction: Direction,
15956 window: &mut Window,
15957 cx: &mut Context<Editor>,
15958 ) {
15959 let row = if direction == Direction::Next {
15960 self.hunk_after_position(snapshot, position)
15961 .map(|hunk| hunk.row_range.start)
15962 } else {
15963 self.hunk_before_position(snapshot, position)
15964 };
15965
15966 if let Some(row) = row {
15967 let destination = Point::new(row.0, 0);
15968 let autoscroll = Autoscroll::center();
15969
15970 self.unfold_ranges(&[destination..destination], false, false, cx);
15971 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15972 s.select_ranges([destination..destination]);
15973 });
15974 }
15975 }
15976
15977 fn hunk_after_position(
15978 &mut self,
15979 snapshot: &EditorSnapshot,
15980 position: Point,
15981 ) -> Option<MultiBufferDiffHunk> {
15982 snapshot
15983 .buffer_snapshot
15984 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15985 .find(|hunk| hunk.row_range.start.0 > position.row)
15986 .or_else(|| {
15987 snapshot
15988 .buffer_snapshot
15989 .diff_hunks_in_range(Point::zero()..position)
15990 .find(|hunk| hunk.row_range.end.0 < position.row)
15991 })
15992 }
15993
15994 fn go_to_prev_hunk(
15995 &mut self,
15996 _: &GoToPreviousHunk,
15997 window: &mut Window,
15998 cx: &mut Context<Self>,
15999 ) {
16000 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16001 let snapshot = self.snapshot(window, cx);
16002 let selection = self.selections.newest::<Point>(cx);
16003 self.go_to_hunk_before_or_after_position(
16004 &snapshot,
16005 selection.head(),
16006 Direction::Prev,
16007 window,
16008 cx,
16009 );
16010 }
16011
16012 fn hunk_before_position(
16013 &mut self,
16014 snapshot: &EditorSnapshot,
16015 position: Point,
16016 ) -> Option<MultiBufferRow> {
16017 snapshot
16018 .buffer_snapshot
16019 .diff_hunk_before(position)
16020 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
16021 }
16022
16023 fn go_to_next_change(
16024 &mut self,
16025 _: &GoToNextChange,
16026 window: &mut Window,
16027 cx: &mut Context<Self>,
16028 ) {
16029 if let Some(selections) = self
16030 .change_list
16031 .next_change(1, Direction::Next)
16032 .map(|s| s.to_vec())
16033 {
16034 self.change_selections(Default::default(), window, cx, |s| {
16035 let map = s.display_map();
16036 s.select_display_ranges(selections.iter().map(|a| {
16037 let point = a.to_display_point(&map);
16038 point..point
16039 }))
16040 })
16041 }
16042 }
16043
16044 fn go_to_previous_change(
16045 &mut self,
16046 _: &GoToPreviousChange,
16047 window: &mut Window,
16048 cx: &mut Context<Self>,
16049 ) {
16050 if let Some(selections) = self
16051 .change_list
16052 .next_change(1, Direction::Prev)
16053 .map(|s| s.to_vec())
16054 {
16055 self.change_selections(Default::default(), window, cx, |s| {
16056 let map = s.display_map();
16057 s.select_display_ranges(selections.iter().map(|a| {
16058 let point = a.to_display_point(&map);
16059 point..point
16060 }))
16061 })
16062 }
16063 }
16064
16065 pub fn go_to_next_document_highlight(
16066 &mut self,
16067 _: &GoToNextDocumentHighlight,
16068 window: &mut Window,
16069 cx: &mut Context<Self>,
16070 ) {
16071 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16072 }
16073
16074 pub fn go_to_prev_document_highlight(
16075 &mut self,
16076 _: &GoToPreviousDocumentHighlight,
16077 window: &mut Window,
16078 cx: &mut Context<Self>,
16079 ) {
16080 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16081 }
16082
16083 pub fn go_to_document_highlight_before_or_after_position(
16084 &mut self,
16085 direction: Direction,
16086 window: &mut Window,
16087 cx: &mut Context<Editor>,
16088 ) {
16089 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16090 let snapshot = self.snapshot(window, cx);
16091 let buffer = &snapshot.buffer_snapshot;
16092 let position = self.selections.newest::<Point>(cx).head();
16093 let anchor_position = buffer.anchor_after(position);
16094
16095 // Get all document highlights (both read and write)
16096 let mut all_highlights = Vec::new();
16097
16098 if let Some((_, read_highlights)) = self
16099 .background_highlights
16100 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16101 {
16102 all_highlights.extend(read_highlights.iter());
16103 }
16104
16105 if let Some((_, write_highlights)) = self
16106 .background_highlights
16107 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16108 {
16109 all_highlights.extend(write_highlights.iter());
16110 }
16111
16112 if all_highlights.is_empty() {
16113 return;
16114 }
16115
16116 // Sort highlights by position
16117 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16118
16119 let target_highlight = match direction {
16120 Direction::Next => {
16121 // Find the first highlight after the current position
16122 all_highlights
16123 .iter()
16124 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16125 }
16126 Direction::Prev => {
16127 // Find the last highlight before the current position
16128 all_highlights
16129 .iter()
16130 .rev()
16131 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16132 }
16133 };
16134
16135 if let Some(highlight) = target_highlight {
16136 let destination = highlight.start.to_point(buffer);
16137 let autoscroll = Autoscroll::center();
16138
16139 self.unfold_ranges(&[destination..destination], false, false, cx);
16140 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16141 s.select_ranges([destination..destination]);
16142 });
16143 }
16144 }
16145
16146 fn go_to_line<T: 'static>(
16147 &mut self,
16148 position: Anchor,
16149 highlight_color: Option<Hsla>,
16150 window: &mut Window,
16151 cx: &mut Context<Self>,
16152 ) {
16153 let snapshot = self.snapshot(window, cx).display_snapshot;
16154 let position = position.to_point(&snapshot.buffer_snapshot);
16155 let start = snapshot
16156 .buffer_snapshot
16157 .clip_point(Point::new(position.row, 0), Bias::Left);
16158 let end = start + Point::new(1, 0);
16159 let start = snapshot.buffer_snapshot.anchor_before(start);
16160 let end = snapshot.buffer_snapshot.anchor_before(end);
16161
16162 self.highlight_rows::<T>(
16163 start..end,
16164 highlight_color
16165 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16166 Default::default(),
16167 cx,
16168 );
16169
16170 if self.buffer.read(cx).is_singleton() {
16171 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16172 }
16173 }
16174
16175 pub fn go_to_definition(
16176 &mut self,
16177 _: &GoToDefinition,
16178 window: &mut Window,
16179 cx: &mut Context<Self>,
16180 ) -> Task<Result<Navigated>> {
16181 let definition =
16182 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16183 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16184 cx.spawn_in(window, async move |editor, cx| {
16185 if definition.await? == Navigated::Yes {
16186 return Ok(Navigated::Yes);
16187 }
16188 match fallback_strategy {
16189 GoToDefinitionFallback::None => Ok(Navigated::No),
16190 GoToDefinitionFallback::FindAllReferences => {
16191 match editor.update_in(cx, |editor, window, cx| {
16192 editor.find_all_references(&FindAllReferences, window, cx)
16193 })? {
16194 Some(references) => references.await,
16195 None => Ok(Navigated::No),
16196 }
16197 }
16198 }
16199 })
16200 }
16201
16202 pub fn go_to_declaration(
16203 &mut self,
16204 _: &GoToDeclaration,
16205 window: &mut Window,
16206 cx: &mut Context<Self>,
16207 ) -> Task<Result<Navigated>> {
16208 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16209 }
16210
16211 pub fn go_to_declaration_split(
16212 &mut self,
16213 _: &GoToDeclaration,
16214 window: &mut Window,
16215 cx: &mut Context<Self>,
16216 ) -> Task<Result<Navigated>> {
16217 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16218 }
16219
16220 pub fn go_to_implementation(
16221 &mut self,
16222 _: &GoToImplementation,
16223 window: &mut Window,
16224 cx: &mut Context<Self>,
16225 ) -> Task<Result<Navigated>> {
16226 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16227 }
16228
16229 pub fn go_to_implementation_split(
16230 &mut self,
16231 _: &GoToImplementationSplit,
16232 window: &mut Window,
16233 cx: &mut Context<Self>,
16234 ) -> Task<Result<Navigated>> {
16235 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16236 }
16237
16238 pub fn go_to_type_definition(
16239 &mut self,
16240 _: &GoToTypeDefinition,
16241 window: &mut Window,
16242 cx: &mut Context<Self>,
16243 ) -> Task<Result<Navigated>> {
16244 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16245 }
16246
16247 pub fn go_to_definition_split(
16248 &mut self,
16249 _: &GoToDefinitionSplit,
16250 window: &mut Window,
16251 cx: &mut Context<Self>,
16252 ) -> Task<Result<Navigated>> {
16253 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16254 }
16255
16256 pub fn go_to_type_definition_split(
16257 &mut self,
16258 _: &GoToTypeDefinitionSplit,
16259 window: &mut Window,
16260 cx: &mut Context<Self>,
16261 ) -> Task<Result<Navigated>> {
16262 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16263 }
16264
16265 fn go_to_definition_of_kind(
16266 &mut self,
16267 kind: GotoDefinitionKind,
16268 split: bool,
16269 window: &mut Window,
16270 cx: &mut Context<Self>,
16271 ) -> Task<Result<Navigated>> {
16272 let Some(provider) = self.semantics_provider.clone() else {
16273 return Task::ready(Ok(Navigated::No));
16274 };
16275 let head = self.selections.newest::<usize>(cx).head();
16276 let buffer = self.buffer.read(cx);
16277 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16278 return Task::ready(Ok(Navigated::No));
16279 };
16280 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16281 return Task::ready(Ok(Navigated::No));
16282 };
16283
16284 cx.spawn_in(window, async move |editor, cx| {
16285 let Some(definitions) = definitions.await? else {
16286 return Ok(Navigated::No);
16287 };
16288 let navigated = editor
16289 .update_in(cx, |editor, window, cx| {
16290 editor.navigate_to_hover_links(
16291 Some(kind),
16292 definitions
16293 .into_iter()
16294 .filter(|location| {
16295 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16296 })
16297 .map(HoverLink::Text)
16298 .collect::<Vec<_>>(),
16299 split,
16300 window,
16301 cx,
16302 )
16303 })?
16304 .await?;
16305 anyhow::Ok(navigated)
16306 })
16307 }
16308
16309 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16310 let selection = self.selections.newest_anchor();
16311 let head = selection.head();
16312 let tail = selection.tail();
16313
16314 let Some((buffer, start_position)) =
16315 self.buffer.read(cx).text_anchor_for_position(head, cx)
16316 else {
16317 return;
16318 };
16319
16320 let end_position = if head != tail {
16321 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16322 return;
16323 };
16324 Some(pos)
16325 } else {
16326 None
16327 };
16328
16329 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16330 let url = if let Some(end_pos) = end_position {
16331 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16332 } else {
16333 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16334 };
16335
16336 if let Some(url) = url {
16337 editor.update(cx, |_, cx| {
16338 cx.open_url(&url);
16339 })
16340 } else {
16341 Ok(())
16342 }
16343 });
16344
16345 url_finder.detach();
16346 }
16347
16348 pub fn open_selected_filename(
16349 &mut self,
16350 _: &OpenSelectedFilename,
16351 window: &mut Window,
16352 cx: &mut Context<Self>,
16353 ) {
16354 let Some(workspace) = self.workspace() else {
16355 return;
16356 };
16357
16358 let position = self.selections.newest_anchor().head();
16359
16360 let Some((buffer, buffer_position)) =
16361 self.buffer.read(cx).text_anchor_for_position(position, cx)
16362 else {
16363 return;
16364 };
16365
16366 let project = self.project.clone();
16367
16368 cx.spawn_in(window, async move |_, cx| {
16369 let result = find_file(&buffer, project, buffer_position, cx).await;
16370
16371 if let Some((_, path)) = result {
16372 workspace
16373 .update_in(cx, |workspace, window, cx| {
16374 workspace.open_resolved_path(path, window, cx)
16375 })?
16376 .await?;
16377 }
16378 anyhow::Ok(())
16379 })
16380 .detach();
16381 }
16382
16383 pub(crate) fn navigate_to_hover_links(
16384 &mut self,
16385 kind: Option<GotoDefinitionKind>,
16386 definitions: Vec<HoverLink>,
16387 split: bool,
16388 window: &mut Window,
16389 cx: &mut Context<Editor>,
16390 ) -> Task<Result<Navigated>> {
16391 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16392 let mut first_url_or_file = None;
16393 let definitions: Vec<_> = definitions
16394 .into_iter()
16395 .filter_map(|def| match def {
16396 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16397 HoverLink::InlayHint(lsp_location, server_id) => {
16398 let computation =
16399 self.compute_target_location(lsp_location, server_id, window, cx);
16400 Some(cx.background_spawn(computation))
16401 }
16402 HoverLink::Url(url) => {
16403 first_url_or_file = Some(Either::Left(url));
16404 None
16405 }
16406 HoverLink::File(path) => {
16407 first_url_or_file = Some(Either::Right(path));
16408 None
16409 }
16410 })
16411 .collect();
16412
16413 let workspace = self.workspace();
16414
16415 cx.spawn_in(window, async move |editor, cx| {
16416 let locations: Vec<Location> = future::join_all(definitions)
16417 .await
16418 .into_iter()
16419 .filter_map(|location| location.transpose())
16420 .collect::<Result<_>>()
16421 .context("location tasks")?;
16422 let mut locations = cx.update(|_, cx| {
16423 locations
16424 .into_iter()
16425 .map(|location| {
16426 let buffer = location.buffer.read(cx);
16427 (location.buffer, location.range.to_point(buffer))
16428 })
16429 .into_group_map()
16430 })?;
16431 let mut num_locations = 0;
16432 for ranges in locations.values_mut() {
16433 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16434 ranges.dedup();
16435 num_locations += ranges.len();
16436 }
16437
16438 if num_locations > 1 {
16439 let Some(workspace) = workspace else {
16440 return Ok(Navigated::No);
16441 };
16442
16443 let tab_kind = match kind {
16444 Some(GotoDefinitionKind::Implementation) => "Implementations",
16445 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16446 Some(GotoDefinitionKind::Declaration) => "Declarations",
16447 Some(GotoDefinitionKind::Type) => "Types",
16448 };
16449 let title = editor
16450 .update_in(cx, |_, _, cx| {
16451 let target = locations
16452 .iter()
16453 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16454 .map(|(buffer, location)| {
16455 buffer
16456 .read(cx)
16457 .text_for_range(location.clone())
16458 .collect::<String>()
16459 })
16460 .filter(|text| !text.contains('\n'))
16461 .unique()
16462 .take(3)
16463 .join(", ");
16464 if target.is_empty() {
16465 tab_kind.to_owned()
16466 } else {
16467 format!("{tab_kind} for {target}")
16468 }
16469 })
16470 .context("buffer title")?;
16471
16472 let opened = workspace
16473 .update_in(cx, |workspace, window, cx| {
16474 Self::open_locations_in_multibuffer(
16475 workspace,
16476 locations,
16477 title,
16478 split,
16479 MultibufferSelectionMode::First,
16480 window,
16481 cx,
16482 )
16483 })
16484 .is_ok();
16485
16486 anyhow::Ok(Navigated::from_bool(opened))
16487 } else if num_locations == 0 {
16488 // If there is one url or file, open it directly
16489 match first_url_or_file {
16490 Some(Either::Left(url)) => {
16491 cx.update(|_, cx| cx.open_url(&url))?;
16492 Ok(Navigated::Yes)
16493 }
16494 Some(Either::Right(path)) => {
16495 let Some(workspace) = workspace else {
16496 return Ok(Navigated::No);
16497 };
16498
16499 workspace
16500 .update_in(cx, |workspace, window, cx| {
16501 workspace.open_resolved_path(path, window, cx)
16502 })?
16503 .await?;
16504 Ok(Navigated::Yes)
16505 }
16506 None => Ok(Navigated::No),
16507 }
16508 } else {
16509 let Some(workspace) = workspace else {
16510 return Ok(Navigated::No);
16511 };
16512
16513 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16514 let target_range = target_ranges.first().unwrap().clone();
16515
16516 editor.update_in(cx, |editor, window, cx| {
16517 let range = target_range.to_point(target_buffer.read(cx));
16518 let range = editor.range_for_match(&range);
16519 let range = collapse_multiline_range(range);
16520
16521 if !split
16522 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16523 {
16524 editor.go_to_singleton_buffer_range(range, window, cx);
16525 } else {
16526 let pane = workspace.read(cx).active_pane().clone();
16527 window.defer(cx, move |window, cx| {
16528 let target_editor: Entity<Self> =
16529 workspace.update(cx, |workspace, cx| {
16530 let pane = if split {
16531 workspace.adjacent_pane(window, cx)
16532 } else {
16533 workspace.active_pane().clone()
16534 };
16535
16536 workspace.open_project_item(
16537 pane,
16538 target_buffer.clone(),
16539 true,
16540 true,
16541 window,
16542 cx,
16543 )
16544 });
16545 target_editor.update(cx, |target_editor, cx| {
16546 // When selecting a definition in a different buffer, disable the nav history
16547 // to avoid creating a history entry at the previous cursor location.
16548 pane.update(cx, |pane, _| pane.disable_history());
16549 target_editor.go_to_singleton_buffer_range(range, window, cx);
16550 pane.update(cx, |pane, _| pane.enable_history());
16551 });
16552 });
16553 }
16554 Navigated::Yes
16555 })
16556 }
16557 })
16558 }
16559
16560 fn compute_target_location(
16561 &self,
16562 lsp_location: lsp::Location,
16563 server_id: LanguageServerId,
16564 window: &mut Window,
16565 cx: &mut Context<Self>,
16566 ) -> Task<anyhow::Result<Option<Location>>> {
16567 let Some(project) = self.project.clone() else {
16568 return Task::ready(Ok(None));
16569 };
16570
16571 cx.spawn_in(window, async move |editor, cx| {
16572 let location_task = editor.update(cx, |_, cx| {
16573 project.update(cx, |project, cx| {
16574 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16575 })
16576 })?;
16577 let location = Some({
16578 let target_buffer_handle = location_task.await.context("open local buffer")?;
16579 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16580 let target_start = target_buffer
16581 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16582 let target_end = target_buffer
16583 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16584 target_buffer.anchor_after(target_start)
16585 ..target_buffer.anchor_before(target_end)
16586 })?;
16587 Location {
16588 buffer: target_buffer_handle,
16589 range,
16590 }
16591 });
16592 Ok(location)
16593 })
16594 }
16595
16596 pub fn find_all_references(
16597 &mut self,
16598 _: &FindAllReferences,
16599 window: &mut Window,
16600 cx: &mut Context<Self>,
16601 ) -> Option<Task<Result<Navigated>>> {
16602 let selection = self.selections.newest::<usize>(cx);
16603 let multi_buffer = self.buffer.read(cx);
16604 let head = selection.head();
16605
16606 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16607 let head_anchor = multi_buffer_snapshot.anchor_at(
16608 head,
16609 if head < selection.tail() {
16610 Bias::Right
16611 } else {
16612 Bias::Left
16613 },
16614 );
16615
16616 match self
16617 .find_all_references_task_sources
16618 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16619 {
16620 Ok(_) => {
16621 log::info!(
16622 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16623 );
16624 return None;
16625 }
16626 Err(i) => {
16627 self.find_all_references_task_sources.insert(i, head_anchor);
16628 }
16629 }
16630
16631 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16632 let workspace = self.workspace()?;
16633 let project = workspace.read(cx).project().clone();
16634 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16635 Some(cx.spawn_in(window, async move |editor, cx| {
16636 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16637 if let Ok(i) = editor
16638 .find_all_references_task_sources
16639 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16640 {
16641 editor.find_all_references_task_sources.remove(i);
16642 }
16643 });
16644
16645 let Some(locations) = references.await? else {
16646 return anyhow::Ok(Navigated::No);
16647 };
16648 let mut locations = cx.update(|_, cx| {
16649 locations
16650 .into_iter()
16651 .map(|location| {
16652 let buffer = location.buffer.read(cx);
16653 (location.buffer, location.range.to_point(buffer))
16654 })
16655 .into_group_map()
16656 })?;
16657 if locations.is_empty() {
16658 return anyhow::Ok(Navigated::No);
16659 }
16660 for ranges in locations.values_mut() {
16661 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16662 ranges.dedup();
16663 }
16664
16665 workspace.update_in(cx, |workspace, window, cx| {
16666 let target = locations
16667 .iter()
16668 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16669 .map(|(buffer, location)| {
16670 buffer
16671 .read(cx)
16672 .text_for_range(location.clone())
16673 .collect::<String>()
16674 })
16675 .filter(|text| !text.contains('\n'))
16676 .unique()
16677 .take(3)
16678 .join(", ");
16679 let title = if target.is_empty() {
16680 "References".to_owned()
16681 } else {
16682 format!("References to {target}")
16683 };
16684 Self::open_locations_in_multibuffer(
16685 workspace,
16686 locations,
16687 title,
16688 false,
16689 MultibufferSelectionMode::First,
16690 window,
16691 cx,
16692 );
16693 Navigated::Yes
16694 })
16695 }))
16696 }
16697
16698 /// Opens a multibuffer with the given project locations in it
16699 pub fn open_locations_in_multibuffer(
16700 workspace: &mut Workspace,
16701 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16702 title: String,
16703 split: bool,
16704 multibuffer_selection_mode: MultibufferSelectionMode,
16705 window: &mut Window,
16706 cx: &mut Context<Workspace>,
16707 ) {
16708 if locations.is_empty() {
16709 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16710 return;
16711 }
16712
16713 let capability = workspace.project().read(cx).capability();
16714 let mut ranges = <Vec<Range<Anchor>>>::new();
16715
16716 // a key to find existing multibuffer editors with the same set of locations
16717 // to prevent us from opening more and more multibuffer tabs for searches and the like
16718 let mut key = (title.clone(), vec![]);
16719 let excerpt_buffer = cx.new(|cx| {
16720 let key = &mut key.1;
16721 let mut multibuffer = MultiBuffer::new(capability);
16722 for (buffer, mut ranges_for_buffer) in locations {
16723 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16724 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16725 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16726 PathKey::for_buffer(&buffer, cx),
16727 buffer.clone(),
16728 ranges_for_buffer,
16729 multibuffer_context_lines(cx),
16730 cx,
16731 );
16732 ranges.extend(new_ranges)
16733 }
16734
16735 multibuffer.with_title(title)
16736 });
16737 let existing = workspace.active_pane().update(cx, |pane, cx| {
16738 pane.items()
16739 .filter_map(|item| item.downcast::<Editor>())
16740 .find(|editor| {
16741 editor
16742 .read(cx)
16743 .lookup_key
16744 .as_ref()
16745 .and_then(|it| {
16746 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16747 })
16748 .is_some_and(|it| *it == key)
16749 })
16750 });
16751 let editor = existing.unwrap_or_else(|| {
16752 cx.new(|cx| {
16753 let mut editor = Editor::for_multibuffer(
16754 excerpt_buffer,
16755 Some(workspace.project().clone()),
16756 window,
16757 cx,
16758 );
16759 editor.lookup_key = Some(Box::new(key));
16760 editor
16761 })
16762 });
16763 editor.update(cx, |editor, cx| {
16764 match multibuffer_selection_mode {
16765 MultibufferSelectionMode::First => {
16766 if let Some(first_range) = ranges.first() {
16767 editor.change_selections(
16768 SelectionEffects::no_scroll(),
16769 window,
16770 cx,
16771 |selections| {
16772 selections.clear_disjoint();
16773 selections
16774 .select_anchor_ranges(std::iter::once(first_range.clone()));
16775 },
16776 );
16777 }
16778 editor.highlight_background::<Self>(
16779 &ranges,
16780 |theme| theme.colors().editor_highlighted_line_background,
16781 cx,
16782 );
16783 }
16784 MultibufferSelectionMode::All => {
16785 editor.change_selections(
16786 SelectionEffects::no_scroll(),
16787 window,
16788 cx,
16789 |selections| {
16790 selections.clear_disjoint();
16791 selections.select_anchor_ranges(ranges);
16792 },
16793 );
16794 }
16795 }
16796 editor.register_buffers_with_language_servers(cx);
16797 });
16798
16799 let item = Box::new(editor);
16800 let item_id = item.item_id();
16801
16802 if split {
16803 workspace.split_item(SplitDirection::Right, item, window, cx);
16804 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16805 let (preview_item_id, preview_item_idx) =
16806 workspace.active_pane().read_with(cx, |pane, _| {
16807 (pane.preview_item_id(), pane.preview_item_idx())
16808 });
16809
16810 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16811
16812 if let Some(preview_item_id) = preview_item_id {
16813 workspace.active_pane().update(cx, |pane, cx| {
16814 pane.remove_item(preview_item_id, false, false, window, cx);
16815 });
16816 }
16817 } else {
16818 workspace.add_item_to_active_pane(item, None, true, window, cx);
16819 }
16820 workspace.active_pane().update(cx, |pane, cx| {
16821 pane.set_preview_item_id(Some(item_id), cx);
16822 });
16823 }
16824
16825 pub fn rename(
16826 &mut self,
16827 _: &Rename,
16828 window: &mut Window,
16829 cx: &mut Context<Self>,
16830 ) -> Option<Task<Result<()>>> {
16831 use language::ToOffset as _;
16832
16833 let provider = self.semantics_provider.clone()?;
16834 let selection = self.selections.newest_anchor().clone();
16835 let (cursor_buffer, cursor_buffer_position) = self
16836 .buffer
16837 .read(cx)
16838 .text_anchor_for_position(selection.head(), cx)?;
16839 let (tail_buffer, cursor_buffer_position_end) = self
16840 .buffer
16841 .read(cx)
16842 .text_anchor_for_position(selection.tail(), cx)?;
16843 if tail_buffer != cursor_buffer {
16844 return None;
16845 }
16846
16847 let snapshot = cursor_buffer.read(cx).snapshot();
16848 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16849 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16850 let prepare_rename = provider
16851 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16852 .unwrap_or_else(|| Task::ready(Ok(None)));
16853 drop(snapshot);
16854
16855 Some(cx.spawn_in(window, async move |this, cx| {
16856 let rename_range = if let Some(range) = prepare_rename.await? {
16857 Some(range)
16858 } else {
16859 this.update(cx, |this, cx| {
16860 let buffer = this.buffer.read(cx).snapshot(cx);
16861 let mut buffer_highlights = this
16862 .document_highlights_for_position(selection.head(), &buffer)
16863 .filter(|highlight| {
16864 highlight.start.excerpt_id == selection.head().excerpt_id
16865 && highlight.end.excerpt_id == selection.head().excerpt_id
16866 });
16867 buffer_highlights
16868 .next()
16869 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16870 })?
16871 };
16872 if let Some(rename_range) = rename_range {
16873 this.update_in(cx, |this, window, cx| {
16874 let snapshot = cursor_buffer.read(cx).snapshot();
16875 let rename_buffer_range = rename_range.to_offset(&snapshot);
16876 let cursor_offset_in_rename_range =
16877 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16878 let cursor_offset_in_rename_range_end =
16879 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16880
16881 this.take_rename(false, window, cx);
16882 let buffer = this.buffer.read(cx).read(cx);
16883 let cursor_offset = selection.head().to_offset(&buffer);
16884 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16885 let rename_end = rename_start + rename_buffer_range.len();
16886 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16887 let mut old_highlight_id = None;
16888 let old_name: Arc<str> = buffer
16889 .chunks(rename_start..rename_end, true)
16890 .map(|chunk| {
16891 if old_highlight_id.is_none() {
16892 old_highlight_id = chunk.syntax_highlight_id;
16893 }
16894 chunk.text
16895 })
16896 .collect::<String>()
16897 .into();
16898
16899 drop(buffer);
16900
16901 // Position the selection in the rename editor so that it matches the current selection.
16902 this.show_local_selections = false;
16903 let rename_editor = cx.new(|cx| {
16904 let mut editor = Editor::single_line(window, cx);
16905 editor.buffer.update(cx, |buffer, cx| {
16906 buffer.edit([(0..0, old_name.clone())], None, cx)
16907 });
16908 let rename_selection_range = match cursor_offset_in_rename_range
16909 .cmp(&cursor_offset_in_rename_range_end)
16910 {
16911 Ordering::Equal => {
16912 editor.select_all(&SelectAll, window, cx);
16913 return editor;
16914 }
16915 Ordering::Less => {
16916 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16917 }
16918 Ordering::Greater => {
16919 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16920 }
16921 };
16922 if rename_selection_range.end > old_name.len() {
16923 editor.select_all(&SelectAll, window, cx);
16924 } else {
16925 editor.change_selections(Default::default(), window, cx, |s| {
16926 s.select_ranges([rename_selection_range]);
16927 });
16928 }
16929 editor
16930 });
16931 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16932 if e == &EditorEvent::Focused {
16933 cx.emit(EditorEvent::FocusedIn)
16934 }
16935 })
16936 .detach();
16937
16938 let write_highlights =
16939 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16940 let read_highlights =
16941 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16942 let ranges = write_highlights
16943 .iter()
16944 .flat_map(|(_, ranges)| ranges.iter())
16945 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16946 .cloned()
16947 .collect();
16948
16949 this.highlight_text::<Rename>(
16950 ranges,
16951 HighlightStyle {
16952 fade_out: Some(0.6),
16953 ..Default::default()
16954 },
16955 cx,
16956 );
16957 let rename_focus_handle = rename_editor.focus_handle(cx);
16958 window.focus(&rename_focus_handle);
16959 let block_id = this.insert_blocks(
16960 [BlockProperties {
16961 style: BlockStyle::Flex,
16962 placement: BlockPlacement::Below(range.start),
16963 height: Some(1),
16964 render: Arc::new({
16965 let rename_editor = rename_editor.clone();
16966 move |cx: &mut BlockContext| {
16967 let mut text_style = cx.editor_style.text.clone();
16968 if let Some(highlight_style) = old_highlight_id
16969 .and_then(|h| h.style(&cx.editor_style.syntax))
16970 {
16971 text_style = text_style.highlight(highlight_style);
16972 }
16973 div()
16974 .block_mouse_except_scroll()
16975 .pl(cx.anchor_x)
16976 .child(EditorElement::new(
16977 &rename_editor,
16978 EditorStyle {
16979 background: cx.theme().system().transparent,
16980 local_player: cx.editor_style.local_player,
16981 text: text_style,
16982 scrollbar_width: cx.editor_style.scrollbar_width,
16983 syntax: cx.editor_style.syntax.clone(),
16984 status: cx.editor_style.status.clone(),
16985 inlay_hints_style: HighlightStyle {
16986 font_weight: Some(FontWeight::BOLD),
16987 ..make_inlay_hints_style(cx.app)
16988 },
16989 edit_prediction_styles: make_suggestion_styles(
16990 cx.app,
16991 ),
16992 ..EditorStyle::default()
16993 },
16994 ))
16995 .into_any_element()
16996 }
16997 }),
16998 priority: 0,
16999 }],
17000 Some(Autoscroll::fit()),
17001 cx,
17002 )[0];
17003 this.pending_rename = Some(RenameState {
17004 range,
17005 old_name,
17006 editor: rename_editor,
17007 block_id,
17008 });
17009 })?;
17010 }
17011
17012 Ok(())
17013 }))
17014 }
17015
17016 pub fn confirm_rename(
17017 &mut self,
17018 _: &ConfirmRename,
17019 window: &mut Window,
17020 cx: &mut Context<Self>,
17021 ) -> Option<Task<Result<()>>> {
17022 let rename = self.take_rename(false, window, cx)?;
17023 let workspace = self.workspace()?.downgrade();
17024 let (buffer, start) = self
17025 .buffer
17026 .read(cx)
17027 .text_anchor_for_position(rename.range.start, cx)?;
17028 let (end_buffer, _) = self
17029 .buffer
17030 .read(cx)
17031 .text_anchor_for_position(rename.range.end, cx)?;
17032 if buffer != end_buffer {
17033 return None;
17034 }
17035
17036 let old_name = rename.old_name;
17037 let new_name = rename.editor.read(cx).text(cx);
17038
17039 let rename = self.semantics_provider.as_ref()?.perform_rename(
17040 &buffer,
17041 start,
17042 new_name.clone(),
17043 cx,
17044 )?;
17045
17046 Some(cx.spawn_in(window, async move |editor, cx| {
17047 let project_transaction = rename.await?;
17048 Self::open_project_transaction(
17049 &editor,
17050 workspace,
17051 project_transaction,
17052 format!("Rename: {} → {}", old_name, new_name),
17053 cx,
17054 )
17055 .await?;
17056
17057 editor.update(cx, |editor, cx| {
17058 editor.refresh_document_highlights(cx);
17059 })?;
17060 Ok(())
17061 }))
17062 }
17063
17064 fn take_rename(
17065 &mut self,
17066 moving_cursor: bool,
17067 window: &mut Window,
17068 cx: &mut Context<Self>,
17069 ) -> Option<RenameState> {
17070 let rename = self.pending_rename.take()?;
17071 if rename.editor.focus_handle(cx).is_focused(window) {
17072 window.focus(&self.focus_handle);
17073 }
17074
17075 self.remove_blocks(
17076 [rename.block_id].into_iter().collect(),
17077 Some(Autoscroll::fit()),
17078 cx,
17079 );
17080 self.clear_highlights::<Rename>(cx);
17081 self.show_local_selections = true;
17082
17083 if moving_cursor {
17084 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17085 editor.selections.newest::<usize>(cx).head()
17086 });
17087
17088 // Update the selection to match the position of the selection inside
17089 // the rename editor.
17090 let snapshot = self.buffer.read(cx).read(cx);
17091 let rename_range = rename.range.to_offset(&snapshot);
17092 let cursor_in_editor = snapshot
17093 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17094 .min(rename_range.end);
17095 drop(snapshot);
17096
17097 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17098 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17099 });
17100 } else {
17101 self.refresh_document_highlights(cx);
17102 }
17103
17104 Some(rename)
17105 }
17106
17107 pub fn pending_rename(&self) -> Option<&RenameState> {
17108 self.pending_rename.as_ref()
17109 }
17110
17111 fn format(
17112 &mut self,
17113 _: &Format,
17114 window: &mut Window,
17115 cx: &mut Context<Self>,
17116 ) -> Option<Task<Result<()>>> {
17117 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17118
17119 let project = match &self.project {
17120 Some(project) => project.clone(),
17121 None => return None,
17122 };
17123
17124 Some(self.perform_format(
17125 project,
17126 FormatTrigger::Manual,
17127 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17128 window,
17129 cx,
17130 ))
17131 }
17132
17133 fn format_selections(
17134 &mut self,
17135 _: &FormatSelections,
17136 window: &mut Window,
17137 cx: &mut Context<Self>,
17138 ) -> Option<Task<Result<()>>> {
17139 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17140
17141 let project = match &self.project {
17142 Some(project) => project.clone(),
17143 None => return None,
17144 };
17145
17146 let ranges = self
17147 .selections
17148 .all_adjusted(cx)
17149 .into_iter()
17150 .map(|selection| selection.range())
17151 .collect_vec();
17152
17153 Some(self.perform_format(
17154 project,
17155 FormatTrigger::Manual,
17156 FormatTarget::Ranges(ranges),
17157 window,
17158 cx,
17159 ))
17160 }
17161
17162 fn perform_format(
17163 &mut self,
17164 project: Entity<Project>,
17165 trigger: FormatTrigger,
17166 target: FormatTarget,
17167 window: &mut Window,
17168 cx: &mut Context<Self>,
17169 ) -> Task<Result<()>> {
17170 let buffer = self.buffer.clone();
17171 let (buffers, target) = match target {
17172 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17173 FormatTarget::Ranges(selection_ranges) => {
17174 let multi_buffer = buffer.read(cx);
17175 let snapshot = multi_buffer.read(cx);
17176 let mut buffers = HashSet::default();
17177 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17178 BTreeMap::new();
17179 for selection_range in selection_ranges {
17180 for (buffer, buffer_range, _) in
17181 snapshot.range_to_buffer_ranges(selection_range)
17182 {
17183 let buffer_id = buffer.remote_id();
17184 let start = buffer.anchor_before(buffer_range.start);
17185 let end = buffer.anchor_after(buffer_range.end);
17186 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17187 buffer_id_to_ranges
17188 .entry(buffer_id)
17189 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17190 .or_insert_with(|| vec![start..end]);
17191 }
17192 }
17193 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17194 }
17195 };
17196
17197 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17198 let selections_prev = transaction_id_prev
17199 .and_then(|transaction_id_prev| {
17200 // default to selections as they were after the last edit, if we have them,
17201 // instead of how they are now.
17202 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17203 // will take you back to where you made the last edit, instead of staying where you scrolled
17204 self.selection_history
17205 .transaction(transaction_id_prev)
17206 .map(|t| t.0.clone())
17207 })
17208 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17209
17210 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17211 let format = project.update(cx, |project, cx| {
17212 project.format(buffers, target, true, trigger, cx)
17213 });
17214
17215 cx.spawn_in(window, async move |editor, cx| {
17216 let transaction = futures::select_biased! {
17217 transaction = format.log_err().fuse() => transaction,
17218 () = timeout => {
17219 log::warn!("timed out waiting for formatting");
17220 None
17221 }
17222 };
17223
17224 buffer
17225 .update(cx, |buffer, cx| {
17226 if let Some(transaction) = transaction
17227 && !buffer.is_singleton()
17228 {
17229 buffer.push_transaction(&transaction.0, cx);
17230 }
17231 cx.notify();
17232 })
17233 .ok();
17234
17235 if let Some(transaction_id_now) =
17236 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17237 {
17238 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17239 if has_new_transaction {
17240 _ = editor.update(cx, |editor, _| {
17241 editor
17242 .selection_history
17243 .insert_transaction(transaction_id_now, selections_prev);
17244 });
17245 }
17246 }
17247
17248 Ok(())
17249 })
17250 }
17251
17252 fn organize_imports(
17253 &mut self,
17254 _: &OrganizeImports,
17255 window: &mut Window,
17256 cx: &mut Context<Self>,
17257 ) -> Option<Task<Result<()>>> {
17258 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17259 let project = match &self.project {
17260 Some(project) => project.clone(),
17261 None => return None,
17262 };
17263 Some(self.perform_code_action_kind(
17264 project,
17265 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17266 window,
17267 cx,
17268 ))
17269 }
17270
17271 fn perform_code_action_kind(
17272 &mut self,
17273 project: Entity<Project>,
17274 kind: CodeActionKind,
17275 window: &mut Window,
17276 cx: &mut Context<Self>,
17277 ) -> Task<Result<()>> {
17278 let buffer = self.buffer.clone();
17279 let buffers = buffer.read(cx).all_buffers();
17280 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17281 let apply_action = project.update(cx, |project, cx| {
17282 project.apply_code_action_kind(buffers, kind, true, cx)
17283 });
17284 cx.spawn_in(window, async move |_, cx| {
17285 let transaction = futures::select_biased! {
17286 () = timeout => {
17287 log::warn!("timed out waiting for executing code action");
17288 None
17289 }
17290 transaction = apply_action.log_err().fuse() => transaction,
17291 };
17292 buffer
17293 .update(cx, |buffer, cx| {
17294 // check if we need this
17295 if let Some(transaction) = transaction
17296 && !buffer.is_singleton()
17297 {
17298 buffer.push_transaction(&transaction.0, cx);
17299 }
17300 cx.notify();
17301 })
17302 .ok();
17303 Ok(())
17304 })
17305 }
17306
17307 pub fn restart_language_server(
17308 &mut self,
17309 _: &RestartLanguageServer,
17310 _: &mut Window,
17311 cx: &mut Context<Self>,
17312 ) {
17313 if let Some(project) = self.project.clone() {
17314 self.buffer.update(cx, |multi_buffer, cx| {
17315 project.update(cx, |project, cx| {
17316 project.restart_language_servers_for_buffers(
17317 multi_buffer.all_buffers().into_iter().collect(),
17318 HashSet::default(),
17319 cx,
17320 );
17321 });
17322 })
17323 }
17324 }
17325
17326 pub fn stop_language_server(
17327 &mut self,
17328 _: &StopLanguageServer,
17329 _: &mut Window,
17330 cx: &mut Context<Self>,
17331 ) {
17332 if let Some(project) = self.project.clone() {
17333 self.buffer.update(cx, |multi_buffer, cx| {
17334 project.update(cx, |project, cx| {
17335 project.stop_language_servers_for_buffers(
17336 multi_buffer.all_buffers().into_iter().collect(),
17337 HashSet::default(),
17338 cx,
17339 );
17340 cx.emit(project::Event::RefreshInlayHints);
17341 });
17342 });
17343 }
17344 }
17345
17346 fn cancel_language_server_work(
17347 workspace: &mut Workspace,
17348 _: &actions::CancelLanguageServerWork,
17349 _: &mut Window,
17350 cx: &mut Context<Workspace>,
17351 ) {
17352 let project = workspace.project();
17353 let buffers = workspace
17354 .active_item(cx)
17355 .and_then(|item| item.act_as::<Editor>(cx))
17356 .map_or(HashSet::default(), |editor| {
17357 editor.read(cx).buffer.read(cx).all_buffers()
17358 });
17359 project.update(cx, |project, cx| {
17360 project.cancel_language_server_work_for_buffers(buffers, cx);
17361 });
17362 }
17363
17364 fn show_character_palette(
17365 &mut self,
17366 _: &ShowCharacterPalette,
17367 window: &mut Window,
17368 _: &mut Context<Self>,
17369 ) {
17370 window.show_character_palette();
17371 }
17372
17373 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17374 if !self.diagnostics_enabled() {
17375 return;
17376 }
17377
17378 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17379 let buffer = self.buffer.read(cx).snapshot(cx);
17380 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17381 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17382 let is_valid = buffer
17383 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17384 .any(|entry| {
17385 entry.diagnostic.is_primary
17386 && !entry.range.is_empty()
17387 && entry.range.start == primary_range_start
17388 && entry.diagnostic.message == active_diagnostics.active_message
17389 });
17390
17391 if !is_valid {
17392 self.dismiss_diagnostics(cx);
17393 }
17394 }
17395 }
17396
17397 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17398 match &self.active_diagnostics {
17399 ActiveDiagnostic::Group(group) => Some(group),
17400 _ => None,
17401 }
17402 }
17403
17404 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17405 if !self.diagnostics_enabled() {
17406 return;
17407 }
17408 self.dismiss_diagnostics(cx);
17409 self.active_diagnostics = ActiveDiagnostic::All;
17410 }
17411
17412 fn activate_diagnostics(
17413 &mut self,
17414 buffer_id: BufferId,
17415 diagnostic: DiagnosticEntry<usize>,
17416 window: &mut Window,
17417 cx: &mut Context<Self>,
17418 ) {
17419 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17420 return;
17421 }
17422 self.dismiss_diagnostics(cx);
17423 let snapshot = self.snapshot(window, cx);
17424 let buffer = self.buffer.read(cx).snapshot(cx);
17425 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17426 return;
17427 };
17428
17429 let diagnostic_group = buffer
17430 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17431 .collect::<Vec<_>>();
17432
17433 let blocks =
17434 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17435
17436 let blocks = self.display_map.update(cx, |display_map, cx| {
17437 display_map.insert_blocks(blocks, cx).into_iter().collect()
17438 });
17439 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17440 active_range: buffer.anchor_before(diagnostic.range.start)
17441 ..buffer.anchor_after(diagnostic.range.end),
17442 active_message: diagnostic.diagnostic.message.clone(),
17443 group_id: diagnostic.diagnostic.group_id,
17444 blocks,
17445 });
17446 cx.notify();
17447 }
17448
17449 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17450 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17451 return;
17452 };
17453
17454 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17455 if let ActiveDiagnostic::Group(group) = prev {
17456 self.display_map.update(cx, |display_map, cx| {
17457 display_map.remove_blocks(group.blocks, cx);
17458 });
17459 cx.notify();
17460 }
17461 }
17462
17463 /// Disable inline diagnostics rendering for this editor.
17464 pub fn disable_inline_diagnostics(&mut self) {
17465 self.inline_diagnostics_enabled = false;
17466 self.inline_diagnostics_update = Task::ready(());
17467 self.inline_diagnostics.clear();
17468 }
17469
17470 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17471 self.diagnostics_enabled = false;
17472 self.dismiss_diagnostics(cx);
17473 self.inline_diagnostics_update = Task::ready(());
17474 self.inline_diagnostics.clear();
17475 }
17476
17477 pub fn disable_word_completions(&mut self) {
17478 self.word_completions_enabled = false;
17479 }
17480
17481 pub fn diagnostics_enabled(&self) -> bool {
17482 self.diagnostics_enabled && self.mode.is_full()
17483 }
17484
17485 pub fn inline_diagnostics_enabled(&self) -> bool {
17486 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17487 }
17488
17489 pub fn show_inline_diagnostics(&self) -> bool {
17490 self.show_inline_diagnostics
17491 }
17492
17493 pub fn toggle_inline_diagnostics(
17494 &mut self,
17495 _: &ToggleInlineDiagnostics,
17496 window: &mut Window,
17497 cx: &mut Context<Editor>,
17498 ) {
17499 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17500 self.refresh_inline_diagnostics(false, window, cx);
17501 }
17502
17503 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17504 self.diagnostics_max_severity = severity;
17505 self.display_map.update(cx, |display_map, _| {
17506 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17507 });
17508 }
17509
17510 pub fn toggle_diagnostics(
17511 &mut self,
17512 _: &ToggleDiagnostics,
17513 window: &mut Window,
17514 cx: &mut Context<Editor>,
17515 ) {
17516 if !self.diagnostics_enabled() {
17517 return;
17518 }
17519
17520 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17521 EditorSettings::get_global(cx)
17522 .diagnostics_max_severity
17523 .filter(|severity| severity != &DiagnosticSeverity::Off)
17524 .unwrap_or(DiagnosticSeverity::Hint)
17525 } else {
17526 DiagnosticSeverity::Off
17527 };
17528 self.set_max_diagnostics_severity(new_severity, cx);
17529 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17530 self.active_diagnostics = ActiveDiagnostic::None;
17531 self.inline_diagnostics_update = Task::ready(());
17532 self.inline_diagnostics.clear();
17533 } else {
17534 self.refresh_inline_diagnostics(false, window, cx);
17535 }
17536
17537 cx.notify();
17538 }
17539
17540 pub fn toggle_minimap(
17541 &mut self,
17542 _: &ToggleMinimap,
17543 window: &mut Window,
17544 cx: &mut Context<Editor>,
17545 ) {
17546 if self.supports_minimap(cx) {
17547 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17548 }
17549 }
17550
17551 fn refresh_inline_diagnostics(
17552 &mut self,
17553 debounce: bool,
17554 window: &mut Window,
17555 cx: &mut Context<Self>,
17556 ) {
17557 let max_severity = ProjectSettings::get_global(cx)
17558 .diagnostics
17559 .inline
17560 .max_severity
17561 .unwrap_or(self.diagnostics_max_severity);
17562
17563 if !self.inline_diagnostics_enabled()
17564 || !self.show_inline_diagnostics
17565 || max_severity == DiagnosticSeverity::Off
17566 {
17567 self.inline_diagnostics_update = Task::ready(());
17568 self.inline_diagnostics.clear();
17569 return;
17570 }
17571
17572 let debounce_ms = ProjectSettings::get_global(cx)
17573 .diagnostics
17574 .inline
17575 .update_debounce_ms;
17576 let debounce = if debounce && debounce_ms > 0 {
17577 Some(Duration::from_millis(debounce_ms))
17578 } else {
17579 None
17580 };
17581 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17582 if let Some(debounce) = debounce {
17583 cx.background_executor().timer(debounce).await;
17584 }
17585 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17586 editor
17587 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17588 .ok()
17589 }) else {
17590 return;
17591 };
17592
17593 let new_inline_diagnostics = cx
17594 .background_spawn(async move {
17595 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17596 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17597 let message = diagnostic_entry
17598 .diagnostic
17599 .message
17600 .split_once('\n')
17601 .map(|(line, _)| line)
17602 .map(SharedString::new)
17603 .unwrap_or_else(|| {
17604 SharedString::from(diagnostic_entry.diagnostic.message)
17605 });
17606 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17607 let (Ok(i) | Err(i)) = inline_diagnostics
17608 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17609 inline_diagnostics.insert(
17610 i,
17611 (
17612 start_anchor,
17613 InlineDiagnostic {
17614 message,
17615 group_id: diagnostic_entry.diagnostic.group_id,
17616 start: diagnostic_entry.range.start.to_point(&snapshot),
17617 is_primary: diagnostic_entry.diagnostic.is_primary,
17618 severity: diagnostic_entry.diagnostic.severity,
17619 },
17620 ),
17621 );
17622 }
17623 inline_diagnostics
17624 })
17625 .await;
17626
17627 editor
17628 .update(cx, |editor, cx| {
17629 editor.inline_diagnostics = new_inline_diagnostics;
17630 cx.notify();
17631 })
17632 .ok();
17633 });
17634 }
17635
17636 fn pull_diagnostics(
17637 &mut self,
17638 buffer_id: Option<BufferId>,
17639 window: &Window,
17640 cx: &mut Context<Self>,
17641 ) -> Option<()> {
17642 if !self.mode().is_full() {
17643 return None;
17644 }
17645 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17646 .diagnostics
17647 .lsp_pull_diagnostics;
17648 if !pull_diagnostics_settings.enabled {
17649 return None;
17650 }
17651 let project = self.project()?.downgrade();
17652 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17653 let mut buffers = self.buffer.read(cx).all_buffers();
17654 if let Some(buffer_id) = buffer_id {
17655 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17656 }
17657
17658 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17659 cx.background_executor().timer(debounce).await;
17660
17661 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17662 buffers
17663 .into_iter()
17664 .filter_map(|buffer| {
17665 project
17666 .update(cx, |project, cx| {
17667 project.lsp_store().update(cx, |lsp_store, cx| {
17668 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17669 })
17670 })
17671 .ok()
17672 })
17673 .collect::<FuturesUnordered<_>>()
17674 }) else {
17675 return;
17676 };
17677
17678 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17679 match pull_task {
17680 Ok(()) => {
17681 if editor
17682 .update_in(cx, |editor, window, cx| {
17683 editor.update_diagnostics_state(window, cx);
17684 })
17685 .is_err()
17686 {
17687 return;
17688 }
17689 }
17690 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17691 }
17692 }
17693 });
17694
17695 Some(())
17696 }
17697
17698 pub fn set_selections_from_remote(
17699 &mut self,
17700 selections: Vec<Selection<Anchor>>,
17701 pending_selection: Option<Selection<Anchor>>,
17702 window: &mut Window,
17703 cx: &mut Context<Self>,
17704 ) {
17705 let old_cursor_position = self.selections.newest_anchor().head();
17706 self.selections.change_with(cx, |s| {
17707 s.select_anchors(selections);
17708 if let Some(pending_selection) = pending_selection {
17709 s.set_pending(pending_selection, SelectMode::Character);
17710 } else {
17711 s.clear_pending();
17712 }
17713 });
17714 self.selections_did_change(
17715 false,
17716 &old_cursor_position,
17717 SelectionEffects::default(),
17718 window,
17719 cx,
17720 );
17721 }
17722
17723 pub fn transact(
17724 &mut self,
17725 window: &mut Window,
17726 cx: &mut Context<Self>,
17727 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17728 ) -> Option<TransactionId> {
17729 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17730 this.start_transaction_at(Instant::now(), window, cx);
17731 update(this, window, cx);
17732 this.end_transaction_at(Instant::now(), cx)
17733 })
17734 }
17735
17736 pub fn start_transaction_at(
17737 &mut self,
17738 now: Instant,
17739 window: &mut Window,
17740 cx: &mut Context<Self>,
17741 ) -> Option<TransactionId> {
17742 self.end_selection(window, cx);
17743 if let Some(tx_id) = self
17744 .buffer
17745 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17746 {
17747 self.selection_history
17748 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17749 cx.emit(EditorEvent::TransactionBegun {
17750 transaction_id: tx_id,
17751 });
17752 Some(tx_id)
17753 } else {
17754 None
17755 }
17756 }
17757
17758 pub fn end_transaction_at(
17759 &mut self,
17760 now: Instant,
17761 cx: &mut Context<Self>,
17762 ) -> Option<TransactionId> {
17763 if let Some(transaction_id) = self
17764 .buffer
17765 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17766 {
17767 if let Some((_, end_selections)) =
17768 self.selection_history.transaction_mut(transaction_id)
17769 {
17770 *end_selections = Some(self.selections.disjoint_anchors_arc());
17771 } else {
17772 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17773 }
17774
17775 cx.emit(EditorEvent::Edited { transaction_id });
17776 Some(transaction_id)
17777 } else {
17778 None
17779 }
17780 }
17781
17782 pub fn modify_transaction_selection_history(
17783 &mut self,
17784 transaction_id: TransactionId,
17785 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17786 ) -> bool {
17787 self.selection_history
17788 .transaction_mut(transaction_id)
17789 .map(modify)
17790 .is_some()
17791 }
17792
17793 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17794 if self.selection_mark_mode {
17795 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17796 s.move_with(|_, sel| {
17797 sel.collapse_to(sel.head(), SelectionGoal::None);
17798 });
17799 })
17800 }
17801 self.selection_mark_mode = true;
17802 cx.notify();
17803 }
17804
17805 pub fn swap_selection_ends(
17806 &mut self,
17807 _: &actions::SwapSelectionEnds,
17808 window: &mut Window,
17809 cx: &mut Context<Self>,
17810 ) {
17811 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17812 s.move_with(|_, sel| {
17813 if sel.start != sel.end {
17814 sel.reversed = !sel.reversed
17815 }
17816 });
17817 });
17818 self.request_autoscroll(Autoscroll::newest(), cx);
17819 cx.notify();
17820 }
17821
17822 pub fn toggle_focus(
17823 workspace: &mut Workspace,
17824 _: &actions::ToggleFocus,
17825 window: &mut Window,
17826 cx: &mut Context<Workspace>,
17827 ) {
17828 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17829 return;
17830 };
17831 workspace.activate_item(&item, true, true, window, cx);
17832 }
17833
17834 pub fn toggle_fold(
17835 &mut self,
17836 _: &actions::ToggleFold,
17837 window: &mut Window,
17838 cx: &mut Context<Self>,
17839 ) {
17840 if self.is_singleton(cx) {
17841 let selection = self.selections.newest::<Point>(cx);
17842
17843 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17844 let range = if selection.is_empty() {
17845 let point = selection.head().to_display_point(&display_map);
17846 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17847 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17848 .to_point(&display_map);
17849 start..end
17850 } else {
17851 selection.range()
17852 };
17853 if display_map.folds_in_range(range).next().is_some() {
17854 self.unfold_lines(&Default::default(), window, cx)
17855 } else {
17856 self.fold(&Default::default(), window, cx)
17857 }
17858 } else {
17859 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17860 let buffer_ids: HashSet<_> = self
17861 .selections
17862 .disjoint_anchor_ranges()
17863 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17864 .collect();
17865
17866 let should_unfold = buffer_ids
17867 .iter()
17868 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17869
17870 for buffer_id in buffer_ids {
17871 if should_unfold {
17872 self.unfold_buffer(buffer_id, cx);
17873 } else {
17874 self.fold_buffer(buffer_id, cx);
17875 }
17876 }
17877 }
17878 }
17879
17880 pub fn toggle_fold_recursive(
17881 &mut self,
17882 _: &actions::ToggleFoldRecursive,
17883 window: &mut Window,
17884 cx: &mut Context<Self>,
17885 ) {
17886 let selection = self.selections.newest::<Point>(cx);
17887
17888 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17889 let range = if selection.is_empty() {
17890 let point = selection.head().to_display_point(&display_map);
17891 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17892 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17893 .to_point(&display_map);
17894 start..end
17895 } else {
17896 selection.range()
17897 };
17898 if display_map.folds_in_range(range).next().is_some() {
17899 self.unfold_recursive(&Default::default(), window, cx)
17900 } else {
17901 self.fold_recursive(&Default::default(), window, cx)
17902 }
17903 }
17904
17905 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17906 if self.is_singleton(cx) {
17907 let mut to_fold = Vec::new();
17908 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17909 let selections = self.selections.all_adjusted(cx);
17910
17911 for selection in selections {
17912 let range = selection.range().sorted();
17913 let buffer_start_row = range.start.row;
17914
17915 if range.start.row != range.end.row {
17916 let mut found = false;
17917 let mut row = range.start.row;
17918 while row <= range.end.row {
17919 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17920 {
17921 found = true;
17922 row = crease.range().end.row + 1;
17923 to_fold.push(crease);
17924 } else {
17925 row += 1
17926 }
17927 }
17928 if found {
17929 continue;
17930 }
17931 }
17932
17933 for row in (0..=range.start.row).rev() {
17934 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17935 && crease.range().end.row >= buffer_start_row
17936 {
17937 to_fold.push(crease);
17938 if row <= range.start.row {
17939 break;
17940 }
17941 }
17942 }
17943 }
17944
17945 self.fold_creases(to_fold, true, window, cx);
17946 } else {
17947 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17948 let buffer_ids = self
17949 .selections
17950 .disjoint_anchor_ranges()
17951 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17952 .collect::<HashSet<_>>();
17953 for buffer_id in buffer_ids {
17954 self.fold_buffer(buffer_id, cx);
17955 }
17956 }
17957 }
17958
17959 pub fn toggle_fold_all(
17960 &mut self,
17961 _: &actions::ToggleFoldAll,
17962 window: &mut Window,
17963 cx: &mut Context<Self>,
17964 ) {
17965 if self.buffer.read(cx).is_singleton() {
17966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17967 let has_folds = display_map
17968 .folds_in_range(0..display_map.buffer_snapshot.len())
17969 .next()
17970 .is_some();
17971
17972 if has_folds {
17973 self.unfold_all(&actions::UnfoldAll, window, cx);
17974 } else {
17975 self.fold_all(&actions::FoldAll, window, cx);
17976 }
17977 } else {
17978 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17979 let should_unfold = buffer_ids
17980 .iter()
17981 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17982
17983 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17984 editor
17985 .update_in(cx, |editor, _, cx| {
17986 for buffer_id in buffer_ids {
17987 if should_unfold {
17988 editor.unfold_buffer(buffer_id, cx);
17989 } else {
17990 editor.fold_buffer(buffer_id, cx);
17991 }
17992 }
17993 })
17994 .ok();
17995 });
17996 }
17997 }
17998
17999 fn fold_at_level(
18000 &mut self,
18001 fold_at: &FoldAtLevel,
18002 window: &mut Window,
18003 cx: &mut Context<Self>,
18004 ) {
18005 if !self.buffer.read(cx).is_singleton() {
18006 return;
18007 }
18008
18009 let fold_at_level = fold_at.0;
18010 let snapshot = self.buffer.read(cx).snapshot(cx);
18011 let mut to_fold = Vec::new();
18012 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18013
18014 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18015 while start_row < end_row {
18016 match self
18017 .snapshot(window, cx)
18018 .crease_for_buffer_row(MultiBufferRow(start_row))
18019 {
18020 Some(crease) => {
18021 let nested_start_row = crease.range().start.row + 1;
18022 let nested_end_row = crease.range().end.row;
18023
18024 if current_level < fold_at_level {
18025 stack.push((nested_start_row, nested_end_row, current_level + 1));
18026 } else if current_level == fold_at_level {
18027 to_fold.push(crease);
18028 }
18029
18030 start_row = nested_end_row + 1;
18031 }
18032 None => start_row += 1,
18033 }
18034 }
18035 }
18036
18037 self.fold_creases(to_fold, true, window, cx);
18038 }
18039
18040 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18041 if self.buffer.read(cx).is_singleton() {
18042 let mut fold_ranges = Vec::new();
18043 let snapshot = self.buffer.read(cx).snapshot(cx);
18044
18045 for row in 0..snapshot.max_row().0 {
18046 if let Some(foldable_range) = self
18047 .snapshot(window, cx)
18048 .crease_for_buffer_row(MultiBufferRow(row))
18049 {
18050 fold_ranges.push(foldable_range);
18051 }
18052 }
18053
18054 self.fold_creases(fold_ranges, true, window, cx);
18055 } else {
18056 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18057 editor
18058 .update_in(cx, |editor, _, cx| {
18059 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18060 editor.fold_buffer(buffer_id, cx);
18061 }
18062 })
18063 .ok();
18064 });
18065 }
18066 }
18067
18068 pub fn fold_function_bodies(
18069 &mut self,
18070 _: &actions::FoldFunctionBodies,
18071 window: &mut Window,
18072 cx: &mut Context<Self>,
18073 ) {
18074 let snapshot = self.buffer.read(cx).snapshot(cx);
18075
18076 let ranges = snapshot
18077 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18078 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18079 .collect::<Vec<_>>();
18080
18081 let creases = ranges
18082 .into_iter()
18083 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18084 .collect();
18085
18086 self.fold_creases(creases, true, window, cx);
18087 }
18088
18089 pub fn fold_recursive(
18090 &mut self,
18091 _: &actions::FoldRecursive,
18092 window: &mut Window,
18093 cx: &mut Context<Self>,
18094 ) {
18095 let mut to_fold = Vec::new();
18096 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18097 let selections = self.selections.all_adjusted(cx);
18098
18099 for selection in selections {
18100 let range = selection.range().sorted();
18101 let buffer_start_row = range.start.row;
18102
18103 if range.start.row != range.end.row {
18104 let mut found = false;
18105 for row in range.start.row..=range.end.row {
18106 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18107 found = true;
18108 to_fold.push(crease);
18109 }
18110 }
18111 if found {
18112 continue;
18113 }
18114 }
18115
18116 for row in (0..=range.start.row).rev() {
18117 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18118 if crease.range().end.row >= buffer_start_row {
18119 to_fold.push(crease);
18120 } else {
18121 break;
18122 }
18123 }
18124 }
18125 }
18126
18127 self.fold_creases(to_fold, true, window, cx);
18128 }
18129
18130 pub fn fold_at(
18131 &mut self,
18132 buffer_row: MultiBufferRow,
18133 window: &mut Window,
18134 cx: &mut Context<Self>,
18135 ) {
18136 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18137
18138 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18139 let autoscroll = self
18140 .selections
18141 .all::<Point>(cx)
18142 .iter()
18143 .any(|selection| crease.range().overlaps(&selection.range()));
18144
18145 self.fold_creases(vec![crease], autoscroll, window, cx);
18146 }
18147 }
18148
18149 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18150 if self.is_singleton(cx) {
18151 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18152 let buffer = &display_map.buffer_snapshot;
18153 let selections = self.selections.all::<Point>(cx);
18154 let ranges = selections
18155 .iter()
18156 .map(|s| {
18157 let range = s.display_range(&display_map).sorted();
18158 let mut start = range.start.to_point(&display_map);
18159 let mut end = range.end.to_point(&display_map);
18160 start.column = 0;
18161 end.column = buffer.line_len(MultiBufferRow(end.row));
18162 start..end
18163 })
18164 .collect::<Vec<_>>();
18165
18166 self.unfold_ranges(&ranges, true, true, cx);
18167 } else {
18168 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18169 let buffer_ids = self
18170 .selections
18171 .disjoint_anchor_ranges()
18172 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18173 .collect::<HashSet<_>>();
18174 for buffer_id in buffer_ids {
18175 self.unfold_buffer(buffer_id, cx);
18176 }
18177 }
18178 }
18179
18180 pub fn unfold_recursive(
18181 &mut self,
18182 _: &UnfoldRecursive,
18183 _window: &mut Window,
18184 cx: &mut Context<Self>,
18185 ) {
18186 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18187 let selections = self.selections.all::<Point>(cx);
18188 let ranges = selections
18189 .iter()
18190 .map(|s| {
18191 let mut range = s.display_range(&display_map).sorted();
18192 *range.start.column_mut() = 0;
18193 *range.end.column_mut() = display_map.line_len(range.end.row());
18194 let start = range.start.to_point(&display_map);
18195 let end = range.end.to_point(&display_map);
18196 start..end
18197 })
18198 .collect::<Vec<_>>();
18199
18200 self.unfold_ranges(&ranges, true, true, cx);
18201 }
18202
18203 pub fn unfold_at(
18204 &mut self,
18205 buffer_row: MultiBufferRow,
18206 _window: &mut Window,
18207 cx: &mut Context<Self>,
18208 ) {
18209 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18210
18211 let intersection_range = Point::new(buffer_row.0, 0)
18212 ..Point::new(
18213 buffer_row.0,
18214 display_map.buffer_snapshot.line_len(buffer_row),
18215 );
18216
18217 let autoscroll = self
18218 .selections
18219 .all::<Point>(cx)
18220 .iter()
18221 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18222
18223 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18224 }
18225
18226 pub fn unfold_all(
18227 &mut self,
18228 _: &actions::UnfoldAll,
18229 _window: &mut Window,
18230 cx: &mut Context<Self>,
18231 ) {
18232 if self.buffer.read(cx).is_singleton() {
18233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18234 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18235 } else {
18236 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18237 editor
18238 .update(cx, |editor, cx| {
18239 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18240 editor.unfold_buffer(buffer_id, cx);
18241 }
18242 })
18243 .ok();
18244 });
18245 }
18246 }
18247
18248 pub fn fold_selected_ranges(
18249 &mut self,
18250 _: &FoldSelectedRanges,
18251 window: &mut Window,
18252 cx: &mut Context<Self>,
18253 ) {
18254 let selections = self.selections.all_adjusted(cx);
18255 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18256 let ranges = selections
18257 .into_iter()
18258 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18259 .collect::<Vec<_>>();
18260 self.fold_creases(ranges, true, window, cx);
18261 }
18262
18263 pub fn fold_ranges<T: ToOffset + Clone>(
18264 &mut self,
18265 ranges: Vec<Range<T>>,
18266 auto_scroll: bool,
18267 window: &mut Window,
18268 cx: &mut Context<Self>,
18269 ) {
18270 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18271 let ranges = ranges
18272 .into_iter()
18273 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18274 .collect::<Vec<_>>();
18275 self.fold_creases(ranges, auto_scroll, window, cx);
18276 }
18277
18278 pub fn fold_creases<T: ToOffset + Clone>(
18279 &mut self,
18280 creases: Vec<Crease<T>>,
18281 auto_scroll: bool,
18282 _window: &mut Window,
18283 cx: &mut Context<Self>,
18284 ) {
18285 if creases.is_empty() {
18286 return;
18287 }
18288
18289 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18290
18291 if auto_scroll {
18292 self.request_autoscroll(Autoscroll::fit(), cx);
18293 }
18294
18295 cx.notify();
18296
18297 self.scrollbar_marker_state.dirty = true;
18298 self.folds_did_change(cx);
18299 }
18300
18301 /// Removes any folds whose ranges intersect any of the given ranges.
18302 pub fn unfold_ranges<T: ToOffset + Clone>(
18303 &mut self,
18304 ranges: &[Range<T>],
18305 inclusive: bool,
18306 auto_scroll: bool,
18307 cx: &mut Context<Self>,
18308 ) {
18309 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18310 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18311 });
18312 self.folds_did_change(cx);
18313 }
18314
18315 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18316 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18317 return;
18318 }
18319 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18320 self.display_map.update(cx, |display_map, cx| {
18321 display_map.fold_buffers([buffer_id], cx)
18322 });
18323 cx.emit(EditorEvent::BufferFoldToggled {
18324 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18325 folded: true,
18326 });
18327 cx.notify();
18328 }
18329
18330 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18331 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18332 return;
18333 }
18334 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18335 self.display_map.update(cx, |display_map, cx| {
18336 display_map.unfold_buffers([buffer_id], cx);
18337 });
18338 cx.emit(EditorEvent::BufferFoldToggled {
18339 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18340 folded: false,
18341 });
18342 cx.notify();
18343 }
18344
18345 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18346 self.display_map.read(cx).is_buffer_folded(buffer)
18347 }
18348
18349 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18350 self.display_map.read(cx).folded_buffers()
18351 }
18352
18353 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18354 self.display_map.update(cx, |display_map, cx| {
18355 display_map.disable_header_for_buffer(buffer_id, cx);
18356 });
18357 cx.notify();
18358 }
18359
18360 /// Removes any folds with the given ranges.
18361 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18362 &mut self,
18363 ranges: &[Range<T>],
18364 type_id: TypeId,
18365 auto_scroll: bool,
18366 cx: &mut Context<Self>,
18367 ) {
18368 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18369 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18370 });
18371 self.folds_did_change(cx);
18372 }
18373
18374 fn remove_folds_with<T: ToOffset + Clone>(
18375 &mut self,
18376 ranges: &[Range<T>],
18377 auto_scroll: bool,
18378 cx: &mut Context<Self>,
18379 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18380 ) {
18381 if ranges.is_empty() {
18382 return;
18383 }
18384
18385 let mut buffers_affected = HashSet::default();
18386 let multi_buffer = self.buffer().read(cx);
18387 for range in ranges {
18388 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18389 buffers_affected.insert(buffer.read(cx).remote_id());
18390 };
18391 }
18392
18393 self.display_map.update(cx, update);
18394
18395 if auto_scroll {
18396 self.request_autoscroll(Autoscroll::fit(), cx);
18397 }
18398
18399 cx.notify();
18400 self.scrollbar_marker_state.dirty = true;
18401 self.active_indent_guides_state.dirty = true;
18402 }
18403
18404 pub fn update_renderer_widths(
18405 &mut self,
18406 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18407 cx: &mut Context<Self>,
18408 ) -> bool {
18409 self.display_map
18410 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18411 }
18412
18413 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18414 self.display_map.read(cx).fold_placeholder.clone()
18415 }
18416
18417 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18418 self.buffer.update(cx, |buffer, cx| {
18419 buffer.set_all_diff_hunks_expanded(cx);
18420 });
18421 }
18422
18423 pub fn expand_all_diff_hunks(
18424 &mut self,
18425 _: &ExpandAllDiffHunks,
18426 _window: &mut Window,
18427 cx: &mut Context<Self>,
18428 ) {
18429 self.buffer.update(cx, |buffer, cx| {
18430 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18431 });
18432 }
18433
18434 pub fn toggle_selected_diff_hunks(
18435 &mut self,
18436 _: &ToggleSelectedDiffHunks,
18437 _window: &mut Window,
18438 cx: &mut Context<Self>,
18439 ) {
18440 let ranges: Vec<_> = self
18441 .selections
18442 .disjoint_anchors()
18443 .iter()
18444 .map(|s| s.range())
18445 .collect();
18446 self.toggle_diff_hunks_in_ranges(ranges, cx);
18447 }
18448
18449 pub fn diff_hunks_in_ranges<'a>(
18450 &'a self,
18451 ranges: &'a [Range<Anchor>],
18452 buffer: &'a MultiBufferSnapshot,
18453 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18454 ranges.iter().flat_map(move |range| {
18455 let end_excerpt_id = range.end.excerpt_id;
18456 let range = range.to_point(buffer);
18457 let mut peek_end = range.end;
18458 if range.end.row < buffer.max_row().0 {
18459 peek_end = Point::new(range.end.row + 1, 0);
18460 }
18461 buffer
18462 .diff_hunks_in_range(range.start..peek_end)
18463 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18464 })
18465 }
18466
18467 pub fn has_stageable_diff_hunks_in_ranges(
18468 &self,
18469 ranges: &[Range<Anchor>],
18470 snapshot: &MultiBufferSnapshot,
18471 ) -> bool {
18472 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18473 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18474 }
18475
18476 pub fn toggle_staged_selected_diff_hunks(
18477 &mut self,
18478 _: &::git::ToggleStaged,
18479 _: &mut Window,
18480 cx: &mut Context<Self>,
18481 ) {
18482 let snapshot = self.buffer.read(cx).snapshot(cx);
18483 let ranges: Vec<_> = self
18484 .selections
18485 .disjoint_anchors()
18486 .iter()
18487 .map(|s| s.range())
18488 .collect();
18489 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18490 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18491 }
18492
18493 pub fn set_render_diff_hunk_controls(
18494 &mut self,
18495 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18496 cx: &mut Context<Self>,
18497 ) {
18498 self.render_diff_hunk_controls = render_diff_hunk_controls;
18499 cx.notify();
18500 }
18501
18502 pub fn stage_and_next(
18503 &mut self,
18504 _: &::git::StageAndNext,
18505 window: &mut Window,
18506 cx: &mut Context<Self>,
18507 ) {
18508 self.do_stage_or_unstage_and_next(true, window, cx);
18509 }
18510
18511 pub fn unstage_and_next(
18512 &mut self,
18513 _: &::git::UnstageAndNext,
18514 window: &mut Window,
18515 cx: &mut Context<Self>,
18516 ) {
18517 self.do_stage_or_unstage_and_next(false, window, cx);
18518 }
18519
18520 pub fn stage_or_unstage_diff_hunks(
18521 &mut self,
18522 stage: bool,
18523 ranges: Vec<Range<Anchor>>,
18524 cx: &mut Context<Self>,
18525 ) {
18526 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18527 cx.spawn(async move |this, cx| {
18528 task.await?;
18529 this.update(cx, |this, cx| {
18530 let snapshot = this.buffer.read(cx).snapshot(cx);
18531 let chunk_by = this
18532 .diff_hunks_in_ranges(&ranges, &snapshot)
18533 .chunk_by(|hunk| hunk.buffer_id);
18534 for (buffer_id, hunks) in &chunk_by {
18535 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18536 }
18537 })
18538 })
18539 .detach_and_log_err(cx);
18540 }
18541
18542 fn save_buffers_for_ranges_if_needed(
18543 &mut self,
18544 ranges: &[Range<Anchor>],
18545 cx: &mut Context<Editor>,
18546 ) -> Task<Result<()>> {
18547 let multibuffer = self.buffer.read(cx);
18548 let snapshot = multibuffer.read(cx);
18549 let buffer_ids: HashSet<_> = ranges
18550 .iter()
18551 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18552 .collect();
18553 drop(snapshot);
18554
18555 let mut buffers = HashSet::default();
18556 for buffer_id in buffer_ids {
18557 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18558 let buffer = buffer_entity.read(cx);
18559 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18560 {
18561 buffers.insert(buffer_entity);
18562 }
18563 }
18564 }
18565
18566 if let Some(project) = &self.project {
18567 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18568 } else {
18569 Task::ready(Ok(()))
18570 }
18571 }
18572
18573 fn do_stage_or_unstage_and_next(
18574 &mut self,
18575 stage: bool,
18576 window: &mut Window,
18577 cx: &mut Context<Self>,
18578 ) {
18579 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18580
18581 if ranges.iter().any(|range| range.start != range.end) {
18582 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18583 return;
18584 }
18585
18586 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18587 let snapshot = self.snapshot(window, cx);
18588 let position = self.selections.newest::<Point>(cx).head();
18589 let mut row = snapshot
18590 .buffer_snapshot
18591 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18592 .find(|hunk| hunk.row_range.start.0 > position.row)
18593 .map(|hunk| hunk.row_range.start);
18594
18595 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18596 // Outside of the project diff editor, wrap around to the beginning.
18597 if !all_diff_hunks_expanded {
18598 row = row.or_else(|| {
18599 snapshot
18600 .buffer_snapshot
18601 .diff_hunks_in_range(Point::zero()..position)
18602 .find(|hunk| hunk.row_range.end.0 < position.row)
18603 .map(|hunk| hunk.row_range.start)
18604 });
18605 }
18606
18607 if let Some(row) = row {
18608 let destination = Point::new(row.0, 0);
18609 let autoscroll = Autoscroll::center();
18610
18611 self.unfold_ranges(&[destination..destination], false, false, cx);
18612 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18613 s.select_ranges([destination..destination]);
18614 });
18615 }
18616 }
18617
18618 fn do_stage_or_unstage(
18619 &self,
18620 stage: bool,
18621 buffer_id: BufferId,
18622 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18623 cx: &mut App,
18624 ) -> Option<()> {
18625 let project = self.project()?;
18626 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18627 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18628 let buffer_snapshot = buffer.read(cx).snapshot();
18629 let file_exists = buffer_snapshot
18630 .file()
18631 .is_some_and(|file| file.disk_state().exists());
18632 diff.update(cx, |diff, cx| {
18633 diff.stage_or_unstage_hunks(
18634 stage,
18635 &hunks
18636 .map(|hunk| buffer_diff::DiffHunk {
18637 buffer_range: hunk.buffer_range,
18638 diff_base_byte_range: hunk.diff_base_byte_range,
18639 secondary_status: hunk.secondary_status,
18640 range: Point::zero()..Point::zero(), // unused
18641 })
18642 .collect::<Vec<_>>(),
18643 &buffer_snapshot,
18644 file_exists,
18645 cx,
18646 )
18647 });
18648 None
18649 }
18650
18651 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18652 let ranges: Vec<_> = self
18653 .selections
18654 .disjoint_anchors()
18655 .iter()
18656 .map(|s| s.range())
18657 .collect();
18658 self.buffer
18659 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18660 }
18661
18662 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18663 self.buffer.update(cx, |buffer, cx| {
18664 let ranges = vec![Anchor::min()..Anchor::max()];
18665 if !buffer.all_diff_hunks_expanded()
18666 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18667 {
18668 buffer.collapse_diff_hunks(ranges, cx);
18669 true
18670 } else {
18671 false
18672 }
18673 })
18674 }
18675
18676 fn toggle_diff_hunks_in_ranges(
18677 &mut self,
18678 ranges: Vec<Range<Anchor>>,
18679 cx: &mut Context<Editor>,
18680 ) {
18681 self.buffer.update(cx, |buffer, cx| {
18682 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18683 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18684 })
18685 }
18686
18687 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18688 self.buffer.update(cx, |buffer, cx| {
18689 let snapshot = buffer.snapshot(cx);
18690 let excerpt_id = range.end.excerpt_id;
18691 let point_range = range.to_point(&snapshot);
18692 let expand = !buffer.single_hunk_is_expanded(range, cx);
18693 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18694 })
18695 }
18696
18697 pub(crate) fn apply_all_diff_hunks(
18698 &mut self,
18699 _: &ApplyAllDiffHunks,
18700 window: &mut Window,
18701 cx: &mut Context<Self>,
18702 ) {
18703 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18704
18705 let buffers = self.buffer.read(cx).all_buffers();
18706 for branch_buffer in buffers {
18707 branch_buffer.update(cx, |branch_buffer, cx| {
18708 branch_buffer.merge_into_base(Vec::new(), cx);
18709 });
18710 }
18711
18712 if let Some(project) = self.project.clone() {
18713 self.save(
18714 SaveOptions {
18715 format: true,
18716 autosave: false,
18717 },
18718 project,
18719 window,
18720 cx,
18721 )
18722 .detach_and_log_err(cx);
18723 }
18724 }
18725
18726 pub(crate) fn apply_selected_diff_hunks(
18727 &mut self,
18728 _: &ApplyDiffHunk,
18729 window: &mut Window,
18730 cx: &mut Context<Self>,
18731 ) {
18732 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18733 let snapshot = self.snapshot(window, cx);
18734 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18735 let mut ranges_by_buffer = HashMap::default();
18736 self.transact(window, cx, |editor, _window, cx| {
18737 for hunk in hunks {
18738 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18739 ranges_by_buffer
18740 .entry(buffer.clone())
18741 .or_insert_with(Vec::new)
18742 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18743 }
18744 }
18745
18746 for (buffer, ranges) in ranges_by_buffer {
18747 buffer.update(cx, |buffer, cx| {
18748 buffer.merge_into_base(ranges, cx);
18749 });
18750 }
18751 });
18752
18753 if let Some(project) = self.project.clone() {
18754 self.save(
18755 SaveOptions {
18756 format: true,
18757 autosave: false,
18758 },
18759 project,
18760 window,
18761 cx,
18762 )
18763 .detach_and_log_err(cx);
18764 }
18765 }
18766
18767 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18768 if hovered != self.gutter_hovered {
18769 self.gutter_hovered = hovered;
18770 cx.notify();
18771 }
18772 }
18773
18774 pub fn insert_blocks(
18775 &mut self,
18776 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18777 autoscroll: Option<Autoscroll>,
18778 cx: &mut Context<Self>,
18779 ) -> Vec<CustomBlockId> {
18780 let blocks = self
18781 .display_map
18782 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18783 if let Some(autoscroll) = autoscroll {
18784 self.request_autoscroll(autoscroll, cx);
18785 }
18786 cx.notify();
18787 blocks
18788 }
18789
18790 pub fn resize_blocks(
18791 &mut self,
18792 heights: HashMap<CustomBlockId, u32>,
18793 autoscroll: Option<Autoscroll>,
18794 cx: &mut Context<Self>,
18795 ) {
18796 self.display_map
18797 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18798 if let Some(autoscroll) = autoscroll {
18799 self.request_autoscroll(autoscroll, cx);
18800 }
18801 cx.notify();
18802 }
18803
18804 pub fn replace_blocks(
18805 &mut self,
18806 renderers: HashMap<CustomBlockId, RenderBlock>,
18807 autoscroll: Option<Autoscroll>,
18808 cx: &mut Context<Self>,
18809 ) {
18810 self.display_map
18811 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18812 if let Some(autoscroll) = autoscroll {
18813 self.request_autoscroll(autoscroll, cx);
18814 }
18815 cx.notify();
18816 }
18817
18818 pub fn remove_blocks(
18819 &mut self,
18820 block_ids: HashSet<CustomBlockId>,
18821 autoscroll: Option<Autoscroll>,
18822 cx: &mut Context<Self>,
18823 ) {
18824 self.display_map.update(cx, |display_map, cx| {
18825 display_map.remove_blocks(block_ids, cx)
18826 });
18827 if let Some(autoscroll) = autoscroll {
18828 self.request_autoscroll(autoscroll, cx);
18829 }
18830 cx.notify();
18831 }
18832
18833 pub fn row_for_block(
18834 &self,
18835 block_id: CustomBlockId,
18836 cx: &mut Context<Self>,
18837 ) -> Option<DisplayRow> {
18838 self.display_map
18839 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18840 }
18841
18842 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18843 self.focused_block = Some(focused_block);
18844 }
18845
18846 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18847 self.focused_block.take()
18848 }
18849
18850 pub fn insert_creases(
18851 &mut self,
18852 creases: impl IntoIterator<Item = Crease<Anchor>>,
18853 cx: &mut Context<Self>,
18854 ) -> Vec<CreaseId> {
18855 self.display_map
18856 .update(cx, |map, cx| map.insert_creases(creases, cx))
18857 }
18858
18859 pub fn remove_creases(
18860 &mut self,
18861 ids: impl IntoIterator<Item = CreaseId>,
18862 cx: &mut Context<Self>,
18863 ) -> Vec<(CreaseId, Range<Anchor>)> {
18864 self.display_map
18865 .update(cx, |map, cx| map.remove_creases(ids, cx))
18866 }
18867
18868 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18869 self.display_map
18870 .update(cx, |map, cx| map.snapshot(cx))
18871 .longest_row()
18872 }
18873
18874 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18875 self.display_map
18876 .update(cx, |map, cx| map.snapshot(cx))
18877 .max_point()
18878 }
18879
18880 pub fn text(&self, cx: &App) -> String {
18881 self.buffer.read(cx).read(cx).text()
18882 }
18883
18884 pub fn is_empty(&self, cx: &App) -> bool {
18885 self.buffer.read(cx).read(cx).is_empty()
18886 }
18887
18888 pub fn text_option(&self, cx: &App) -> Option<String> {
18889 let text = self.text(cx);
18890 let text = text.trim();
18891
18892 if text.is_empty() {
18893 return None;
18894 }
18895
18896 Some(text.to_string())
18897 }
18898
18899 pub fn set_text(
18900 &mut self,
18901 text: impl Into<Arc<str>>,
18902 window: &mut Window,
18903 cx: &mut Context<Self>,
18904 ) {
18905 self.transact(window, cx, |this, _, cx| {
18906 this.buffer
18907 .read(cx)
18908 .as_singleton()
18909 .expect("you can only call set_text on editors for singleton buffers")
18910 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18911 });
18912 }
18913
18914 pub fn display_text(&self, cx: &mut App) -> String {
18915 self.display_map
18916 .update(cx, |map, cx| map.snapshot(cx))
18917 .text()
18918 }
18919
18920 fn create_minimap(
18921 &self,
18922 minimap_settings: MinimapSettings,
18923 window: &mut Window,
18924 cx: &mut Context<Self>,
18925 ) -> Option<Entity<Self>> {
18926 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18927 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18928 }
18929
18930 fn initialize_new_minimap(
18931 &self,
18932 minimap_settings: MinimapSettings,
18933 window: &mut Window,
18934 cx: &mut Context<Self>,
18935 ) -> Entity<Self> {
18936 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18937
18938 let mut minimap = Editor::new_internal(
18939 EditorMode::Minimap {
18940 parent: cx.weak_entity(),
18941 },
18942 self.buffer.clone(),
18943 None,
18944 Some(self.display_map.clone()),
18945 window,
18946 cx,
18947 );
18948 minimap.scroll_manager.clone_state(&self.scroll_manager);
18949 minimap.set_text_style_refinement(TextStyleRefinement {
18950 font_size: Some(MINIMAP_FONT_SIZE),
18951 font_weight: Some(MINIMAP_FONT_WEIGHT),
18952 ..Default::default()
18953 });
18954 minimap.update_minimap_configuration(minimap_settings, cx);
18955 cx.new(|_| minimap)
18956 }
18957
18958 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18959 let current_line_highlight = minimap_settings
18960 .current_line_highlight
18961 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18962 self.set_current_line_highlight(Some(current_line_highlight));
18963 }
18964
18965 pub fn minimap(&self) -> Option<&Entity<Self>> {
18966 self.minimap
18967 .as_ref()
18968 .filter(|_| self.minimap_visibility.visible())
18969 }
18970
18971 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18972 let mut wrap_guides = smallvec![];
18973
18974 if self.show_wrap_guides == Some(false) {
18975 return wrap_guides;
18976 }
18977
18978 let settings = self.buffer.read(cx).language_settings(cx);
18979 if settings.show_wrap_guides {
18980 match self.soft_wrap_mode(cx) {
18981 SoftWrap::Column(soft_wrap) => {
18982 wrap_guides.push((soft_wrap as usize, true));
18983 }
18984 SoftWrap::Bounded(soft_wrap) => {
18985 wrap_guides.push((soft_wrap as usize, true));
18986 }
18987 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18988 }
18989 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18990 }
18991
18992 wrap_guides
18993 }
18994
18995 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18996 let settings = self.buffer.read(cx).language_settings(cx);
18997 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18998 match mode {
18999 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19000 SoftWrap::None
19001 }
19002 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19003 language_settings::SoftWrap::PreferredLineLength => {
19004 SoftWrap::Column(settings.preferred_line_length)
19005 }
19006 language_settings::SoftWrap::Bounded => {
19007 SoftWrap::Bounded(settings.preferred_line_length)
19008 }
19009 }
19010 }
19011
19012 pub fn set_soft_wrap_mode(
19013 &mut self,
19014 mode: language_settings::SoftWrap,
19015
19016 cx: &mut Context<Self>,
19017 ) {
19018 self.soft_wrap_mode_override = Some(mode);
19019 cx.notify();
19020 }
19021
19022 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19023 self.hard_wrap = hard_wrap;
19024 cx.notify();
19025 }
19026
19027 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19028 self.text_style_refinement = Some(style);
19029 }
19030
19031 /// called by the Element so we know what style we were most recently rendered with.
19032 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19033 // We intentionally do not inform the display map about the minimap style
19034 // so that wrapping is not recalculated and stays consistent for the editor
19035 // and its linked minimap.
19036 if !self.mode.is_minimap() {
19037 let font = style.text.font();
19038 let font_size = style.text.font_size.to_pixels(window.rem_size());
19039 let display_map = self
19040 .placeholder_display_map
19041 .as_ref()
19042 .filter(|_| self.is_empty(cx))
19043 .unwrap_or(&self.display_map);
19044
19045 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19046 }
19047 self.style = Some(style);
19048 }
19049
19050 pub fn style(&self) -> Option<&EditorStyle> {
19051 self.style.as_ref()
19052 }
19053
19054 // Called by the element. This method is not designed to be called outside of the editor
19055 // element's layout code because it does not notify when rewrapping is computed synchronously.
19056 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19057 if self.is_empty(cx) {
19058 self.placeholder_display_map
19059 .as_ref()
19060 .map_or(false, |display_map| {
19061 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19062 })
19063 } else {
19064 self.display_map
19065 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19066 }
19067 }
19068
19069 pub fn set_soft_wrap(&mut self) {
19070 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19071 }
19072
19073 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19074 if self.soft_wrap_mode_override.is_some() {
19075 self.soft_wrap_mode_override.take();
19076 } else {
19077 let soft_wrap = match self.soft_wrap_mode(cx) {
19078 SoftWrap::GitDiff => return,
19079 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19080 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19081 language_settings::SoftWrap::None
19082 }
19083 };
19084 self.soft_wrap_mode_override = Some(soft_wrap);
19085 }
19086 cx.notify();
19087 }
19088
19089 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19090 let Some(workspace) = self.workspace() else {
19091 return;
19092 };
19093 let fs = workspace.read(cx).app_state().fs.clone();
19094 let current_show = TabBarSettings::get_global(cx).show;
19095 update_settings_file(fs, cx, move |setting, _| {
19096 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19097 });
19098 }
19099
19100 pub fn toggle_indent_guides(
19101 &mut self,
19102 _: &ToggleIndentGuides,
19103 _: &mut Window,
19104 cx: &mut Context<Self>,
19105 ) {
19106 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19107 self.buffer
19108 .read(cx)
19109 .language_settings(cx)
19110 .indent_guides
19111 .enabled
19112 });
19113 self.show_indent_guides = Some(!currently_enabled);
19114 cx.notify();
19115 }
19116
19117 fn should_show_indent_guides(&self) -> Option<bool> {
19118 self.show_indent_guides
19119 }
19120
19121 pub fn toggle_line_numbers(
19122 &mut self,
19123 _: &ToggleLineNumbers,
19124 _: &mut Window,
19125 cx: &mut Context<Self>,
19126 ) {
19127 let mut editor_settings = EditorSettings::get_global(cx).clone();
19128 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19129 EditorSettings::override_global(editor_settings, cx);
19130 }
19131
19132 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19133 if let Some(show_line_numbers) = self.show_line_numbers {
19134 return show_line_numbers;
19135 }
19136 EditorSettings::get_global(cx).gutter.line_numbers
19137 }
19138
19139 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19140 self.use_relative_line_numbers
19141 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19142 }
19143
19144 pub fn toggle_relative_line_numbers(
19145 &mut self,
19146 _: &ToggleRelativeLineNumbers,
19147 _: &mut Window,
19148 cx: &mut Context<Self>,
19149 ) {
19150 let is_relative = self.should_use_relative_line_numbers(cx);
19151 self.set_relative_line_number(Some(!is_relative), cx)
19152 }
19153
19154 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19155 self.use_relative_line_numbers = is_relative;
19156 cx.notify();
19157 }
19158
19159 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19160 self.show_gutter = show_gutter;
19161 cx.notify();
19162 }
19163
19164 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19165 self.show_scrollbars = ScrollbarAxes {
19166 horizontal: show,
19167 vertical: show,
19168 };
19169 cx.notify();
19170 }
19171
19172 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19173 self.show_scrollbars.vertical = show;
19174 cx.notify();
19175 }
19176
19177 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19178 self.show_scrollbars.horizontal = show;
19179 cx.notify();
19180 }
19181
19182 pub fn set_minimap_visibility(
19183 &mut self,
19184 minimap_visibility: MinimapVisibility,
19185 window: &mut Window,
19186 cx: &mut Context<Self>,
19187 ) {
19188 if self.minimap_visibility != minimap_visibility {
19189 if minimap_visibility.visible() && self.minimap.is_none() {
19190 let minimap_settings = EditorSettings::get_global(cx).minimap;
19191 self.minimap =
19192 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19193 }
19194 self.minimap_visibility = minimap_visibility;
19195 cx.notify();
19196 }
19197 }
19198
19199 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19200 self.set_show_scrollbars(false, cx);
19201 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19202 }
19203
19204 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19205 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19206 }
19207
19208 /// Normally the text in full mode and auto height editors is padded on the
19209 /// left side by roughly half a character width for improved hit testing.
19210 ///
19211 /// Use this method to disable this for cases where this is not wanted (e.g.
19212 /// if you want to align the editor text with some other text above or below)
19213 /// or if you want to add this padding to single-line editors.
19214 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19215 self.offset_content = offset_content;
19216 cx.notify();
19217 }
19218
19219 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19220 self.show_line_numbers = Some(show_line_numbers);
19221 cx.notify();
19222 }
19223
19224 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19225 self.disable_expand_excerpt_buttons = true;
19226 cx.notify();
19227 }
19228
19229 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19230 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19231 cx.notify();
19232 }
19233
19234 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19235 self.show_code_actions = Some(show_code_actions);
19236 cx.notify();
19237 }
19238
19239 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19240 self.show_runnables = Some(show_runnables);
19241 cx.notify();
19242 }
19243
19244 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19245 self.show_breakpoints = Some(show_breakpoints);
19246 cx.notify();
19247 }
19248
19249 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19250 if self.display_map.read(cx).masked != masked {
19251 self.display_map.update(cx, |map, _| map.masked = masked);
19252 }
19253 cx.notify()
19254 }
19255
19256 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19257 self.show_wrap_guides = Some(show_wrap_guides);
19258 cx.notify();
19259 }
19260
19261 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19262 self.show_indent_guides = Some(show_indent_guides);
19263 cx.notify();
19264 }
19265
19266 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19267 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19268 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19269 && let Some(dir) = file.abs_path(cx).parent()
19270 {
19271 return Some(dir.to_owned());
19272 }
19273 }
19274
19275 None
19276 }
19277
19278 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19279 self.active_excerpt(cx)?
19280 .1
19281 .read(cx)
19282 .file()
19283 .and_then(|f| f.as_local())
19284 }
19285
19286 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19287 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19288 let buffer = buffer.read(cx);
19289 if let Some(project_path) = buffer.project_path(cx) {
19290 let project = self.project()?.read(cx);
19291 project.absolute_path(&project_path, cx)
19292 } else {
19293 buffer
19294 .file()
19295 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19296 }
19297 })
19298 }
19299
19300 pub fn reveal_in_finder(
19301 &mut self,
19302 _: &RevealInFileManager,
19303 _window: &mut Window,
19304 cx: &mut Context<Self>,
19305 ) {
19306 if let Some(target) = self.target_file(cx) {
19307 cx.reveal_path(&target.abs_path(cx));
19308 }
19309 }
19310
19311 pub fn copy_path(
19312 &mut self,
19313 _: &zed_actions::workspace::CopyPath,
19314 _window: &mut Window,
19315 cx: &mut Context<Self>,
19316 ) {
19317 if let Some(path) = self.target_file_abs_path(cx)
19318 && let Some(path) = path.to_str()
19319 {
19320 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19321 } else {
19322 cx.propagate();
19323 }
19324 }
19325
19326 pub fn copy_relative_path(
19327 &mut self,
19328 _: &zed_actions::workspace::CopyRelativePath,
19329 _window: &mut Window,
19330 cx: &mut Context<Self>,
19331 ) {
19332 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19333 let project = self.project()?.read(cx);
19334 let path = buffer.read(cx).file()?.path();
19335 let path = path.display(project.path_style(cx));
19336 Some(path)
19337 }) {
19338 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19339 } else {
19340 cx.propagate();
19341 }
19342 }
19343
19344 /// Returns the project path for the editor's buffer, if any buffer is
19345 /// opened in the editor.
19346 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19347 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19348 buffer.read(cx).project_path(cx)
19349 } else {
19350 None
19351 }
19352 }
19353
19354 // Returns true if the editor handled a go-to-line request
19355 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19356 maybe!({
19357 let breakpoint_store = self.breakpoint_store.as_ref()?;
19358
19359 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19360 else {
19361 self.clear_row_highlights::<ActiveDebugLine>();
19362 return None;
19363 };
19364
19365 let position = active_stack_frame.position;
19366 let buffer_id = position.buffer_id?;
19367 let snapshot = self
19368 .project
19369 .as_ref()?
19370 .read(cx)
19371 .buffer_for_id(buffer_id, cx)?
19372 .read(cx)
19373 .snapshot();
19374
19375 let mut handled = false;
19376 for (id, ExcerptRange { context, .. }) in
19377 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19378 {
19379 if context.start.cmp(&position, &snapshot).is_ge()
19380 || context.end.cmp(&position, &snapshot).is_lt()
19381 {
19382 continue;
19383 }
19384 let snapshot = self.buffer.read(cx).snapshot(cx);
19385 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19386
19387 handled = true;
19388 self.clear_row_highlights::<ActiveDebugLine>();
19389
19390 self.go_to_line::<ActiveDebugLine>(
19391 multibuffer_anchor,
19392 Some(cx.theme().colors().editor_debugger_active_line_background),
19393 window,
19394 cx,
19395 );
19396
19397 cx.notify();
19398 }
19399
19400 handled.then_some(())
19401 })
19402 .is_some()
19403 }
19404
19405 pub fn copy_file_name_without_extension(
19406 &mut self,
19407 _: &CopyFileNameWithoutExtension,
19408 _: &mut Window,
19409 cx: &mut Context<Self>,
19410 ) {
19411 if let Some(file) = self.target_file(cx)
19412 && let Some(file_stem) = file.path().file_stem()
19413 {
19414 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19415 }
19416 }
19417
19418 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19419 if let Some(file) = self.target_file(cx)
19420 && let Some(name) = file.path().file_name()
19421 {
19422 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19423 }
19424 }
19425
19426 pub fn toggle_git_blame(
19427 &mut self,
19428 _: &::git::Blame,
19429 window: &mut Window,
19430 cx: &mut Context<Self>,
19431 ) {
19432 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19433
19434 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19435 self.start_git_blame(true, window, cx);
19436 }
19437
19438 cx.notify();
19439 }
19440
19441 pub fn toggle_git_blame_inline(
19442 &mut self,
19443 _: &ToggleGitBlameInline,
19444 window: &mut Window,
19445 cx: &mut Context<Self>,
19446 ) {
19447 self.toggle_git_blame_inline_internal(true, window, cx);
19448 cx.notify();
19449 }
19450
19451 pub fn open_git_blame_commit(
19452 &mut self,
19453 _: &OpenGitBlameCommit,
19454 window: &mut Window,
19455 cx: &mut Context<Self>,
19456 ) {
19457 self.open_git_blame_commit_internal(window, cx);
19458 }
19459
19460 fn open_git_blame_commit_internal(
19461 &mut self,
19462 window: &mut Window,
19463 cx: &mut Context<Self>,
19464 ) -> Option<()> {
19465 let blame = self.blame.as_ref()?;
19466 let snapshot = self.snapshot(window, cx);
19467 let cursor = self.selections.newest::<Point>(cx).head();
19468 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19469 let (_, blame_entry) = blame
19470 .update(cx, |blame, cx| {
19471 blame
19472 .blame_for_rows(
19473 &[RowInfo {
19474 buffer_id: Some(buffer.remote_id()),
19475 buffer_row: Some(point.row),
19476 ..Default::default()
19477 }],
19478 cx,
19479 )
19480 .next()
19481 })
19482 .flatten()?;
19483 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19484 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19485 let workspace = self.workspace()?.downgrade();
19486 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19487 None
19488 }
19489
19490 pub fn git_blame_inline_enabled(&self) -> bool {
19491 self.git_blame_inline_enabled
19492 }
19493
19494 pub fn toggle_selection_menu(
19495 &mut self,
19496 _: &ToggleSelectionMenu,
19497 _: &mut Window,
19498 cx: &mut Context<Self>,
19499 ) {
19500 self.show_selection_menu = self
19501 .show_selection_menu
19502 .map(|show_selections_menu| !show_selections_menu)
19503 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19504
19505 cx.notify();
19506 }
19507
19508 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19509 self.show_selection_menu
19510 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19511 }
19512
19513 fn start_git_blame(
19514 &mut self,
19515 user_triggered: bool,
19516 window: &mut Window,
19517 cx: &mut Context<Self>,
19518 ) {
19519 if let Some(project) = self.project() {
19520 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19521 && buffer.read(cx).file().is_none()
19522 {
19523 return;
19524 }
19525
19526 let focused = self.focus_handle(cx).contains_focused(window, cx);
19527
19528 let project = project.clone();
19529 let blame = cx
19530 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19531 self.blame_subscription =
19532 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19533 self.blame = Some(blame);
19534 }
19535 }
19536
19537 fn toggle_git_blame_inline_internal(
19538 &mut self,
19539 user_triggered: bool,
19540 window: &mut Window,
19541 cx: &mut Context<Self>,
19542 ) {
19543 if self.git_blame_inline_enabled {
19544 self.git_blame_inline_enabled = false;
19545 self.show_git_blame_inline = false;
19546 self.show_git_blame_inline_delay_task.take();
19547 } else {
19548 self.git_blame_inline_enabled = true;
19549 self.start_git_blame_inline(user_triggered, window, cx);
19550 }
19551
19552 cx.notify();
19553 }
19554
19555 fn start_git_blame_inline(
19556 &mut self,
19557 user_triggered: bool,
19558 window: &mut Window,
19559 cx: &mut Context<Self>,
19560 ) {
19561 self.start_git_blame(user_triggered, window, cx);
19562
19563 if ProjectSettings::get_global(cx)
19564 .git
19565 .inline_blame_delay()
19566 .is_some()
19567 {
19568 self.start_inline_blame_timer(window, cx);
19569 } else {
19570 self.show_git_blame_inline = true
19571 }
19572 }
19573
19574 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19575 self.blame.as_ref()
19576 }
19577
19578 pub fn show_git_blame_gutter(&self) -> bool {
19579 self.show_git_blame_gutter
19580 }
19581
19582 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19583 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19584 }
19585
19586 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19587 self.show_git_blame_inline
19588 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19589 && !self.newest_selection_head_on_empty_line(cx)
19590 && self.has_blame_entries(cx)
19591 }
19592
19593 fn has_blame_entries(&self, cx: &App) -> bool {
19594 self.blame()
19595 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19596 }
19597
19598 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19599 let cursor_anchor = self.selections.newest_anchor().head();
19600
19601 let snapshot = self.buffer.read(cx).snapshot(cx);
19602 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19603
19604 snapshot.line_len(buffer_row) == 0
19605 }
19606
19607 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19608 let buffer_and_selection = maybe!({
19609 let selection = self.selections.newest::<Point>(cx);
19610 let selection_range = selection.range();
19611
19612 let multi_buffer = self.buffer().read(cx);
19613 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19614 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19615
19616 let (buffer, range, _) = if selection.reversed {
19617 buffer_ranges.first()
19618 } else {
19619 buffer_ranges.last()
19620 }?;
19621
19622 let selection = text::ToPoint::to_point(&range.start, buffer).row
19623 ..text::ToPoint::to_point(&range.end, buffer).row;
19624 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19625 });
19626
19627 let Some((buffer, selection)) = buffer_and_selection else {
19628 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19629 };
19630
19631 let Some(project) = self.project() else {
19632 return Task::ready(Err(anyhow!("editor does not have project")));
19633 };
19634
19635 project.update(cx, |project, cx| {
19636 project.get_permalink_to_line(&buffer, selection, cx)
19637 })
19638 }
19639
19640 pub fn copy_permalink_to_line(
19641 &mut self,
19642 _: &CopyPermalinkToLine,
19643 window: &mut Window,
19644 cx: &mut Context<Self>,
19645 ) {
19646 let permalink_task = self.get_permalink_to_line(cx);
19647 let workspace = self.workspace();
19648
19649 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19650 Ok(permalink) => {
19651 cx.update(|_, cx| {
19652 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19653 })
19654 .ok();
19655 }
19656 Err(err) => {
19657 let message = format!("Failed to copy permalink: {err}");
19658
19659 anyhow::Result::<()>::Err(err).log_err();
19660
19661 if let Some(workspace) = workspace {
19662 workspace
19663 .update_in(cx, |workspace, _, cx| {
19664 struct CopyPermalinkToLine;
19665
19666 workspace.show_toast(
19667 Toast::new(
19668 NotificationId::unique::<CopyPermalinkToLine>(),
19669 message,
19670 ),
19671 cx,
19672 )
19673 })
19674 .ok();
19675 }
19676 }
19677 })
19678 .detach();
19679 }
19680
19681 pub fn copy_file_location(
19682 &mut self,
19683 _: &CopyFileLocation,
19684 _: &mut Window,
19685 cx: &mut Context<Self>,
19686 ) {
19687 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19688 if let Some(file) = self.target_file(cx) {
19689 let path = file.path().display(file.path_style(cx));
19690 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19691 }
19692 }
19693
19694 pub fn open_permalink_to_line(
19695 &mut self,
19696 _: &OpenPermalinkToLine,
19697 window: &mut Window,
19698 cx: &mut Context<Self>,
19699 ) {
19700 let permalink_task = self.get_permalink_to_line(cx);
19701 let workspace = self.workspace();
19702
19703 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19704 Ok(permalink) => {
19705 cx.update(|_, cx| {
19706 cx.open_url(permalink.as_ref());
19707 })
19708 .ok();
19709 }
19710 Err(err) => {
19711 let message = format!("Failed to open permalink: {err}");
19712
19713 anyhow::Result::<()>::Err(err).log_err();
19714
19715 if let Some(workspace) = workspace {
19716 workspace
19717 .update(cx, |workspace, cx| {
19718 struct OpenPermalinkToLine;
19719
19720 workspace.show_toast(
19721 Toast::new(
19722 NotificationId::unique::<OpenPermalinkToLine>(),
19723 message,
19724 ),
19725 cx,
19726 )
19727 })
19728 .ok();
19729 }
19730 }
19731 })
19732 .detach();
19733 }
19734
19735 pub fn insert_uuid_v4(
19736 &mut self,
19737 _: &InsertUuidV4,
19738 window: &mut Window,
19739 cx: &mut Context<Self>,
19740 ) {
19741 self.insert_uuid(UuidVersion::V4, window, cx);
19742 }
19743
19744 pub fn insert_uuid_v7(
19745 &mut self,
19746 _: &InsertUuidV7,
19747 window: &mut Window,
19748 cx: &mut Context<Self>,
19749 ) {
19750 self.insert_uuid(UuidVersion::V7, window, cx);
19751 }
19752
19753 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19755 self.transact(window, cx, |this, window, cx| {
19756 let edits = this
19757 .selections
19758 .all::<Point>(cx)
19759 .into_iter()
19760 .map(|selection| {
19761 let uuid = match version {
19762 UuidVersion::V4 => uuid::Uuid::new_v4(),
19763 UuidVersion::V7 => uuid::Uuid::now_v7(),
19764 };
19765
19766 (selection.range(), uuid.to_string())
19767 });
19768 this.edit(edits, cx);
19769 this.refresh_edit_prediction(true, false, window, cx);
19770 });
19771 }
19772
19773 pub fn open_selections_in_multibuffer(
19774 &mut self,
19775 _: &OpenSelectionsInMultibuffer,
19776 window: &mut Window,
19777 cx: &mut Context<Self>,
19778 ) {
19779 let multibuffer = self.buffer.read(cx);
19780
19781 let Some(buffer) = multibuffer.as_singleton() else {
19782 return;
19783 };
19784
19785 let Some(workspace) = self.workspace() else {
19786 return;
19787 };
19788
19789 let title = multibuffer.title(cx).to_string();
19790
19791 let locations = self
19792 .selections
19793 .all_anchors(cx)
19794 .iter()
19795 .map(|selection| {
19796 (
19797 buffer.clone(),
19798 (selection.start.text_anchor..selection.end.text_anchor)
19799 .to_point(buffer.read(cx)),
19800 )
19801 })
19802 .into_group_map();
19803
19804 cx.spawn_in(window, async move |_, cx| {
19805 workspace.update_in(cx, |workspace, window, cx| {
19806 Self::open_locations_in_multibuffer(
19807 workspace,
19808 locations,
19809 format!("Selections for '{title}'"),
19810 false,
19811 MultibufferSelectionMode::All,
19812 window,
19813 cx,
19814 );
19815 })
19816 })
19817 .detach();
19818 }
19819
19820 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19821 /// last highlight added will be used.
19822 ///
19823 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19824 pub fn highlight_rows<T: 'static>(
19825 &mut self,
19826 range: Range<Anchor>,
19827 color: Hsla,
19828 options: RowHighlightOptions,
19829 cx: &mut Context<Self>,
19830 ) {
19831 let snapshot = self.buffer().read(cx).snapshot(cx);
19832 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19833 let ix = row_highlights.binary_search_by(|highlight| {
19834 Ordering::Equal
19835 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19836 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19837 });
19838
19839 if let Err(mut ix) = ix {
19840 let index = post_inc(&mut self.highlight_order);
19841
19842 // If this range intersects with the preceding highlight, then merge it with
19843 // the preceding highlight. Otherwise insert a new highlight.
19844 let mut merged = false;
19845 if ix > 0 {
19846 let prev_highlight = &mut row_highlights[ix - 1];
19847 if prev_highlight
19848 .range
19849 .end
19850 .cmp(&range.start, &snapshot)
19851 .is_ge()
19852 {
19853 ix -= 1;
19854 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19855 prev_highlight.range.end = range.end;
19856 }
19857 merged = true;
19858 prev_highlight.index = index;
19859 prev_highlight.color = color;
19860 prev_highlight.options = options;
19861 }
19862 }
19863
19864 if !merged {
19865 row_highlights.insert(
19866 ix,
19867 RowHighlight {
19868 range,
19869 index,
19870 color,
19871 options,
19872 type_id: TypeId::of::<T>(),
19873 },
19874 );
19875 }
19876
19877 // If any of the following highlights intersect with this one, merge them.
19878 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19879 let highlight = &row_highlights[ix];
19880 if next_highlight
19881 .range
19882 .start
19883 .cmp(&highlight.range.end, &snapshot)
19884 .is_le()
19885 {
19886 if next_highlight
19887 .range
19888 .end
19889 .cmp(&highlight.range.end, &snapshot)
19890 .is_gt()
19891 {
19892 row_highlights[ix].range.end = next_highlight.range.end;
19893 }
19894 row_highlights.remove(ix + 1);
19895 } else {
19896 break;
19897 }
19898 }
19899 }
19900 }
19901
19902 /// Remove any highlighted row ranges of the given type that intersect the
19903 /// given ranges.
19904 pub fn remove_highlighted_rows<T: 'static>(
19905 &mut self,
19906 ranges_to_remove: Vec<Range<Anchor>>,
19907 cx: &mut Context<Self>,
19908 ) {
19909 let snapshot = self.buffer().read(cx).snapshot(cx);
19910 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19911 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19912 row_highlights.retain(|highlight| {
19913 while let Some(range_to_remove) = ranges_to_remove.peek() {
19914 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19915 Ordering::Less | Ordering::Equal => {
19916 ranges_to_remove.next();
19917 }
19918 Ordering::Greater => {
19919 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19920 Ordering::Less | Ordering::Equal => {
19921 return false;
19922 }
19923 Ordering::Greater => break,
19924 }
19925 }
19926 }
19927 }
19928
19929 true
19930 })
19931 }
19932
19933 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19934 pub fn clear_row_highlights<T: 'static>(&mut self) {
19935 self.highlighted_rows.remove(&TypeId::of::<T>());
19936 }
19937
19938 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19939 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19940 self.highlighted_rows
19941 .get(&TypeId::of::<T>())
19942 .map_or(&[] as &[_], |vec| vec.as_slice())
19943 .iter()
19944 .map(|highlight| (highlight.range.clone(), highlight.color))
19945 }
19946
19947 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19948 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19949 /// Allows to ignore certain kinds of highlights.
19950 pub fn highlighted_display_rows(
19951 &self,
19952 window: &mut Window,
19953 cx: &mut App,
19954 ) -> BTreeMap<DisplayRow, LineHighlight> {
19955 let snapshot = self.snapshot(window, cx);
19956 let mut used_highlight_orders = HashMap::default();
19957 self.highlighted_rows
19958 .iter()
19959 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19960 .fold(
19961 BTreeMap::<DisplayRow, LineHighlight>::new(),
19962 |mut unique_rows, highlight| {
19963 let start = highlight.range.start.to_display_point(&snapshot);
19964 let end = highlight.range.end.to_display_point(&snapshot);
19965 let start_row = start.row().0;
19966 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19967 && end.column() == 0
19968 {
19969 end.row().0.saturating_sub(1)
19970 } else {
19971 end.row().0
19972 };
19973 for row in start_row..=end_row {
19974 let used_index =
19975 used_highlight_orders.entry(row).or_insert(highlight.index);
19976 if highlight.index >= *used_index {
19977 *used_index = highlight.index;
19978 unique_rows.insert(
19979 DisplayRow(row),
19980 LineHighlight {
19981 include_gutter: highlight.options.include_gutter,
19982 border: None,
19983 background: highlight.color.into(),
19984 type_id: Some(highlight.type_id),
19985 },
19986 );
19987 }
19988 }
19989 unique_rows
19990 },
19991 )
19992 }
19993
19994 pub fn highlighted_display_row_for_autoscroll(
19995 &self,
19996 snapshot: &DisplaySnapshot,
19997 ) -> Option<DisplayRow> {
19998 self.highlighted_rows
19999 .values()
20000 .flat_map(|highlighted_rows| highlighted_rows.iter())
20001 .filter_map(|highlight| {
20002 if highlight.options.autoscroll {
20003 Some(highlight.range.start.to_display_point(snapshot).row())
20004 } else {
20005 None
20006 }
20007 })
20008 .min()
20009 }
20010
20011 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20012 self.highlight_background::<SearchWithinRange>(
20013 ranges,
20014 |colors| colors.colors().editor_document_highlight_read_background,
20015 cx,
20016 )
20017 }
20018
20019 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20020 self.breadcrumb_header = Some(new_header);
20021 }
20022
20023 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20024 self.clear_background_highlights::<SearchWithinRange>(cx);
20025 }
20026
20027 pub fn highlight_background<T: 'static>(
20028 &mut self,
20029 ranges: &[Range<Anchor>],
20030 color_fetcher: fn(&Theme) -> Hsla,
20031 cx: &mut Context<Self>,
20032 ) {
20033 self.background_highlights.insert(
20034 HighlightKey::Type(TypeId::of::<T>()),
20035 (color_fetcher, Arc::from(ranges)),
20036 );
20037 self.scrollbar_marker_state.dirty = true;
20038 cx.notify();
20039 }
20040
20041 pub fn highlight_background_key<T: 'static>(
20042 &mut self,
20043 key: usize,
20044 ranges: &[Range<Anchor>],
20045 color_fetcher: fn(&Theme) -> Hsla,
20046 cx: &mut Context<Self>,
20047 ) {
20048 self.background_highlights.insert(
20049 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20050 (color_fetcher, Arc::from(ranges)),
20051 );
20052 self.scrollbar_marker_state.dirty = true;
20053 cx.notify();
20054 }
20055
20056 pub fn clear_background_highlights<T: 'static>(
20057 &mut self,
20058 cx: &mut Context<Self>,
20059 ) -> Option<BackgroundHighlight> {
20060 let text_highlights = self
20061 .background_highlights
20062 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20063 if !text_highlights.1.is_empty() {
20064 self.scrollbar_marker_state.dirty = true;
20065 cx.notify();
20066 }
20067 Some(text_highlights)
20068 }
20069
20070 pub fn highlight_gutter<T: 'static>(
20071 &mut self,
20072 ranges: impl Into<Vec<Range<Anchor>>>,
20073 color_fetcher: fn(&App) -> Hsla,
20074 cx: &mut Context<Self>,
20075 ) {
20076 self.gutter_highlights
20077 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20078 cx.notify();
20079 }
20080
20081 pub fn clear_gutter_highlights<T: 'static>(
20082 &mut self,
20083 cx: &mut Context<Self>,
20084 ) -> Option<GutterHighlight> {
20085 cx.notify();
20086 self.gutter_highlights.remove(&TypeId::of::<T>())
20087 }
20088
20089 pub fn insert_gutter_highlight<T: 'static>(
20090 &mut self,
20091 range: Range<Anchor>,
20092 color_fetcher: fn(&App) -> Hsla,
20093 cx: &mut Context<Self>,
20094 ) {
20095 let snapshot = self.buffer().read(cx).snapshot(cx);
20096 let mut highlights = self
20097 .gutter_highlights
20098 .remove(&TypeId::of::<T>())
20099 .map(|(_, highlights)| highlights)
20100 .unwrap_or_default();
20101 let ix = highlights.binary_search_by(|highlight| {
20102 Ordering::Equal
20103 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20104 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20105 });
20106 if let Err(ix) = ix {
20107 highlights.insert(ix, range);
20108 }
20109 self.gutter_highlights
20110 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20111 }
20112
20113 pub fn remove_gutter_highlights<T: 'static>(
20114 &mut self,
20115 ranges_to_remove: Vec<Range<Anchor>>,
20116 cx: &mut Context<Self>,
20117 ) {
20118 let snapshot = self.buffer().read(cx).snapshot(cx);
20119 let Some((color_fetcher, mut gutter_highlights)) =
20120 self.gutter_highlights.remove(&TypeId::of::<T>())
20121 else {
20122 return;
20123 };
20124 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20125 gutter_highlights.retain(|highlight| {
20126 while let Some(range_to_remove) = ranges_to_remove.peek() {
20127 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20128 Ordering::Less | Ordering::Equal => {
20129 ranges_to_remove.next();
20130 }
20131 Ordering::Greater => {
20132 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20133 Ordering::Less | Ordering::Equal => {
20134 return false;
20135 }
20136 Ordering::Greater => break,
20137 }
20138 }
20139 }
20140 }
20141
20142 true
20143 });
20144 self.gutter_highlights
20145 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20146 }
20147
20148 #[cfg(feature = "test-support")]
20149 pub fn all_text_highlights(
20150 &self,
20151 window: &mut Window,
20152 cx: &mut Context<Self>,
20153 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20154 let snapshot = self.snapshot(window, cx);
20155 self.display_map.update(cx, |display_map, _| {
20156 display_map
20157 .all_text_highlights()
20158 .map(|highlight| {
20159 let (style, ranges) = highlight.as_ref();
20160 (
20161 *style,
20162 ranges
20163 .iter()
20164 .map(|range| range.clone().to_display_points(&snapshot))
20165 .collect(),
20166 )
20167 })
20168 .collect()
20169 })
20170 }
20171
20172 #[cfg(feature = "test-support")]
20173 pub fn all_text_background_highlights(
20174 &self,
20175 window: &mut Window,
20176 cx: &mut Context<Self>,
20177 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20178 let snapshot = self.snapshot(window, cx);
20179 let buffer = &snapshot.buffer_snapshot;
20180 let start = buffer.anchor_before(0);
20181 let end = buffer.anchor_after(buffer.len());
20182 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20183 }
20184
20185 #[cfg(any(test, feature = "test-support"))]
20186 pub fn sorted_background_highlights_in_range(
20187 &self,
20188 search_range: Range<Anchor>,
20189 display_snapshot: &DisplaySnapshot,
20190 theme: &Theme,
20191 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20192 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20193 res.sort_by(|a, b| {
20194 a.0.start
20195 .cmp(&b.0.start)
20196 .then_with(|| a.0.end.cmp(&b.0.end))
20197 .then_with(|| a.1.cmp(&b.1))
20198 });
20199 res
20200 }
20201
20202 #[cfg(feature = "test-support")]
20203 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20204 let snapshot = self.buffer().read(cx).snapshot(cx);
20205
20206 let highlights = self
20207 .background_highlights
20208 .get(&HighlightKey::Type(TypeId::of::<
20209 items::BufferSearchHighlights,
20210 >()));
20211
20212 if let Some((_color, ranges)) = highlights {
20213 ranges
20214 .iter()
20215 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20216 .collect_vec()
20217 } else {
20218 vec![]
20219 }
20220 }
20221
20222 fn document_highlights_for_position<'a>(
20223 &'a self,
20224 position: Anchor,
20225 buffer: &'a MultiBufferSnapshot,
20226 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20227 let read_highlights = self
20228 .background_highlights
20229 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20230 .map(|h| &h.1);
20231 let write_highlights = self
20232 .background_highlights
20233 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20234 .map(|h| &h.1);
20235 let left_position = position.bias_left(buffer);
20236 let right_position = position.bias_right(buffer);
20237 read_highlights
20238 .into_iter()
20239 .chain(write_highlights)
20240 .flat_map(move |ranges| {
20241 let start_ix = match ranges.binary_search_by(|probe| {
20242 let cmp = probe.end.cmp(&left_position, buffer);
20243 if cmp.is_ge() {
20244 Ordering::Greater
20245 } else {
20246 Ordering::Less
20247 }
20248 }) {
20249 Ok(i) | Err(i) => i,
20250 };
20251
20252 ranges[start_ix..]
20253 .iter()
20254 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20255 })
20256 }
20257
20258 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20259 self.background_highlights
20260 .get(&HighlightKey::Type(TypeId::of::<T>()))
20261 .is_some_and(|(_, highlights)| !highlights.is_empty())
20262 }
20263
20264 /// Returns all background highlights for a given range.
20265 ///
20266 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20267 pub fn background_highlights_in_range(
20268 &self,
20269 search_range: Range<Anchor>,
20270 display_snapshot: &DisplaySnapshot,
20271 theme: &Theme,
20272 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20273 let mut results = Vec::new();
20274 for (color_fetcher, ranges) in self.background_highlights.values() {
20275 let color = color_fetcher(theme);
20276 let start_ix = match ranges.binary_search_by(|probe| {
20277 let cmp = probe
20278 .end
20279 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20280 if cmp.is_gt() {
20281 Ordering::Greater
20282 } else {
20283 Ordering::Less
20284 }
20285 }) {
20286 Ok(i) | Err(i) => i,
20287 };
20288 for range in &ranges[start_ix..] {
20289 if range
20290 .start
20291 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20292 .is_ge()
20293 {
20294 break;
20295 }
20296
20297 let start = range.start.to_display_point(display_snapshot);
20298 let end = range.end.to_display_point(display_snapshot);
20299 results.push((start..end, color))
20300 }
20301 }
20302 results
20303 }
20304
20305 pub fn gutter_highlights_in_range(
20306 &self,
20307 search_range: Range<Anchor>,
20308 display_snapshot: &DisplaySnapshot,
20309 cx: &App,
20310 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20311 let mut results = Vec::new();
20312 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20313 let color = color_fetcher(cx);
20314 let start_ix = match ranges.binary_search_by(|probe| {
20315 let cmp = probe
20316 .end
20317 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20318 if cmp.is_gt() {
20319 Ordering::Greater
20320 } else {
20321 Ordering::Less
20322 }
20323 }) {
20324 Ok(i) | Err(i) => i,
20325 };
20326 for range in &ranges[start_ix..] {
20327 if range
20328 .start
20329 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20330 .is_ge()
20331 {
20332 break;
20333 }
20334
20335 let start = range.start.to_display_point(display_snapshot);
20336 let end = range.end.to_display_point(display_snapshot);
20337 results.push((start..end, color))
20338 }
20339 }
20340 results
20341 }
20342
20343 /// Get the text ranges corresponding to the redaction query
20344 pub fn redacted_ranges(
20345 &self,
20346 search_range: Range<Anchor>,
20347 display_snapshot: &DisplaySnapshot,
20348 cx: &App,
20349 ) -> Vec<Range<DisplayPoint>> {
20350 display_snapshot
20351 .buffer_snapshot
20352 .redacted_ranges(search_range, |file| {
20353 if let Some(file) = file {
20354 file.is_private()
20355 && EditorSettings::get(
20356 Some(SettingsLocation {
20357 worktree_id: file.worktree_id(cx),
20358 path: file.path().as_ref(),
20359 }),
20360 cx,
20361 )
20362 .redact_private_values
20363 } else {
20364 false
20365 }
20366 })
20367 .map(|range| {
20368 range.start.to_display_point(display_snapshot)
20369 ..range.end.to_display_point(display_snapshot)
20370 })
20371 .collect()
20372 }
20373
20374 pub fn highlight_text_key<T: 'static>(
20375 &mut self,
20376 key: usize,
20377 ranges: Vec<Range<Anchor>>,
20378 style: HighlightStyle,
20379 cx: &mut Context<Self>,
20380 ) {
20381 self.display_map.update(cx, |map, _| {
20382 map.highlight_text(
20383 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20384 ranges,
20385 style,
20386 );
20387 });
20388 cx.notify();
20389 }
20390
20391 pub fn highlight_text<T: 'static>(
20392 &mut self,
20393 ranges: Vec<Range<Anchor>>,
20394 style: HighlightStyle,
20395 cx: &mut Context<Self>,
20396 ) {
20397 self.display_map.update(cx, |map, _| {
20398 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20399 });
20400 cx.notify();
20401 }
20402
20403 pub(crate) fn highlight_inlays<T: 'static>(
20404 &mut self,
20405 highlights: Vec<InlayHighlight>,
20406 style: HighlightStyle,
20407 cx: &mut Context<Self>,
20408 ) {
20409 self.display_map.update(cx, |map, _| {
20410 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20411 });
20412 cx.notify();
20413 }
20414
20415 pub fn text_highlights<'a, T: 'static>(
20416 &'a self,
20417 cx: &'a App,
20418 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20419 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20420 }
20421
20422 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20423 let cleared = self
20424 .display_map
20425 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20426 if cleared {
20427 cx.notify();
20428 }
20429 }
20430
20431 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20432 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20433 && self.focus_handle.is_focused(window)
20434 }
20435
20436 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20437 self.show_cursor_when_unfocused = is_enabled;
20438 cx.notify();
20439 }
20440
20441 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20442 cx.notify();
20443 }
20444
20445 fn on_debug_session_event(
20446 &mut self,
20447 _session: Entity<Session>,
20448 event: &SessionEvent,
20449 cx: &mut Context<Self>,
20450 ) {
20451 if let SessionEvent::InvalidateInlineValue = event {
20452 self.refresh_inline_values(cx);
20453 }
20454 }
20455
20456 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20457 let Some(project) = self.project.clone() else {
20458 return;
20459 };
20460
20461 if !self.inline_value_cache.enabled {
20462 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20463 self.splice_inlays(&inlays, Vec::new(), cx);
20464 return;
20465 }
20466
20467 let current_execution_position = self
20468 .highlighted_rows
20469 .get(&TypeId::of::<ActiveDebugLine>())
20470 .and_then(|lines| lines.last().map(|line| line.range.end));
20471
20472 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20473 let inline_values = editor
20474 .update(cx, |editor, cx| {
20475 let Some(current_execution_position) = current_execution_position else {
20476 return Some(Task::ready(Ok(Vec::new())));
20477 };
20478
20479 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20480 let snapshot = buffer.snapshot(cx);
20481
20482 let excerpt = snapshot.excerpt_containing(
20483 current_execution_position..current_execution_position,
20484 )?;
20485
20486 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20487 })?;
20488
20489 let range =
20490 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20491
20492 project.inline_values(buffer, range, cx)
20493 })
20494 .ok()
20495 .flatten()?
20496 .await
20497 .context("refreshing debugger inlays")
20498 .log_err()?;
20499
20500 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20501
20502 for (buffer_id, inline_value) in inline_values
20503 .into_iter()
20504 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20505 {
20506 buffer_inline_values
20507 .entry(buffer_id)
20508 .or_default()
20509 .push(inline_value);
20510 }
20511
20512 editor
20513 .update(cx, |editor, cx| {
20514 let snapshot = editor.buffer.read(cx).snapshot(cx);
20515 let mut new_inlays = Vec::default();
20516
20517 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20518 let buffer_id = buffer_snapshot.remote_id();
20519 buffer_inline_values
20520 .get(&buffer_id)
20521 .into_iter()
20522 .flatten()
20523 .for_each(|hint| {
20524 let inlay = Inlay::debugger(
20525 post_inc(&mut editor.next_inlay_id),
20526 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20527 hint.text(),
20528 );
20529 if !inlay.text.chars().contains(&'\n') {
20530 new_inlays.push(inlay);
20531 }
20532 });
20533 }
20534
20535 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20536 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20537
20538 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20539 })
20540 .ok()?;
20541 Some(())
20542 });
20543 }
20544
20545 fn on_buffer_event(
20546 &mut self,
20547 multibuffer: &Entity<MultiBuffer>,
20548 event: &multi_buffer::Event,
20549 window: &mut Window,
20550 cx: &mut Context<Self>,
20551 ) {
20552 match event {
20553 multi_buffer::Event::Edited {
20554 singleton_buffer_edited,
20555 edited_buffer,
20556 } => {
20557 self.scrollbar_marker_state.dirty = true;
20558 self.active_indent_guides_state.dirty = true;
20559 self.refresh_active_diagnostics(cx);
20560 self.refresh_code_actions(window, cx);
20561 self.refresh_selected_text_highlights(true, window, cx);
20562 self.refresh_single_line_folds(window, cx);
20563 refresh_matching_bracket_highlights(self, window, cx);
20564 if self.has_active_edit_prediction() {
20565 self.update_visible_edit_prediction(window, cx);
20566 }
20567 if let Some(project) = self.project.as_ref()
20568 && let Some(edited_buffer) = edited_buffer
20569 {
20570 project.update(cx, |project, cx| {
20571 self.registered_buffers
20572 .entry(edited_buffer.read(cx).remote_id())
20573 .or_insert_with(|| {
20574 project.register_buffer_with_language_servers(edited_buffer, cx)
20575 });
20576 });
20577 }
20578 cx.emit(EditorEvent::BufferEdited);
20579 cx.emit(SearchEvent::MatchesInvalidated);
20580
20581 if let Some(buffer) = edited_buffer {
20582 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20583 }
20584
20585 if *singleton_buffer_edited {
20586 if let Some(buffer) = edited_buffer
20587 && buffer.read(cx).file().is_none()
20588 {
20589 cx.emit(EditorEvent::TitleChanged);
20590 }
20591 if let Some(project) = &self.project {
20592 #[allow(clippy::mutable_key_type)]
20593 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20594 multibuffer
20595 .all_buffers()
20596 .into_iter()
20597 .filter_map(|buffer| {
20598 buffer.update(cx, |buffer, cx| {
20599 let language = buffer.language()?;
20600 let should_discard = project.update(cx, |project, cx| {
20601 project.is_local()
20602 && !project.has_language_servers_for(buffer, cx)
20603 });
20604 should_discard.not().then_some(language.clone())
20605 })
20606 })
20607 .collect::<HashSet<_>>()
20608 });
20609 if !languages_affected.is_empty() {
20610 self.refresh_inlay_hints(
20611 InlayHintRefreshReason::BufferEdited(languages_affected),
20612 cx,
20613 );
20614 }
20615 }
20616 }
20617
20618 let Some(project) = &self.project else { return };
20619 let (telemetry, is_via_ssh) = {
20620 let project = project.read(cx);
20621 let telemetry = project.client().telemetry().clone();
20622 let is_via_ssh = project.is_via_remote_server();
20623 (telemetry, is_via_ssh)
20624 };
20625 refresh_linked_ranges(self, window, cx);
20626 telemetry.log_edit_event("editor", is_via_ssh);
20627 }
20628 multi_buffer::Event::ExcerptsAdded {
20629 buffer,
20630 predecessor,
20631 excerpts,
20632 } => {
20633 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20634 let buffer_id = buffer.read(cx).remote_id();
20635 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20636 && let Some(project) = &self.project
20637 {
20638 update_uncommitted_diff_for_buffer(
20639 cx.entity(),
20640 project,
20641 [buffer.clone()],
20642 self.buffer.clone(),
20643 cx,
20644 )
20645 .detach();
20646 }
20647 if self.active_diagnostics != ActiveDiagnostic::All {
20648 self.update_lsp_data(false, Some(buffer_id), window, cx);
20649 }
20650 cx.emit(EditorEvent::ExcerptsAdded {
20651 buffer: buffer.clone(),
20652 predecessor: *predecessor,
20653 excerpts: excerpts.clone(),
20654 });
20655 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20656 }
20657 multi_buffer::Event::ExcerptsRemoved {
20658 ids,
20659 removed_buffer_ids,
20660 } => {
20661 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20662 let buffer = self.buffer.read(cx);
20663 self.registered_buffers
20664 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20665 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20666 cx.emit(EditorEvent::ExcerptsRemoved {
20667 ids: ids.clone(),
20668 removed_buffer_ids: removed_buffer_ids.clone(),
20669 });
20670 }
20671 multi_buffer::Event::ExcerptsEdited {
20672 excerpt_ids,
20673 buffer_ids,
20674 } => {
20675 self.display_map.update(cx, |map, cx| {
20676 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20677 });
20678 cx.emit(EditorEvent::ExcerptsEdited {
20679 ids: excerpt_ids.clone(),
20680 });
20681 }
20682 multi_buffer::Event::ExcerptsExpanded { ids } => {
20683 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20684 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20685 }
20686 multi_buffer::Event::Reparsed(buffer_id) => {
20687 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20688 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20689
20690 cx.emit(EditorEvent::Reparsed(*buffer_id));
20691 }
20692 multi_buffer::Event::DiffHunksToggled => {
20693 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20694 }
20695 multi_buffer::Event::LanguageChanged(buffer_id) => {
20696 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20697 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20698 cx.emit(EditorEvent::Reparsed(*buffer_id));
20699 cx.notify();
20700 }
20701 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20702 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20703 multi_buffer::Event::FileHandleChanged
20704 | multi_buffer::Event::Reloaded
20705 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20706 multi_buffer::Event::DiagnosticsUpdated => {
20707 self.update_diagnostics_state(window, cx);
20708 }
20709 _ => {}
20710 };
20711 }
20712
20713 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20714 if !self.diagnostics_enabled() {
20715 return;
20716 }
20717 self.refresh_active_diagnostics(cx);
20718 self.refresh_inline_diagnostics(true, window, cx);
20719 self.scrollbar_marker_state.dirty = true;
20720 cx.notify();
20721 }
20722
20723 pub fn start_temporary_diff_override(&mut self) {
20724 self.load_diff_task.take();
20725 self.temporary_diff_override = true;
20726 }
20727
20728 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20729 self.temporary_diff_override = false;
20730 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20731 self.buffer.update(cx, |buffer, cx| {
20732 buffer.set_all_diff_hunks_collapsed(cx);
20733 });
20734
20735 if let Some(project) = self.project.clone() {
20736 self.load_diff_task = Some(
20737 update_uncommitted_diff_for_buffer(
20738 cx.entity(),
20739 &project,
20740 self.buffer.read(cx).all_buffers(),
20741 self.buffer.clone(),
20742 cx,
20743 )
20744 .shared(),
20745 );
20746 }
20747 }
20748
20749 fn on_display_map_changed(
20750 &mut self,
20751 _: Entity<DisplayMap>,
20752 _: &mut Window,
20753 cx: &mut Context<Self>,
20754 ) {
20755 cx.notify();
20756 }
20757
20758 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20759 if self.diagnostics_enabled() {
20760 let new_severity = EditorSettings::get_global(cx)
20761 .diagnostics_max_severity
20762 .unwrap_or(DiagnosticSeverity::Hint);
20763 self.set_max_diagnostics_severity(new_severity, cx);
20764 }
20765 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20766 self.update_edit_prediction_settings(cx);
20767 self.refresh_edit_prediction(true, false, window, cx);
20768 self.refresh_inline_values(cx);
20769 self.refresh_inlay_hints(
20770 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20771 self.selections.newest_anchor().head(),
20772 &self.buffer.read(cx).snapshot(cx),
20773 cx,
20774 )),
20775 cx,
20776 );
20777
20778 let old_cursor_shape = self.cursor_shape;
20779 let old_show_breadcrumbs = self.show_breadcrumbs;
20780
20781 {
20782 let editor_settings = EditorSettings::get_global(cx);
20783 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20784 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20785 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20786 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20787 }
20788
20789 if old_cursor_shape != self.cursor_shape {
20790 cx.emit(EditorEvent::CursorShapeChanged);
20791 }
20792
20793 if old_show_breadcrumbs != self.show_breadcrumbs {
20794 cx.emit(EditorEvent::BreadcrumbsChanged);
20795 }
20796
20797 let project_settings = ProjectSettings::get_global(cx);
20798 self.serialize_dirty_buffers =
20799 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20800
20801 if self.mode.is_full() {
20802 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20803 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20804 if self.show_inline_diagnostics != show_inline_diagnostics {
20805 self.show_inline_diagnostics = show_inline_diagnostics;
20806 self.refresh_inline_diagnostics(false, window, cx);
20807 }
20808
20809 if self.git_blame_inline_enabled != inline_blame_enabled {
20810 self.toggle_git_blame_inline_internal(false, window, cx);
20811 }
20812
20813 let minimap_settings = EditorSettings::get_global(cx).minimap;
20814 if self.minimap_visibility != MinimapVisibility::Disabled {
20815 if self.minimap_visibility.settings_visibility()
20816 != minimap_settings.minimap_enabled()
20817 {
20818 self.set_minimap_visibility(
20819 MinimapVisibility::for_mode(self.mode(), cx),
20820 window,
20821 cx,
20822 );
20823 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20824 minimap_entity.update(cx, |minimap_editor, cx| {
20825 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20826 })
20827 }
20828 }
20829 }
20830
20831 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20832 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20833 }) {
20834 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20835 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20836 }
20837 self.refresh_colors(false, None, window, cx);
20838 }
20839
20840 cx.notify();
20841 }
20842
20843 pub fn set_searchable(&mut self, searchable: bool) {
20844 self.searchable = searchable;
20845 }
20846
20847 pub fn searchable(&self) -> bool {
20848 self.searchable
20849 }
20850
20851 fn open_proposed_changes_editor(
20852 &mut self,
20853 _: &OpenProposedChangesEditor,
20854 window: &mut Window,
20855 cx: &mut Context<Self>,
20856 ) {
20857 let Some(workspace) = self.workspace() else {
20858 cx.propagate();
20859 return;
20860 };
20861
20862 let selections = self.selections.all::<usize>(cx);
20863 let multi_buffer = self.buffer.read(cx);
20864 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20865 let mut new_selections_by_buffer = HashMap::default();
20866 for selection in selections {
20867 for (buffer, range, _) in
20868 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20869 {
20870 let mut range = range.to_point(buffer);
20871 range.start.column = 0;
20872 range.end.column = buffer.line_len(range.end.row);
20873 new_selections_by_buffer
20874 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20875 .or_insert(Vec::new())
20876 .push(range)
20877 }
20878 }
20879
20880 let proposed_changes_buffers = new_selections_by_buffer
20881 .into_iter()
20882 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20883 .collect::<Vec<_>>();
20884 let proposed_changes_editor = cx.new(|cx| {
20885 ProposedChangesEditor::new(
20886 "Proposed changes",
20887 proposed_changes_buffers,
20888 self.project.clone(),
20889 window,
20890 cx,
20891 )
20892 });
20893
20894 window.defer(cx, move |window, cx| {
20895 workspace.update(cx, |workspace, cx| {
20896 workspace.active_pane().update(cx, |pane, cx| {
20897 pane.add_item(
20898 Box::new(proposed_changes_editor),
20899 true,
20900 true,
20901 None,
20902 window,
20903 cx,
20904 );
20905 });
20906 });
20907 });
20908 }
20909
20910 pub fn open_excerpts_in_split(
20911 &mut self,
20912 _: &OpenExcerptsSplit,
20913 window: &mut Window,
20914 cx: &mut Context<Self>,
20915 ) {
20916 self.open_excerpts_common(None, true, window, cx)
20917 }
20918
20919 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20920 self.open_excerpts_common(None, false, window, cx)
20921 }
20922
20923 fn open_excerpts_common(
20924 &mut self,
20925 jump_data: Option<JumpData>,
20926 split: bool,
20927 window: &mut Window,
20928 cx: &mut Context<Self>,
20929 ) {
20930 let Some(workspace) = self.workspace() else {
20931 cx.propagate();
20932 return;
20933 };
20934
20935 if self.buffer.read(cx).is_singleton() {
20936 cx.propagate();
20937 return;
20938 }
20939
20940 let mut new_selections_by_buffer = HashMap::default();
20941 match &jump_data {
20942 Some(JumpData::MultiBufferPoint {
20943 excerpt_id,
20944 position,
20945 anchor,
20946 line_offset_from_top,
20947 }) => {
20948 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20949 if let Some(buffer) = multi_buffer_snapshot
20950 .buffer_id_for_excerpt(*excerpt_id)
20951 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20952 {
20953 let buffer_snapshot = buffer.read(cx).snapshot();
20954 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20955 language::ToPoint::to_point(anchor, &buffer_snapshot)
20956 } else {
20957 buffer_snapshot.clip_point(*position, Bias::Left)
20958 };
20959 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20960 new_selections_by_buffer.insert(
20961 buffer,
20962 (
20963 vec![jump_to_offset..jump_to_offset],
20964 Some(*line_offset_from_top),
20965 ),
20966 );
20967 }
20968 }
20969 Some(JumpData::MultiBufferRow {
20970 row,
20971 line_offset_from_top,
20972 }) => {
20973 let point = MultiBufferPoint::new(row.0, 0);
20974 if let Some((buffer, buffer_point, _)) =
20975 self.buffer.read(cx).point_to_buffer_point(point, cx)
20976 {
20977 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20978 new_selections_by_buffer
20979 .entry(buffer)
20980 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20981 .0
20982 .push(buffer_offset..buffer_offset)
20983 }
20984 }
20985 None => {
20986 let selections = self.selections.all::<usize>(cx);
20987 let multi_buffer = self.buffer.read(cx);
20988 for selection in selections {
20989 for (snapshot, range, _, anchor) in multi_buffer
20990 .snapshot(cx)
20991 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20992 {
20993 if let Some(anchor) = anchor {
20994 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20995 else {
20996 continue;
20997 };
20998 let offset = text::ToOffset::to_offset(
20999 &anchor.text_anchor,
21000 &buffer_handle.read(cx).snapshot(),
21001 );
21002 let range = offset..offset;
21003 new_selections_by_buffer
21004 .entry(buffer_handle)
21005 .or_insert((Vec::new(), None))
21006 .0
21007 .push(range)
21008 } else {
21009 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21010 else {
21011 continue;
21012 };
21013 new_selections_by_buffer
21014 .entry(buffer_handle)
21015 .or_insert((Vec::new(), None))
21016 .0
21017 .push(range)
21018 }
21019 }
21020 }
21021 }
21022 }
21023
21024 new_selections_by_buffer
21025 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21026
21027 if new_selections_by_buffer.is_empty() {
21028 return;
21029 }
21030
21031 // We defer the pane interaction because we ourselves are a workspace item
21032 // and activating a new item causes the pane to call a method on us reentrantly,
21033 // which panics if we're on the stack.
21034 window.defer(cx, move |window, cx| {
21035 workspace.update(cx, |workspace, cx| {
21036 let pane = if split {
21037 workspace.adjacent_pane(window, cx)
21038 } else {
21039 workspace.active_pane().clone()
21040 };
21041
21042 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21043 let editor = buffer
21044 .read(cx)
21045 .file()
21046 .is_none()
21047 .then(|| {
21048 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21049 // so `workspace.open_project_item` will never find them, always opening a new editor.
21050 // Instead, we try to activate the existing editor in the pane first.
21051 let (editor, pane_item_index) =
21052 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21053 let editor = item.downcast::<Editor>()?;
21054 let singleton_buffer =
21055 editor.read(cx).buffer().read(cx).as_singleton()?;
21056 if singleton_buffer == buffer {
21057 Some((editor, i))
21058 } else {
21059 None
21060 }
21061 })?;
21062 pane.update(cx, |pane, cx| {
21063 pane.activate_item(pane_item_index, true, true, window, cx)
21064 });
21065 Some(editor)
21066 })
21067 .flatten()
21068 .unwrap_or_else(|| {
21069 workspace.open_project_item::<Self>(
21070 pane.clone(),
21071 buffer,
21072 true,
21073 true,
21074 window,
21075 cx,
21076 )
21077 });
21078
21079 editor.update(cx, |editor, cx| {
21080 let autoscroll = match scroll_offset {
21081 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21082 None => Autoscroll::newest(),
21083 };
21084 let nav_history = editor.nav_history.take();
21085 editor.change_selections(
21086 SelectionEffects::scroll(autoscroll),
21087 window,
21088 cx,
21089 |s| {
21090 s.select_ranges(ranges);
21091 },
21092 );
21093 editor.nav_history = nav_history;
21094 });
21095 }
21096 })
21097 });
21098 }
21099
21100 // For now, don't allow opening excerpts in buffers that aren't backed by
21101 // regular project files.
21102 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21103 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21104 }
21105
21106 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21107 let snapshot = self.buffer.read(cx).read(cx);
21108 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21109 Some(
21110 ranges
21111 .iter()
21112 .map(move |range| {
21113 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21114 })
21115 .collect(),
21116 )
21117 }
21118
21119 fn selection_replacement_ranges(
21120 &self,
21121 range: Range<OffsetUtf16>,
21122 cx: &mut App,
21123 ) -> Vec<Range<OffsetUtf16>> {
21124 let selections = self.selections.all::<OffsetUtf16>(cx);
21125 let newest_selection = selections
21126 .iter()
21127 .max_by_key(|selection| selection.id)
21128 .unwrap();
21129 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21130 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21131 let snapshot = self.buffer.read(cx).read(cx);
21132 selections
21133 .into_iter()
21134 .map(|mut selection| {
21135 selection.start.0 =
21136 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21137 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21138 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21139 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21140 })
21141 .collect()
21142 }
21143
21144 fn report_editor_event(
21145 &self,
21146 reported_event: ReportEditorEvent,
21147 file_extension: Option<String>,
21148 cx: &App,
21149 ) {
21150 if cfg!(any(test, feature = "test-support")) {
21151 return;
21152 }
21153
21154 let Some(project) = &self.project else { return };
21155
21156 // If None, we are in a file without an extension
21157 let file = self
21158 .buffer
21159 .read(cx)
21160 .as_singleton()
21161 .and_then(|b| b.read(cx).file());
21162 let file_extension = file_extension.or(file
21163 .as_ref()
21164 .and_then(|file| Path::new(file.file_name(cx)).extension())
21165 .and_then(|e| e.to_str())
21166 .map(|a| a.to_string()));
21167
21168 let vim_mode = vim_enabled(cx);
21169
21170 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21171 let copilot_enabled = edit_predictions_provider
21172 == language::language_settings::EditPredictionProvider::Copilot;
21173 let copilot_enabled_for_language = self
21174 .buffer
21175 .read(cx)
21176 .language_settings(cx)
21177 .show_edit_predictions;
21178
21179 let project = project.read(cx);
21180 let event_type = reported_event.event_type();
21181
21182 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21183 telemetry::event!(
21184 event_type,
21185 type = if auto_saved {"autosave"} else {"manual"},
21186 file_extension,
21187 vim_mode,
21188 copilot_enabled,
21189 copilot_enabled_for_language,
21190 edit_predictions_provider,
21191 is_via_ssh = project.is_via_remote_server(),
21192 );
21193 } else {
21194 telemetry::event!(
21195 event_type,
21196 file_extension,
21197 vim_mode,
21198 copilot_enabled,
21199 copilot_enabled_for_language,
21200 edit_predictions_provider,
21201 is_via_ssh = project.is_via_remote_server(),
21202 );
21203 };
21204 }
21205
21206 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21207 /// with each line being an array of {text, highlight} objects.
21208 fn copy_highlight_json(
21209 &mut self,
21210 _: &CopyHighlightJson,
21211 window: &mut Window,
21212 cx: &mut Context<Self>,
21213 ) {
21214 #[derive(Serialize)]
21215 struct Chunk<'a> {
21216 text: String,
21217 highlight: Option<&'a str>,
21218 }
21219
21220 let snapshot = self.buffer.read(cx).snapshot(cx);
21221 let range = self
21222 .selected_text_range(false, window, cx)
21223 .and_then(|selection| {
21224 if selection.range.is_empty() {
21225 None
21226 } else {
21227 Some(selection.range)
21228 }
21229 })
21230 .unwrap_or_else(|| 0..snapshot.len());
21231
21232 let chunks = snapshot.chunks(range, true);
21233 let mut lines = Vec::new();
21234 let mut line: VecDeque<Chunk> = VecDeque::new();
21235
21236 let Some(style) = self.style.as_ref() else {
21237 return;
21238 };
21239
21240 for chunk in chunks {
21241 let highlight = chunk
21242 .syntax_highlight_id
21243 .and_then(|id| id.name(&style.syntax));
21244 let mut chunk_lines = chunk.text.split('\n').peekable();
21245 while let Some(text) = chunk_lines.next() {
21246 let mut merged_with_last_token = false;
21247 if let Some(last_token) = line.back_mut()
21248 && last_token.highlight == highlight
21249 {
21250 last_token.text.push_str(text);
21251 merged_with_last_token = true;
21252 }
21253
21254 if !merged_with_last_token {
21255 line.push_back(Chunk {
21256 text: text.into(),
21257 highlight,
21258 });
21259 }
21260
21261 if chunk_lines.peek().is_some() {
21262 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21263 line.pop_front();
21264 }
21265 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21266 line.pop_back();
21267 }
21268
21269 lines.push(mem::take(&mut line));
21270 }
21271 }
21272 }
21273
21274 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21275 return;
21276 };
21277 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21278 }
21279
21280 pub fn open_context_menu(
21281 &mut self,
21282 _: &OpenContextMenu,
21283 window: &mut Window,
21284 cx: &mut Context<Self>,
21285 ) {
21286 self.request_autoscroll(Autoscroll::newest(), cx);
21287 let position = self.selections.newest_display(cx).start;
21288 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21289 }
21290
21291 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21292 &self.inlay_hint_cache
21293 }
21294
21295 pub fn replay_insert_event(
21296 &mut self,
21297 text: &str,
21298 relative_utf16_range: Option<Range<isize>>,
21299 window: &mut Window,
21300 cx: &mut Context<Self>,
21301 ) {
21302 if !self.input_enabled {
21303 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21304 return;
21305 }
21306 if let Some(relative_utf16_range) = relative_utf16_range {
21307 let selections = self.selections.all::<OffsetUtf16>(cx);
21308 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21309 let new_ranges = selections.into_iter().map(|range| {
21310 let start = OffsetUtf16(
21311 range
21312 .head()
21313 .0
21314 .saturating_add_signed(relative_utf16_range.start),
21315 );
21316 let end = OffsetUtf16(
21317 range
21318 .head()
21319 .0
21320 .saturating_add_signed(relative_utf16_range.end),
21321 );
21322 start..end
21323 });
21324 s.select_ranges(new_ranges);
21325 });
21326 }
21327
21328 self.handle_input(text, window, cx);
21329 }
21330
21331 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21332 let Some(provider) = self.semantics_provider.as_ref() else {
21333 return false;
21334 };
21335
21336 let mut supports = false;
21337 self.buffer().update(cx, |this, cx| {
21338 this.for_each_buffer(|buffer| {
21339 supports |= provider.supports_inlay_hints(buffer, cx);
21340 });
21341 });
21342
21343 supports
21344 }
21345
21346 pub fn is_focused(&self, window: &Window) -> bool {
21347 self.focus_handle.is_focused(window)
21348 }
21349
21350 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21351 cx.emit(EditorEvent::Focused);
21352
21353 if let Some(descendant) = self
21354 .last_focused_descendant
21355 .take()
21356 .and_then(|descendant| descendant.upgrade())
21357 {
21358 window.focus(&descendant);
21359 } else {
21360 if let Some(blame) = self.blame.as_ref() {
21361 blame.update(cx, GitBlame::focus)
21362 }
21363
21364 self.blink_manager.update(cx, BlinkManager::enable);
21365 self.show_cursor_names(window, cx);
21366 self.buffer.update(cx, |buffer, cx| {
21367 buffer.finalize_last_transaction(cx);
21368 if self.leader_id.is_none() {
21369 buffer.set_active_selections(
21370 &self.selections.disjoint_anchors_arc(),
21371 self.selections.line_mode(),
21372 self.cursor_shape,
21373 cx,
21374 );
21375 }
21376 });
21377 }
21378 }
21379
21380 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21381 cx.emit(EditorEvent::FocusedIn)
21382 }
21383
21384 fn handle_focus_out(
21385 &mut self,
21386 event: FocusOutEvent,
21387 _window: &mut Window,
21388 cx: &mut Context<Self>,
21389 ) {
21390 if event.blurred != self.focus_handle {
21391 self.last_focused_descendant = Some(event.blurred);
21392 }
21393 self.selection_drag_state = SelectionDragState::None;
21394 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21395 }
21396
21397 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21398 self.blink_manager.update(cx, BlinkManager::disable);
21399 self.buffer
21400 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21401
21402 if let Some(blame) = self.blame.as_ref() {
21403 blame.update(cx, GitBlame::blur)
21404 }
21405 if !self.hover_state.focused(window, cx) {
21406 hide_hover(self, cx);
21407 }
21408 if !self
21409 .context_menu
21410 .borrow()
21411 .as_ref()
21412 .is_some_and(|context_menu| context_menu.focused(window, cx))
21413 {
21414 self.hide_context_menu(window, cx);
21415 }
21416 self.discard_edit_prediction(false, cx);
21417 cx.emit(EditorEvent::Blurred);
21418 cx.notify();
21419 }
21420
21421 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21422 let mut pending: String = window
21423 .pending_input_keystrokes()
21424 .into_iter()
21425 .flatten()
21426 .filter_map(|keystroke| {
21427 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21428 keystroke.key_char.clone()
21429 } else {
21430 None
21431 }
21432 })
21433 .collect();
21434
21435 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21436 pending = "".to_string();
21437 }
21438
21439 let existing_pending = self
21440 .text_highlights::<PendingInput>(cx)
21441 .map(|(_, ranges)| ranges.to_vec());
21442 if existing_pending.is_none() && pending.is_empty() {
21443 return;
21444 }
21445 let transaction =
21446 self.transact(window, cx, |this, window, cx| {
21447 let selections = this.selections.all::<usize>(cx);
21448 let edits = selections
21449 .iter()
21450 .map(|selection| (selection.end..selection.end, pending.clone()));
21451 this.edit(edits, cx);
21452 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21453 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21454 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21455 }));
21456 });
21457 if let Some(existing_ranges) = existing_pending {
21458 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21459 this.edit(edits, cx);
21460 }
21461 });
21462
21463 let snapshot = self.snapshot(window, cx);
21464 let ranges = self
21465 .selections
21466 .all::<usize>(cx)
21467 .into_iter()
21468 .map(|selection| {
21469 snapshot.buffer_snapshot.anchor_after(selection.end)
21470 ..snapshot
21471 .buffer_snapshot
21472 .anchor_before(selection.end + pending.len())
21473 })
21474 .collect();
21475
21476 if pending.is_empty() {
21477 self.clear_highlights::<PendingInput>(cx);
21478 } else {
21479 self.highlight_text::<PendingInput>(
21480 ranges,
21481 HighlightStyle {
21482 underline: Some(UnderlineStyle {
21483 thickness: px(1.),
21484 color: None,
21485 wavy: false,
21486 }),
21487 ..Default::default()
21488 },
21489 cx,
21490 );
21491 }
21492
21493 self.ime_transaction = self.ime_transaction.or(transaction);
21494 if let Some(transaction) = self.ime_transaction {
21495 self.buffer.update(cx, |buffer, cx| {
21496 buffer.group_until_transaction(transaction, cx);
21497 });
21498 }
21499
21500 if self.text_highlights::<PendingInput>(cx).is_none() {
21501 self.ime_transaction.take();
21502 }
21503 }
21504
21505 pub fn register_action_renderer(
21506 &mut self,
21507 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21508 ) -> Subscription {
21509 let id = self.next_editor_action_id.post_inc();
21510 self.editor_actions
21511 .borrow_mut()
21512 .insert(id, Box::new(listener));
21513
21514 let editor_actions = self.editor_actions.clone();
21515 Subscription::new(move || {
21516 editor_actions.borrow_mut().remove(&id);
21517 })
21518 }
21519
21520 pub fn register_action<A: Action>(
21521 &mut self,
21522 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21523 ) -> Subscription {
21524 let id = self.next_editor_action_id.post_inc();
21525 let listener = Arc::new(listener);
21526 self.editor_actions.borrow_mut().insert(
21527 id,
21528 Box::new(move |_, window, _| {
21529 let listener = listener.clone();
21530 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21531 let action = action.downcast_ref().unwrap();
21532 if phase == DispatchPhase::Bubble {
21533 listener(action, window, cx)
21534 }
21535 })
21536 }),
21537 );
21538
21539 let editor_actions = self.editor_actions.clone();
21540 Subscription::new(move || {
21541 editor_actions.borrow_mut().remove(&id);
21542 })
21543 }
21544
21545 pub fn file_header_size(&self) -> u32 {
21546 FILE_HEADER_HEIGHT
21547 }
21548
21549 pub fn restore(
21550 &mut self,
21551 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21552 window: &mut Window,
21553 cx: &mut Context<Self>,
21554 ) {
21555 let workspace = self.workspace();
21556 let project = self.project();
21557 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21558 let mut tasks = Vec::new();
21559 for (buffer_id, changes) in revert_changes {
21560 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21561 buffer.update(cx, |buffer, cx| {
21562 buffer.edit(
21563 changes
21564 .into_iter()
21565 .map(|(range, text)| (range, text.to_string())),
21566 None,
21567 cx,
21568 );
21569 });
21570
21571 if let Some(project) =
21572 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21573 {
21574 project.update(cx, |project, cx| {
21575 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21576 })
21577 }
21578 }
21579 }
21580 tasks
21581 });
21582 cx.spawn_in(window, async move |_, cx| {
21583 for (buffer, task) in save_tasks {
21584 let result = task.await;
21585 if result.is_err() {
21586 let Some(path) = buffer
21587 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21588 .ok()
21589 else {
21590 continue;
21591 };
21592 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21593 let Some(task) = cx
21594 .update_window_entity(workspace, |workspace, window, cx| {
21595 workspace
21596 .open_path_preview(path, None, false, false, false, window, cx)
21597 })
21598 .ok()
21599 else {
21600 continue;
21601 };
21602 task.await.log_err();
21603 }
21604 }
21605 }
21606 })
21607 .detach();
21608 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21609 selections.refresh()
21610 });
21611 }
21612
21613 pub fn to_pixel_point(
21614 &self,
21615 source: multi_buffer::Anchor,
21616 editor_snapshot: &EditorSnapshot,
21617 window: &mut Window,
21618 ) -> Option<gpui::Point<Pixels>> {
21619 let source_point = source.to_display_point(editor_snapshot);
21620 self.display_to_pixel_point(source_point, editor_snapshot, window)
21621 }
21622
21623 pub fn display_to_pixel_point(
21624 &self,
21625 source: DisplayPoint,
21626 editor_snapshot: &EditorSnapshot,
21627 window: &mut Window,
21628 ) -> Option<gpui::Point<Pixels>> {
21629 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21630 let text_layout_details = self.text_layout_details(window);
21631 let scroll_top = text_layout_details
21632 .scroll_anchor
21633 .scroll_position(editor_snapshot)
21634 .y;
21635
21636 if source.row().as_f32() < scroll_top.floor() {
21637 return None;
21638 }
21639 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21640 let source_y = line_height * (source.row().as_f32() - scroll_top);
21641 Some(gpui::Point::new(source_x, source_y))
21642 }
21643
21644 pub fn has_visible_completions_menu(&self) -> bool {
21645 !self.edit_prediction_preview_is_active()
21646 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21647 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21648 })
21649 }
21650
21651 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21652 if self.mode.is_minimap() {
21653 return;
21654 }
21655 self.addons
21656 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21657 }
21658
21659 pub fn unregister_addon<T: Addon>(&mut self) {
21660 self.addons.remove(&std::any::TypeId::of::<T>());
21661 }
21662
21663 pub fn addon<T: Addon>(&self) -> Option<&T> {
21664 let type_id = std::any::TypeId::of::<T>();
21665 self.addons
21666 .get(&type_id)
21667 .and_then(|item| item.to_any().downcast_ref::<T>())
21668 }
21669
21670 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21671 let type_id = std::any::TypeId::of::<T>();
21672 self.addons
21673 .get_mut(&type_id)
21674 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21675 }
21676
21677 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21678 let text_layout_details = self.text_layout_details(window);
21679 let style = &text_layout_details.editor_style;
21680 let font_id = window.text_system().resolve_font(&style.text.font());
21681 let font_size = style.text.font_size.to_pixels(window.rem_size());
21682 let line_height = style.text.line_height_in_pixels(window.rem_size());
21683 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21684 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21685
21686 CharacterDimensions {
21687 em_width,
21688 em_advance,
21689 line_height,
21690 }
21691 }
21692
21693 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21694 self.load_diff_task.clone()
21695 }
21696
21697 fn read_metadata_from_db(
21698 &mut self,
21699 item_id: u64,
21700 workspace_id: WorkspaceId,
21701 window: &mut Window,
21702 cx: &mut Context<Editor>,
21703 ) {
21704 if self.is_singleton(cx)
21705 && !self.mode.is_minimap()
21706 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21707 {
21708 let buffer_snapshot = OnceCell::new();
21709
21710 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21711 && !folds.is_empty()
21712 {
21713 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21714 self.fold_ranges(
21715 folds
21716 .into_iter()
21717 .map(|(start, end)| {
21718 snapshot.clip_offset(start, Bias::Left)
21719 ..snapshot.clip_offset(end, Bias::Right)
21720 })
21721 .collect(),
21722 false,
21723 window,
21724 cx,
21725 );
21726 }
21727
21728 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21729 && !selections.is_empty()
21730 {
21731 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21732 // skip adding the initial selection to selection history
21733 self.selection_history.mode = SelectionHistoryMode::Skipping;
21734 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21735 s.select_ranges(selections.into_iter().map(|(start, end)| {
21736 snapshot.clip_offset(start, Bias::Left)
21737 ..snapshot.clip_offset(end, Bias::Right)
21738 }));
21739 });
21740 self.selection_history.mode = SelectionHistoryMode::Normal;
21741 };
21742 }
21743
21744 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21745 }
21746
21747 fn update_lsp_data(
21748 &mut self,
21749 ignore_cache: bool,
21750 for_buffer: Option<BufferId>,
21751 window: &mut Window,
21752 cx: &mut Context<'_, Self>,
21753 ) {
21754 self.pull_diagnostics(for_buffer, window, cx);
21755 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21756 }
21757}
21758
21759fn edit_for_markdown_paste<'a>(
21760 buffer: &MultiBufferSnapshot,
21761 range: Range<usize>,
21762 to_insert: &'a str,
21763 url: Option<url::Url>,
21764) -> (Range<usize>, Cow<'a, str>) {
21765 if url.is_none() {
21766 return (range, Cow::Borrowed(to_insert));
21767 };
21768
21769 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21770
21771 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21772 Cow::Borrowed(to_insert)
21773 } else {
21774 Cow::Owned(format!("[{old_text}]({to_insert})"))
21775 };
21776 (range, new_text)
21777}
21778
21779fn vim_enabled(cx: &App) -> bool {
21780 vim_mode_setting::VimModeSetting::try_get(cx)
21781 .map(|vim_mode| vim_mode.0)
21782 .unwrap_or(false)
21783}
21784
21785fn process_completion_for_edit(
21786 completion: &Completion,
21787 intent: CompletionIntent,
21788 buffer: &Entity<Buffer>,
21789 cursor_position: &text::Anchor,
21790 cx: &mut Context<Editor>,
21791) -> CompletionEdit {
21792 let buffer = buffer.read(cx);
21793 let buffer_snapshot = buffer.snapshot();
21794 let (snippet, new_text) = if completion.is_snippet() {
21795 // Workaround for typescript language server issues so that methods don't expand within
21796 // strings and functions with type expressions. The previous point is used because the query
21797 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21798 let mut snippet_source = completion.new_text.clone();
21799 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21800 previous_point.column = previous_point.column.saturating_sub(1);
21801 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21802 && scope.prefers_label_for_snippet_in_completion()
21803 && let Some(label) = completion.label()
21804 && matches!(
21805 completion.kind(),
21806 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21807 )
21808 {
21809 snippet_source = label;
21810 }
21811 match Snippet::parse(&snippet_source).log_err() {
21812 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21813 None => (None, completion.new_text.clone()),
21814 }
21815 } else {
21816 (None, completion.new_text.clone())
21817 };
21818
21819 let mut range_to_replace = {
21820 let replace_range = &completion.replace_range;
21821 if let CompletionSource::Lsp {
21822 insert_range: Some(insert_range),
21823 ..
21824 } = &completion.source
21825 {
21826 debug_assert_eq!(
21827 insert_range.start, replace_range.start,
21828 "insert_range and replace_range should start at the same position"
21829 );
21830 debug_assert!(
21831 insert_range
21832 .start
21833 .cmp(cursor_position, &buffer_snapshot)
21834 .is_le(),
21835 "insert_range should start before or at cursor position"
21836 );
21837 debug_assert!(
21838 replace_range
21839 .start
21840 .cmp(cursor_position, &buffer_snapshot)
21841 .is_le(),
21842 "replace_range should start before or at cursor position"
21843 );
21844
21845 let should_replace = match intent {
21846 CompletionIntent::CompleteWithInsert => false,
21847 CompletionIntent::CompleteWithReplace => true,
21848 CompletionIntent::Complete | CompletionIntent::Compose => {
21849 let insert_mode =
21850 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21851 .completions
21852 .lsp_insert_mode;
21853 match insert_mode {
21854 LspInsertMode::Insert => false,
21855 LspInsertMode::Replace => true,
21856 LspInsertMode::ReplaceSubsequence => {
21857 let mut text_to_replace = buffer.chars_for_range(
21858 buffer.anchor_before(replace_range.start)
21859 ..buffer.anchor_after(replace_range.end),
21860 );
21861 let mut current_needle = text_to_replace.next();
21862 for haystack_ch in completion.label.text.chars() {
21863 if let Some(needle_ch) = current_needle
21864 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21865 {
21866 current_needle = text_to_replace.next();
21867 }
21868 }
21869 current_needle.is_none()
21870 }
21871 LspInsertMode::ReplaceSuffix => {
21872 if replace_range
21873 .end
21874 .cmp(cursor_position, &buffer_snapshot)
21875 .is_gt()
21876 {
21877 let range_after_cursor = *cursor_position..replace_range.end;
21878 let text_after_cursor = buffer
21879 .text_for_range(
21880 buffer.anchor_before(range_after_cursor.start)
21881 ..buffer.anchor_after(range_after_cursor.end),
21882 )
21883 .collect::<String>()
21884 .to_ascii_lowercase();
21885 completion
21886 .label
21887 .text
21888 .to_ascii_lowercase()
21889 .ends_with(&text_after_cursor)
21890 } else {
21891 true
21892 }
21893 }
21894 }
21895 }
21896 };
21897
21898 if should_replace {
21899 replace_range.clone()
21900 } else {
21901 insert_range.clone()
21902 }
21903 } else {
21904 replace_range.clone()
21905 }
21906 };
21907
21908 if range_to_replace
21909 .end
21910 .cmp(cursor_position, &buffer_snapshot)
21911 .is_lt()
21912 {
21913 range_to_replace.end = *cursor_position;
21914 }
21915
21916 CompletionEdit {
21917 new_text,
21918 replace_range: range_to_replace.to_offset(buffer),
21919 snippet,
21920 }
21921}
21922
21923struct CompletionEdit {
21924 new_text: String,
21925 replace_range: Range<usize>,
21926 snippet: Option<Snippet>,
21927}
21928
21929fn insert_extra_newline_brackets(
21930 buffer: &MultiBufferSnapshot,
21931 range: Range<usize>,
21932 language: &language::LanguageScope,
21933) -> bool {
21934 let leading_whitespace_len = buffer
21935 .reversed_chars_at(range.start)
21936 .take_while(|c| c.is_whitespace() && *c != '\n')
21937 .map(|c| c.len_utf8())
21938 .sum::<usize>();
21939 let trailing_whitespace_len = buffer
21940 .chars_at(range.end)
21941 .take_while(|c| c.is_whitespace() && *c != '\n')
21942 .map(|c| c.len_utf8())
21943 .sum::<usize>();
21944 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21945
21946 language.brackets().any(|(pair, enabled)| {
21947 let pair_start = pair.start.trim_end();
21948 let pair_end = pair.end.trim_start();
21949
21950 enabled
21951 && pair.newline
21952 && buffer.contains_str_at(range.end, pair_end)
21953 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21954 })
21955}
21956
21957fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21958 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21959 [(buffer, range, _)] => (*buffer, range.clone()),
21960 _ => return false,
21961 };
21962 let pair = {
21963 let mut result: Option<BracketMatch> = None;
21964
21965 for pair in buffer
21966 .all_bracket_ranges(range.clone())
21967 .filter(move |pair| {
21968 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21969 })
21970 {
21971 let len = pair.close_range.end - pair.open_range.start;
21972
21973 if let Some(existing) = &result {
21974 let existing_len = existing.close_range.end - existing.open_range.start;
21975 if len > existing_len {
21976 continue;
21977 }
21978 }
21979
21980 result = Some(pair);
21981 }
21982
21983 result
21984 };
21985 let Some(pair) = pair else {
21986 return false;
21987 };
21988 pair.newline_only
21989 && buffer
21990 .chars_for_range(pair.open_range.end..range.start)
21991 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21992 .all(|c| c.is_whitespace() && c != '\n')
21993}
21994
21995fn update_uncommitted_diff_for_buffer(
21996 editor: Entity<Editor>,
21997 project: &Entity<Project>,
21998 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21999 buffer: Entity<MultiBuffer>,
22000 cx: &mut App,
22001) -> Task<()> {
22002 let mut tasks = Vec::new();
22003 project.update(cx, |project, cx| {
22004 for buffer in buffers {
22005 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22006 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22007 }
22008 }
22009 });
22010 cx.spawn(async move |cx| {
22011 let diffs = future::join_all(tasks).await;
22012 if editor
22013 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22014 .unwrap_or(false)
22015 {
22016 return;
22017 }
22018
22019 buffer
22020 .update(cx, |buffer, cx| {
22021 for diff in diffs.into_iter().flatten() {
22022 buffer.add_diff(diff, cx);
22023 }
22024 })
22025 .ok();
22026 })
22027}
22028
22029fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22030 let tab_size = tab_size.get() as usize;
22031 let mut width = offset;
22032
22033 for ch in text.chars() {
22034 width += if ch == '\t' {
22035 tab_size - (width % tab_size)
22036 } else {
22037 1
22038 };
22039 }
22040
22041 width - offset
22042}
22043
22044#[cfg(test)]
22045mod tests {
22046 use super::*;
22047
22048 #[test]
22049 fn test_string_size_with_expanded_tabs() {
22050 let nz = |val| NonZeroU32::new(val).unwrap();
22051 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22052 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22053 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22054 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22055 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22056 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22057 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22058 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22059 }
22060}
22061
22062/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22063struct WordBreakingTokenizer<'a> {
22064 input: &'a str,
22065}
22066
22067impl<'a> WordBreakingTokenizer<'a> {
22068 fn new(input: &'a str) -> Self {
22069 Self { input }
22070 }
22071}
22072
22073fn is_char_ideographic(ch: char) -> bool {
22074 use unicode_script::Script::*;
22075 use unicode_script::UnicodeScript;
22076 matches!(ch.script(), Han | Tangut | Yi)
22077}
22078
22079fn is_grapheme_ideographic(text: &str) -> bool {
22080 text.chars().any(is_char_ideographic)
22081}
22082
22083fn is_grapheme_whitespace(text: &str) -> bool {
22084 text.chars().any(|x| x.is_whitespace())
22085}
22086
22087fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22088 text.chars()
22089 .next()
22090 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22091}
22092
22093#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22094enum WordBreakToken<'a> {
22095 Word { token: &'a str, grapheme_len: usize },
22096 InlineWhitespace { token: &'a str, grapheme_len: usize },
22097 Newline,
22098}
22099
22100impl<'a> Iterator for WordBreakingTokenizer<'a> {
22101 /// Yields a span, the count of graphemes in the token, and whether it was
22102 /// whitespace. Note that it also breaks at word boundaries.
22103 type Item = WordBreakToken<'a>;
22104
22105 fn next(&mut self) -> Option<Self::Item> {
22106 use unicode_segmentation::UnicodeSegmentation;
22107 if self.input.is_empty() {
22108 return None;
22109 }
22110
22111 let mut iter = self.input.graphemes(true).peekable();
22112 let mut offset = 0;
22113 let mut grapheme_len = 0;
22114 if let Some(first_grapheme) = iter.next() {
22115 let is_newline = first_grapheme == "\n";
22116 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22117 offset += first_grapheme.len();
22118 grapheme_len += 1;
22119 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22120 if let Some(grapheme) = iter.peek().copied()
22121 && should_stay_with_preceding_ideograph(grapheme)
22122 {
22123 offset += grapheme.len();
22124 grapheme_len += 1;
22125 }
22126 } else {
22127 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22128 let mut next_word_bound = words.peek().copied();
22129 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22130 next_word_bound = words.next();
22131 }
22132 while let Some(grapheme) = iter.peek().copied() {
22133 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22134 break;
22135 };
22136 if is_grapheme_whitespace(grapheme) != is_whitespace
22137 || (grapheme == "\n") != is_newline
22138 {
22139 break;
22140 };
22141 offset += grapheme.len();
22142 grapheme_len += 1;
22143 iter.next();
22144 }
22145 }
22146 let token = &self.input[..offset];
22147 self.input = &self.input[offset..];
22148 if token == "\n" {
22149 Some(WordBreakToken::Newline)
22150 } else if is_whitespace {
22151 Some(WordBreakToken::InlineWhitespace {
22152 token,
22153 grapheme_len,
22154 })
22155 } else {
22156 Some(WordBreakToken::Word {
22157 token,
22158 grapheme_len,
22159 })
22160 }
22161 } else {
22162 None
22163 }
22164 }
22165}
22166
22167#[test]
22168fn test_word_breaking_tokenizer() {
22169 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22170 ("", &[]),
22171 (" ", &[whitespace(" ", 2)]),
22172 ("Ʒ", &[word("Ʒ", 1)]),
22173 ("Ǽ", &[word("Ǽ", 1)]),
22174 ("⋑", &[word("⋑", 1)]),
22175 ("⋑⋑", &[word("⋑⋑", 2)]),
22176 (
22177 "原理,进而",
22178 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22179 ),
22180 (
22181 "hello world",
22182 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22183 ),
22184 (
22185 "hello, world",
22186 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22187 ),
22188 (
22189 " hello world",
22190 &[
22191 whitespace(" ", 2),
22192 word("hello", 5),
22193 whitespace(" ", 1),
22194 word("world", 5),
22195 ],
22196 ),
22197 (
22198 "这是什么 \n 钢笔",
22199 &[
22200 word("这", 1),
22201 word("是", 1),
22202 word("什", 1),
22203 word("么", 1),
22204 whitespace(" ", 1),
22205 newline(),
22206 whitespace(" ", 1),
22207 word("钢", 1),
22208 word("笔", 1),
22209 ],
22210 ),
22211 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22212 ];
22213
22214 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22215 WordBreakToken::Word {
22216 token,
22217 grapheme_len,
22218 }
22219 }
22220
22221 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22222 WordBreakToken::InlineWhitespace {
22223 token,
22224 grapheme_len,
22225 }
22226 }
22227
22228 fn newline() -> WordBreakToken<'static> {
22229 WordBreakToken::Newline
22230 }
22231
22232 for (input, result) in tests {
22233 assert_eq!(
22234 WordBreakingTokenizer::new(input)
22235 .collect::<Vec<_>>()
22236 .as_slice(),
22237 *result,
22238 );
22239 }
22240}
22241
22242fn wrap_with_prefix(
22243 first_line_prefix: String,
22244 subsequent_lines_prefix: String,
22245 unwrapped_text: String,
22246 wrap_column: usize,
22247 tab_size: NonZeroU32,
22248 preserve_existing_whitespace: bool,
22249) -> String {
22250 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22251 let subsequent_lines_prefix_len =
22252 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22253 let mut wrapped_text = String::new();
22254 let mut current_line = first_line_prefix;
22255 let mut is_first_line = true;
22256
22257 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22258 let mut current_line_len = first_line_prefix_len;
22259 let mut in_whitespace = false;
22260 for token in tokenizer {
22261 let have_preceding_whitespace = in_whitespace;
22262 match token {
22263 WordBreakToken::Word {
22264 token,
22265 grapheme_len,
22266 } => {
22267 in_whitespace = false;
22268 let current_prefix_len = if is_first_line {
22269 first_line_prefix_len
22270 } else {
22271 subsequent_lines_prefix_len
22272 };
22273 if current_line_len + grapheme_len > wrap_column
22274 && current_line_len != current_prefix_len
22275 {
22276 wrapped_text.push_str(current_line.trim_end());
22277 wrapped_text.push('\n');
22278 is_first_line = false;
22279 current_line = subsequent_lines_prefix.clone();
22280 current_line_len = subsequent_lines_prefix_len;
22281 }
22282 current_line.push_str(token);
22283 current_line_len += grapheme_len;
22284 }
22285 WordBreakToken::InlineWhitespace {
22286 mut token,
22287 mut grapheme_len,
22288 } => {
22289 in_whitespace = true;
22290 if have_preceding_whitespace && !preserve_existing_whitespace {
22291 continue;
22292 }
22293 if !preserve_existing_whitespace {
22294 token = " ";
22295 grapheme_len = 1;
22296 }
22297 let current_prefix_len = if is_first_line {
22298 first_line_prefix_len
22299 } else {
22300 subsequent_lines_prefix_len
22301 };
22302 if current_line_len + grapheme_len > wrap_column {
22303 wrapped_text.push_str(current_line.trim_end());
22304 wrapped_text.push('\n');
22305 is_first_line = false;
22306 current_line = subsequent_lines_prefix.clone();
22307 current_line_len = subsequent_lines_prefix_len;
22308 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22309 current_line.push_str(token);
22310 current_line_len += grapheme_len;
22311 }
22312 }
22313 WordBreakToken::Newline => {
22314 in_whitespace = true;
22315 let current_prefix_len = if is_first_line {
22316 first_line_prefix_len
22317 } else {
22318 subsequent_lines_prefix_len
22319 };
22320 if preserve_existing_whitespace {
22321 wrapped_text.push_str(current_line.trim_end());
22322 wrapped_text.push('\n');
22323 is_first_line = false;
22324 current_line = subsequent_lines_prefix.clone();
22325 current_line_len = subsequent_lines_prefix_len;
22326 } else if have_preceding_whitespace {
22327 continue;
22328 } else if current_line_len + 1 > wrap_column
22329 && current_line_len != current_prefix_len
22330 {
22331 wrapped_text.push_str(current_line.trim_end());
22332 wrapped_text.push('\n');
22333 is_first_line = false;
22334 current_line = subsequent_lines_prefix.clone();
22335 current_line_len = subsequent_lines_prefix_len;
22336 } else if current_line_len != current_prefix_len {
22337 current_line.push(' ');
22338 current_line_len += 1;
22339 }
22340 }
22341 }
22342 }
22343
22344 if !current_line.is_empty() {
22345 wrapped_text.push_str(¤t_line);
22346 }
22347 wrapped_text
22348}
22349
22350#[test]
22351fn test_wrap_with_prefix() {
22352 assert_eq!(
22353 wrap_with_prefix(
22354 "# ".to_string(),
22355 "# ".to_string(),
22356 "abcdefg".to_string(),
22357 4,
22358 NonZeroU32::new(4).unwrap(),
22359 false,
22360 ),
22361 "# abcdefg"
22362 );
22363 assert_eq!(
22364 wrap_with_prefix(
22365 "".to_string(),
22366 "".to_string(),
22367 "\thello world".to_string(),
22368 8,
22369 NonZeroU32::new(4).unwrap(),
22370 false,
22371 ),
22372 "hello\nworld"
22373 );
22374 assert_eq!(
22375 wrap_with_prefix(
22376 "// ".to_string(),
22377 "// ".to_string(),
22378 "xx \nyy zz aa bb cc".to_string(),
22379 12,
22380 NonZeroU32::new(4).unwrap(),
22381 false,
22382 ),
22383 "// xx yy zz\n// aa bb cc"
22384 );
22385 assert_eq!(
22386 wrap_with_prefix(
22387 String::new(),
22388 String::new(),
22389 "这是什么 \n 钢笔".to_string(),
22390 3,
22391 NonZeroU32::new(4).unwrap(),
22392 false,
22393 ),
22394 "这是什\n么 钢\n笔"
22395 );
22396}
22397
22398pub trait CollaborationHub {
22399 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22400 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22401 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22402}
22403
22404impl CollaborationHub for Entity<Project> {
22405 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22406 self.read(cx).collaborators()
22407 }
22408
22409 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22410 self.read(cx).user_store().read(cx).participant_indices()
22411 }
22412
22413 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22414 let this = self.read(cx);
22415 let user_ids = this.collaborators().values().map(|c| c.user_id);
22416 this.user_store().read(cx).participant_names(user_ids, cx)
22417 }
22418}
22419
22420pub trait SemanticsProvider {
22421 fn hover(
22422 &self,
22423 buffer: &Entity<Buffer>,
22424 position: text::Anchor,
22425 cx: &mut App,
22426 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22427
22428 fn inline_values(
22429 &self,
22430 buffer_handle: Entity<Buffer>,
22431 range: Range<text::Anchor>,
22432 cx: &mut App,
22433 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22434
22435 fn inlay_hints(
22436 &self,
22437 buffer_handle: Entity<Buffer>,
22438 range: Range<text::Anchor>,
22439 cx: &mut App,
22440 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22441
22442 fn resolve_inlay_hint(
22443 &self,
22444 hint: InlayHint,
22445 buffer_handle: Entity<Buffer>,
22446 server_id: LanguageServerId,
22447 cx: &mut App,
22448 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22449
22450 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22451
22452 fn document_highlights(
22453 &self,
22454 buffer: &Entity<Buffer>,
22455 position: text::Anchor,
22456 cx: &mut App,
22457 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22458
22459 fn definitions(
22460 &self,
22461 buffer: &Entity<Buffer>,
22462 position: text::Anchor,
22463 kind: GotoDefinitionKind,
22464 cx: &mut App,
22465 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22466
22467 fn range_for_rename(
22468 &self,
22469 buffer: &Entity<Buffer>,
22470 position: text::Anchor,
22471 cx: &mut App,
22472 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22473
22474 fn perform_rename(
22475 &self,
22476 buffer: &Entity<Buffer>,
22477 position: text::Anchor,
22478 new_name: String,
22479 cx: &mut App,
22480 ) -> Option<Task<Result<ProjectTransaction>>>;
22481}
22482
22483pub trait CompletionProvider {
22484 fn completions(
22485 &self,
22486 excerpt_id: ExcerptId,
22487 buffer: &Entity<Buffer>,
22488 buffer_position: text::Anchor,
22489 trigger: CompletionContext,
22490 window: &mut Window,
22491 cx: &mut Context<Editor>,
22492 ) -> Task<Result<Vec<CompletionResponse>>>;
22493
22494 fn resolve_completions(
22495 &self,
22496 _buffer: Entity<Buffer>,
22497 _completion_indices: Vec<usize>,
22498 _completions: Rc<RefCell<Box<[Completion]>>>,
22499 _cx: &mut Context<Editor>,
22500 ) -> Task<Result<bool>> {
22501 Task::ready(Ok(false))
22502 }
22503
22504 fn apply_additional_edits_for_completion(
22505 &self,
22506 _buffer: Entity<Buffer>,
22507 _completions: Rc<RefCell<Box<[Completion]>>>,
22508 _completion_index: usize,
22509 _push_to_history: bool,
22510 _cx: &mut Context<Editor>,
22511 ) -> Task<Result<Option<language::Transaction>>> {
22512 Task::ready(Ok(None))
22513 }
22514
22515 fn is_completion_trigger(
22516 &self,
22517 buffer: &Entity<Buffer>,
22518 position: language::Anchor,
22519 text: &str,
22520 trigger_in_words: bool,
22521 menu_is_open: bool,
22522 cx: &mut Context<Editor>,
22523 ) -> bool;
22524
22525 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22526
22527 fn sort_completions(&self) -> bool {
22528 true
22529 }
22530
22531 fn filter_completions(&self) -> bool {
22532 true
22533 }
22534}
22535
22536pub trait CodeActionProvider {
22537 fn id(&self) -> Arc<str>;
22538
22539 fn code_actions(
22540 &self,
22541 buffer: &Entity<Buffer>,
22542 range: Range<text::Anchor>,
22543 window: &mut Window,
22544 cx: &mut App,
22545 ) -> Task<Result<Vec<CodeAction>>>;
22546
22547 fn apply_code_action(
22548 &self,
22549 buffer_handle: Entity<Buffer>,
22550 action: CodeAction,
22551 excerpt_id: ExcerptId,
22552 push_to_history: bool,
22553 window: &mut Window,
22554 cx: &mut App,
22555 ) -> Task<Result<ProjectTransaction>>;
22556}
22557
22558impl CodeActionProvider for Entity<Project> {
22559 fn id(&self) -> Arc<str> {
22560 "project".into()
22561 }
22562
22563 fn code_actions(
22564 &self,
22565 buffer: &Entity<Buffer>,
22566 range: Range<text::Anchor>,
22567 _window: &mut Window,
22568 cx: &mut App,
22569 ) -> Task<Result<Vec<CodeAction>>> {
22570 self.update(cx, |project, cx| {
22571 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22572 let code_actions = project.code_actions(buffer, range, None, cx);
22573 cx.background_spawn(async move {
22574 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22575 Ok(code_lens_actions
22576 .context("code lens fetch")?
22577 .into_iter()
22578 .flatten()
22579 .chain(
22580 code_actions
22581 .context("code action fetch")?
22582 .into_iter()
22583 .flatten(),
22584 )
22585 .collect())
22586 })
22587 })
22588 }
22589
22590 fn apply_code_action(
22591 &self,
22592 buffer_handle: Entity<Buffer>,
22593 action: CodeAction,
22594 _excerpt_id: ExcerptId,
22595 push_to_history: bool,
22596 _window: &mut Window,
22597 cx: &mut App,
22598 ) -> Task<Result<ProjectTransaction>> {
22599 self.update(cx, |project, cx| {
22600 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22601 })
22602 }
22603}
22604
22605fn snippet_completions(
22606 project: &Project,
22607 buffer: &Entity<Buffer>,
22608 buffer_position: text::Anchor,
22609 cx: &mut App,
22610) -> Task<Result<CompletionResponse>> {
22611 let languages = buffer.read(cx).languages_at(buffer_position);
22612 let snippet_store = project.snippets().read(cx);
22613
22614 let scopes: Vec<_> = languages
22615 .iter()
22616 .filter_map(|language| {
22617 let language_name = language.lsp_id();
22618 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22619
22620 if snippets.is_empty() {
22621 None
22622 } else {
22623 Some((language.default_scope(), snippets))
22624 }
22625 })
22626 .collect();
22627
22628 if scopes.is_empty() {
22629 return Task::ready(Ok(CompletionResponse {
22630 completions: vec![],
22631 display_options: CompletionDisplayOptions::default(),
22632 is_incomplete: false,
22633 }));
22634 }
22635
22636 let snapshot = buffer.read(cx).text_snapshot();
22637 let chars: String = snapshot
22638 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22639 .collect();
22640 let executor = cx.background_executor().clone();
22641
22642 cx.background_spawn(async move {
22643 let mut is_incomplete = false;
22644 let mut completions: Vec<Completion> = Vec::new();
22645 for (scope, snippets) in scopes.into_iter() {
22646 let classifier =
22647 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22648 let mut last_word = chars
22649 .chars()
22650 .take_while(|c| classifier.is_word(*c))
22651 .collect::<String>();
22652 last_word = last_word.chars().rev().collect();
22653
22654 if last_word.is_empty() {
22655 return Ok(CompletionResponse {
22656 completions: vec![],
22657 display_options: CompletionDisplayOptions::default(),
22658 is_incomplete: true,
22659 });
22660 }
22661
22662 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22663 let to_lsp = |point: &text::Anchor| {
22664 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22665 point_to_lsp(end)
22666 };
22667 let lsp_end = to_lsp(&buffer_position);
22668
22669 let candidates = snippets
22670 .iter()
22671 .enumerate()
22672 .flat_map(|(ix, snippet)| {
22673 snippet
22674 .prefix
22675 .iter()
22676 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22677 })
22678 .collect::<Vec<StringMatchCandidate>>();
22679
22680 const MAX_RESULTS: usize = 100;
22681 let mut matches = fuzzy::match_strings(
22682 &candidates,
22683 &last_word,
22684 last_word.chars().any(|c| c.is_uppercase()),
22685 true,
22686 MAX_RESULTS,
22687 &Default::default(),
22688 executor.clone(),
22689 )
22690 .await;
22691
22692 if matches.len() >= MAX_RESULTS {
22693 is_incomplete = true;
22694 }
22695
22696 // Remove all candidates where the query's start does not match the start of any word in the candidate
22697 if let Some(query_start) = last_word.chars().next() {
22698 matches.retain(|string_match| {
22699 split_words(&string_match.string).any(|word| {
22700 // Check that the first codepoint of the word as lowercase matches the first
22701 // codepoint of the query as lowercase
22702 word.chars()
22703 .flat_map(|codepoint| codepoint.to_lowercase())
22704 .zip(query_start.to_lowercase())
22705 .all(|(word_cp, query_cp)| word_cp == query_cp)
22706 })
22707 });
22708 }
22709
22710 let matched_strings = matches
22711 .into_iter()
22712 .map(|m| m.string)
22713 .collect::<HashSet<_>>();
22714
22715 completions.extend(snippets.iter().filter_map(|snippet| {
22716 let matching_prefix = snippet
22717 .prefix
22718 .iter()
22719 .find(|prefix| matched_strings.contains(*prefix))?;
22720 let start = as_offset - last_word.len();
22721 let start = snapshot.anchor_before(start);
22722 let range = start..buffer_position;
22723 let lsp_start = to_lsp(&start);
22724 let lsp_range = lsp::Range {
22725 start: lsp_start,
22726 end: lsp_end,
22727 };
22728 Some(Completion {
22729 replace_range: range,
22730 new_text: snippet.body.clone(),
22731 source: CompletionSource::Lsp {
22732 insert_range: None,
22733 server_id: LanguageServerId(usize::MAX),
22734 resolved: true,
22735 lsp_completion: Box::new(lsp::CompletionItem {
22736 label: snippet.prefix.first().unwrap().clone(),
22737 kind: Some(CompletionItemKind::SNIPPET),
22738 label_details: snippet.description.as_ref().map(|description| {
22739 lsp::CompletionItemLabelDetails {
22740 detail: Some(description.clone()),
22741 description: None,
22742 }
22743 }),
22744 insert_text_format: Some(InsertTextFormat::SNIPPET),
22745 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22746 lsp::InsertReplaceEdit {
22747 new_text: snippet.body.clone(),
22748 insert: lsp_range,
22749 replace: lsp_range,
22750 },
22751 )),
22752 filter_text: Some(snippet.body.clone()),
22753 sort_text: Some(char::MAX.to_string()),
22754 ..lsp::CompletionItem::default()
22755 }),
22756 lsp_defaults: None,
22757 },
22758 label: CodeLabel {
22759 text: matching_prefix.clone(),
22760 runs: Vec::new(),
22761 filter_range: 0..matching_prefix.len(),
22762 },
22763 icon_path: None,
22764 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22765 single_line: snippet.name.clone().into(),
22766 plain_text: snippet
22767 .description
22768 .clone()
22769 .map(|description| description.into()),
22770 }),
22771 insert_text_mode: None,
22772 confirm: None,
22773 })
22774 }))
22775 }
22776
22777 Ok(CompletionResponse {
22778 completions,
22779 display_options: CompletionDisplayOptions::default(),
22780 is_incomplete,
22781 })
22782 })
22783}
22784
22785impl CompletionProvider for Entity<Project> {
22786 fn completions(
22787 &self,
22788 _excerpt_id: ExcerptId,
22789 buffer: &Entity<Buffer>,
22790 buffer_position: text::Anchor,
22791 options: CompletionContext,
22792 _window: &mut Window,
22793 cx: &mut Context<Editor>,
22794 ) -> Task<Result<Vec<CompletionResponse>>> {
22795 self.update(cx, |project, cx| {
22796 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22797 let project_completions = project.completions(buffer, buffer_position, options, cx);
22798 cx.background_spawn(async move {
22799 let mut responses = project_completions.await?;
22800 let snippets = snippets.await?;
22801 if !snippets.completions.is_empty() {
22802 responses.push(snippets);
22803 }
22804 Ok(responses)
22805 })
22806 })
22807 }
22808
22809 fn resolve_completions(
22810 &self,
22811 buffer: Entity<Buffer>,
22812 completion_indices: Vec<usize>,
22813 completions: Rc<RefCell<Box<[Completion]>>>,
22814 cx: &mut Context<Editor>,
22815 ) -> Task<Result<bool>> {
22816 self.update(cx, |project, cx| {
22817 project.lsp_store().update(cx, |lsp_store, cx| {
22818 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22819 })
22820 })
22821 }
22822
22823 fn apply_additional_edits_for_completion(
22824 &self,
22825 buffer: Entity<Buffer>,
22826 completions: Rc<RefCell<Box<[Completion]>>>,
22827 completion_index: usize,
22828 push_to_history: bool,
22829 cx: &mut Context<Editor>,
22830 ) -> Task<Result<Option<language::Transaction>>> {
22831 self.update(cx, |project, cx| {
22832 project.lsp_store().update(cx, |lsp_store, cx| {
22833 lsp_store.apply_additional_edits_for_completion(
22834 buffer,
22835 completions,
22836 completion_index,
22837 push_to_history,
22838 cx,
22839 )
22840 })
22841 })
22842 }
22843
22844 fn is_completion_trigger(
22845 &self,
22846 buffer: &Entity<Buffer>,
22847 position: language::Anchor,
22848 text: &str,
22849 trigger_in_words: bool,
22850 menu_is_open: bool,
22851 cx: &mut Context<Editor>,
22852 ) -> bool {
22853 let mut chars = text.chars();
22854 let char = if let Some(char) = chars.next() {
22855 char
22856 } else {
22857 return false;
22858 };
22859 if chars.next().is_some() {
22860 return false;
22861 }
22862
22863 let buffer = buffer.read(cx);
22864 let snapshot = buffer.snapshot();
22865 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22866 return false;
22867 }
22868 let classifier = snapshot
22869 .char_classifier_at(position)
22870 .scope_context(Some(CharScopeContext::Completion));
22871 if trigger_in_words && classifier.is_word(char) {
22872 return true;
22873 }
22874
22875 buffer.completion_triggers().contains(text)
22876 }
22877}
22878
22879impl SemanticsProvider for Entity<Project> {
22880 fn hover(
22881 &self,
22882 buffer: &Entity<Buffer>,
22883 position: text::Anchor,
22884 cx: &mut App,
22885 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22886 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22887 }
22888
22889 fn document_highlights(
22890 &self,
22891 buffer: &Entity<Buffer>,
22892 position: text::Anchor,
22893 cx: &mut App,
22894 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22895 Some(self.update(cx, |project, cx| {
22896 project.document_highlights(buffer, position, cx)
22897 }))
22898 }
22899
22900 fn definitions(
22901 &self,
22902 buffer: &Entity<Buffer>,
22903 position: text::Anchor,
22904 kind: GotoDefinitionKind,
22905 cx: &mut App,
22906 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22907 Some(self.update(cx, |project, cx| match kind {
22908 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22909 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22910 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22911 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22912 }))
22913 }
22914
22915 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22916 self.update(cx, |project, cx| {
22917 if project
22918 .active_debug_session(cx)
22919 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22920 {
22921 return true;
22922 }
22923
22924 buffer.update(cx, |buffer, cx| {
22925 project.any_language_server_supports_inlay_hints(buffer, cx)
22926 })
22927 })
22928 }
22929
22930 fn inline_values(
22931 &self,
22932 buffer_handle: Entity<Buffer>,
22933 range: Range<text::Anchor>,
22934 cx: &mut App,
22935 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22936 self.update(cx, |project, cx| {
22937 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22938
22939 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22940 })
22941 }
22942
22943 fn inlay_hints(
22944 &self,
22945 buffer_handle: Entity<Buffer>,
22946 range: Range<text::Anchor>,
22947 cx: &mut App,
22948 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22949 Some(self.update(cx, |project, cx| {
22950 project.inlay_hints(buffer_handle, range, cx)
22951 }))
22952 }
22953
22954 fn resolve_inlay_hint(
22955 &self,
22956 hint: InlayHint,
22957 buffer_handle: Entity<Buffer>,
22958 server_id: LanguageServerId,
22959 cx: &mut App,
22960 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22961 Some(self.update(cx, |project, cx| {
22962 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22963 }))
22964 }
22965
22966 fn range_for_rename(
22967 &self,
22968 buffer: &Entity<Buffer>,
22969 position: text::Anchor,
22970 cx: &mut App,
22971 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22972 Some(self.update(cx, |project, cx| {
22973 let buffer = buffer.clone();
22974 let task = project.prepare_rename(buffer.clone(), position, cx);
22975 cx.spawn(async move |_, cx| {
22976 Ok(match task.await? {
22977 PrepareRenameResponse::Success(range) => Some(range),
22978 PrepareRenameResponse::InvalidPosition => None,
22979 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22980 // Fallback on using TreeSitter info to determine identifier range
22981 buffer.read_with(cx, |buffer, _| {
22982 let snapshot = buffer.snapshot();
22983 let (range, kind) = snapshot.surrounding_word(position, None);
22984 if kind != Some(CharKind::Word) {
22985 return None;
22986 }
22987 Some(
22988 snapshot.anchor_before(range.start)
22989 ..snapshot.anchor_after(range.end),
22990 )
22991 })?
22992 }
22993 })
22994 })
22995 }))
22996 }
22997
22998 fn perform_rename(
22999 &self,
23000 buffer: &Entity<Buffer>,
23001 position: text::Anchor,
23002 new_name: String,
23003 cx: &mut App,
23004 ) -> Option<Task<Result<ProjectTransaction>>> {
23005 Some(self.update(cx, |project, cx| {
23006 project.perform_rename(buffer.clone(), position, new_name, cx)
23007 }))
23008 }
23009}
23010
23011fn inlay_hint_settings(
23012 location: Anchor,
23013 snapshot: &MultiBufferSnapshot,
23014 cx: &mut Context<Editor>,
23015) -> InlayHintSettings {
23016 let file = snapshot.file_at(location);
23017 let language = snapshot.language_at(location).map(|l| l.name());
23018 language_settings(language, file, cx).inlay_hints
23019}
23020
23021fn consume_contiguous_rows(
23022 contiguous_row_selections: &mut Vec<Selection<Point>>,
23023 selection: &Selection<Point>,
23024 display_map: &DisplaySnapshot,
23025 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23026) -> (MultiBufferRow, MultiBufferRow) {
23027 contiguous_row_selections.push(selection.clone());
23028 let start_row = starting_row(selection, display_map);
23029 let mut end_row = ending_row(selection, display_map);
23030
23031 while let Some(next_selection) = selections.peek() {
23032 if next_selection.start.row <= end_row.0 {
23033 end_row = ending_row(next_selection, display_map);
23034 contiguous_row_selections.push(selections.next().unwrap().clone());
23035 } else {
23036 break;
23037 }
23038 }
23039 (start_row, end_row)
23040}
23041
23042fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23043 if selection.start.column > 0 {
23044 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23045 } else {
23046 MultiBufferRow(selection.start.row)
23047 }
23048}
23049
23050fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23051 if next_selection.end.column > 0 || next_selection.is_empty() {
23052 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23053 } else {
23054 MultiBufferRow(next_selection.end.row)
23055 }
23056}
23057
23058impl EditorSnapshot {
23059 pub fn remote_selections_in_range<'a>(
23060 &'a self,
23061 range: &'a Range<Anchor>,
23062 collaboration_hub: &dyn CollaborationHub,
23063 cx: &'a App,
23064 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23065 let participant_names = collaboration_hub.user_names(cx);
23066 let participant_indices = collaboration_hub.user_participant_indices(cx);
23067 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23068 let collaborators_by_replica_id = collaborators_by_peer_id
23069 .values()
23070 .map(|collaborator| (collaborator.replica_id, collaborator))
23071 .collect::<HashMap<_, _>>();
23072 self.buffer_snapshot
23073 .selections_in_range(range, false)
23074 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23075 if replica_id == AGENT_REPLICA_ID {
23076 Some(RemoteSelection {
23077 replica_id,
23078 selection,
23079 cursor_shape,
23080 line_mode,
23081 collaborator_id: CollaboratorId::Agent,
23082 user_name: Some("Agent".into()),
23083 color: cx.theme().players().agent(),
23084 })
23085 } else {
23086 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23087 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23088 let user_name = participant_names.get(&collaborator.user_id).cloned();
23089 Some(RemoteSelection {
23090 replica_id,
23091 selection,
23092 cursor_shape,
23093 line_mode,
23094 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23095 user_name,
23096 color: if let Some(index) = participant_index {
23097 cx.theme().players().color_for_participant(index.0)
23098 } else {
23099 cx.theme().players().absent()
23100 },
23101 })
23102 }
23103 })
23104 }
23105
23106 pub fn hunks_for_ranges(
23107 &self,
23108 ranges: impl IntoIterator<Item = Range<Point>>,
23109 ) -> Vec<MultiBufferDiffHunk> {
23110 let mut hunks = Vec::new();
23111 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23112 HashMap::default();
23113 for query_range in ranges {
23114 let query_rows =
23115 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23116 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23117 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23118 ) {
23119 // Include deleted hunks that are adjacent to the query range, because
23120 // otherwise they would be missed.
23121 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23122 if hunk.status().is_deleted() {
23123 intersects_range |= hunk.row_range.start == query_rows.end;
23124 intersects_range |= hunk.row_range.end == query_rows.start;
23125 }
23126 if intersects_range {
23127 if !processed_buffer_rows
23128 .entry(hunk.buffer_id)
23129 .or_default()
23130 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23131 {
23132 continue;
23133 }
23134 hunks.push(hunk);
23135 }
23136 }
23137 }
23138
23139 hunks
23140 }
23141
23142 fn display_diff_hunks_for_rows<'a>(
23143 &'a self,
23144 display_rows: Range<DisplayRow>,
23145 folded_buffers: &'a HashSet<BufferId>,
23146 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23147 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23148 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23149
23150 self.buffer_snapshot
23151 .diff_hunks_in_range(buffer_start..buffer_end)
23152 .filter_map(|hunk| {
23153 if folded_buffers.contains(&hunk.buffer_id) {
23154 return None;
23155 }
23156
23157 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23158 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23159
23160 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23161 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23162
23163 let display_hunk = if hunk_display_start.column() != 0 {
23164 DisplayDiffHunk::Folded {
23165 display_row: hunk_display_start.row(),
23166 }
23167 } else {
23168 let mut end_row = hunk_display_end.row();
23169 if hunk_display_end.column() > 0 {
23170 end_row.0 += 1;
23171 }
23172 let is_created_file = hunk.is_created_file();
23173 DisplayDiffHunk::Unfolded {
23174 status: hunk.status(),
23175 diff_base_byte_range: hunk.diff_base_byte_range,
23176 display_row_range: hunk_display_start.row()..end_row,
23177 multi_buffer_range: Anchor::range_in_buffer(
23178 hunk.excerpt_id,
23179 hunk.buffer_id,
23180 hunk.buffer_range,
23181 ),
23182 is_created_file,
23183 }
23184 };
23185
23186 Some(display_hunk)
23187 })
23188 }
23189
23190 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23191 self.display_snapshot.buffer_snapshot.language_at(position)
23192 }
23193
23194 pub fn is_focused(&self) -> bool {
23195 self.is_focused
23196 }
23197
23198 pub fn placeholder_text(&self) -> Option<String> {
23199 self.placeholder_display_snapshot
23200 .as_ref()
23201 .map(|display_map| display_map.text())
23202 }
23203
23204 pub fn scroll_position(&self) -> gpui::Point<f32> {
23205 self.scroll_anchor.scroll_position(&self.display_snapshot)
23206 }
23207
23208 fn gutter_dimensions(
23209 &self,
23210 font_id: FontId,
23211 font_size: Pixels,
23212 max_line_number_width: Pixels,
23213 cx: &App,
23214 ) -> Option<GutterDimensions> {
23215 if !self.show_gutter {
23216 return None;
23217 }
23218
23219 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23220 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23221
23222 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23223 matches!(
23224 ProjectSettings::get_global(cx).git.git_gutter,
23225 GitGutterSetting::TrackedFiles
23226 )
23227 });
23228 let gutter_settings = EditorSettings::get_global(cx).gutter;
23229 let show_line_numbers = self
23230 .show_line_numbers
23231 .unwrap_or(gutter_settings.line_numbers);
23232 let line_gutter_width = if show_line_numbers {
23233 // Avoid flicker-like gutter resizes when the line number gains another digit by
23234 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23235 let min_width_for_number_on_gutter =
23236 ch_advance * gutter_settings.min_line_number_digits as f32;
23237 max_line_number_width.max(min_width_for_number_on_gutter)
23238 } else {
23239 0.0.into()
23240 };
23241
23242 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23243 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23244
23245 let git_blame_entries_width =
23246 self.git_blame_gutter_max_author_length
23247 .map(|max_author_length| {
23248 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23249 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23250
23251 /// The number of characters to dedicate to gaps and margins.
23252 const SPACING_WIDTH: usize = 4;
23253
23254 let max_char_count = max_author_length.min(renderer.max_author_length())
23255 + ::git::SHORT_SHA_LENGTH
23256 + MAX_RELATIVE_TIMESTAMP.len()
23257 + SPACING_WIDTH;
23258
23259 ch_advance * max_char_count
23260 });
23261
23262 let is_singleton = self.buffer_snapshot.is_singleton();
23263
23264 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23265 left_padding += if !is_singleton {
23266 ch_width * 4.0
23267 } else if show_runnables || show_breakpoints {
23268 ch_width * 3.0
23269 } else if show_git_gutter && show_line_numbers {
23270 ch_width * 2.0
23271 } else if show_git_gutter || show_line_numbers {
23272 ch_width
23273 } else {
23274 px(0.)
23275 };
23276
23277 let shows_folds = is_singleton && gutter_settings.folds;
23278
23279 let right_padding = if shows_folds && show_line_numbers {
23280 ch_width * 4.0
23281 } else if shows_folds || (!is_singleton && show_line_numbers) {
23282 ch_width * 3.0
23283 } else if show_line_numbers {
23284 ch_width
23285 } else {
23286 px(0.)
23287 };
23288
23289 Some(GutterDimensions {
23290 left_padding,
23291 right_padding,
23292 width: line_gutter_width + left_padding + right_padding,
23293 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23294 git_blame_entries_width,
23295 })
23296 }
23297
23298 pub fn render_crease_toggle(
23299 &self,
23300 buffer_row: MultiBufferRow,
23301 row_contains_cursor: bool,
23302 editor: Entity<Editor>,
23303 window: &mut Window,
23304 cx: &mut App,
23305 ) -> Option<AnyElement> {
23306 let folded = self.is_line_folded(buffer_row);
23307 let mut is_foldable = false;
23308
23309 if let Some(crease) = self
23310 .crease_snapshot
23311 .query_row(buffer_row, &self.buffer_snapshot)
23312 {
23313 is_foldable = true;
23314 match crease {
23315 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23316 if let Some(render_toggle) = render_toggle {
23317 let toggle_callback =
23318 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23319 if folded {
23320 editor.update(cx, |editor, cx| {
23321 editor.fold_at(buffer_row, window, cx)
23322 });
23323 } else {
23324 editor.update(cx, |editor, cx| {
23325 editor.unfold_at(buffer_row, window, cx)
23326 });
23327 }
23328 });
23329 return Some((render_toggle)(
23330 buffer_row,
23331 folded,
23332 toggle_callback,
23333 window,
23334 cx,
23335 ));
23336 }
23337 }
23338 }
23339 }
23340
23341 is_foldable |= self.starts_indent(buffer_row);
23342
23343 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23344 Some(
23345 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23346 .toggle_state(folded)
23347 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23348 if folded {
23349 this.unfold_at(buffer_row, window, cx);
23350 } else {
23351 this.fold_at(buffer_row, window, cx);
23352 }
23353 }))
23354 .into_any_element(),
23355 )
23356 } else {
23357 None
23358 }
23359 }
23360
23361 pub fn render_crease_trailer(
23362 &self,
23363 buffer_row: MultiBufferRow,
23364 window: &mut Window,
23365 cx: &mut App,
23366 ) -> Option<AnyElement> {
23367 let folded = self.is_line_folded(buffer_row);
23368 if let Crease::Inline { render_trailer, .. } = self
23369 .crease_snapshot
23370 .query_row(buffer_row, &self.buffer_snapshot)?
23371 {
23372 let render_trailer = render_trailer.as_ref()?;
23373 Some(render_trailer(buffer_row, folded, window, cx))
23374 } else {
23375 None
23376 }
23377 }
23378}
23379
23380impl Deref for EditorSnapshot {
23381 type Target = DisplaySnapshot;
23382
23383 fn deref(&self) -> &Self::Target {
23384 &self.display_snapshot
23385 }
23386}
23387
23388#[derive(Clone, Debug, PartialEq, Eq)]
23389pub enum EditorEvent {
23390 InputIgnored {
23391 text: Arc<str>,
23392 },
23393 InputHandled {
23394 utf16_range_to_replace: Option<Range<isize>>,
23395 text: Arc<str>,
23396 },
23397 ExcerptsAdded {
23398 buffer: Entity<Buffer>,
23399 predecessor: ExcerptId,
23400 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23401 },
23402 ExcerptsRemoved {
23403 ids: Vec<ExcerptId>,
23404 removed_buffer_ids: Vec<BufferId>,
23405 },
23406 BufferFoldToggled {
23407 ids: Vec<ExcerptId>,
23408 folded: bool,
23409 },
23410 ExcerptsEdited {
23411 ids: Vec<ExcerptId>,
23412 },
23413 ExcerptsExpanded {
23414 ids: Vec<ExcerptId>,
23415 },
23416 BufferEdited,
23417 Edited {
23418 transaction_id: clock::Lamport,
23419 },
23420 Reparsed(BufferId),
23421 Focused,
23422 FocusedIn,
23423 Blurred,
23424 DirtyChanged,
23425 Saved,
23426 TitleChanged,
23427 SelectionsChanged {
23428 local: bool,
23429 },
23430 ScrollPositionChanged {
23431 local: bool,
23432 autoscroll: bool,
23433 },
23434 TransactionUndone {
23435 transaction_id: clock::Lamport,
23436 },
23437 TransactionBegun {
23438 transaction_id: clock::Lamport,
23439 },
23440 CursorShapeChanged,
23441 BreadcrumbsChanged,
23442 PushedToNavHistory {
23443 anchor: Anchor,
23444 is_deactivate: bool,
23445 },
23446}
23447
23448impl EventEmitter<EditorEvent> for Editor {}
23449
23450impl Focusable for Editor {
23451 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23452 self.focus_handle.clone()
23453 }
23454}
23455
23456impl Render for Editor {
23457 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23458 let settings = ThemeSettings::get_global(cx);
23459
23460 let mut text_style = match self.mode {
23461 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23462 color: cx.theme().colors().editor_foreground,
23463 font_family: settings.ui_font.family.clone(),
23464 font_features: settings.ui_font.features.clone(),
23465 font_fallbacks: settings.ui_font.fallbacks.clone(),
23466 font_size: rems(0.875).into(),
23467 font_weight: settings.ui_font.weight,
23468 line_height: relative(settings.buffer_line_height.value()),
23469 ..Default::default()
23470 },
23471 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23472 color: cx.theme().colors().editor_foreground,
23473 font_family: settings.buffer_font.family.clone(),
23474 font_features: settings.buffer_font.features.clone(),
23475 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23476 font_size: settings.buffer_font_size(cx).into(),
23477 font_weight: settings.buffer_font.weight,
23478 line_height: relative(settings.buffer_line_height.value()),
23479 ..Default::default()
23480 },
23481 };
23482 if let Some(text_style_refinement) = &self.text_style_refinement {
23483 text_style.refine(text_style_refinement)
23484 }
23485
23486 let background = match self.mode {
23487 EditorMode::SingleLine => cx.theme().system().transparent,
23488 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23489 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23490 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23491 };
23492
23493 EditorElement::new(
23494 &cx.entity(),
23495 EditorStyle {
23496 background,
23497 border: cx.theme().colors().border,
23498 local_player: cx.theme().players().local(),
23499 text: text_style,
23500 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23501 syntax: cx.theme().syntax().clone(),
23502 status: cx.theme().status().clone(),
23503 inlay_hints_style: make_inlay_hints_style(cx),
23504 edit_prediction_styles: make_suggestion_styles(cx),
23505 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23506 show_underlines: self.diagnostics_enabled(),
23507 },
23508 )
23509 }
23510}
23511
23512impl EntityInputHandler for Editor {
23513 fn text_for_range(
23514 &mut self,
23515 range_utf16: Range<usize>,
23516 adjusted_range: &mut Option<Range<usize>>,
23517 _: &mut Window,
23518 cx: &mut Context<Self>,
23519 ) -> Option<String> {
23520 let snapshot = self.buffer.read(cx).read(cx);
23521 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23522 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23523 if (start.0..end.0) != range_utf16 {
23524 adjusted_range.replace(start.0..end.0);
23525 }
23526 Some(snapshot.text_for_range(start..end).collect())
23527 }
23528
23529 fn selected_text_range(
23530 &mut self,
23531 ignore_disabled_input: bool,
23532 _: &mut Window,
23533 cx: &mut Context<Self>,
23534 ) -> Option<UTF16Selection> {
23535 // Prevent the IME menu from appearing when holding down an alphabetic key
23536 // while input is disabled.
23537 if !ignore_disabled_input && !self.input_enabled {
23538 return None;
23539 }
23540
23541 let selection = self.selections.newest::<OffsetUtf16>(cx);
23542 let range = selection.range();
23543
23544 Some(UTF16Selection {
23545 range: range.start.0..range.end.0,
23546 reversed: selection.reversed,
23547 })
23548 }
23549
23550 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23551 let snapshot = self.buffer.read(cx).read(cx);
23552 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23553 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23554 }
23555
23556 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23557 self.clear_highlights::<InputComposition>(cx);
23558 self.ime_transaction.take();
23559 }
23560
23561 fn replace_text_in_range(
23562 &mut self,
23563 range_utf16: Option<Range<usize>>,
23564 text: &str,
23565 window: &mut Window,
23566 cx: &mut Context<Self>,
23567 ) {
23568 if !self.input_enabled {
23569 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23570 return;
23571 }
23572
23573 self.transact(window, cx, |this, window, cx| {
23574 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23575 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23576 Some(this.selection_replacement_ranges(range_utf16, cx))
23577 } else {
23578 this.marked_text_ranges(cx)
23579 };
23580
23581 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23582 let newest_selection_id = this.selections.newest_anchor().id;
23583 this.selections
23584 .all::<OffsetUtf16>(cx)
23585 .iter()
23586 .zip(ranges_to_replace.iter())
23587 .find_map(|(selection, range)| {
23588 if selection.id == newest_selection_id {
23589 Some(
23590 (range.start.0 as isize - selection.head().0 as isize)
23591 ..(range.end.0 as isize - selection.head().0 as isize),
23592 )
23593 } else {
23594 None
23595 }
23596 })
23597 });
23598
23599 cx.emit(EditorEvent::InputHandled {
23600 utf16_range_to_replace: range_to_replace,
23601 text: text.into(),
23602 });
23603
23604 if let Some(new_selected_ranges) = new_selected_ranges {
23605 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23606 selections.select_ranges(new_selected_ranges)
23607 });
23608 this.backspace(&Default::default(), window, cx);
23609 }
23610
23611 this.handle_input(text, window, cx);
23612 });
23613
23614 if let Some(transaction) = self.ime_transaction {
23615 self.buffer.update(cx, |buffer, cx| {
23616 buffer.group_until_transaction(transaction, cx);
23617 });
23618 }
23619
23620 self.unmark_text(window, cx);
23621 }
23622
23623 fn replace_and_mark_text_in_range(
23624 &mut self,
23625 range_utf16: Option<Range<usize>>,
23626 text: &str,
23627 new_selected_range_utf16: Option<Range<usize>>,
23628 window: &mut Window,
23629 cx: &mut Context<Self>,
23630 ) {
23631 if !self.input_enabled {
23632 return;
23633 }
23634
23635 let transaction = self.transact(window, cx, |this, window, cx| {
23636 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23637 let snapshot = this.buffer.read(cx).read(cx);
23638 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23639 for marked_range in &mut marked_ranges {
23640 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23641 marked_range.start.0 += relative_range_utf16.start;
23642 marked_range.start =
23643 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23644 marked_range.end =
23645 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23646 }
23647 }
23648 Some(marked_ranges)
23649 } else if let Some(range_utf16) = range_utf16 {
23650 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23651 Some(this.selection_replacement_ranges(range_utf16, cx))
23652 } else {
23653 None
23654 };
23655
23656 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23657 let newest_selection_id = this.selections.newest_anchor().id;
23658 this.selections
23659 .all::<OffsetUtf16>(cx)
23660 .iter()
23661 .zip(ranges_to_replace.iter())
23662 .find_map(|(selection, range)| {
23663 if selection.id == newest_selection_id {
23664 Some(
23665 (range.start.0 as isize - selection.head().0 as isize)
23666 ..(range.end.0 as isize - selection.head().0 as isize),
23667 )
23668 } else {
23669 None
23670 }
23671 })
23672 });
23673
23674 cx.emit(EditorEvent::InputHandled {
23675 utf16_range_to_replace: range_to_replace,
23676 text: text.into(),
23677 });
23678
23679 if let Some(ranges) = ranges_to_replace {
23680 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23681 s.select_ranges(ranges)
23682 });
23683 }
23684
23685 let marked_ranges = {
23686 let snapshot = this.buffer.read(cx).read(cx);
23687 this.selections
23688 .disjoint_anchors_arc()
23689 .iter()
23690 .map(|selection| {
23691 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23692 })
23693 .collect::<Vec<_>>()
23694 };
23695
23696 if text.is_empty() {
23697 this.unmark_text(window, cx);
23698 } else {
23699 this.highlight_text::<InputComposition>(
23700 marked_ranges.clone(),
23701 HighlightStyle {
23702 underline: Some(UnderlineStyle {
23703 thickness: px(1.),
23704 color: None,
23705 wavy: false,
23706 }),
23707 ..Default::default()
23708 },
23709 cx,
23710 );
23711 }
23712
23713 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23714 let use_autoclose = this.use_autoclose;
23715 let use_auto_surround = this.use_auto_surround;
23716 this.set_use_autoclose(false);
23717 this.set_use_auto_surround(false);
23718 this.handle_input(text, window, cx);
23719 this.set_use_autoclose(use_autoclose);
23720 this.set_use_auto_surround(use_auto_surround);
23721
23722 if let Some(new_selected_range) = new_selected_range_utf16 {
23723 let snapshot = this.buffer.read(cx).read(cx);
23724 let new_selected_ranges = marked_ranges
23725 .into_iter()
23726 .map(|marked_range| {
23727 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23728 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23729 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23730 snapshot.clip_offset_utf16(new_start, Bias::Left)
23731 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23732 })
23733 .collect::<Vec<_>>();
23734
23735 drop(snapshot);
23736 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23737 selections.select_ranges(new_selected_ranges)
23738 });
23739 }
23740 });
23741
23742 self.ime_transaction = self.ime_transaction.or(transaction);
23743 if let Some(transaction) = self.ime_transaction {
23744 self.buffer.update(cx, |buffer, cx| {
23745 buffer.group_until_transaction(transaction, cx);
23746 });
23747 }
23748
23749 if self.text_highlights::<InputComposition>(cx).is_none() {
23750 self.ime_transaction.take();
23751 }
23752 }
23753
23754 fn bounds_for_range(
23755 &mut self,
23756 range_utf16: Range<usize>,
23757 element_bounds: gpui::Bounds<Pixels>,
23758 window: &mut Window,
23759 cx: &mut Context<Self>,
23760 ) -> Option<gpui::Bounds<Pixels>> {
23761 let text_layout_details = self.text_layout_details(window);
23762 let CharacterDimensions {
23763 em_width,
23764 em_advance,
23765 line_height,
23766 } = self.character_dimensions(window);
23767
23768 let snapshot = self.snapshot(window, cx);
23769 let scroll_position = snapshot.scroll_position();
23770 let scroll_left = scroll_position.x * em_advance;
23771
23772 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23773 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23774 + self.gutter_dimensions.full_width();
23775 let y = line_height * (start.row().as_f32() - scroll_position.y);
23776
23777 Some(Bounds {
23778 origin: element_bounds.origin + point(x, y),
23779 size: size(em_width, line_height),
23780 })
23781 }
23782
23783 fn character_index_for_point(
23784 &mut self,
23785 point: gpui::Point<Pixels>,
23786 _window: &mut Window,
23787 _cx: &mut Context<Self>,
23788 ) -> Option<usize> {
23789 let position_map = self.last_position_map.as_ref()?;
23790 if !position_map.text_hitbox.contains(&point) {
23791 return None;
23792 }
23793 let display_point = position_map.point_for_position(point).previous_valid;
23794 let anchor = position_map
23795 .snapshot
23796 .display_point_to_anchor(display_point, Bias::Left);
23797 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23798 Some(utf16_offset.0)
23799 }
23800}
23801
23802trait SelectionExt {
23803 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23804 fn spanned_rows(
23805 &self,
23806 include_end_if_at_line_start: bool,
23807 map: &DisplaySnapshot,
23808 ) -> Range<MultiBufferRow>;
23809}
23810
23811impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23812 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23813 let start = self
23814 .start
23815 .to_point(&map.buffer_snapshot)
23816 .to_display_point(map);
23817 let end = self
23818 .end
23819 .to_point(&map.buffer_snapshot)
23820 .to_display_point(map);
23821 if self.reversed {
23822 end..start
23823 } else {
23824 start..end
23825 }
23826 }
23827
23828 fn spanned_rows(
23829 &self,
23830 include_end_if_at_line_start: bool,
23831 map: &DisplaySnapshot,
23832 ) -> Range<MultiBufferRow> {
23833 let start = self.start.to_point(&map.buffer_snapshot);
23834 let mut end = self.end.to_point(&map.buffer_snapshot);
23835 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23836 end.row -= 1;
23837 }
23838
23839 let buffer_start = map.prev_line_boundary(start).0;
23840 let buffer_end = map.next_line_boundary(end).0;
23841 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23842 }
23843}
23844
23845impl<T: InvalidationRegion> InvalidationStack<T> {
23846 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23847 where
23848 S: Clone + ToOffset,
23849 {
23850 while let Some(region) = self.last() {
23851 let all_selections_inside_invalidation_ranges =
23852 if selections.len() == region.ranges().len() {
23853 selections
23854 .iter()
23855 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23856 .all(|(selection, invalidation_range)| {
23857 let head = selection.head().to_offset(buffer);
23858 invalidation_range.start <= head && invalidation_range.end >= head
23859 })
23860 } else {
23861 false
23862 };
23863
23864 if all_selections_inside_invalidation_ranges {
23865 break;
23866 } else {
23867 self.pop();
23868 }
23869 }
23870 }
23871}
23872
23873impl<T> Default for InvalidationStack<T> {
23874 fn default() -> Self {
23875 Self(Default::default())
23876 }
23877}
23878
23879impl<T> Deref for InvalidationStack<T> {
23880 type Target = Vec<T>;
23881
23882 fn deref(&self) -> &Self::Target {
23883 &self.0
23884 }
23885}
23886
23887impl<T> DerefMut for InvalidationStack<T> {
23888 fn deref_mut(&mut self) -> &mut Self::Target {
23889 &mut self.0
23890 }
23891}
23892
23893impl InvalidationRegion for SnippetState {
23894 fn ranges(&self) -> &[Range<Anchor>] {
23895 &self.ranges[self.active_index]
23896 }
23897}
23898
23899fn edit_prediction_edit_text(
23900 current_snapshot: &BufferSnapshot,
23901 edits: &[(Range<Anchor>, String)],
23902 edit_preview: &EditPreview,
23903 include_deletions: bool,
23904 cx: &App,
23905) -> HighlightedText {
23906 let edits = edits
23907 .iter()
23908 .map(|(anchor, text)| {
23909 (
23910 anchor.start.text_anchor..anchor.end.text_anchor,
23911 text.clone(),
23912 )
23913 })
23914 .collect::<Vec<_>>();
23915
23916 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23917}
23918
23919fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23920 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23921 // Just show the raw edit text with basic styling
23922 let mut text = String::new();
23923 let mut highlights = Vec::new();
23924
23925 let insertion_highlight_style = HighlightStyle {
23926 color: Some(cx.theme().colors().text),
23927 ..Default::default()
23928 };
23929
23930 for (_, edit_text) in edits {
23931 let start_offset = text.len();
23932 text.push_str(edit_text);
23933 let end_offset = text.len();
23934
23935 if start_offset < end_offset {
23936 highlights.push((start_offset..end_offset, insertion_highlight_style));
23937 }
23938 }
23939
23940 HighlightedText {
23941 text: text.into(),
23942 highlights,
23943 }
23944}
23945
23946pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23947 match severity {
23948 lsp::DiagnosticSeverity::ERROR => colors.error,
23949 lsp::DiagnosticSeverity::WARNING => colors.warning,
23950 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23951 lsp::DiagnosticSeverity::HINT => colors.info,
23952 _ => colors.ignored,
23953 }
23954}
23955
23956pub fn styled_runs_for_code_label<'a>(
23957 label: &'a CodeLabel,
23958 syntax_theme: &'a theme::SyntaxTheme,
23959) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23960 let fade_out = HighlightStyle {
23961 fade_out: Some(0.35),
23962 ..Default::default()
23963 };
23964
23965 let mut prev_end = label.filter_range.end;
23966 label
23967 .runs
23968 .iter()
23969 .enumerate()
23970 .flat_map(move |(ix, (range, highlight_id))| {
23971 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23972 style
23973 } else {
23974 return Default::default();
23975 };
23976 let muted_style = style.highlight(fade_out);
23977
23978 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23979 if range.start >= label.filter_range.end {
23980 if range.start > prev_end {
23981 runs.push((prev_end..range.start, fade_out));
23982 }
23983 runs.push((range.clone(), muted_style));
23984 } else if range.end <= label.filter_range.end {
23985 runs.push((range.clone(), style));
23986 } else {
23987 runs.push((range.start..label.filter_range.end, style));
23988 runs.push((label.filter_range.end..range.end, muted_style));
23989 }
23990 prev_end = cmp::max(prev_end, range.end);
23991
23992 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23993 runs.push((prev_end..label.text.len(), fade_out));
23994 }
23995
23996 runs
23997 })
23998}
23999
24000pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24001 let mut prev_index = 0;
24002 let mut prev_codepoint: Option<char> = None;
24003 text.char_indices()
24004 .chain([(text.len(), '\0')])
24005 .filter_map(move |(index, codepoint)| {
24006 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24007 let is_boundary = index == text.len()
24008 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24009 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24010 if is_boundary {
24011 let chunk = &text[prev_index..index];
24012 prev_index = index;
24013 Some(chunk)
24014 } else {
24015 None
24016 }
24017 })
24018}
24019
24020pub trait RangeToAnchorExt: Sized {
24021 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24022
24023 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24024 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
24025 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24026 }
24027}
24028
24029impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24030 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24031 let start_offset = self.start.to_offset(snapshot);
24032 let end_offset = self.end.to_offset(snapshot);
24033 if start_offset == end_offset {
24034 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24035 } else {
24036 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24037 }
24038 }
24039}
24040
24041pub trait RowExt {
24042 fn as_f32(&self) -> f32;
24043
24044 fn next_row(&self) -> Self;
24045
24046 fn previous_row(&self) -> Self;
24047
24048 fn minus(&self, other: Self) -> u32;
24049}
24050
24051impl RowExt for DisplayRow {
24052 fn as_f32(&self) -> f32 {
24053 self.0 as f32
24054 }
24055
24056 fn next_row(&self) -> Self {
24057 Self(self.0 + 1)
24058 }
24059
24060 fn previous_row(&self) -> Self {
24061 Self(self.0.saturating_sub(1))
24062 }
24063
24064 fn minus(&self, other: Self) -> u32 {
24065 self.0 - other.0
24066 }
24067}
24068
24069impl RowExt for MultiBufferRow {
24070 fn as_f32(&self) -> f32 {
24071 self.0 as f32
24072 }
24073
24074 fn next_row(&self) -> Self {
24075 Self(self.0 + 1)
24076 }
24077
24078 fn previous_row(&self) -> Self {
24079 Self(self.0.saturating_sub(1))
24080 }
24081
24082 fn minus(&self, other: Self) -> u32 {
24083 self.0 - other.0
24084 }
24085}
24086
24087trait RowRangeExt {
24088 type Row;
24089
24090 fn len(&self) -> usize;
24091
24092 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24093}
24094
24095impl RowRangeExt for Range<MultiBufferRow> {
24096 type Row = MultiBufferRow;
24097
24098 fn len(&self) -> usize {
24099 (self.end.0 - self.start.0) as usize
24100 }
24101
24102 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24103 (self.start.0..self.end.0).map(MultiBufferRow)
24104 }
24105}
24106
24107impl RowRangeExt for Range<DisplayRow> {
24108 type Row = DisplayRow;
24109
24110 fn len(&self) -> usize {
24111 (self.end.0 - self.start.0) as usize
24112 }
24113
24114 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24115 (self.start.0..self.end.0).map(DisplayRow)
24116 }
24117}
24118
24119/// If select range has more than one line, we
24120/// just point the cursor to range.start.
24121fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24122 if range.start.row == range.end.row {
24123 range
24124 } else {
24125 range.start..range.start
24126 }
24127}
24128pub struct KillRing(ClipboardItem);
24129impl Global for KillRing {}
24130
24131const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24132
24133enum BreakpointPromptEditAction {
24134 Log,
24135 Condition,
24136 HitCondition,
24137}
24138
24139struct BreakpointPromptEditor {
24140 pub(crate) prompt: Entity<Editor>,
24141 editor: WeakEntity<Editor>,
24142 breakpoint_anchor: Anchor,
24143 breakpoint: Breakpoint,
24144 edit_action: BreakpointPromptEditAction,
24145 block_ids: HashSet<CustomBlockId>,
24146 editor_margins: Arc<Mutex<EditorMargins>>,
24147 _subscriptions: Vec<Subscription>,
24148}
24149
24150impl BreakpointPromptEditor {
24151 const MAX_LINES: u8 = 4;
24152
24153 fn new(
24154 editor: WeakEntity<Editor>,
24155 breakpoint_anchor: Anchor,
24156 breakpoint: Breakpoint,
24157 edit_action: BreakpointPromptEditAction,
24158 window: &mut Window,
24159 cx: &mut Context<Self>,
24160 ) -> Self {
24161 let base_text = match edit_action {
24162 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24163 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24164 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24165 }
24166 .map(|msg| msg.to_string())
24167 .unwrap_or_default();
24168
24169 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24170 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24171
24172 let prompt = cx.new(|cx| {
24173 let mut prompt = Editor::new(
24174 EditorMode::AutoHeight {
24175 min_lines: 1,
24176 max_lines: Some(Self::MAX_LINES as usize),
24177 },
24178 buffer,
24179 None,
24180 window,
24181 cx,
24182 );
24183 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24184 prompt.set_show_cursor_when_unfocused(false, cx);
24185 prompt.set_placeholder_text(
24186 match edit_action {
24187 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24188 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24189 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24190 },
24191 window,
24192 cx,
24193 );
24194
24195 prompt
24196 });
24197
24198 Self {
24199 prompt,
24200 editor,
24201 breakpoint_anchor,
24202 breakpoint,
24203 edit_action,
24204 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24205 block_ids: Default::default(),
24206 _subscriptions: vec![],
24207 }
24208 }
24209
24210 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24211 self.block_ids.extend(block_ids)
24212 }
24213
24214 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24215 if let Some(editor) = self.editor.upgrade() {
24216 let message = self
24217 .prompt
24218 .read(cx)
24219 .buffer
24220 .read(cx)
24221 .as_singleton()
24222 .expect("A multi buffer in breakpoint prompt isn't possible")
24223 .read(cx)
24224 .as_rope()
24225 .to_string();
24226
24227 editor.update(cx, |editor, cx| {
24228 editor.edit_breakpoint_at_anchor(
24229 self.breakpoint_anchor,
24230 self.breakpoint.clone(),
24231 match self.edit_action {
24232 BreakpointPromptEditAction::Log => {
24233 BreakpointEditAction::EditLogMessage(message.into())
24234 }
24235 BreakpointPromptEditAction::Condition => {
24236 BreakpointEditAction::EditCondition(message.into())
24237 }
24238 BreakpointPromptEditAction::HitCondition => {
24239 BreakpointEditAction::EditHitCondition(message.into())
24240 }
24241 },
24242 cx,
24243 );
24244
24245 editor.remove_blocks(self.block_ids.clone(), None, cx);
24246 cx.focus_self(window);
24247 });
24248 }
24249 }
24250
24251 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24252 self.editor
24253 .update(cx, |editor, cx| {
24254 editor.remove_blocks(self.block_ids.clone(), None, cx);
24255 window.focus(&editor.focus_handle);
24256 })
24257 .log_err();
24258 }
24259
24260 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24261 let settings = ThemeSettings::get_global(cx);
24262 let text_style = TextStyle {
24263 color: if self.prompt.read(cx).read_only(cx) {
24264 cx.theme().colors().text_disabled
24265 } else {
24266 cx.theme().colors().text
24267 },
24268 font_family: settings.buffer_font.family.clone(),
24269 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24270 font_size: settings.buffer_font_size(cx).into(),
24271 font_weight: settings.buffer_font.weight,
24272 line_height: relative(settings.buffer_line_height.value()),
24273 ..Default::default()
24274 };
24275 EditorElement::new(
24276 &self.prompt,
24277 EditorStyle {
24278 background: cx.theme().colors().editor_background,
24279 local_player: cx.theme().players().local(),
24280 text: text_style,
24281 ..Default::default()
24282 },
24283 )
24284 }
24285}
24286
24287impl Render for BreakpointPromptEditor {
24288 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24289 let editor_margins = *self.editor_margins.lock();
24290 let gutter_dimensions = editor_margins.gutter;
24291 h_flex()
24292 .key_context("Editor")
24293 .bg(cx.theme().colors().editor_background)
24294 .border_y_1()
24295 .border_color(cx.theme().status().info_border)
24296 .size_full()
24297 .py(window.line_height() / 2.5)
24298 .on_action(cx.listener(Self::confirm))
24299 .on_action(cx.listener(Self::cancel))
24300 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24301 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24302 }
24303}
24304
24305impl Focusable for BreakpointPromptEditor {
24306 fn focus_handle(&self, cx: &App) -> FocusHandle {
24307 self.prompt.focus_handle(cx)
24308 }
24309}
24310
24311fn all_edits_insertions_or_deletions(
24312 edits: &Vec<(Range<Anchor>, String)>,
24313 snapshot: &MultiBufferSnapshot,
24314) -> bool {
24315 let mut all_insertions = true;
24316 let mut all_deletions = true;
24317
24318 for (range, new_text) in edits.iter() {
24319 let range_is_empty = range.to_offset(snapshot).is_empty();
24320 let text_is_empty = new_text.is_empty();
24321
24322 if range_is_empty != text_is_empty {
24323 if range_is_empty {
24324 all_deletions = false;
24325 } else {
24326 all_insertions = false;
24327 }
24328 } else {
24329 return false;
24330 }
24331
24332 if !all_insertions && !all_deletions {
24333 return false;
24334 }
24335 }
24336 all_insertions || all_deletions
24337}
24338
24339struct MissingEditPredictionKeybindingTooltip;
24340
24341impl Render for MissingEditPredictionKeybindingTooltip {
24342 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24343 ui::tooltip_container(window, cx, |container, _, cx| {
24344 container
24345 .flex_shrink_0()
24346 .max_w_80()
24347 .min_h(rems_from_px(124.))
24348 .justify_between()
24349 .child(
24350 v_flex()
24351 .flex_1()
24352 .text_ui_sm(cx)
24353 .child(Label::new("Conflict with Accept Keybinding"))
24354 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24355 )
24356 .child(
24357 h_flex()
24358 .pb_1()
24359 .gap_1()
24360 .items_end()
24361 .w_full()
24362 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24363 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24364 }))
24365 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24366 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24367 })),
24368 )
24369 })
24370 }
24371}
24372
24373#[derive(Debug, Clone, Copy, PartialEq)]
24374pub struct LineHighlight {
24375 pub background: Background,
24376 pub border: Option<gpui::Hsla>,
24377 pub include_gutter: bool,
24378 pub type_id: Option<TypeId>,
24379}
24380
24381struct LineManipulationResult {
24382 pub new_text: String,
24383 pub line_count_before: usize,
24384 pub line_count_after: usize,
24385}
24386
24387fn render_diff_hunk_controls(
24388 row: u32,
24389 status: &DiffHunkStatus,
24390 hunk_range: Range<Anchor>,
24391 is_created_file: bool,
24392 line_height: Pixels,
24393 editor: &Entity<Editor>,
24394 _window: &mut Window,
24395 cx: &mut App,
24396) -> AnyElement {
24397 h_flex()
24398 .h(line_height)
24399 .mr_1()
24400 .gap_1()
24401 .px_0p5()
24402 .pb_1()
24403 .border_x_1()
24404 .border_b_1()
24405 .border_color(cx.theme().colors().border_variant)
24406 .rounded_b_lg()
24407 .bg(cx.theme().colors().editor_background)
24408 .gap_1()
24409 .block_mouse_except_scroll()
24410 .shadow_md()
24411 .child(if status.has_secondary_hunk() {
24412 Button::new(("stage", row as u64), "Stage")
24413 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24414 .tooltip({
24415 let focus_handle = editor.focus_handle(cx);
24416 move |window, cx| {
24417 Tooltip::for_action_in(
24418 "Stage Hunk",
24419 &::git::ToggleStaged,
24420 &focus_handle,
24421 window,
24422 cx,
24423 )
24424 }
24425 })
24426 .on_click({
24427 let editor = editor.clone();
24428 move |_event, _window, cx| {
24429 editor.update(cx, |editor, cx| {
24430 editor.stage_or_unstage_diff_hunks(
24431 true,
24432 vec![hunk_range.start..hunk_range.start],
24433 cx,
24434 );
24435 });
24436 }
24437 })
24438 } else {
24439 Button::new(("unstage", row as u64), "Unstage")
24440 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24441 .tooltip({
24442 let focus_handle = editor.focus_handle(cx);
24443 move |window, cx| {
24444 Tooltip::for_action_in(
24445 "Unstage Hunk",
24446 &::git::ToggleStaged,
24447 &focus_handle,
24448 window,
24449 cx,
24450 )
24451 }
24452 })
24453 .on_click({
24454 let editor = editor.clone();
24455 move |_event, _window, cx| {
24456 editor.update(cx, |editor, cx| {
24457 editor.stage_or_unstage_diff_hunks(
24458 false,
24459 vec![hunk_range.start..hunk_range.start],
24460 cx,
24461 );
24462 });
24463 }
24464 })
24465 })
24466 .child(
24467 Button::new(("restore", row as u64), "Restore")
24468 .tooltip({
24469 let focus_handle = editor.focus_handle(cx);
24470 move |window, cx| {
24471 Tooltip::for_action_in(
24472 "Restore Hunk",
24473 &::git::Restore,
24474 &focus_handle,
24475 window,
24476 cx,
24477 )
24478 }
24479 })
24480 .on_click({
24481 let editor = editor.clone();
24482 move |_event, window, cx| {
24483 editor.update(cx, |editor, cx| {
24484 let snapshot = editor.snapshot(window, cx);
24485 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24486 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24487 });
24488 }
24489 })
24490 .disabled(is_created_file),
24491 )
24492 .when(
24493 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24494 |el| {
24495 el.child(
24496 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24497 .shape(IconButtonShape::Square)
24498 .icon_size(IconSize::Small)
24499 // .disabled(!has_multiple_hunks)
24500 .tooltip({
24501 let focus_handle = editor.focus_handle(cx);
24502 move |window, cx| {
24503 Tooltip::for_action_in(
24504 "Next Hunk",
24505 &GoToHunk,
24506 &focus_handle,
24507 window,
24508 cx,
24509 )
24510 }
24511 })
24512 .on_click({
24513 let editor = editor.clone();
24514 move |_event, window, cx| {
24515 editor.update(cx, |editor, cx| {
24516 let snapshot = editor.snapshot(window, cx);
24517 let position =
24518 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24519 editor.go_to_hunk_before_or_after_position(
24520 &snapshot,
24521 position,
24522 Direction::Next,
24523 window,
24524 cx,
24525 );
24526 editor.expand_selected_diff_hunks(cx);
24527 });
24528 }
24529 }),
24530 )
24531 .child(
24532 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24533 .shape(IconButtonShape::Square)
24534 .icon_size(IconSize::Small)
24535 // .disabled(!has_multiple_hunks)
24536 .tooltip({
24537 let focus_handle = editor.focus_handle(cx);
24538 move |window, cx| {
24539 Tooltip::for_action_in(
24540 "Previous Hunk",
24541 &GoToPreviousHunk,
24542 &focus_handle,
24543 window,
24544 cx,
24545 )
24546 }
24547 })
24548 .on_click({
24549 let editor = editor.clone();
24550 move |_event, window, cx| {
24551 editor.update(cx, |editor, cx| {
24552 let snapshot = editor.snapshot(window, cx);
24553 let point =
24554 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24555 editor.go_to_hunk_before_or_after_position(
24556 &snapshot,
24557 point,
24558 Direction::Prev,
24559 window,
24560 cx,
24561 );
24562 editor.expand_selected_diff_hunks(cx);
24563 });
24564 }
24565 }),
24566 )
24567 },
24568 )
24569 .into_any_element()
24570}
24571
24572pub fn multibuffer_context_lines(cx: &App) -> u32 {
24573 EditorSettings::try_get(cx)
24574 .map(|settings| settings.excerpt_context_lines)
24575 .unwrap_or(2)
24576 .min(32)
24577}