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 HighlightStyle {
596 color: Some(cx.theme().status().hint),
597 background_color: show_background.then(|| cx.theme().status().hint_background),
598 ..HighlightStyle::default()
599 }
600}
601
602pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
603 EditPredictionStyles {
604 insertion: HighlightStyle {
605 color: Some(cx.theme().status().predictive),
606 ..HighlightStyle::default()
607 },
608 whitespace: HighlightStyle {
609 background_color: Some(cx.theme().status().created_background),
610 ..HighlightStyle::default()
611 },
612 }
613}
614
615type CompletionId = usize;
616
617pub(crate) enum EditDisplayMode {
618 TabAccept,
619 DiffPopover,
620 Inline,
621}
622
623enum EditPrediction {
624 Edit {
625 edits: Vec<(Range<Anchor>, String)>,
626 edit_preview: Option<EditPreview>,
627 display_mode: EditDisplayMode,
628 snapshot: BufferSnapshot,
629 },
630 Move {
631 target: Anchor,
632 snapshot: BufferSnapshot,
633 },
634}
635
636struct EditPredictionState {
637 inlay_ids: Vec<InlayId>,
638 completion: EditPrediction,
639 completion_id: Option<SharedString>,
640 invalidation_range: Range<Anchor>,
641}
642
643enum EditPredictionSettings {
644 Disabled,
645 Enabled {
646 show_in_menu: bool,
647 preview_requires_modifier: bool,
648 },
649}
650
651enum EditPredictionHighlight {}
652
653#[derive(Debug, Clone)]
654struct InlineDiagnostic {
655 message: SharedString,
656 group_id: usize,
657 is_primary: bool,
658 start: Point,
659 severity: lsp::DiagnosticSeverity,
660}
661
662pub enum MenuEditPredictionsPolicy {
663 Never,
664 ByProvider,
665}
666
667pub enum EditPredictionPreview {
668 /// Modifier is not pressed
669 Inactive { released_too_fast: bool },
670 /// Modifier pressed
671 Active {
672 since: Instant,
673 previous_scroll_position: Option<ScrollAnchor>,
674 },
675}
676
677impl EditPredictionPreview {
678 pub fn released_too_fast(&self) -> bool {
679 match self {
680 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
681 EditPredictionPreview::Active { .. } => false,
682 }
683 }
684
685 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
686 if let EditPredictionPreview::Active {
687 previous_scroll_position,
688 ..
689 } = self
690 {
691 *previous_scroll_position = scroll_position;
692 }
693 }
694}
695
696pub struct ContextMenuOptions {
697 pub min_entries_visible: usize,
698 pub max_entries_visible: usize,
699 pub placement: Option<ContextMenuPlacement>,
700}
701
702#[derive(Debug, Clone, PartialEq, Eq)]
703pub enum ContextMenuPlacement {
704 Above,
705 Below,
706}
707
708#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
709struct EditorActionId(usize);
710
711impl EditorActionId {
712 pub fn post_inc(&mut self) -> Self {
713 let answer = self.0;
714
715 *self = Self(answer + 1);
716
717 Self(answer)
718 }
719}
720
721// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
722// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
723
724type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
725type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
726
727#[derive(Default)]
728struct ScrollbarMarkerState {
729 scrollbar_size: Size<Pixels>,
730 dirty: bool,
731 markers: Arc<[PaintQuad]>,
732 pending_refresh: Option<Task<Result<()>>>,
733}
734
735impl ScrollbarMarkerState {
736 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
737 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
738 }
739}
740
741#[derive(Clone, Copy, PartialEq, Eq)]
742pub enum MinimapVisibility {
743 Disabled,
744 Enabled {
745 /// The configuration currently present in the users settings.
746 setting_configuration: bool,
747 /// Whether to override the currently set visibility from the users setting.
748 toggle_override: bool,
749 },
750}
751
752impl MinimapVisibility {
753 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
754 if mode.is_full() {
755 Self::Enabled {
756 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
757 toggle_override: false,
758 }
759 } else {
760 Self::Disabled
761 }
762 }
763
764 fn hidden(&self) -> Self {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => Self::Enabled {
770 setting_configuration,
771 toggle_override: setting_configuration,
772 },
773 Self::Disabled => Self::Disabled,
774 }
775 }
776
777 fn disabled(&self) -> bool {
778 matches!(*self, Self::Disabled)
779 }
780
781 fn settings_visibility(&self) -> bool {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => setting_configuration,
787 _ => false,
788 }
789 }
790
791 fn visible(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 toggle_override,
796 } => setting_configuration ^ toggle_override,
797 _ => false,
798 }
799 }
800
801 fn toggle_visibility(&self) -> Self {
802 match *self {
803 Self::Enabled {
804 toggle_override,
805 setting_configuration,
806 } => Self::Enabled {
807 setting_configuration,
808 toggle_override: !toggle_override,
809 },
810 Self::Disabled => Self::Disabled,
811 }
812 }
813}
814
815#[derive(Clone, Debug)]
816struct RunnableTasks {
817 templates: Vec<(TaskSourceKind, TaskTemplate)>,
818 offset: multi_buffer::Anchor,
819 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
820 column: u32,
821 // Values of all named captures, including those starting with '_'
822 extra_variables: HashMap<String, String>,
823 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
824 context_range: Range<BufferOffset>,
825}
826
827impl RunnableTasks {
828 fn resolve<'a>(
829 &'a self,
830 cx: &'a task::TaskContext,
831 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
832 self.templates.iter().filter_map(|(kind, template)| {
833 template
834 .resolve_task(&kind.to_id_base(), cx)
835 .map(|task| (kind.clone(), task))
836 })
837 }
838}
839
840#[derive(Clone)]
841pub struct ResolvedTasks {
842 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
843 position: Anchor,
844}
845
846#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
847struct BufferOffset(usize);
848
849/// Addons allow storing per-editor state in other crates (e.g. Vim)
850pub trait Addon: 'static {
851 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
852
853 fn render_buffer_header_controls(
854 &self,
855 _: &ExcerptInfo,
856 _: &Window,
857 _: &App,
858 ) -> Option<AnyElement> {
859 None
860 }
861
862 fn to_any(&self) -> &dyn std::any::Any;
863
864 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
865 None
866 }
867}
868
869struct ChangeLocation {
870 current: Option<Vec<Anchor>>,
871 original: Vec<Anchor>,
872}
873impl ChangeLocation {
874 fn locations(&self) -> &[Anchor] {
875 self.current.as_ref().unwrap_or(&self.original)
876 }
877}
878
879/// A set of caret positions, registered when the editor was edited.
880pub struct ChangeList {
881 changes: Vec<ChangeLocation>,
882 /// Currently "selected" change.
883 position: Option<usize>,
884}
885
886impl ChangeList {
887 pub fn new() -> Self {
888 Self {
889 changes: Vec::new(),
890 position: None,
891 }
892 }
893
894 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
895 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
896 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
897 if self.changes.is_empty() {
898 return None;
899 }
900
901 let prev = self.position.unwrap_or(self.changes.len());
902 let next = if direction == Direction::Prev {
903 prev.saturating_sub(count)
904 } else {
905 (prev + count).min(self.changes.len() - 1)
906 };
907 self.position = Some(next);
908 self.changes.get(next).map(|change| change.locations())
909 }
910
911 /// Adds a new change to the list, resetting the change list position.
912 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
913 self.position.take();
914 if let Some(last) = self.changes.last_mut()
915 && group
916 {
917 last.current = Some(new_positions)
918 } else {
919 self.changes.push(ChangeLocation {
920 original: new_positions,
921 current: None,
922 });
923 }
924 }
925
926 pub fn last(&self) -> Option<&[Anchor]> {
927 self.changes.last().map(|change| change.locations())
928 }
929
930 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
931 self.changes.last().map(|change| change.original.as_slice())
932 }
933
934 pub fn invert_last_group(&mut self) {
935 if let Some(last) = self.changes.last_mut()
936 && let Some(current) = last.current.as_mut()
937 {
938 mem::swap(&mut last.original, current);
939 }
940 }
941}
942
943#[derive(Clone)]
944struct InlineBlamePopoverState {
945 scroll_handle: ScrollHandle,
946 commit_message: Option<ParsedCommitMessage>,
947 markdown: Entity<Markdown>,
948}
949
950struct InlineBlamePopover {
951 position: gpui::Point<Pixels>,
952 hide_task: Option<Task<()>>,
953 popover_bounds: Option<Bounds<Pixels>>,
954 popover_state: InlineBlamePopoverState,
955 keyboard_grace: bool,
956}
957
958enum SelectionDragState {
959 /// State when no drag related activity is detected.
960 None,
961 /// State when the mouse is down on a selection that is about to be dragged.
962 ReadyToDrag {
963 selection: Selection<Anchor>,
964 click_position: gpui::Point<Pixels>,
965 mouse_down_time: Instant,
966 },
967 /// State when the mouse is dragging the selection in the editor.
968 Dragging {
969 selection: Selection<Anchor>,
970 drop_cursor: Selection<Anchor>,
971 hide_drop_cursor: bool,
972 },
973}
974
975enum ColumnarSelectionState {
976 FromMouse {
977 selection_tail: Anchor,
978 display_point: Option<DisplayPoint>,
979 },
980 FromSelection {
981 selection_tail: Anchor,
982 },
983}
984
985/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
986/// a breakpoint on them.
987#[derive(Clone, Copy, Debug, PartialEq, Eq)]
988struct PhantomBreakpointIndicator {
989 display_row: DisplayRow,
990 /// There's a small debounce between hovering over the line and showing the indicator.
991 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
992 is_active: bool,
993 collides_with_existing_breakpoint: bool,
994}
995
996/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
997///
998/// See the [module level documentation](self) for more information.
999pub struct Editor {
1000 focus_handle: FocusHandle,
1001 last_focused_descendant: Option<WeakFocusHandle>,
1002 /// The text buffer being edited
1003 buffer: Entity<MultiBuffer>,
1004 /// Map of how text in the buffer should be displayed.
1005 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1006 pub display_map: Entity<DisplayMap>,
1007 placeholder_display_map: Option<Entity<DisplayMap>>,
1008 pub selections: SelectionsCollection,
1009 pub scroll_manager: ScrollManager,
1010 /// When inline assist editors are linked, they all render cursors because
1011 /// typing enters text into each of them, even the ones that aren't focused.
1012 pub(crate) show_cursor_when_unfocused: bool,
1013 columnar_selection_state: Option<ColumnarSelectionState>,
1014 add_selections_state: Option<AddSelectionsState>,
1015 select_next_state: Option<SelectNextState>,
1016 select_prev_state: Option<SelectNextState>,
1017 selection_history: SelectionHistory,
1018 defer_selection_effects: bool,
1019 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1020 autoclose_regions: Vec<AutocloseRegion>,
1021 snippet_stack: InvalidationStack<SnippetState>,
1022 select_syntax_node_history: SelectSyntaxNodeHistory,
1023 ime_transaction: Option<TransactionId>,
1024 pub diagnostics_max_severity: DiagnosticSeverity,
1025 active_diagnostics: ActiveDiagnostic,
1026 show_inline_diagnostics: bool,
1027 inline_diagnostics_update: Task<()>,
1028 inline_diagnostics_enabled: bool,
1029 diagnostics_enabled: bool,
1030 word_completions_enabled: bool,
1031 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1032 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1033 hard_wrap: Option<usize>,
1034 project: Option<Entity<Project>>,
1035 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1036 completion_provider: Option<Rc<dyn CompletionProvider>>,
1037 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1038 blink_manager: Entity<BlinkManager>,
1039 show_cursor_names: bool,
1040 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1041 pub show_local_selections: bool,
1042 mode: EditorMode,
1043 show_breadcrumbs: bool,
1044 show_gutter: bool,
1045 show_scrollbars: ScrollbarAxes,
1046 minimap_visibility: MinimapVisibility,
1047 offset_content: bool,
1048 disable_expand_excerpt_buttons: bool,
1049 show_line_numbers: Option<bool>,
1050 use_relative_line_numbers: Option<bool>,
1051 show_git_diff_gutter: Option<bool>,
1052 show_code_actions: Option<bool>,
1053 show_runnables: Option<bool>,
1054 show_breakpoints: Option<bool>,
1055 show_wrap_guides: Option<bool>,
1056 show_indent_guides: Option<bool>,
1057 highlight_order: usize,
1058 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1059 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1060 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1061 scrollbar_marker_state: ScrollbarMarkerState,
1062 active_indent_guides_state: ActiveIndentGuidesState,
1063 nav_history: Option<ItemNavHistory>,
1064 context_menu: RefCell<Option<CodeContextMenu>>,
1065 context_menu_options: Option<ContextMenuOptions>,
1066 mouse_context_menu: Option<MouseContextMenu>,
1067 completion_tasks: Vec<(CompletionId, Task<()>)>,
1068 inline_blame_popover: Option<InlineBlamePopover>,
1069 inline_blame_popover_show_task: Option<Task<()>>,
1070 signature_help_state: SignatureHelpState,
1071 auto_signature_help: Option<bool>,
1072 find_all_references_task_sources: Vec<Anchor>,
1073 next_completion_id: CompletionId,
1074 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1075 code_actions_task: Option<Task<Result<()>>>,
1076 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1077 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1078 document_highlights_task: Option<Task<()>>,
1079 linked_editing_range_task: Option<Task<Option<()>>>,
1080 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1081 pending_rename: Option<RenameState>,
1082 searchable: bool,
1083 cursor_shape: CursorShape,
1084 current_line_highlight: Option<CurrentLineHighlight>,
1085 collapse_matches: bool,
1086 autoindent_mode: Option<AutoindentMode>,
1087 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1088 input_enabled: bool,
1089 use_modal_editing: bool,
1090 read_only: bool,
1091 leader_id: Option<CollaboratorId>,
1092 remote_id: Option<ViewId>,
1093 pub hover_state: HoverState,
1094 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1095 gutter_hovered: bool,
1096 hovered_link_state: Option<HoveredLinkState>,
1097 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1098 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1099 active_edit_prediction: Option<EditPredictionState>,
1100 /// Used to prevent flickering as the user types while the menu is open
1101 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1102 edit_prediction_settings: EditPredictionSettings,
1103 edit_predictions_hidden_for_vim_mode: bool,
1104 show_edit_predictions_override: Option<bool>,
1105 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1106 edit_prediction_preview: EditPredictionPreview,
1107 edit_prediction_indent_conflict: bool,
1108 edit_prediction_requires_modifier_in_indent_conflict: bool,
1109 inlay_hint_cache: InlayHintCache,
1110 next_inlay_id: usize,
1111 _subscriptions: Vec<Subscription>,
1112 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1113 gutter_dimensions: GutterDimensions,
1114 style: Option<EditorStyle>,
1115 text_style_refinement: Option<TextStyleRefinement>,
1116 next_editor_action_id: EditorActionId,
1117 editor_actions: Rc<
1118 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1119 >,
1120 use_autoclose: bool,
1121 use_auto_surround: bool,
1122 auto_replace_emoji_shortcode: bool,
1123 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1124 show_git_blame_gutter: bool,
1125 show_git_blame_inline: bool,
1126 show_git_blame_inline_delay_task: Option<Task<()>>,
1127 git_blame_inline_enabled: bool,
1128 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1129 serialize_dirty_buffers: bool,
1130 show_selection_menu: Option<bool>,
1131 blame: Option<Entity<GitBlame>>,
1132 blame_subscription: Option<Subscription>,
1133 custom_context_menu: Option<
1134 Box<
1135 dyn 'static
1136 + Fn(
1137 &mut Self,
1138 DisplayPoint,
1139 &mut Window,
1140 &mut Context<Self>,
1141 ) -> Option<Entity<ui::ContextMenu>>,
1142 >,
1143 >,
1144 last_bounds: Option<Bounds<Pixels>>,
1145 last_position_map: Option<Rc<PositionMap>>,
1146 expect_bounds_change: Option<Bounds<Pixels>>,
1147 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1148 tasks_update_task: Option<Task<()>>,
1149 breakpoint_store: Option<Entity<BreakpointStore>>,
1150 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1151 hovered_diff_hunk_row: Option<DisplayRow>,
1152 pull_diagnostics_task: Task<()>,
1153 in_project_search: bool,
1154 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1155 breadcrumb_header: Option<String>,
1156 focused_block: Option<FocusedBlock>,
1157 next_scroll_position: NextScrollCursorCenterTopBottom,
1158 addons: HashMap<TypeId, Box<dyn Addon>>,
1159 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1160 load_diff_task: Option<Shared<Task<()>>>,
1161 /// Whether we are temporarily displaying a diff other than git's
1162 temporary_diff_override: bool,
1163 selection_mark_mode: bool,
1164 toggle_fold_multiple_buffers: Task<()>,
1165 _scroll_cursor_center_top_bottom_task: Task<()>,
1166 serialize_selections: Task<()>,
1167 serialize_folds: Task<()>,
1168 mouse_cursor_hidden: bool,
1169 minimap: Option<Entity<Self>>,
1170 hide_mouse_mode: HideMouseMode,
1171 pub change_list: ChangeList,
1172 inline_value_cache: InlineValueCache,
1173 selection_drag_state: SelectionDragState,
1174 next_color_inlay_id: usize,
1175 colors: Option<LspColorData>,
1176 folding_newlines: Task<()>,
1177 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1178}
1179
1180#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1181enum NextScrollCursorCenterTopBottom {
1182 #[default]
1183 Center,
1184 Top,
1185 Bottom,
1186}
1187
1188impl NextScrollCursorCenterTopBottom {
1189 fn next(&self) -> Self {
1190 match self {
1191 Self::Center => Self::Top,
1192 Self::Top => Self::Bottom,
1193 Self::Bottom => Self::Center,
1194 }
1195 }
1196}
1197
1198#[derive(Clone)]
1199pub struct EditorSnapshot {
1200 pub mode: EditorMode,
1201 show_gutter: bool,
1202 show_line_numbers: Option<bool>,
1203 show_git_diff_gutter: Option<bool>,
1204 show_code_actions: Option<bool>,
1205 show_runnables: Option<bool>,
1206 show_breakpoints: Option<bool>,
1207 git_blame_gutter_max_author_length: Option<usize>,
1208 pub display_snapshot: DisplaySnapshot,
1209 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1210 is_focused: bool,
1211 scroll_anchor: ScrollAnchor,
1212 ongoing_scroll: OngoingScroll,
1213 current_line_highlight: CurrentLineHighlight,
1214 gutter_hovered: bool,
1215}
1216
1217#[derive(Default, Debug, Clone, Copy)]
1218pub struct GutterDimensions {
1219 pub left_padding: Pixels,
1220 pub right_padding: Pixels,
1221 pub width: Pixels,
1222 pub margin: Pixels,
1223 pub git_blame_entries_width: Option<Pixels>,
1224}
1225
1226impl GutterDimensions {
1227 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1228 Self {
1229 margin: Self::default_gutter_margin(font_id, font_size, cx),
1230 ..Default::default()
1231 }
1232 }
1233
1234 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1235 -cx.text_system().descent(font_id, font_size)
1236 }
1237 /// The full width of the space taken up by the gutter.
1238 pub fn full_width(&self) -> Pixels {
1239 self.margin + self.width
1240 }
1241
1242 /// The width of the space reserved for the fold indicators,
1243 /// use alongside 'justify_end' and `gutter_width` to
1244 /// right align content with the line numbers
1245 pub fn fold_area_width(&self) -> Pixels {
1246 self.margin + self.right_padding
1247 }
1248}
1249
1250struct CharacterDimensions {
1251 em_width: Pixels,
1252 em_advance: Pixels,
1253 line_height: Pixels,
1254}
1255
1256#[derive(Debug)]
1257pub struct RemoteSelection {
1258 pub replica_id: ReplicaId,
1259 pub selection: Selection<Anchor>,
1260 pub cursor_shape: CursorShape,
1261 pub collaborator_id: CollaboratorId,
1262 pub line_mode: bool,
1263 pub user_name: Option<SharedString>,
1264 pub color: PlayerColor,
1265}
1266
1267#[derive(Clone, Debug)]
1268struct SelectionHistoryEntry {
1269 selections: Arc<[Selection<Anchor>]>,
1270 select_next_state: Option<SelectNextState>,
1271 select_prev_state: Option<SelectNextState>,
1272 add_selections_state: Option<AddSelectionsState>,
1273}
1274
1275#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1276enum SelectionHistoryMode {
1277 Normal,
1278 Undoing,
1279 Redoing,
1280 Skipping,
1281}
1282
1283#[derive(Clone, PartialEq, Eq, Hash)]
1284struct HoveredCursor {
1285 replica_id: u16,
1286 selection_id: usize,
1287}
1288
1289impl Default for SelectionHistoryMode {
1290 fn default() -> Self {
1291 Self::Normal
1292 }
1293}
1294
1295#[derive(Debug)]
1296/// SelectionEffects controls the side-effects of updating the selection.
1297///
1298/// The default behaviour does "what you mostly want":
1299/// - it pushes to the nav history if the cursor moved by >10 lines
1300/// - it re-triggers completion requests
1301/// - it scrolls to fit
1302///
1303/// You might want to modify these behaviours. For example when doing a "jump"
1304/// like go to definition, we always want to add to nav history; but when scrolling
1305/// in vim mode we never do.
1306///
1307/// Similarly, you might want to disable scrolling if you don't want the viewport to
1308/// move.
1309#[derive(Clone)]
1310pub struct SelectionEffects {
1311 nav_history: Option<bool>,
1312 completions: bool,
1313 scroll: Option<Autoscroll>,
1314}
1315
1316impl Default for SelectionEffects {
1317 fn default() -> Self {
1318 Self {
1319 nav_history: None,
1320 completions: true,
1321 scroll: Some(Autoscroll::fit()),
1322 }
1323 }
1324}
1325impl SelectionEffects {
1326 pub fn scroll(scroll: Autoscroll) -> Self {
1327 Self {
1328 scroll: Some(scroll),
1329 ..Default::default()
1330 }
1331 }
1332
1333 pub fn no_scroll() -> Self {
1334 Self {
1335 scroll: None,
1336 ..Default::default()
1337 }
1338 }
1339
1340 pub fn completions(self, completions: bool) -> Self {
1341 Self {
1342 completions,
1343 ..self
1344 }
1345 }
1346
1347 pub fn nav_history(self, nav_history: bool) -> Self {
1348 Self {
1349 nav_history: Some(nav_history),
1350 ..self
1351 }
1352 }
1353}
1354
1355struct DeferredSelectionEffectsState {
1356 changed: bool,
1357 effects: SelectionEffects,
1358 old_cursor_position: Anchor,
1359 history_entry: SelectionHistoryEntry,
1360}
1361
1362#[derive(Default)]
1363struct SelectionHistory {
1364 #[allow(clippy::type_complexity)]
1365 selections_by_transaction:
1366 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1367 mode: SelectionHistoryMode,
1368 undo_stack: VecDeque<SelectionHistoryEntry>,
1369 redo_stack: VecDeque<SelectionHistoryEntry>,
1370}
1371
1372impl SelectionHistory {
1373 #[track_caller]
1374 fn insert_transaction(
1375 &mut self,
1376 transaction_id: TransactionId,
1377 selections: Arc<[Selection<Anchor>]>,
1378 ) {
1379 if selections.is_empty() {
1380 log::error!(
1381 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1382 std::panic::Location::caller()
1383 );
1384 return;
1385 }
1386 self.selections_by_transaction
1387 .insert(transaction_id, (selections, None));
1388 }
1389
1390 #[allow(clippy::type_complexity)]
1391 fn transaction(
1392 &self,
1393 transaction_id: TransactionId,
1394 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1395 self.selections_by_transaction.get(&transaction_id)
1396 }
1397
1398 #[allow(clippy::type_complexity)]
1399 fn transaction_mut(
1400 &mut self,
1401 transaction_id: TransactionId,
1402 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1403 self.selections_by_transaction.get_mut(&transaction_id)
1404 }
1405
1406 fn push(&mut self, entry: SelectionHistoryEntry) {
1407 if !entry.selections.is_empty() {
1408 match self.mode {
1409 SelectionHistoryMode::Normal => {
1410 self.push_undo(entry);
1411 self.redo_stack.clear();
1412 }
1413 SelectionHistoryMode::Undoing => self.push_redo(entry),
1414 SelectionHistoryMode::Redoing => self.push_undo(entry),
1415 SelectionHistoryMode::Skipping => {}
1416 }
1417 }
1418 }
1419
1420 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1421 if self
1422 .undo_stack
1423 .back()
1424 .is_none_or(|e| e.selections != entry.selections)
1425 {
1426 self.undo_stack.push_back(entry);
1427 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1428 self.undo_stack.pop_front();
1429 }
1430 }
1431 }
1432
1433 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1434 if self
1435 .redo_stack
1436 .back()
1437 .is_none_or(|e| e.selections != entry.selections)
1438 {
1439 self.redo_stack.push_back(entry);
1440 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1441 self.redo_stack.pop_front();
1442 }
1443 }
1444 }
1445}
1446
1447#[derive(Clone, Copy)]
1448pub struct RowHighlightOptions {
1449 pub autoscroll: bool,
1450 pub include_gutter: bool,
1451}
1452
1453impl Default for RowHighlightOptions {
1454 fn default() -> Self {
1455 Self {
1456 autoscroll: Default::default(),
1457 include_gutter: true,
1458 }
1459 }
1460}
1461
1462struct RowHighlight {
1463 index: usize,
1464 range: Range<Anchor>,
1465 color: Hsla,
1466 options: RowHighlightOptions,
1467 type_id: TypeId,
1468}
1469
1470#[derive(Clone, Debug)]
1471struct AddSelectionsState {
1472 groups: Vec<AddSelectionsGroup>,
1473}
1474
1475#[derive(Clone, Debug)]
1476struct AddSelectionsGroup {
1477 above: bool,
1478 stack: Vec<usize>,
1479}
1480
1481#[derive(Clone)]
1482struct SelectNextState {
1483 query: AhoCorasick,
1484 wordwise: bool,
1485 done: bool,
1486}
1487
1488impl std::fmt::Debug for SelectNextState {
1489 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1490 f.debug_struct(std::any::type_name::<Self>())
1491 .field("wordwise", &self.wordwise)
1492 .field("done", &self.done)
1493 .finish()
1494 }
1495}
1496
1497#[derive(Debug)]
1498struct AutocloseRegion {
1499 selection_id: usize,
1500 range: Range<Anchor>,
1501 pair: BracketPair,
1502}
1503
1504#[derive(Debug)]
1505struct SnippetState {
1506 ranges: Vec<Vec<Range<Anchor>>>,
1507 active_index: usize,
1508 choices: Vec<Option<Vec<String>>>,
1509}
1510
1511#[doc(hidden)]
1512pub struct RenameState {
1513 pub range: Range<Anchor>,
1514 pub old_name: Arc<str>,
1515 pub editor: Entity<Editor>,
1516 block_id: CustomBlockId,
1517}
1518
1519struct InvalidationStack<T>(Vec<T>);
1520
1521struct RegisteredEditPredictionProvider {
1522 provider: Arc<dyn EditPredictionProviderHandle>,
1523 _subscription: Subscription,
1524}
1525
1526#[derive(Debug, PartialEq, Eq)]
1527pub struct ActiveDiagnosticGroup {
1528 pub active_range: Range<Anchor>,
1529 pub active_message: String,
1530 pub group_id: usize,
1531 pub blocks: HashSet<CustomBlockId>,
1532}
1533
1534#[derive(Debug, PartialEq, Eq)]
1535
1536pub(crate) enum ActiveDiagnostic {
1537 None,
1538 All,
1539 Group(ActiveDiagnosticGroup),
1540}
1541
1542#[derive(Serialize, Deserialize, Clone, Debug)]
1543pub struct ClipboardSelection {
1544 /// The number of bytes in this selection.
1545 pub len: usize,
1546 /// Whether this was a full-line selection.
1547 pub is_entire_line: bool,
1548 /// The indentation of the first line when this content was originally copied.
1549 pub first_line_indent: u32,
1550}
1551
1552// selections, scroll behavior, was newest selection reversed
1553type SelectSyntaxNodeHistoryState = (
1554 Box<[Selection<usize>]>,
1555 SelectSyntaxNodeScrollBehavior,
1556 bool,
1557);
1558
1559#[derive(Default)]
1560struct SelectSyntaxNodeHistory {
1561 stack: Vec<SelectSyntaxNodeHistoryState>,
1562 // disable temporarily to allow changing selections without losing the stack
1563 pub disable_clearing: bool,
1564}
1565
1566impl SelectSyntaxNodeHistory {
1567 pub fn try_clear(&mut self) {
1568 if !self.disable_clearing {
1569 self.stack.clear();
1570 }
1571 }
1572
1573 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1574 self.stack.push(selection);
1575 }
1576
1577 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1578 self.stack.pop()
1579 }
1580}
1581
1582enum SelectSyntaxNodeScrollBehavior {
1583 CursorTop,
1584 FitSelection,
1585 CursorBottom,
1586}
1587
1588#[derive(Debug)]
1589pub(crate) struct NavigationData {
1590 cursor_anchor: Anchor,
1591 cursor_position: Point,
1592 scroll_anchor: ScrollAnchor,
1593 scroll_top_row: u32,
1594}
1595
1596#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1597pub enum GotoDefinitionKind {
1598 Symbol,
1599 Declaration,
1600 Type,
1601 Implementation,
1602}
1603
1604#[derive(Debug, Clone)]
1605enum InlayHintRefreshReason {
1606 ModifiersChanged(bool),
1607 Toggle(bool),
1608 SettingsChange(InlayHintSettings),
1609 NewLinesShown,
1610 BufferEdited(HashSet<Arc<Language>>),
1611 RefreshRequested,
1612 ExcerptsRemoved(Vec<ExcerptId>),
1613}
1614
1615impl InlayHintRefreshReason {
1616 fn description(&self) -> &'static str {
1617 match self {
1618 Self::ModifiersChanged(_) => "modifiers changed",
1619 Self::Toggle(_) => "toggle",
1620 Self::SettingsChange(_) => "settings change",
1621 Self::NewLinesShown => "new lines shown",
1622 Self::BufferEdited(_) => "buffer edited",
1623 Self::RefreshRequested => "refresh requested",
1624 Self::ExcerptsRemoved(_) => "excerpts removed",
1625 }
1626 }
1627}
1628
1629pub enum FormatTarget {
1630 Buffers(HashSet<Entity<Buffer>>),
1631 Ranges(Vec<Range<MultiBufferPoint>>),
1632}
1633
1634pub(crate) struct FocusedBlock {
1635 id: BlockId,
1636 focus_handle: WeakFocusHandle,
1637}
1638
1639#[derive(Clone)]
1640enum JumpData {
1641 MultiBufferRow {
1642 row: MultiBufferRow,
1643 line_offset_from_top: u32,
1644 },
1645 MultiBufferPoint {
1646 excerpt_id: ExcerptId,
1647 position: Point,
1648 anchor: text::Anchor,
1649 line_offset_from_top: u32,
1650 },
1651}
1652
1653pub enum MultibufferSelectionMode {
1654 First,
1655 All,
1656}
1657
1658#[derive(Clone, Copy, Debug, Default)]
1659pub struct RewrapOptions {
1660 pub override_language_settings: bool,
1661 pub preserve_existing_whitespace: bool,
1662}
1663
1664impl Editor {
1665 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1666 let buffer = cx.new(|cx| Buffer::local("", cx));
1667 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1668 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1669 }
1670
1671 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1672 let buffer = cx.new(|cx| Buffer::local("", cx));
1673 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1674 Self::new(EditorMode::full(), buffer, None, window, cx)
1675 }
1676
1677 pub fn auto_height(
1678 min_lines: usize,
1679 max_lines: usize,
1680 window: &mut Window,
1681 cx: &mut Context<Self>,
1682 ) -> Self {
1683 let buffer = cx.new(|cx| Buffer::local("", cx));
1684 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1685 Self::new(
1686 EditorMode::AutoHeight {
1687 min_lines,
1688 max_lines: Some(max_lines),
1689 },
1690 buffer,
1691 None,
1692 window,
1693 cx,
1694 )
1695 }
1696
1697 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1698 /// The editor grows as tall as needed to fit its content.
1699 pub fn auto_height_unbounded(
1700 min_lines: usize,
1701 window: &mut Window,
1702 cx: &mut Context<Self>,
1703 ) -> Self {
1704 let buffer = cx.new(|cx| Buffer::local("", cx));
1705 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1706 Self::new(
1707 EditorMode::AutoHeight {
1708 min_lines,
1709 max_lines: None,
1710 },
1711 buffer,
1712 None,
1713 window,
1714 cx,
1715 )
1716 }
1717
1718 pub fn for_buffer(
1719 buffer: Entity<Buffer>,
1720 project: Option<Entity<Project>>,
1721 window: &mut Window,
1722 cx: &mut Context<Self>,
1723 ) -> Self {
1724 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1725 Self::new(EditorMode::full(), buffer, project, window, cx)
1726 }
1727
1728 pub fn for_multibuffer(
1729 buffer: Entity<MultiBuffer>,
1730 project: Option<Entity<Project>>,
1731 window: &mut Window,
1732 cx: &mut Context<Self>,
1733 ) -> Self {
1734 Self::new(EditorMode::full(), buffer, project, window, cx)
1735 }
1736
1737 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1738 let mut clone = Self::new(
1739 self.mode.clone(),
1740 self.buffer.clone(),
1741 self.project.clone(),
1742 window,
1743 cx,
1744 );
1745 self.display_map.update(cx, |display_map, cx| {
1746 let snapshot = display_map.snapshot(cx);
1747 clone.display_map.update(cx, |display_map, cx| {
1748 display_map.set_state(&snapshot, cx);
1749 });
1750 });
1751 clone.folds_did_change(cx);
1752 clone.selections.clone_state(&self.selections);
1753 clone.scroll_manager.clone_state(&self.scroll_manager);
1754 clone.searchable = self.searchable;
1755 clone.read_only = self.read_only;
1756 clone
1757 }
1758
1759 pub fn new(
1760 mode: EditorMode,
1761 buffer: Entity<MultiBuffer>,
1762 project: Option<Entity<Project>>,
1763 window: &mut Window,
1764 cx: &mut Context<Self>,
1765 ) -> Self {
1766 Editor::new_internal(mode, buffer, project, None, window, cx)
1767 }
1768
1769 fn new_internal(
1770 mode: EditorMode,
1771 buffer: Entity<MultiBuffer>,
1772 project: Option<Entity<Project>>,
1773 display_map: Option<Entity<DisplayMap>>,
1774 window: &mut Window,
1775 cx: &mut Context<Self>,
1776 ) -> Self {
1777 debug_assert!(
1778 display_map.is_none() || mode.is_minimap(),
1779 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1780 );
1781
1782 let full_mode = mode.is_full();
1783 let is_minimap = mode.is_minimap();
1784 let diagnostics_max_severity = if full_mode {
1785 EditorSettings::get_global(cx)
1786 .diagnostics_max_severity
1787 .unwrap_or(DiagnosticSeverity::Hint)
1788 } else {
1789 DiagnosticSeverity::Off
1790 };
1791 let style = window.text_style();
1792 let font_size = style.font_size.to_pixels(window.rem_size());
1793 let editor = cx.entity().downgrade();
1794 let fold_placeholder = FoldPlaceholder {
1795 constrain_width: false,
1796 render: Arc::new(move |fold_id, fold_range, cx| {
1797 let editor = editor.clone();
1798 div()
1799 .id(fold_id)
1800 .bg(cx.theme().colors().ghost_element_background)
1801 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1802 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1803 .rounded_xs()
1804 .size_full()
1805 .cursor_pointer()
1806 .child("⋯")
1807 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1808 .on_click(move |_, _window, cx| {
1809 editor
1810 .update(cx, |editor, cx| {
1811 editor.unfold_ranges(
1812 &[fold_range.start..fold_range.end],
1813 true,
1814 false,
1815 cx,
1816 );
1817 cx.stop_propagation();
1818 })
1819 .ok();
1820 })
1821 .into_any()
1822 }),
1823 merge_adjacent: true,
1824 ..FoldPlaceholder::default()
1825 };
1826 let display_map = display_map.unwrap_or_else(|| {
1827 cx.new(|cx| {
1828 DisplayMap::new(
1829 buffer.clone(),
1830 style.font(),
1831 font_size,
1832 None,
1833 FILE_HEADER_HEIGHT,
1834 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1835 fold_placeholder,
1836 diagnostics_max_severity,
1837 cx,
1838 )
1839 })
1840 });
1841
1842 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1843
1844 let blink_manager = cx.new(|cx| {
1845 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1846 if is_minimap {
1847 blink_manager.disable(cx);
1848 }
1849 blink_manager
1850 });
1851
1852 let soft_wrap_mode_override =
1853 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1854
1855 let mut project_subscriptions = Vec::new();
1856 if full_mode && let Some(project) = project.as_ref() {
1857 project_subscriptions.push(cx.subscribe_in(
1858 project,
1859 window,
1860 |editor, _, event, window, cx| match event {
1861 project::Event::RefreshCodeLens => {
1862 // we always query lens with actions, without storing them, always refreshing them
1863 }
1864 project::Event::RefreshInlayHints => {
1865 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1866 }
1867 project::Event::LanguageServerAdded(..)
1868 | project::Event::LanguageServerRemoved(..) => {
1869 if editor.tasks_update_task.is_none() {
1870 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1871 }
1872 }
1873 project::Event::SnippetEdit(id, snippet_edits) => {
1874 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1875 let focus_handle = editor.focus_handle(cx);
1876 if focus_handle.is_focused(window) {
1877 let snapshot = buffer.read(cx).snapshot();
1878 for (range, snippet) in snippet_edits {
1879 let editor_range =
1880 language::range_from_lsp(*range).to_offset(&snapshot);
1881 editor
1882 .insert_snippet(
1883 &[editor_range],
1884 snippet.clone(),
1885 window,
1886 cx,
1887 )
1888 .ok();
1889 }
1890 }
1891 }
1892 }
1893 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1894 if editor.buffer().read(cx).buffer(*buffer_id).is_some() {
1895 editor.update_lsp_data(false, Some(*buffer_id), window, cx);
1896 }
1897 }
1898
1899 project::Event::EntryRenamed(transaction) => {
1900 let Some(workspace) = editor.workspace() else {
1901 return;
1902 };
1903 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1904 else {
1905 return;
1906 };
1907 if active_editor.entity_id() == cx.entity_id() {
1908 let edited_buffers_already_open = {
1909 let other_editors: Vec<Entity<Editor>> = workspace
1910 .read(cx)
1911 .panes()
1912 .iter()
1913 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1914 .filter(|editor| editor.entity_id() != cx.entity_id())
1915 .collect();
1916
1917 transaction.0.keys().all(|buffer| {
1918 other_editors.iter().any(|editor| {
1919 let multi_buffer = editor.read(cx).buffer();
1920 multi_buffer.read(cx).is_singleton()
1921 && multi_buffer.read(cx).as_singleton().map_or(
1922 false,
1923 |singleton| {
1924 singleton.entity_id() == buffer.entity_id()
1925 },
1926 )
1927 })
1928 })
1929 };
1930
1931 if !edited_buffers_already_open {
1932 let workspace = workspace.downgrade();
1933 let transaction = transaction.clone();
1934 cx.defer_in(window, move |_, window, cx| {
1935 cx.spawn_in(window, async move |editor, cx| {
1936 Self::open_project_transaction(
1937 &editor,
1938 workspace,
1939 transaction,
1940 "Rename".to_string(),
1941 cx,
1942 )
1943 .await
1944 .ok()
1945 })
1946 .detach();
1947 });
1948 }
1949 }
1950 }
1951
1952 _ => {}
1953 },
1954 ));
1955 if let Some(task_inventory) = project
1956 .read(cx)
1957 .task_store()
1958 .read(cx)
1959 .task_inventory()
1960 .cloned()
1961 {
1962 project_subscriptions.push(cx.observe_in(
1963 &task_inventory,
1964 window,
1965 |editor, _, window, cx| {
1966 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1967 },
1968 ));
1969 };
1970
1971 project_subscriptions.push(cx.subscribe_in(
1972 &project.read(cx).breakpoint_store(),
1973 window,
1974 |editor, _, event, window, cx| match event {
1975 BreakpointStoreEvent::ClearDebugLines => {
1976 editor.clear_row_highlights::<ActiveDebugLine>();
1977 editor.refresh_inline_values(cx);
1978 }
1979 BreakpointStoreEvent::SetDebugLine => {
1980 if editor.go_to_active_debug_line(window, cx) {
1981 cx.stop_propagation();
1982 }
1983
1984 editor.refresh_inline_values(cx);
1985 }
1986 _ => {}
1987 },
1988 ));
1989 let git_store = project.read(cx).git_store().clone();
1990 let project = project.clone();
1991 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1992 if let GitStoreEvent::RepositoryUpdated(
1993 _,
1994 RepositoryEvent::Updated {
1995 new_instance: true, ..
1996 },
1997 _,
1998 ) = event
1999 {
2000 this.load_diff_task = Some(
2001 update_uncommitted_diff_for_buffer(
2002 cx.entity(),
2003 &project,
2004 this.buffer.read(cx).all_buffers(),
2005 this.buffer.clone(),
2006 cx,
2007 )
2008 .shared(),
2009 );
2010 }
2011 }));
2012 }
2013
2014 let buffer_snapshot = buffer.read(cx).snapshot(cx);
2015
2016 let inlay_hint_settings =
2017 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2018 let focus_handle = cx.focus_handle();
2019 if !is_minimap {
2020 cx.on_focus(&focus_handle, window, Self::handle_focus)
2021 .detach();
2022 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2023 .detach();
2024 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2025 .detach();
2026 cx.on_blur(&focus_handle, window, Self::handle_blur)
2027 .detach();
2028 cx.observe_pending_input(window, Self::observe_pending_input)
2029 .detach();
2030 }
2031
2032 let show_indent_guides =
2033 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2034 Some(false)
2035 } else {
2036 None
2037 };
2038
2039 let breakpoint_store = match (&mode, project.as_ref()) {
2040 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2041 _ => None,
2042 };
2043
2044 let mut code_action_providers = Vec::new();
2045 let mut load_uncommitted_diff = None;
2046 if let Some(project) = project.clone() {
2047 load_uncommitted_diff = Some(
2048 update_uncommitted_diff_for_buffer(
2049 cx.entity(),
2050 &project,
2051 buffer.read(cx).all_buffers(),
2052 buffer.clone(),
2053 cx,
2054 )
2055 .shared(),
2056 );
2057 code_action_providers.push(Rc::new(project) as Rc<_>);
2058 }
2059
2060 let mut editor = Self {
2061 focus_handle,
2062 show_cursor_when_unfocused: false,
2063 last_focused_descendant: None,
2064 buffer: buffer.clone(),
2065 display_map: display_map.clone(),
2066 placeholder_display_map: None,
2067 selections,
2068 scroll_manager: ScrollManager::new(cx),
2069 columnar_selection_state: None,
2070 add_selections_state: None,
2071 select_next_state: None,
2072 select_prev_state: None,
2073 selection_history: SelectionHistory::default(),
2074 defer_selection_effects: false,
2075 deferred_selection_effects_state: None,
2076 autoclose_regions: Vec::new(),
2077 snippet_stack: InvalidationStack::default(),
2078 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2079 ime_transaction: None,
2080 active_diagnostics: ActiveDiagnostic::None,
2081 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2082 inline_diagnostics_update: Task::ready(()),
2083 inline_diagnostics: Vec::new(),
2084 soft_wrap_mode_override,
2085 diagnostics_max_severity,
2086 hard_wrap: None,
2087 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2088 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2089 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2090 project,
2091 blink_manager: blink_manager.clone(),
2092 show_local_selections: true,
2093 show_scrollbars: ScrollbarAxes {
2094 horizontal: full_mode,
2095 vertical: full_mode,
2096 },
2097 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2098 offset_content: !matches!(mode, EditorMode::SingleLine),
2099 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2100 show_gutter: full_mode,
2101 show_line_numbers: (!full_mode).then_some(false),
2102 use_relative_line_numbers: None,
2103 disable_expand_excerpt_buttons: !full_mode,
2104 show_git_diff_gutter: None,
2105 show_code_actions: None,
2106 show_runnables: None,
2107 show_breakpoints: None,
2108 show_wrap_guides: None,
2109 show_indent_guides,
2110 highlight_order: 0,
2111 highlighted_rows: HashMap::default(),
2112 background_highlights: HashMap::default(),
2113 gutter_highlights: HashMap::default(),
2114 scrollbar_marker_state: ScrollbarMarkerState::default(),
2115 active_indent_guides_state: ActiveIndentGuidesState::default(),
2116 nav_history: None,
2117 context_menu: RefCell::new(None),
2118 context_menu_options: None,
2119 mouse_context_menu: None,
2120 completion_tasks: Vec::new(),
2121 inline_blame_popover: None,
2122 inline_blame_popover_show_task: None,
2123 signature_help_state: SignatureHelpState::default(),
2124 auto_signature_help: None,
2125 find_all_references_task_sources: Vec::new(),
2126 next_completion_id: 0,
2127 next_inlay_id: 0,
2128 code_action_providers,
2129 available_code_actions: None,
2130 code_actions_task: None,
2131 quick_selection_highlight_task: None,
2132 debounced_selection_highlight_task: None,
2133 document_highlights_task: None,
2134 linked_editing_range_task: None,
2135 pending_rename: None,
2136 searchable: !is_minimap,
2137 cursor_shape: EditorSettings::get_global(cx)
2138 .cursor_shape
2139 .unwrap_or_default(),
2140 current_line_highlight: None,
2141 autoindent_mode: Some(AutoindentMode::EachLine),
2142 collapse_matches: false,
2143 workspace: None,
2144 input_enabled: !is_minimap,
2145 use_modal_editing: full_mode,
2146 read_only: is_minimap,
2147 use_autoclose: true,
2148 use_auto_surround: true,
2149 auto_replace_emoji_shortcode: false,
2150 jsx_tag_auto_close_enabled_in_any_buffer: false,
2151 leader_id: None,
2152 remote_id: None,
2153 hover_state: HoverState::default(),
2154 pending_mouse_down: None,
2155 hovered_link_state: None,
2156 edit_prediction_provider: None,
2157 active_edit_prediction: None,
2158 stale_edit_prediction_in_menu: None,
2159 edit_prediction_preview: EditPredictionPreview::Inactive {
2160 released_too_fast: false,
2161 },
2162 inline_diagnostics_enabled: full_mode,
2163 diagnostics_enabled: full_mode,
2164 word_completions_enabled: full_mode,
2165 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2166 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2167 gutter_hovered: false,
2168 pixel_position_of_newest_cursor: None,
2169 last_bounds: None,
2170 last_position_map: None,
2171 expect_bounds_change: None,
2172 gutter_dimensions: GutterDimensions::default(),
2173 style: None,
2174 show_cursor_names: false,
2175 hovered_cursors: HashMap::default(),
2176 next_editor_action_id: EditorActionId::default(),
2177 editor_actions: Rc::default(),
2178 edit_predictions_hidden_for_vim_mode: false,
2179 show_edit_predictions_override: None,
2180 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2181 edit_prediction_settings: EditPredictionSettings::Disabled,
2182 edit_prediction_indent_conflict: false,
2183 edit_prediction_requires_modifier_in_indent_conflict: true,
2184 custom_context_menu: None,
2185 show_git_blame_gutter: false,
2186 show_git_blame_inline: false,
2187 show_selection_menu: None,
2188 show_git_blame_inline_delay_task: None,
2189 git_blame_inline_enabled: full_mode
2190 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2191 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2192 serialize_dirty_buffers: !is_minimap
2193 && ProjectSettings::get_global(cx)
2194 .session
2195 .restore_unsaved_buffers,
2196 blame: None,
2197 blame_subscription: None,
2198 tasks: BTreeMap::default(),
2199
2200 breakpoint_store,
2201 gutter_breakpoint_indicator: (None, None),
2202 hovered_diff_hunk_row: None,
2203 _subscriptions: (!is_minimap)
2204 .then(|| {
2205 vec![
2206 cx.observe(&buffer, Self::on_buffer_changed),
2207 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2208 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2209 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2210 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2211 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2212 cx.observe_window_activation(window, |editor, window, cx| {
2213 let active = window.is_window_active();
2214 editor.blink_manager.update(cx, |blink_manager, cx| {
2215 if active {
2216 blink_manager.enable(cx);
2217 } else {
2218 blink_manager.disable(cx);
2219 }
2220 });
2221 if active {
2222 editor.show_mouse_cursor(cx);
2223 }
2224 }),
2225 ]
2226 })
2227 .unwrap_or_default(),
2228 tasks_update_task: None,
2229 pull_diagnostics_task: Task::ready(()),
2230 colors: None,
2231 next_color_inlay_id: 0,
2232 linked_edit_ranges: Default::default(),
2233 in_project_search: false,
2234 previous_search_ranges: None,
2235 breadcrumb_header: None,
2236 focused_block: None,
2237 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2238 addons: HashMap::default(),
2239 registered_buffers: HashMap::default(),
2240 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2241 selection_mark_mode: false,
2242 toggle_fold_multiple_buffers: Task::ready(()),
2243 serialize_selections: Task::ready(()),
2244 serialize_folds: Task::ready(()),
2245 text_style_refinement: None,
2246 load_diff_task: load_uncommitted_diff,
2247 temporary_diff_override: false,
2248 mouse_cursor_hidden: false,
2249 minimap: None,
2250 hide_mouse_mode: EditorSettings::get_global(cx)
2251 .hide_mouse
2252 .unwrap_or_default(),
2253 change_list: ChangeList::new(),
2254 mode,
2255 selection_drag_state: SelectionDragState::None,
2256 folding_newlines: Task::ready(()),
2257 lookup_key: None,
2258 };
2259
2260 if is_minimap {
2261 return editor;
2262 }
2263
2264 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2265 editor
2266 ._subscriptions
2267 .push(cx.observe(breakpoints, |_, _, cx| {
2268 cx.notify();
2269 }));
2270 }
2271 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2272 editor._subscriptions.extend(project_subscriptions);
2273
2274 editor._subscriptions.push(cx.subscribe_in(
2275 &cx.entity(),
2276 window,
2277 |editor, _, e: &EditorEvent, window, cx| match e {
2278 EditorEvent::ScrollPositionChanged { local, .. } => {
2279 if *local {
2280 let new_anchor = editor.scroll_manager.anchor();
2281 let snapshot = editor.snapshot(window, cx);
2282 editor.update_restoration_data(cx, move |data| {
2283 data.scroll_position = (
2284 new_anchor.top_row(&snapshot.buffer_snapshot),
2285 new_anchor.offset,
2286 );
2287 });
2288 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2289 editor.inline_blame_popover.take();
2290 }
2291 }
2292 EditorEvent::Edited { .. } => {
2293 if !vim_enabled(cx) {
2294 let (map, selections) = editor.selections.all_adjusted_display(cx);
2295 let pop_state = editor
2296 .change_list
2297 .last()
2298 .map(|previous| {
2299 previous.len() == selections.len()
2300 && previous.iter().enumerate().all(|(ix, p)| {
2301 p.to_display_point(&map).row()
2302 == selections[ix].head().row()
2303 })
2304 })
2305 .unwrap_or(false);
2306 let new_positions = selections
2307 .into_iter()
2308 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2309 .collect();
2310 editor
2311 .change_list
2312 .push_to_change_list(pop_state, new_positions);
2313 }
2314 }
2315 _ => (),
2316 },
2317 ));
2318
2319 if let Some(dap_store) = editor
2320 .project
2321 .as_ref()
2322 .map(|project| project.read(cx).dap_store())
2323 {
2324 let weak_editor = cx.weak_entity();
2325
2326 editor
2327 ._subscriptions
2328 .push(
2329 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2330 let session_entity = cx.entity();
2331 weak_editor
2332 .update(cx, |editor, cx| {
2333 editor._subscriptions.push(
2334 cx.subscribe(&session_entity, Self::on_debug_session_event),
2335 );
2336 })
2337 .ok();
2338 }),
2339 );
2340
2341 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2342 editor
2343 ._subscriptions
2344 .push(cx.subscribe(&session, Self::on_debug_session_event));
2345 }
2346 }
2347
2348 // skip adding the initial selection to selection history
2349 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2350 editor.end_selection(window, cx);
2351 editor.selection_history.mode = SelectionHistoryMode::Normal;
2352
2353 editor.scroll_manager.show_scrollbars(window, cx);
2354 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2355
2356 if full_mode {
2357 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2358 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2359
2360 if editor.git_blame_inline_enabled {
2361 editor.start_git_blame_inline(false, window, cx);
2362 }
2363
2364 editor.go_to_active_debug_line(window, cx);
2365
2366 if let Some(buffer) = buffer.read(cx).as_singleton()
2367 && let Some(project) = editor.project()
2368 {
2369 let handle = project.update(cx, |project, cx| {
2370 project.register_buffer_with_language_servers(&buffer, cx)
2371 });
2372 editor
2373 .registered_buffers
2374 .insert(buffer.read(cx).remote_id(), handle);
2375 }
2376
2377 editor.minimap =
2378 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2379 editor.colors = Some(LspColorData::new(cx));
2380 editor.update_lsp_data(false, None, window, cx);
2381 }
2382
2383 if editor.mode.is_full() {
2384 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2385 }
2386
2387 editor
2388 }
2389
2390 pub fn deploy_mouse_context_menu(
2391 &mut self,
2392 position: gpui::Point<Pixels>,
2393 context_menu: Entity<ContextMenu>,
2394 window: &mut Window,
2395 cx: &mut Context<Self>,
2396 ) {
2397 self.mouse_context_menu = Some(MouseContextMenu::new(
2398 self,
2399 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2400 context_menu,
2401 window,
2402 cx,
2403 ));
2404 }
2405
2406 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2407 self.mouse_context_menu
2408 .as_ref()
2409 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2410 }
2411
2412 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2413 if self
2414 .selections
2415 .pending_anchor()
2416 .is_some_and(|pending_selection| {
2417 let snapshot = self.buffer().read(cx).snapshot(cx);
2418 pending_selection.range().includes(range, &snapshot)
2419 })
2420 {
2421 return true;
2422 }
2423
2424 self.selections
2425 .disjoint_in_range::<usize>(range.clone(), cx)
2426 .into_iter()
2427 .any(|selection| {
2428 // This is needed to cover a corner case, if we just check for an existing
2429 // selection in the fold range, having a cursor at the start of the fold
2430 // marks it as selected. Non-empty selections don't cause this.
2431 let length = selection.end - selection.start;
2432 length > 0
2433 })
2434 }
2435
2436 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2437 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2438 }
2439
2440 fn key_context_internal(
2441 &self,
2442 has_active_edit_prediction: bool,
2443 window: &Window,
2444 cx: &App,
2445 ) -> KeyContext {
2446 let mut key_context = KeyContext::new_with_defaults();
2447 key_context.add("Editor");
2448 let mode = match self.mode {
2449 EditorMode::SingleLine => "single_line",
2450 EditorMode::AutoHeight { .. } => "auto_height",
2451 EditorMode::Minimap { .. } => "minimap",
2452 EditorMode::Full { .. } => "full",
2453 };
2454
2455 if EditorSettings::jupyter_enabled(cx) {
2456 key_context.add("jupyter");
2457 }
2458
2459 key_context.set("mode", mode);
2460 if self.pending_rename.is_some() {
2461 key_context.add("renaming");
2462 }
2463
2464 match self.context_menu.borrow().as_ref() {
2465 Some(CodeContextMenu::Completions(menu)) => {
2466 if menu.visible() {
2467 key_context.add("menu");
2468 key_context.add("showing_completions");
2469 }
2470 }
2471 Some(CodeContextMenu::CodeActions(menu)) => {
2472 if menu.visible() {
2473 key_context.add("menu");
2474 key_context.add("showing_code_actions")
2475 }
2476 }
2477 None => {}
2478 }
2479
2480 if self.signature_help_state.has_multiple_signatures() {
2481 key_context.add("showing_signature_help");
2482 }
2483
2484 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2485 if !self.focus_handle(cx).contains_focused(window, cx)
2486 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2487 {
2488 for addon in self.addons.values() {
2489 addon.extend_key_context(&mut key_context, cx)
2490 }
2491 }
2492
2493 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2494 if let Some(extension) = singleton_buffer
2495 .read(cx)
2496 .file()
2497 .and_then(|file| file.path().extension()?.to_str())
2498 {
2499 key_context.set("extension", extension.to_string());
2500 }
2501 } else {
2502 key_context.add("multibuffer");
2503 }
2504
2505 if has_active_edit_prediction {
2506 if self.edit_prediction_in_conflict() {
2507 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2508 } else {
2509 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2510 key_context.add("copilot_suggestion");
2511 }
2512 }
2513
2514 if self.selection_mark_mode {
2515 key_context.add("selection_mode");
2516 }
2517
2518 key_context
2519 }
2520
2521 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2522 self.last_bounds.as_ref()
2523 }
2524
2525 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2526 if self.mouse_cursor_hidden {
2527 self.mouse_cursor_hidden = false;
2528 cx.notify();
2529 }
2530 }
2531
2532 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2533 let hide_mouse_cursor = match origin {
2534 HideMouseCursorOrigin::TypingAction => {
2535 matches!(
2536 self.hide_mouse_mode,
2537 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2538 )
2539 }
2540 HideMouseCursorOrigin::MovementAction => {
2541 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2542 }
2543 };
2544 if self.mouse_cursor_hidden != hide_mouse_cursor {
2545 self.mouse_cursor_hidden = hide_mouse_cursor;
2546 cx.notify();
2547 }
2548 }
2549
2550 pub fn edit_prediction_in_conflict(&self) -> bool {
2551 if !self.show_edit_predictions_in_menu() {
2552 return false;
2553 }
2554
2555 let showing_completions = self
2556 .context_menu
2557 .borrow()
2558 .as_ref()
2559 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2560
2561 showing_completions
2562 || self.edit_prediction_requires_modifier()
2563 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2564 // bindings to insert tab characters.
2565 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2566 }
2567
2568 pub fn accept_edit_prediction_keybind(
2569 &self,
2570 accept_partial: bool,
2571 window: &Window,
2572 cx: &App,
2573 ) -> AcceptEditPredictionBinding {
2574 let key_context = self.key_context_internal(true, window, cx);
2575 let in_conflict = self.edit_prediction_in_conflict();
2576
2577 let bindings = if accept_partial {
2578 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2579 } else {
2580 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2581 };
2582
2583 // TODO: if the binding contains multiple keystrokes, display all of them, not
2584 // just the first one.
2585 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2586 !in_conflict
2587 || binding
2588 .keystrokes()
2589 .first()
2590 .is_some_and(|keystroke| keystroke.modifiers().modified())
2591 }))
2592 }
2593
2594 pub fn new_file(
2595 workspace: &mut Workspace,
2596 _: &workspace::NewFile,
2597 window: &mut Window,
2598 cx: &mut Context<Workspace>,
2599 ) {
2600 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2601 "Failed to create buffer",
2602 window,
2603 cx,
2604 |e, _, _| match e.error_code() {
2605 ErrorCode::RemoteUpgradeRequired => Some(format!(
2606 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2607 e.error_tag("required").unwrap_or("the latest version")
2608 )),
2609 _ => None,
2610 },
2611 );
2612 }
2613
2614 pub fn new_in_workspace(
2615 workspace: &mut Workspace,
2616 window: &mut Window,
2617 cx: &mut Context<Workspace>,
2618 ) -> Task<Result<Entity<Editor>>> {
2619 let project = workspace.project().clone();
2620 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2621
2622 cx.spawn_in(window, async move |workspace, cx| {
2623 let buffer = create.await?;
2624 workspace.update_in(cx, |workspace, window, cx| {
2625 let editor =
2626 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2627 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2628 editor
2629 })
2630 })
2631 }
2632
2633 fn new_file_vertical(
2634 workspace: &mut Workspace,
2635 _: &workspace::NewFileSplitVertical,
2636 window: &mut Window,
2637 cx: &mut Context<Workspace>,
2638 ) {
2639 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2640 }
2641
2642 fn new_file_horizontal(
2643 workspace: &mut Workspace,
2644 _: &workspace::NewFileSplitHorizontal,
2645 window: &mut Window,
2646 cx: &mut Context<Workspace>,
2647 ) {
2648 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2649 }
2650
2651 fn new_file_in_direction(
2652 workspace: &mut Workspace,
2653 direction: SplitDirection,
2654 window: &mut Window,
2655 cx: &mut Context<Workspace>,
2656 ) {
2657 let project = workspace.project().clone();
2658 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2659
2660 cx.spawn_in(window, async move |workspace, cx| {
2661 let buffer = create.await?;
2662 workspace.update_in(cx, move |workspace, window, cx| {
2663 workspace.split_item(
2664 direction,
2665 Box::new(
2666 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2667 ),
2668 window,
2669 cx,
2670 )
2671 })?;
2672 anyhow::Ok(())
2673 })
2674 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2675 match e.error_code() {
2676 ErrorCode::RemoteUpgradeRequired => Some(format!(
2677 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2678 e.error_tag("required").unwrap_or("the latest version")
2679 )),
2680 _ => None,
2681 }
2682 });
2683 }
2684
2685 pub fn leader_id(&self) -> Option<CollaboratorId> {
2686 self.leader_id
2687 }
2688
2689 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2690 &self.buffer
2691 }
2692
2693 pub fn project(&self) -> Option<&Entity<Project>> {
2694 self.project.as_ref()
2695 }
2696
2697 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2698 self.workspace.as_ref()?.0.upgrade()
2699 }
2700
2701 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2702 self.buffer().read(cx).title(cx)
2703 }
2704
2705 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2706 let git_blame_gutter_max_author_length = self
2707 .render_git_blame_gutter(cx)
2708 .then(|| {
2709 if let Some(blame) = self.blame.as_ref() {
2710 let max_author_length =
2711 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2712 Some(max_author_length)
2713 } else {
2714 None
2715 }
2716 })
2717 .flatten();
2718
2719 EditorSnapshot {
2720 mode: self.mode.clone(),
2721 show_gutter: self.show_gutter,
2722 show_line_numbers: self.show_line_numbers,
2723 show_git_diff_gutter: self.show_git_diff_gutter,
2724 show_code_actions: self.show_code_actions,
2725 show_runnables: self.show_runnables,
2726 show_breakpoints: self.show_breakpoints,
2727 git_blame_gutter_max_author_length,
2728 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2729 placeholder_display_snapshot: self
2730 .placeholder_display_map
2731 .as_ref()
2732 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2733 scroll_anchor: self.scroll_manager.anchor(),
2734 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2735 is_focused: self.focus_handle.is_focused(window),
2736 current_line_highlight: self
2737 .current_line_highlight
2738 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2739 gutter_hovered: self.gutter_hovered,
2740 }
2741 }
2742
2743 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2744 self.buffer.read(cx).language_at(point, cx)
2745 }
2746
2747 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2748 self.buffer.read(cx).read(cx).file_at(point).cloned()
2749 }
2750
2751 pub fn active_excerpt(
2752 &self,
2753 cx: &App,
2754 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2755 self.buffer
2756 .read(cx)
2757 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2758 }
2759
2760 pub fn mode(&self) -> &EditorMode {
2761 &self.mode
2762 }
2763
2764 pub fn set_mode(&mut self, mode: EditorMode) {
2765 self.mode = mode;
2766 }
2767
2768 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2769 self.collaboration_hub.as_deref()
2770 }
2771
2772 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2773 self.collaboration_hub = Some(hub);
2774 }
2775
2776 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2777 self.in_project_search = in_project_search;
2778 }
2779
2780 pub fn set_custom_context_menu(
2781 &mut self,
2782 f: impl 'static
2783 + Fn(
2784 &mut Self,
2785 DisplayPoint,
2786 &mut Window,
2787 &mut Context<Self>,
2788 ) -> Option<Entity<ui::ContextMenu>>,
2789 ) {
2790 self.custom_context_menu = Some(Box::new(f))
2791 }
2792
2793 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2794 self.completion_provider = provider;
2795 }
2796
2797 #[cfg(any(test, feature = "test-support"))]
2798 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2799 self.completion_provider.clone()
2800 }
2801
2802 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2803 self.semantics_provider.clone()
2804 }
2805
2806 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2807 self.semantics_provider = provider;
2808 }
2809
2810 pub fn set_edit_prediction_provider<T>(
2811 &mut self,
2812 provider: Option<Entity<T>>,
2813 window: &mut Window,
2814 cx: &mut Context<Self>,
2815 ) where
2816 T: EditPredictionProvider,
2817 {
2818 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2819 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2820 if this.focus_handle.is_focused(window) {
2821 this.update_visible_edit_prediction(window, cx);
2822 }
2823 }),
2824 provider: Arc::new(provider),
2825 });
2826 self.update_edit_prediction_settings(cx);
2827 self.refresh_edit_prediction(false, false, window, cx);
2828 }
2829
2830 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2831 self.placeholder_display_map
2832 .as_ref()
2833 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2834 }
2835
2836 pub fn set_placeholder_text(
2837 &mut self,
2838 placeholder_text: &str,
2839 window: &mut Window,
2840 cx: &mut Context<Self>,
2841 ) {
2842 let multibuffer = cx
2843 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2844
2845 let style = window.text_style();
2846
2847 self.placeholder_display_map = Some(cx.new(|cx| {
2848 DisplayMap::new(
2849 multibuffer,
2850 style.font(),
2851 style.font_size.to_pixels(window.rem_size()),
2852 None,
2853 FILE_HEADER_HEIGHT,
2854 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2855 Default::default(),
2856 DiagnosticSeverity::Off,
2857 cx,
2858 )
2859 }));
2860 cx.notify();
2861 }
2862
2863 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2864 self.cursor_shape = cursor_shape;
2865
2866 // Disrupt blink for immediate user feedback that the cursor shape has changed
2867 self.blink_manager.update(cx, BlinkManager::show_cursor);
2868
2869 cx.notify();
2870 }
2871
2872 pub fn set_current_line_highlight(
2873 &mut self,
2874 current_line_highlight: Option<CurrentLineHighlight>,
2875 ) {
2876 self.current_line_highlight = current_line_highlight;
2877 }
2878
2879 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2880 self.collapse_matches = collapse_matches;
2881 }
2882
2883 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2884 let buffers = self.buffer.read(cx).all_buffers();
2885 let Some(project) = self.project.as_ref() else {
2886 return;
2887 };
2888 project.update(cx, |project, cx| {
2889 for buffer in buffers {
2890 self.registered_buffers
2891 .entry(buffer.read(cx).remote_id())
2892 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2893 }
2894 })
2895 }
2896
2897 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2898 if self.collapse_matches {
2899 return range.start..range.start;
2900 }
2901 range.clone()
2902 }
2903
2904 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2905 if self.display_map.read(cx).clip_at_line_ends != clip {
2906 self.display_map
2907 .update(cx, |map, _| map.clip_at_line_ends = clip);
2908 }
2909 }
2910
2911 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2912 self.input_enabled = input_enabled;
2913 }
2914
2915 pub fn set_edit_predictions_hidden_for_vim_mode(
2916 &mut self,
2917 hidden: bool,
2918 window: &mut Window,
2919 cx: &mut Context<Self>,
2920 ) {
2921 if hidden != self.edit_predictions_hidden_for_vim_mode {
2922 self.edit_predictions_hidden_for_vim_mode = hidden;
2923 if hidden {
2924 self.update_visible_edit_prediction(window, cx);
2925 } else {
2926 self.refresh_edit_prediction(true, false, window, cx);
2927 }
2928 }
2929 }
2930
2931 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2932 self.menu_edit_predictions_policy = value;
2933 }
2934
2935 pub fn set_autoindent(&mut self, autoindent: bool) {
2936 if autoindent {
2937 self.autoindent_mode = Some(AutoindentMode::EachLine);
2938 } else {
2939 self.autoindent_mode = None;
2940 }
2941 }
2942
2943 pub fn read_only(&self, cx: &App) -> bool {
2944 self.read_only || self.buffer.read(cx).read_only()
2945 }
2946
2947 pub fn set_read_only(&mut self, read_only: bool) {
2948 self.read_only = read_only;
2949 }
2950
2951 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2952 self.use_autoclose = autoclose;
2953 }
2954
2955 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2956 self.use_auto_surround = auto_surround;
2957 }
2958
2959 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2960 self.auto_replace_emoji_shortcode = auto_replace;
2961 }
2962
2963 pub fn toggle_edit_predictions(
2964 &mut self,
2965 _: &ToggleEditPrediction,
2966 window: &mut Window,
2967 cx: &mut Context<Self>,
2968 ) {
2969 if self.show_edit_predictions_override.is_some() {
2970 self.set_show_edit_predictions(None, window, cx);
2971 } else {
2972 let show_edit_predictions = !self.edit_predictions_enabled();
2973 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2974 }
2975 }
2976
2977 pub fn set_show_edit_predictions(
2978 &mut self,
2979 show_edit_predictions: Option<bool>,
2980 window: &mut Window,
2981 cx: &mut Context<Self>,
2982 ) {
2983 self.show_edit_predictions_override = show_edit_predictions;
2984 self.update_edit_prediction_settings(cx);
2985
2986 if let Some(false) = show_edit_predictions {
2987 self.discard_edit_prediction(false, cx);
2988 } else {
2989 self.refresh_edit_prediction(false, true, window, cx);
2990 }
2991 }
2992
2993 fn edit_predictions_disabled_in_scope(
2994 &self,
2995 buffer: &Entity<Buffer>,
2996 buffer_position: language::Anchor,
2997 cx: &App,
2998 ) -> bool {
2999 let snapshot = buffer.read(cx).snapshot();
3000 let settings = snapshot.settings_at(buffer_position, cx);
3001
3002 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3003 return false;
3004 };
3005
3006 scope.override_name().is_some_and(|scope_name| {
3007 settings
3008 .edit_predictions_disabled_in
3009 .iter()
3010 .any(|s| s == scope_name)
3011 })
3012 }
3013
3014 pub fn set_use_modal_editing(&mut self, to: bool) {
3015 self.use_modal_editing = to;
3016 }
3017
3018 pub fn use_modal_editing(&self) -> bool {
3019 self.use_modal_editing
3020 }
3021
3022 fn selections_did_change(
3023 &mut self,
3024 local: bool,
3025 old_cursor_position: &Anchor,
3026 effects: SelectionEffects,
3027 window: &mut Window,
3028 cx: &mut Context<Self>,
3029 ) {
3030 window.invalidate_character_coordinates();
3031
3032 // Copy selections to primary selection buffer
3033 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3034 if local {
3035 let selections = self.selections.all::<usize>(cx);
3036 let buffer_handle = self.buffer.read(cx).read(cx);
3037
3038 let mut text = String::new();
3039 for (index, selection) in selections.iter().enumerate() {
3040 let text_for_selection = buffer_handle
3041 .text_for_range(selection.start..selection.end)
3042 .collect::<String>();
3043
3044 text.push_str(&text_for_selection);
3045 if index != selections.len() - 1 {
3046 text.push('\n');
3047 }
3048 }
3049
3050 if !text.is_empty() {
3051 cx.write_to_primary(ClipboardItem::new_string(text));
3052 }
3053 }
3054
3055 let selection_anchors = self.selections.disjoint_anchors_arc();
3056
3057 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3058 self.buffer.update(cx, |buffer, cx| {
3059 buffer.set_active_selections(
3060 &selection_anchors,
3061 self.selections.line_mode,
3062 self.cursor_shape,
3063 cx,
3064 )
3065 });
3066 }
3067 let display_map = self
3068 .display_map
3069 .update(cx, |display_map, cx| display_map.snapshot(cx));
3070 let buffer = &display_map.buffer_snapshot;
3071 if self.selections.count() == 1 {
3072 self.add_selections_state = None;
3073 }
3074 self.select_next_state = None;
3075 self.select_prev_state = None;
3076 self.select_syntax_node_history.try_clear();
3077 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3078 self.snippet_stack.invalidate(&selection_anchors, buffer);
3079 self.take_rename(false, window, cx);
3080
3081 let newest_selection = self.selections.newest_anchor();
3082 let new_cursor_position = newest_selection.head();
3083 let selection_start = newest_selection.start;
3084
3085 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3086 self.push_to_nav_history(
3087 *old_cursor_position,
3088 Some(new_cursor_position.to_point(buffer)),
3089 false,
3090 effects.nav_history == Some(true),
3091 cx,
3092 );
3093 }
3094
3095 if local {
3096 if let Some(buffer_id) = new_cursor_position.buffer_id
3097 && !self.registered_buffers.contains_key(&buffer_id)
3098 && let Some(project) = self.project.as_ref()
3099 {
3100 project.update(cx, |project, cx| {
3101 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
3102 return;
3103 };
3104 self.registered_buffers.insert(
3105 buffer_id,
3106 project.register_buffer_with_language_servers(&buffer, cx),
3107 );
3108 })
3109 }
3110
3111 let mut context_menu = self.context_menu.borrow_mut();
3112 let completion_menu = match context_menu.as_ref() {
3113 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3114 Some(CodeContextMenu::CodeActions(_)) => {
3115 *context_menu = None;
3116 None
3117 }
3118 None => None,
3119 };
3120 let completion_position = completion_menu.map(|menu| menu.initial_position);
3121 drop(context_menu);
3122
3123 if effects.completions
3124 && let Some(completion_position) = completion_position
3125 {
3126 let start_offset = selection_start.to_offset(buffer);
3127 let position_matches = start_offset == completion_position.to_offset(buffer);
3128 let continue_showing = if position_matches {
3129 if self.snippet_stack.is_empty() {
3130 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3131 == Some(CharKind::Word)
3132 } else {
3133 // Snippet choices can be shown even when the cursor is in whitespace.
3134 // Dismissing the menu with actions like backspace is handled by
3135 // invalidation regions.
3136 true
3137 }
3138 } else {
3139 false
3140 };
3141
3142 if continue_showing {
3143 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3144 } else {
3145 self.hide_context_menu(window, cx);
3146 }
3147 }
3148
3149 hide_hover(self, cx);
3150
3151 if old_cursor_position.to_display_point(&display_map).row()
3152 != new_cursor_position.to_display_point(&display_map).row()
3153 {
3154 self.available_code_actions.take();
3155 }
3156 self.refresh_code_actions(window, cx);
3157 self.refresh_document_highlights(cx);
3158 self.refresh_selected_text_highlights(false, window, cx);
3159 refresh_matching_bracket_highlights(self, window, cx);
3160 self.update_visible_edit_prediction(window, cx);
3161 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3162 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
3163 self.inline_blame_popover.take();
3164 if self.git_blame_inline_enabled {
3165 self.start_inline_blame_timer(window, cx);
3166 }
3167 }
3168
3169 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3170 cx.emit(EditorEvent::SelectionsChanged { local });
3171
3172 let selections = &self.selections.disjoint_anchors_arc();
3173 if selections.len() == 1 {
3174 cx.emit(SearchEvent::ActiveMatchChanged)
3175 }
3176 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3177 let inmemory_selections = selections
3178 .iter()
3179 .map(|s| {
3180 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3181 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3182 })
3183 .collect();
3184 self.update_restoration_data(cx, |data| {
3185 data.selections = inmemory_selections;
3186 });
3187
3188 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3189 && let Some(workspace_id) =
3190 self.workspace.as_ref().and_then(|workspace| workspace.1)
3191 {
3192 let snapshot = self.buffer().read(cx).snapshot(cx);
3193 let selections = selections.clone();
3194 let background_executor = cx.background_executor().clone();
3195 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3196 self.serialize_selections = cx.background_spawn(async move {
3197 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3198 let db_selections = selections
3199 .iter()
3200 .map(|selection| {
3201 (
3202 selection.start.to_offset(&snapshot),
3203 selection.end.to_offset(&snapshot),
3204 )
3205 })
3206 .collect();
3207
3208 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3209 .await
3210 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3211 .log_err();
3212 });
3213 }
3214 }
3215
3216 cx.notify();
3217 }
3218
3219 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3220 use text::ToOffset as _;
3221 use text::ToPoint as _;
3222
3223 if self.mode.is_minimap()
3224 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3225 {
3226 return;
3227 }
3228
3229 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3230 return;
3231 };
3232
3233 let snapshot = singleton.read(cx).snapshot();
3234 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3235 let display_snapshot = display_map.snapshot(cx);
3236
3237 display_snapshot
3238 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3239 .map(|fold| {
3240 fold.range.start.text_anchor.to_point(&snapshot)
3241 ..fold.range.end.text_anchor.to_point(&snapshot)
3242 })
3243 .collect()
3244 });
3245 self.update_restoration_data(cx, |data| {
3246 data.folds = inmemory_folds;
3247 });
3248
3249 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3250 return;
3251 };
3252 let background_executor = cx.background_executor().clone();
3253 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3254 let db_folds = self.display_map.update(cx, |display_map, cx| {
3255 display_map
3256 .snapshot(cx)
3257 .folds_in_range(0..snapshot.len())
3258 .map(|fold| {
3259 (
3260 fold.range.start.text_anchor.to_offset(&snapshot),
3261 fold.range.end.text_anchor.to_offset(&snapshot),
3262 )
3263 })
3264 .collect()
3265 });
3266 self.serialize_folds = cx.background_spawn(async move {
3267 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3268 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3269 .await
3270 .with_context(|| {
3271 format!(
3272 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3273 )
3274 })
3275 .log_err();
3276 });
3277 }
3278
3279 pub fn sync_selections(
3280 &mut self,
3281 other: Entity<Editor>,
3282 cx: &mut Context<Self>,
3283 ) -> gpui::Subscription {
3284 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3285 self.selections.change_with(cx, |selections| {
3286 selections.select_anchors(other_selections);
3287 });
3288
3289 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3290 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3291 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3292 if other_selections.is_empty() {
3293 return;
3294 }
3295 this.selections.change_with(cx, |selections| {
3296 selections.select_anchors(other_selections);
3297 });
3298 }
3299 });
3300
3301 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3302 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3303 let these_selections = this.selections.disjoint_anchors().to_vec();
3304 if these_selections.is_empty() {
3305 return;
3306 }
3307 other.update(cx, |other_editor, cx| {
3308 other_editor.selections.change_with(cx, |selections| {
3309 selections.select_anchors(these_selections);
3310 })
3311 });
3312 }
3313 });
3314
3315 Subscription::join(other_subscription, this_subscription)
3316 }
3317
3318 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3319 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3320 /// effects of selection change occur at the end of the transaction.
3321 pub fn change_selections<R>(
3322 &mut self,
3323 effects: SelectionEffects,
3324 window: &mut Window,
3325 cx: &mut Context<Self>,
3326 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3327 ) -> R {
3328 if let Some(state) = &mut self.deferred_selection_effects_state {
3329 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3330 state.effects.completions = effects.completions;
3331 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3332 let (changed, result) = self.selections.change_with(cx, change);
3333 state.changed |= changed;
3334 return result;
3335 }
3336 let mut state = DeferredSelectionEffectsState {
3337 changed: false,
3338 effects,
3339 old_cursor_position: self.selections.newest_anchor().head(),
3340 history_entry: SelectionHistoryEntry {
3341 selections: self.selections.disjoint_anchors_arc(),
3342 select_next_state: self.select_next_state.clone(),
3343 select_prev_state: self.select_prev_state.clone(),
3344 add_selections_state: self.add_selections_state.clone(),
3345 },
3346 };
3347 let (changed, result) = self.selections.change_with(cx, change);
3348 state.changed = state.changed || changed;
3349 if self.defer_selection_effects {
3350 self.deferred_selection_effects_state = Some(state);
3351 } else {
3352 self.apply_selection_effects(state, window, cx);
3353 }
3354 result
3355 }
3356
3357 /// Defers the effects of selection change, so that the effects of multiple calls to
3358 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3359 /// to selection history and the state of popovers based on selection position aren't
3360 /// erroneously updated.
3361 pub fn with_selection_effects_deferred<R>(
3362 &mut self,
3363 window: &mut Window,
3364 cx: &mut Context<Self>,
3365 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3366 ) -> R {
3367 let already_deferred = self.defer_selection_effects;
3368 self.defer_selection_effects = true;
3369 let result = update(self, window, cx);
3370 if !already_deferred {
3371 self.defer_selection_effects = false;
3372 if let Some(state) = self.deferred_selection_effects_state.take() {
3373 self.apply_selection_effects(state, window, cx);
3374 }
3375 }
3376 result
3377 }
3378
3379 fn apply_selection_effects(
3380 &mut self,
3381 state: DeferredSelectionEffectsState,
3382 window: &mut Window,
3383 cx: &mut Context<Self>,
3384 ) {
3385 if state.changed {
3386 self.selection_history.push(state.history_entry);
3387
3388 if let Some(autoscroll) = state.effects.scroll {
3389 self.request_autoscroll(autoscroll, cx);
3390 }
3391
3392 let old_cursor_position = &state.old_cursor_position;
3393
3394 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3395
3396 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3397 self.show_signature_help(&ShowSignatureHelp, window, cx);
3398 }
3399 }
3400 }
3401
3402 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3403 where
3404 I: IntoIterator<Item = (Range<S>, T)>,
3405 S: ToOffset,
3406 T: Into<Arc<str>>,
3407 {
3408 if self.read_only(cx) {
3409 return;
3410 }
3411
3412 self.buffer
3413 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3414 }
3415
3416 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3417 where
3418 I: IntoIterator<Item = (Range<S>, T)>,
3419 S: ToOffset,
3420 T: Into<Arc<str>>,
3421 {
3422 if self.read_only(cx) {
3423 return;
3424 }
3425
3426 self.buffer.update(cx, |buffer, cx| {
3427 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3428 });
3429 }
3430
3431 pub fn edit_with_block_indent<I, S, T>(
3432 &mut self,
3433 edits: I,
3434 original_indent_columns: Vec<Option<u32>>,
3435 cx: &mut Context<Self>,
3436 ) where
3437 I: IntoIterator<Item = (Range<S>, T)>,
3438 S: ToOffset,
3439 T: Into<Arc<str>>,
3440 {
3441 if self.read_only(cx) {
3442 return;
3443 }
3444
3445 self.buffer.update(cx, |buffer, cx| {
3446 buffer.edit(
3447 edits,
3448 Some(AutoindentMode::Block {
3449 original_indent_columns,
3450 }),
3451 cx,
3452 )
3453 });
3454 }
3455
3456 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3457 self.hide_context_menu(window, cx);
3458
3459 match phase {
3460 SelectPhase::Begin {
3461 position,
3462 add,
3463 click_count,
3464 } => self.begin_selection(position, add, click_count, window, cx),
3465 SelectPhase::BeginColumnar {
3466 position,
3467 goal_column,
3468 reset,
3469 mode,
3470 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3471 SelectPhase::Extend {
3472 position,
3473 click_count,
3474 } => self.extend_selection(position, click_count, window, cx),
3475 SelectPhase::Update {
3476 position,
3477 goal_column,
3478 scroll_delta,
3479 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3480 SelectPhase::End => self.end_selection(window, cx),
3481 }
3482 }
3483
3484 fn extend_selection(
3485 &mut self,
3486 position: DisplayPoint,
3487 click_count: usize,
3488 window: &mut Window,
3489 cx: &mut Context<Self>,
3490 ) {
3491 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3492 let tail = self.selections.newest::<usize>(cx).tail();
3493 self.begin_selection(position, false, click_count, window, cx);
3494
3495 let position = position.to_offset(&display_map, Bias::Left);
3496 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3497
3498 let mut pending_selection = self
3499 .selections
3500 .pending_anchor()
3501 .cloned()
3502 .expect("extend_selection not called with pending selection");
3503 if position >= tail {
3504 pending_selection.start = tail_anchor;
3505 } else {
3506 pending_selection.end = tail_anchor;
3507 pending_selection.reversed = true;
3508 }
3509
3510 let mut pending_mode = self.selections.pending_mode().unwrap();
3511 match &mut pending_mode {
3512 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3513 _ => {}
3514 }
3515
3516 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3517 SelectionEffects::scroll(Autoscroll::fit())
3518 } else {
3519 SelectionEffects::no_scroll()
3520 };
3521
3522 self.change_selections(effects, window, cx, |s| {
3523 s.set_pending(pending_selection.clone(), pending_mode)
3524 });
3525 }
3526
3527 fn begin_selection(
3528 &mut self,
3529 position: DisplayPoint,
3530 add: bool,
3531 click_count: usize,
3532 window: &mut Window,
3533 cx: &mut Context<Self>,
3534 ) {
3535 if !self.focus_handle.is_focused(window) {
3536 self.last_focused_descendant = None;
3537 window.focus(&self.focus_handle);
3538 }
3539
3540 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3541 let buffer = &display_map.buffer_snapshot;
3542 let position = display_map.clip_point(position, Bias::Left);
3543
3544 let start;
3545 let end;
3546 let mode;
3547 let mut auto_scroll;
3548 match click_count {
3549 1 => {
3550 start = buffer.anchor_before(position.to_point(&display_map));
3551 end = start;
3552 mode = SelectMode::Character;
3553 auto_scroll = true;
3554 }
3555 2 => {
3556 let position = display_map
3557 .clip_point(position, Bias::Left)
3558 .to_offset(&display_map, Bias::Left);
3559 let (range, _) = buffer.surrounding_word(position, None);
3560 start = buffer.anchor_before(range.start);
3561 end = buffer.anchor_before(range.end);
3562 mode = SelectMode::Word(start..end);
3563 auto_scroll = true;
3564 }
3565 3 => {
3566 let position = display_map
3567 .clip_point(position, Bias::Left)
3568 .to_point(&display_map);
3569 let line_start = display_map.prev_line_boundary(position).0;
3570 let next_line_start = buffer.clip_point(
3571 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3572 Bias::Left,
3573 );
3574 start = buffer.anchor_before(line_start);
3575 end = buffer.anchor_before(next_line_start);
3576 mode = SelectMode::Line(start..end);
3577 auto_scroll = true;
3578 }
3579 _ => {
3580 start = buffer.anchor_before(0);
3581 end = buffer.anchor_before(buffer.len());
3582 mode = SelectMode::All;
3583 auto_scroll = false;
3584 }
3585 }
3586 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3587
3588 let point_to_delete: Option<usize> = {
3589 let selected_points: Vec<Selection<Point>> =
3590 self.selections.disjoint_in_range(start..end, cx);
3591
3592 if !add || click_count > 1 {
3593 None
3594 } else if !selected_points.is_empty() {
3595 Some(selected_points[0].id)
3596 } else {
3597 let clicked_point_already_selected =
3598 self.selections.disjoint_anchors().iter().find(|selection| {
3599 selection.start.to_point(buffer) == start.to_point(buffer)
3600 || selection.end.to_point(buffer) == end.to_point(buffer)
3601 });
3602
3603 clicked_point_already_selected.map(|selection| selection.id)
3604 }
3605 };
3606
3607 let selections_count = self.selections.count();
3608 let effects = if auto_scroll {
3609 SelectionEffects::default()
3610 } else {
3611 SelectionEffects::no_scroll()
3612 };
3613
3614 self.change_selections(effects, window, cx, |s| {
3615 if let Some(point_to_delete) = point_to_delete {
3616 s.delete(point_to_delete);
3617
3618 if selections_count == 1 {
3619 s.set_pending_anchor_range(start..end, mode);
3620 }
3621 } else {
3622 if !add {
3623 s.clear_disjoint();
3624 }
3625
3626 s.set_pending_anchor_range(start..end, mode);
3627 }
3628 });
3629 }
3630
3631 fn begin_columnar_selection(
3632 &mut self,
3633 position: DisplayPoint,
3634 goal_column: u32,
3635 reset: bool,
3636 mode: ColumnarMode,
3637 window: &mut Window,
3638 cx: &mut Context<Self>,
3639 ) {
3640 if !self.focus_handle.is_focused(window) {
3641 self.last_focused_descendant = None;
3642 window.focus(&self.focus_handle);
3643 }
3644
3645 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3646
3647 if reset {
3648 let pointer_position = display_map
3649 .buffer_snapshot
3650 .anchor_before(position.to_point(&display_map));
3651
3652 self.change_selections(
3653 SelectionEffects::scroll(Autoscroll::newest()),
3654 window,
3655 cx,
3656 |s| {
3657 s.clear_disjoint();
3658 s.set_pending_anchor_range(
3659 pointer_position..pointer_position,
3660 SelectMode::Character,
3661 );
3662 },
3663 );
3664 };
3665
3666 let tail = self.selections.newest::<Point>(cx).tail();
3667 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3668 self.columnar_selection_state = match mode {
3669 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3670 selection_tail: selection_anchor,
3671 display_point: if reset {
3672 if position.column() != goal_column {
3673 Some(DisplayPoint::new(position.row(), goal_column))
3674 } else {
3675 None
3676 }
3677 } else {
3678 None
3679 },
3680 }),
3681 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3682 selection_tail: selection_anchor,
3683 }),
3684 };
3685
3686 if !reset {
3687 self.select_columns(position, goal_column, &display_map, window, cx);
3688 }
3689 }
3690
3691 fn update_selection(
3692 &mut self,
3693 position: DisplayPoint,
3694 goal_column: u32,
3695 scroll_delta: gpui::Point<f32>,
3696 window: &mut Window,
3697 cx: &mut Context<Self>,
3698 ) {
3699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3700
3701 if self.columnar_selection_state.is_some() {
3702 self.select_columns(position, goal_column, &display_map, window, cx);
3703 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3704 let buffer = &display_map.buffer_snapshot;
3705 let head;
3706 let tail;
3707 let mode = self.selections.pending_mode().unwrap();
3708 match &mode {
3709 SelectMode::Character => {
3710 head = position.to_point(&display_map);
3711 tail = pending.tail().to_point(buffer);
3712 }
3713 SelectMode::Word(original_range) => {
3714 let offset = display_map
3715 .clip_point(position, Bias::Left)
3716 .to_offset(&display_map, Bias::Left);
3717 let original_range = original_range.to_offset(buffer);
3718
3719 let head_offset = if buffer.is_inside_word(offset, None)
3720 || original_range.contains(&offset)
3721 {
3722 let (word_range, _) = buffer.surrounding_word(offset, None);
3723 if word_range.start < original_range.start {
3724 word_range.start
3725 } else {
3726 word_range.end
3727 }
3728 } else {
3729 offset
3730 };
3731
3732 head = head_offset.to_point(buffer);
3733 if head_offset <= original_range.start {
3734 tail = original_range.end.to_point(buffer);
3735 } else {
3736 tail = original_range.start.to_point(buffer);
3737 }
3738 }
3739 SelectMode::Line(original_range) => {
3740 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3741
3742 let position = display_map
3743 .clip_point(position, Bias::Left)
3744 .to_point(&display_map);
3745 let line_start = display_map.prev_line_boundary(position).0;
3746 let next_line_start = buffer.clip_point(
3747 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3748 Bias::Left,
3749 );
3750
3751 if line_start < original_range.start {
3752 head = line_start
3753 } else {
3754 head = next_line_start
3755 }
3756
3757 if head <= original_range.start {
3758 tail = original_range.end;
3759 } else {
3760 tail = original_range.start;
3761 }
3762 }
3763 SelectMode::All => {
3764 return;
3765 }
3766 };
3767
3768 if head < tail {
3769 pending.start = buffer.anchor_before(head);
3770 pending.end = buffer.anchor_before(tail);
3771 pending.reversed = true;
3772 } else {
3773 pending.start = buffer.anchor_before(tail);
3774 pending.end = buffer.anchor_before(head);
3775 pending.reversed = false;
3776 }
3777
3778 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3779 s.set_pending(pending.clone(), mode);
3780 });
3781 } else {
3782 log::error!("update_selection dispatched with no pending selection");
3783 return;
3784 }
3785
3786 self.apply_scroll_delta(scroll_delta, window, cx);
3787 cx.notify();
3788 }
3789
3790 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3791 self.columnar_selection_state.take();
3792 if self.selections.pending_anchor().is_some() {
3793 let selections = self.selections.all::<usize>(cx);
3794 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3795 s.select(selections);
3796 s.clear_pending();
3797 });
3798 }
3799 }
3800
3801 fn select_columns(
3802 &mut self,
3803 head: DisplayPoint,
3804 goal_column: u32,
3805 display_map: &DisplaySnapshot,
3806 window: &mut Window,
3807 cx: &mut Context<Self>,
3808 ) {
3809 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3810 return;
3811 };
3812
3813 let tail = match columnar_state {
3814 ColumnarSelectionState::FromMouse {
3815 selection_tail,
3816 display_point,
3817 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3818 ColumnarSelectionState::FromSelection { selection_tail } => {
3819 selection_tail.to_display_point(display_map)
3820 }
3821 };
3822
3823 let start_row = cmp::min(tail.row(), head.row());
3824 let end_row = cmp::max(tail.row(), head.row());
3825 let start_column = cmp::min(tail.column(), goal_column);
3826 let end_column = cmp::max(tail.column(), goal_column);
3827 let reversed = start_column < tail.column();
3828
3829 let selection_ranges = (start_row.0..=end_row.0)
3830 .map(DisplayRow)
3831 .filter_map(|row| {
3832 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3833 || start_column <= display_map.line_len(row))
3834 && !display_map.is_block_line(row)
3835 {
3836 let start = display_map
3837 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3838 .to_point(display_map);
3839 let end = display_map
3840 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3841 .to_point(display_map);
3842 if reversed {
3843 Some(end..start)
3844 } else {
3845 Some(start..end)
3846 }
3847 } else {
3848 None
3849 }
3850 })
3851 .collect::<Vec<_>>();
3852
3853 let ranges = match columnar_state {
3854 ColumnarSelectionState::FromMouse { .. } => {
3855 let mut non_empty_ranges = selection_ranges
3856 .iter()
3857 .filter(|selection_range| selection_range.start != selection_range.end)
3858 .peekable();
3859 if non_empty_ranges.peek().is_some() {
3860 non_empty_ranges.cloned().collect()
3861 } else {
3862 selection_ranges
3863 }
3864 }
3865 _ => selection_ranges,
3866 };
3867
3868 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3869 s.select_ranges(ranges);
3870 });
3871 cx.notify();
3872 }
3873
3874 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3875 self.selections
3876 .all_adjusted(cx)
3877 .iter()
3878 .any(|selection| !selection.is_empty())
3879 }
3880
3881 pub fn has_pending_nonempty_selection(&self) -> bool {
3882 let pending_nonempty_selection = match self.selections.pending_anchor() {
3883 Some(Selection { start, end, .. }) => start != end,
3884 None => false,
3885 };
3886
3887 pending_nonempty_selection
3888 || (self.columnar_selection_state.is_some()
3889 && self.selections.disjoint_anchors().len() > 1)
3890 }
3891
3892 pub fn has_pending_selection(&self) -> bool {
3893 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3894 }
3895
3896 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3897 self.selection_mark_mode = false;
3898 self.selection_drag_state = SelectionDragState::None;
3899
3900 if self.clear_expanded_diff_hunks(cx) {
3901 cx.notify();
3902 return;
3903 }
3904 if self.dismiss_menus_and_popups(true, window, cx) {
3905 return;
3906 }
3907
3908 if self.mode.is_full()
3909 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3910 {
3911 return;
3912 }
3913
3914 cx.propagate();
3915 }
3916
3917 pub fn dismiss_menus_and_popups(
3918 &mut self,
3919 is_user_requested: bool,
3920 window: &mut Window,
3921 cx: &mut Context<Self>,
3922 ) -> bool {
3923 if self.take_rename(false, window, cx).is_some() {
3924 return true;
3925 }
3926
3927 if hide_hover(self, cx) {
3928 return true;
3929 }
3930
3931 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3932 return true;
3933 }
3934
3935 if self.hide_context_menu(window, cx).is_some() {
3936 return true;
3937 }
3938
3939 if self.mouse_context_menu.take().is_some() {
3940 return true;
3941 }
3942
3943 if is_user_requested && self.discard_edit_prediction(true, cx) {
3944 return true;
3945 }
3946
3947 if self.snippet_stack.pop().is_some() {
3948 return true;
3949 }
3950
3951 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3952 self.dismiss_diagnostics(cx);
3953 return true;
3954 }
3955
3956 false
3957 }
3958
3959 fn linked_editing_ranges_for(
3960 &self,
3961 selection: Range<text::Anchor>,
3962 cx: &App,
3963 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3964 if self.linked_edit_ranges.is_empty() {
3965 return None;
3966 }
3967 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3968 selection.end.buffer_id.and_then(|end_buffer_id| {
3969 if selection.start.buffer_id != Some(end_buffer_id) {
3970 return None;
3971 }
3972 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3973 let snapshot = buffer.read(cx).snapshot();
3974 self.linked_edit_ranges
3975 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3976 .map(|ranges| (ranges, snapshot, buffer))
3977 })?;
3978 use text::ToOffset as TO;
3979 // find offset from the start of current range to current cursor position
3980 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3981
3982 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3983 let start_difference = start_offset - start_byte_offset;
3984 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3985 let end_difference = end_offset - start_byte_offset;
3986 // Current range has associated linked ranges.
3987 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3988 for range in linked_ranges.iter() {
3989 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3990 let end_offset = start_offset + end_difference;
3991 let start_offset = start_offset + start_difference;
3992 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3993 continue;
3994 }
3995 if self.selections.disjoint_anchor_ranges().any(|s| {
3996 if s.start.buffer_id != selection.start.buffer_id
3997 || s.end.buffer_id != selection.end.buffer_id
3998 {
3999 return false;
4000 }
4001 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4002 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4003 }) {
4004 continue;
4005 }
4006 let start = buffer_snapshot.anchor_after(start_offset);
4007 let end = buffer_snapshot.anchor_after(end_offset);
4008 linked_edits
4009 .entry(buffer.clone())
4010 .or_default()
4011 .push(start..end);
4012 }
4013 Some(linked_edits)
4014 }
4015
4016 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4017 let text: Arc<str> = text.into();
4018
4019 if self.read_only(cx) {
4020 return;
4021 }
4022
4023 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4024
4025 let selections = self.selections.all_adjusted(cx);
4026 let mut bracket_inserted = false;
4027 let mut edits = Vec::new();
4028 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4029 let mut new_selections = Vec::with_capacity(selections.len());
4030 let mut new_autoclose_regions = Vec::new();
4031 let snapshot = self.buffer.read(cx).read(cx);
4032 let mut clear_linked_edit_ranges = false;
4033
4034 for (selection, autoclose_region) in
4035 self.selections_with_autoclose_regions(selections, &snapshot)
4036 {
4037 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4038 // Determine if the inserted text matches the opening or closing
4039 // bracket of any of this language's bracket pairs.
4040 let mut bracket_pair = None;
4041 let mut is_bracket_pair_start = false;
4042 let mut is_bracket_pair_end = false;
4043 if !text.is_empty() {
4044 let mut bracket_pair_matching_end = None;
4045 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4046 // and they are removing the character that triggered IME popup.
4047 for (pair, enabled) in scope.brackets() {
4048 if !pair.close && !pair.surround {
4049 continue;
4050 }
4051
4052 if enabled && pair.start.ends_with(text.as_ref()) {
4053 let prefix_len = pair.start.len() - text.len();
4054 let preceding_text_matches_prefix = prefix_len == 0
4055 || (selection.start.column >= (prefix_len as u32)
4056 && snapshot.contains_str_at(
4057 Point::new(
4058 selection.start.row,
4059 selection.start.column - (prefix_len as u32),
4060 ),
4061 &pair.start[..prefix_len],
4062 ));
4063 if preceding_text_matches_prefix {
4064 bracket_pair = Some(pair.clone());
4065 is_bracket_pair_start = true;
4066 break;
4067 }
4068 }
4069 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4070 {
4071 // take first bracket pair matching end, but don't break in case a later bracket
4072 // pair matches start
4073 bracket_pair_matching_end = Some(pair.clone());
4074 }
4075 }
4076 if let Some(end) = bracket_pair_matching_end
4077 && bracket_pair.is_none()
4078 {
4079 bracket_pair = Some(end);
4080 is_bracket_pair_end = true;
4081 }
4082 }
4083
4084 if let Some(bracket_pair) = bracket_pair {
4085 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4086 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4087 let auto_surround =
4088 self.use_auto_surround && snapshot_settings.use_auto_surround;
4089 if selection.is_empty() {
4090 if is_bracket_pair_start {
4091 // If the inserted text is a suffix of an opening bracket and the
4092 // selection is preceded by the rest of the opening bracket, then
4093 // insert the closing bracket.
4094 let following_text_allows_autoclose = snapshot
4095 .chars_at(selection.start)
4096 .next()
4097 .is_none_or(|c| scope.should_autoclose_before(c));
4098
4099 let preceding_text_allows_autoclose = selection.start.column == 0
4100 || snapshot
4101 .reversed_chars_at(selection.start)
4102 .next()
4103 .is_none_or(|c| {
4104 bracket_pair.start != bracket_pair.end
4105 || !snapshot
4106 .char_classifier_at(selection.start)
4107 .is_word(c)
4108 });
4109
4110 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4111 && bracket_pair.start.len() == 1
4112 {
4113 let target = bracket_pair.start.chars().next().unwrap();
4114 let current_line_count = snapshot
4115 .reversed_chars_at(selection.start)
4116 .take_while(|&c| c != '\n')
4117 .filter(|&c| c == target)
4118 .count();
4119 current_line_count % 2 == 1
4120 } else {
4121 false
4122 };
4123
4124 if autoclose
4125 && bracket_pair.close
4126 && following_text_allows_autoclose
4127 && preceding_text_allows_autoclose
4128 && !is_closing_quote
4129 {
4130 let anchor = snapshot.anchor_before(selection.end);
4131 new_selections.push((selection.map(|_| anchor), text.len()));
4132 new_autoclose_regions.push((
4133 anchor,
4134 text.len(),
4135 selection.id,
4136 bracket_pair.clone(),
4137 ));
4138 edits.push((
4139 selection.range(),
4140 format!("{}{}", text, bracket_pair.end).into(),
4141 ));
4142 bracket_inserted = true;
4143 continue;
4144 }
4145 }
4146
4147 if let Some(region) = autoclose_region {
4148 // If the selection is followed by an auto-inserted closing bracket,
4149 // then don't insert that closing bracket again; just move the selection
4150 // past the closing bracket.
4151 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4152 && text.as_ref() == region.pair.end.as_str()
4153 && snapshot.contains_str_at(region.range.end, text.as_ref());
4154 if should_skip {
4155 let anchor = snapshot.anchor_after(selection.end);
4156 new_selections
4157 .push((selection.map(|_| anchor), region.pair.end.len()));
4158 continue;
4159 }
4160 }
4161
4162 let always_treat_brackets_as_autoclosed = snapshot
4163 .language_settings_at(selection.start, cx)
4164 .always_treat_brackets_as_autoclosed;
4165 if always_treat_brackets_as_autoclosed
4166 && is_bracket_pair_end
4167 && snapshot.contains_str_at(selection.end, text.as_ref())
4168 {
4169 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4170 // and the inserted text is a closing bracket and the selection is followed
4171 // by the closing bracket then move the selection past the closing bracket.
4172 let anchor = snapshot.anchor_after(selection.end);
4173 new_selections.push((selection.map(|_| anchor), text.len()));
4174 continue;
4175 }
4176 }
4177 // If an opening bracket is 1 character long and is typed while
4178 // text is selected, then surround that text with the bracket pair.
4179 else if auto_surround
4180 && bracket_pair.surround
4181 && is_bracket_pair_start
4182 && bracket_pair.start.chars().count() == 1
4183 {
4184 edits.push((selection.start..selection.start, text.clone()));
4185 edits.push((
4186 selection.end..selection.end,
4187 bracket_pair.end.as_str().into(),
4188 ));
4189 bracket_inserted = true;
4190 new_selections.push((
4191 Selection {
4192 id: selection.id,
4193 start: snapshot.anchor_after(selection.start),
4194 end: snapshot.anchor_before(selection.end),
4195 reversed: selection.reversed,
4196 goal: selection.goal,
4197 },
4198 0,
4199 ));
4200 continue;
4201 }
4202 }
4203 }
4204
4205 if self.auto_replace_emoji_shortcode
4206 && selection.is_empty()
4207 && text.as_ref().ends_with(':')
4208 && let Some(possible_emoji_short_code) =
4209 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4210 && !possible_emoji_short_code.is_empty()
4211 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4212 {
4213 let emoji_shortcode_start = Point::new(
4214 selection.start.row,
4215 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4216 );
4217
4218 // Remove shortcode from buffer
4219 edits.push((
4220 emoji_shortcode_start..selection.start,
4221 "".to_string().into(),
4222 ));
4223 new_selections.push((
4224 Selection {
4225 id: selection.id,
4226 start: snapshot.anchor_after(emoji_shortcode_start),
4227 end: snapshot.anchor_before(selection.start),
4228 reversed: selection.reversed,
4229 goal: selection.goal,
4230 },
4231 0,
4232 ));
4233
4234 // Insert emoji
4235 let selection_start_anchor = snapshot.anchor_after(selection.start);
4236 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4237 edits.push((selection.start..selection.end, emoji.to_string().into()));
4238
4239 continue;
4240 }
4241
4242 // If not handling any auto-close operation, then just replace the selected
4243 // text with the given input and move the selection to the end of the
4244 // newly inserted text.
4245 let anchor = snapshot.anchor_after(selection.end);
4246 if !self.linked_edit_ranges.is_empty() {
4247 let start_anchor = snapshot.anchor_before(selection.start);
4248
4249 let is_word_char = text.chars().next().is_none_or(|char| {
4250 let classifier = snapshot
4251 .char_classifier_at(start_anchor.to_offset(&snapshot))
4252 .scope_context(Some(CharScopeContext::LinkedEdit));
4253 classifier.is_word(char)
4254 });
4255
4256 if is_word_char {
4257 if let Some(ranges) = self
4258 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4259 {
4260 for (buffer, edits) in ranges {
4261 linked_edits
4262 .entry(buffer.clone())
4263 .or_default()
4264 .extend(edits.into_iter().map(|range| (range, text.clone())));
4265 }
4266 }
4267 } else {
4268 clear_linked_edit_ranges = true;
4269 }
4270 }
4271
4272 new_selections.push((selection.map(|_| anchor), 0));
4273 edits.push((selection.start..selection.end, text.clone()));
4274 }
4275
4276 drop(snapshot);
4277
4278 self.transact(window, cx, |this, window, cx| {
4279 if clear_linked_edit_ranges {
4280 this.linked_edit_ranges.clear();
4281 }
4282 let initial_buffer_versions =
4283 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4284
4285 this.buffer.update(cx, |buffer, cx| {
4286 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4287 });
4288 for (buffer, edits) in linked_edits {
4289 buffer.update(cx, |buffer, cx| {
4290 let snapshot = buffer.snapshot();
4291 let edits = edits
4292 .into_iter()
4293 .map(|(range, text)| {
4294 use text::ToPoint as TP;
4295 let end_point = TP::to_point(&range.end, &snapshot);
4296 let start_point = TP::to_point(&range.start, &snapshot);
4297 (start_point..end_point, text)
4298 })
4299 .sorted_by_key(|(range, _)| range.start);
4300 buffer.edit(edits, None, cx);
4301 })
4302 }
4303 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4304 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4305 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4306 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4307 .zip(new_selection_deltas)
4308 .map(|(selection, delta)| Selection {
4309 id: selection.id,
4310 start: selection.start + delta,
4311 end: selection.end + delta,
4312 reversed: selection.reversed,
4313 goal: SelectionGoal::None,
4314 })
4315 .collect::<Vec<_>>();
4316
4317 let mut i = 0;
4318 for (position, delta, selection_id, pair) in new_autoclose_regions {
4319 let position = position.to_offset(&map.buffer_snapshot) + delta;
4320 let start = map.buffer_snapshot.anchor_before(position);
4321 let end = map.buffer_snapshot.anchor_after(position);
4322 while let Some(existing_state) = this.autoclose_regions.get(i) {
4323 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4324 Ordering::Less => i += 1,
4325 Ordering::Greater => break,
4326 Ordering::Equal => {
4327 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4328 Ordering::Less => i += 1,
4329 Ordering::Equal => break,
4330 Ordering::Greater => break,
4331 }
4332 }
4333 }
4334 }
4335 this.autoclose_regions.insert(
4336 i,
4337 AutocloseRegion {
4338 selection_id,
4339 range: start..end,
4340 pair,
4341 },
4342 );
4343 }
4344
4345 let had_active_edit_prediction = this.has_active_edit_prediction();
4346 this.change_selections(
4347 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4348 window,
4349 cx,
4350 |s| s.select(new_selections),
4351 );
4352
4353 if !bracket_inserted
4354 && let Some(on_type_format_task) =
4355 this.trigger_on_type_formatting(text.to_string(), window, cx)
4356 {
4357 on_type_format_task.detach_and_log_err(cx);
4358 }
4359
4360 let editor_settings = EditorSettings::get_global(cx);
4361 if bracket_inserted
4362 && (editor_settings.auto_signature_help
4363 || editor_settings.show_signature_help_after_edits)
4364 {
4365 this.show_signature_help(&ShowSignatureHelp, window, cx);
4366 }
4367
4368 let trigger_in_words =
4369 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4370 if this.hard_wrap.is_some() {
4371 let latest: Range<Point> = this.selections.newest(cx).range();
4372 if latest.is_empty()
4373 && this
4374 .buffer()
4375 .read(cx)
4376 .snapshot(cx)
4377 .line_len(MultiBufferRow(latest.start.row))
4378 == latest.start.column
4379 {
4380 this.rewrap_impl(
4381 RewrapOptions {
4382 override_language_settings: true,
4383 preserve_existing_whitespace: true,
4384 },
4385 cx,
4386 )
4387 }
4388 }
4389 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4390 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4391 this.refresh_edit_prediction(true, false, window, cx);
4392 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4393 });
4394 }
4395
4396 fn find_possible_emoji_shortcode_at_position(
4397 snapshot: &MultiBufferSnapshot,
4398 position: Point,
4399 ) -> Option<String> {
4400 let mut chars = Vec::new();
4401 let mut found_colon = false;
4402 for char in snapshot.reversed_chars_at(position).take(100) {
4403 // Found a possible emoji shortcode in the middle of the buffer
4404 if found_colon {
4405 if char.is_whitespace() {
4406 chars.reverse();
4407 return Some(chars.iter().collect());
4408 }
4409 // If the previous character is not a whitespace, we are in the middle of a word
4410 // and we only want to complete the shortcode if the word is made up of other emojis
4411 let mut containing_word = String::new();
4412 for ch in snapshot
4413 .reversed_chars_at(position)
4414 .skip(chars.len() + 1)
4415 .take(100)
4416 {
4417 if ch.is_whitespace() {
4418 break;
4419 }
4420 containing_word.push(ch);
4421 }
4422 let containing_word = containing_word.chars().rev().collect::<String>();
4423 if util::word_consists_of_emojis(containing_word.as_str()) {
4424 chars.reverse();
4425 return Some(chars.iter().collect());
4426 }
4427 }
4428
4429 if char.is_whitespace() || !char.is_ascii() {
4430 return None;
4431 }
4432 if char == ':' {
4433 found_colon = true;
4434 } else {
4435 chars.push(char);
4436 }
4437 }
4438 // Found a possible emoji shortcode at the beginning of the buffer
4439 chars.reverse();
4440 Some(chars.iter().collect())
4441 }
4442
4443 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4444 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4445 self.transact(window, cx, |this, window, cx| {
4446 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4447 let selections = this.selections.all::<usize>(cx);
4448 let multi_buffer = this.buffer.read(cx);
4449 let buffer = multi_buffer.snapshot(cx);
4450 selections
4451 .iter()
4452 .map(|selection| {
4453 let start_point = selection.start.to_point(&buffer);
4454 let mut existing_indent =
4455 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4456 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4457 let start = selection.start;
4458 let end = selection.end;
4459 let selection_is_empty = start == end;
4460 let language_scope = buffer.language_scope_at(start);
4461 let (
4462 comment_delimiter,
4463 doc_delimiter,
4464 insert_extra_newline,
4465 indent_on_newline,
4466 indent_on_extra_newline,
4467 ) = if let Some(language) = &language_scope {
4468 let mut insert_extra_newline =
4469 insert_extra_newline_brackets(&buffer, start..end, language)
4470 || insert_extra_newline_tree_sitter(&buffer, start..end);
4471
4472 // Comment extension on newline is allowed only for cursor selections
4473 let comment_delimiter = maybe!({
4474 if !selection_is_empty {
4475 return None;
4476 }
4477
4478 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4479 return None;
4480 }
4481
4482 let delimiters = language.line_comment_prefixes();
4483 let max_len_of_delimiter =
4484 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4485 let (snapshot, range) =
4486 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4487
4488 let num_of_whitespaces = snapshot
4489 .chars_for_range(range.clone())
4490 .take_while(|c| c.is_whitespace())
4491 .count();
4492 let comment_candidate = snapshot
4493 .chars_for_range(range.clone())
4494 .skip(num_of_whitespaces)
4495 .take(max_len_of_delimiter)
4496 .collect::<String>();
4497 let (delimiter, trimmed_len) = delimiters
4498 .iter()
4499 .filter_map(|delimiter| {
4500 let prefix = delimiter.trim_end();
4501 if comment_candidate.starts_with(prefix) {
4502 Some((delimiter, prefix.len()))
4503 } else {
4504 None
4505 }
4506 })
4507 .max_by_key(|(_, len)| *len)?;
4508
4509 if let Some(BlockCommentConfig {
4510 start: block_start, ..
4511 }) = language.block_comment()
4512 {
4513 let block_start_trimmed = block_start.trim_end();
4514 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4515 let line_content = snapshot
4516 .chars_for_range(range)
4517 .skip(num_of_whitespaces)
4518 .take(block_start_trimmed.len())
4519 .collect::<String>();
4520
4521 if line_content.starts_with(block_start_trimmed) {
4522 return None;
4523 }
4524 }
4525 }
4526
4527 let cursor_is_placed_after_comment_marker =
4528 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4529 if cursor_is_placed_after_comment_marker {
4530 Some(delimiter.clone())
4531 } else {
4532 None
4533 }
4534 });
4535
4536 let mut indent_on_newline = IndentSize::spaces(0);
4537 let mut indent_on_extra_newline = IndentSize::spaces(0);
4538
4539 let doc_delimiter = maybe!({
4540 if !selection_is_empty {
4541 return None;
4542 }
4543
4544 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4545 return None;
4546 }
4547
4548 let BlockCommentConfig {
4549 start: start_tag,
4550 end: end_tag,
4551 prefix: delimiter,
4552 tab_size: len,
4553 } = language.documentation_comment()?;
4554 let is_within_block_comment = buffer
4555 .language_scope_at(start_point)
4556 .is_some_and(|scope| scope.override_name() == Some("comment"));
4557 if !is_within_block_comment {
4558 return None;
4559 }
4560
4561 let (snapshot, range) =
4562 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4563
4564 let num_of_whitespaces = snapshot
4565 .chars_for_range(range.clone())
4566 .take_while(|c| c.is_whitespace())
4567 .count();
4568
4569 // 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.
4570 let column = start_point.column;
4571 let cursor_is_after_start_tag = {
4572 let start_tag_len = start_tag.len();
4573 let start_tag_line = snapshot
4574 .chars_for_range(range.clone())
4575 .skip(num_of_whitespaces)
4576 .take(start_tag_len)
4577 .collect::<String>();
4578 if start_tag_line.starts_with(start_tag.as_ref()) {
4579 num_of_whitespaces + start_tag_len <= column as usize
4580 } else {
4581 false
4582 }
4583 };
4584
4585 let cursor_is_after_delimiter = {
4586 let delimiter_trim = delimiter.trim_end();
4587 let delimiter_line = snapshot
4588 .chars_for_range(range.clone())
4589 .skip(num_of_whitespaces)
4590 .take(delimiter_trim.len())
4591 .collect::<String>();
4592 if delimiter_line.starts_with(delimiter_trim) {
4593 num_of_whitespaces + delimiter_trim.len() <= column as usize
4594 } else {
4595 false
4596 }
4597 };
4598
4599 let cursor_is_before_end_tag_if_exists = {
4600 let mut char_position = 0u32;
4601 let mut end_tag_offset = None;
4602
4603 'outer: for chunk in snapshot.text_for_range(range) {
4604 if let Some(byte_pos) = chunk.find(&**end_tag) {
4605 let chars_before_match =
4606 chunk[..byte_pos].chars().count() as u32;
4607 end_tag_offset =
4608 Some(char_position + chars_before_match);
4609 break 'outer;
4610 }
4611 char_position += chunk.chars().count() as u32;
4612 }
4613
4614 if let Some(end_tag_offset) = end_tag_offset {
4615 let cursor_is_before_end_tag = column <= end_tag_offset;
4616 if cursor_is_after_start_tag {
4617 if cursor_is_before_end_tag {
4618 insert_extra_newline = true;
4619 }
4620 let cursor_is_at_start_of_end_tag =
4621 column == end_tag_offset;
4622 if cursor_is_at_start_of_end_tag {
4623 indent_on_extra_newline.len = *len;
4624 }
4625 }
4626 cursor_is_before_end_tag
4627 } else {
4628 true
4629 }
4630 };
4631
4632 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4633 && cursor_is_before_end_tag_if_exists
4634 {
4635 if cursor_is_after_start_tag {
4636 indent_on_newline.len = *len;
4637 }
4638 Some(delimiter.clone())
4639 } else {
4640 None
4641 }
4642 });
4643
4644 (
4645 comment_delimiter,
4646 doc_delimiter,
4647 insert_extra_newline,
4648 indent_on_newline,
4649 indent_on_extra_newline,
4650 )
4651 } else {
4652 (
4653 None,
4654 None,
4655 false,
4656 IndentSize::default(),
4657 IndentSize::default(),
4658 )
4659 };
4660
4661 let prevent_auto_indent = doc_delimiter.is_some();
4662 let delimiter = comment_delimiter.or(doc_delimiter);
4663
4664 let capacity_for_delimiter =
4665 delimiter.as_deref().map(str::len).unwrap_or_default();
4666 let mut new_text = String::with_capacity(
4667 1 + capacity_for_delimiter
4668 + existing_indent.len as usize
4669 + indent_on_newline.len as usize
4670 + indent_on_extra_newline.len as usize,
4671 );
4672 new_text.push('\n');
4673 new_text.extend(existing_indent.chars());
4674 new_text.extend(indent_on_newline.chars());
4675
4676 if let Some(delimiter) = &delimiter {
4677 new_text.push_str(delimiter);
4678 }
4679
4680 if insert_extra_newline {
4681 new_text.push('\n');
4682 new_text.extend(existing_indent.chars());
4683 new_text.extend(indent_on_extra_newline.chars());
4684 }
4685
4686 let anchor = buffer.anchor_after(end);
4687 let new_selection = selection.map(|_| anchor);
4688 (
4689 ((start..end, new_text), prevent_auto_indent),
4690 (insert_extra_newline, new_selection),
4691 )
4692 })
4693 .unzip()
4694 };
4695
4696 let mut auto_indent_edits = Vec::new();
4697 let mut edits = Vec::new();
4698 for (edit, prevent_auto_indent) in edits_with_flags {
4699 if prevent_auto_indent {
4700 edits.push(edit);
4701 } else {
4702 auto_indent_edits.push(edit);
4703 }
4704 }
4705 if !edits.is_empty() {
4706 this.edit(edits, cx);
4707 }
4708 if !auto_indent_edits.is_empty() {
4709 this.edit_with_autoindent(auto_indent_edits, cx);
4710 }
4711
4712 let buffer = this.buffer.read(cx).snapshot(cx);
4713 let new_selections = selection_info
4714 .into_iter()
4715 .map(|(extra_newline_inserted, new_selection)| {
4716 let mut cursor = new_selection.end.to_point(&buffer);
4717 if extra_newline_inserted {
4718 cursor.row -= 1;
4719 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4720 }
4721 new_selection.map(|_| cursor)
4722 })
4723 .collect();
4724
4725 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4726 this.refresh_edit_prediction(true, false, window, cx);
4727 });
4728 }
4729
4730 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4731 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4732
4733 let buffer = self.buffer.read(cx);
4734 let snapshot = buffer.snapshot(cx);
4735
4736 let mut edits = Vec::new();
4737 let mut rows = Vec::new();
4738
4739 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4740 let cursor = selection.head();
4741 let row = cursor.row;
4742
4743 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4744
4745 let newline = "\n".to_string();
4746 edits.push((start_of_line..start_of_line, newline));
4747
4748 rows.push(row + rows_inserted as u32);
4749 }
4750
4751 self.transact(window, cx, |editor, window, cx| {
4752 editor.edit(edits, cx);
4753
4754 editor.change_selections(Default::default(), window, cx, |s| {
4755 let mut index = 0;
4756 s.move_cursors_with(|map, _, _| {
4757 let row = rows[index];
4758 index += 1;
4759
4760 let point = Point::new(row, 0);
4761 let boundary = map.next_line_boundary(point).1;
4762 let clipped = map.clip_point(boundary, Bias::Left);
4763
4764 (clipped, SelectionGoal::None)
4765 });
4766 });
4767
4768 let mut indent_edits = Vec::new();
4769 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4770 for row in rows {
4771 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4772 for (row, indent) in indents {
4773 if indent.len == 0 {
4774 continue;
4775 }
4776
4777 let text = match indent.kind {
4778 IndentKind::Space => " ".repeat(indent.len as usize),
4779 IndentKind::Tab => "\t".repeat(indent.len as usize),
4780 };
4781 let point = Point::new(row.0, 0);
4782 indent_edits.push((point..point, text));
4783 }
4784 }
4785 editor.edit(indent_edits, cx);
4786 });
4787 }
4788
4789 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4790 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4791
4792 let buffer = self.buffer.read(cx);
4793 let snapshot = buffer.snapshot(cx);
4794
4795 let mut edits = Vec::new();
4796 let mut rows = Vec::new();
4797 let mut rows_inserted = 0;
4798
4799 for selection in self.selections.all_adjusted(cx) {
4800 let cursor = selection.head();
4801 let row = cursor.row;
4802
4803 let point = Point::new(row + 1, 0);
4804 let start_of_line = snapshot.clip_point(point, Bias::Left);
4805
4806 let newline = "\n".to_string();
4807 edits.push((start_of_line..start_of_line, newline));
4808
4809 rows_inserted += 1;
4810 rows.push(row + rows_inserted);
4811 }
4812
4813 self.transact(window, cx, |editor, window, cx| {
4814 editor.edit(edits, cx);
4815
4816 editor.change_selections(Default::default(), window, cx, |s| {
4817 let mut index = 0;
4818 s.move_cursors_with(|map, _, _| {
4819 let row = rows[index];
4820 index += 1;
4821
4822 let point = Point::new(row, 0);
4823 let boundary = map.next_line_boundary(point).1;
4824 let clipped = map.clip_point(boundary, Bias::Left);
4825
4826 (clipped, SelectionGoal::None)
4827 });
4828 });
4829
4830 let mut indent_edits = Vec::new();
4831 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4832 for row in rows {
4833 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4834 for (row, indent) in indents {
4835 if indent.len == 0 {
4836 continue;
4837 }
4838
4839 let text = match indent.kind {
4840 IndentKind::Space => " ".repeat(indent.len as usize),
4841 IndentKind::Tab => "\t".repeat(indent.len as usize),
4842 };
4843 let point = Point::new(row.0, 0);
4844 indent_edits.push((point..point, text));
4845 }
4846 }
4847 editor.edit(indent_edits, cx);
4848 });
4849 }
4850
4851 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4852 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4853 original_indent_columns: Vec::new(),
4854 });
4855 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4856 }
4857
4858 fn insert_with_autoindent_mode(
4859 &mut self,
4860 text: &str,
4861 autoindent_mode: Option<AutoindentMode>,
4862 window: &mut Window,
4863 cx: &mut Context<Self>,
4864 ) {
4865 if self.read_only(cx) {
4866 return;
4867 }
4868
4869 let text: Arc<str> = text.into();
4870 self.transact(window, cx, |this, window, cx| {
4871 let old_selections = this.selections.all_adjusted(cx);
4872 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4873 let anchors = {
4874 let snapshot = buffer.read(cx);
4875 old_selections
4876 .iter()
4877 .map(|s| {
4878 let anchor = snapshot.anchor_after(s.head());
4879 s.map(|_| anchor)
4880 })
4881 .collect::<Vec<_>>()
4882 };
4883 buffer.edit(
4884 old_selections
4885 .iter()
4886 .map(|s| (s.start..s.end, text.clone())),
4887 autoindent_mode,
4888 cx,
4889 );
4890 anchors
4891 });
4892
4893 this.change_selections(Default::default(), window, cx, |s| {
4894 s.select_anchors(selection_anchors);
4895 });
4896
4897 cx.notify();
4898 });
4899 }
4900
4901 fn trigger_completion_on_input(
4902 &mut self,
4903 text: &str,
4904 trigger_in_words: bool,
4905 window: &mut Window,
4906 cx: &mut Context<Self>,
4907 ) {
4908 let completions_source = self
4909 .context_menu
4910 .borrow()
4911 .as_ref()
4912 .and_then(|menu| match menu {
4913 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4914 CodeContextMenu::CodeActions(_) => None,
4915 });
4916
4917 match completions_source {
4918 Some(CompletionsMenuSource::Words { .. }) => {
4919 self.open_or_update_completions_menu(
4920 Some(CompletionsMenuSource::Words {
4921 ignore_threshold: false,
4922 }),
4923 None,
4924 window,
4925 cx,
4926 );
4927 }
4928 Some(CompletionsMenuSource::Normal)
4929 | Some(CompletionsMenuSource::SnippetChoices)
4930 | None
4931 if self.is_completion_trigger(
4932 text,
4933 trigger_in_words,
4934 completions_source.is_some(),
4935 cx,
4936 ) =>
4937 {
4938 self.show_completions(
4939 &ShowCompletions {
4940 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4941 },
4942 window,
4943 cx,
4944 )
4945 }
4946 _ => {
4947 self.hide_context_menu(window, cx);
4948 }
4949 }
4950 }
4951
4952 fn is_completion_trigger(
4953 &self,
4954 text: &str,
4955 trigger_in_words: bool,
4956 menu_is_open: bool,
4957 cx: &mut Context<Self>,
4958 ) -> bool {
4959 let position = self.selections.newest_anchor().head();
4960 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4961 return false;
4962 };
4963
4964 if let Some(completion_provider) = &self.completion_provider {
4965 completion_provider.is_completion_trigger(
4966 &buffer,
4967 position.text_anchor,
4968 text,
4969 trigger_in_words,
4970 menu_is_open,
4971 cx,
4972 )
4973 } else {
4974 false
4975 }
4976 }
4977
4978 /// If any empty selections is touching the start of its innermost containing autoclose
4979 /// region, expand it to select the brackets.
4980 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4981 let selections = self.selections.all::<usize>(cx);
4982 let buffer = self.buffer.read(cx).read(cx);
4983 let new_selections = self
4984 .selections_with_autoclose_regions(selections, &buffer)
4985 .map(|(mut selection, region)| {
4986 if !selection.is_empty() {
4987 return selection;
4988 }
4989
4990 if let Some(region) = region {
4991 let mut range = region.range.to_offset(&buffer);
4992 if selection.start == range.start && range.start >= region.pair.start.len() {
4993 range.start -= region.pair.start.len();
4994 if buffer.contains_str_at(range.start, ®ion.pair.start)
4995 && buffer.contains_str_at(range.end, ®ion.pair.end)
4996 {
4997 range.end += region.pair.end.len();
4998 selection.start = range.start;
4999 selection.end = range.end;
5000
5001 return selection;
5002 }
5003 }
5004 }
5005
5006 let always_treat_brackets_as_autoclosed = buffer
5007 .language_settings_at(selection.start, cx)
5008 .always_treat_brackets_as_autoclosed;
5009
5010 if !always_treat_brackets_as_autoclosed {
5011 return selection;
5012 }
5013
5014 if let Some(scope) = buffer.language_scope_at(selection.start) {
5015 for (pair, enabled) in scope.brackets() {
5016 if !enabled || !pair.close {
5017 continue;
5018 }
5019
5020 if buffer.contains_str_at(selection.start, &pair.end) {
5021 let pair_start_len = pair.start.len();
5022 if buffer.contains_str_at(
5023 selection.start.saturating_sub(pair_start_len),
5024 &pair.start,
5025 ) {
5026 selection.start -= pair_start_len;
5027 selection.end += pair.end.len();
5028
5029 return selection;
5030 }
5031 }
5032 }
5033 }
5034
5035 selection
5036 })
5037 .collect();
5038
5039 drop(buffer);
5040 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5041 selections.select(new_selections)
5042 });
5043 }
5044
5045 /// Iterate the given selections, and for each one, find the smallest surrounding
5046 /// autoclose region. This uses the ordering of the selections and the autoclose
5047 /// regions to avoid repeated comparisons.
5048 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5049 &'a self,
5050 selections: impl IntoIterator<Item = Selection<D>>,
5051 buffer: &'a MultiBufferSnapshot,
5052 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5053 let mut i = 0;
5054 let mut regions = self.autoclose_regions.as_slice();
5055 selections.into_iter().map(move |selection| {
5056 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5057
5058 let mut enclosing = None;
5059 while let Some(pair_state) = regions.get(i) {
5060 if pair_state.range.end.to_offset(buffer) < range.start {
5061 regions = ®ions[i + 1..];
5062 i = 0;
5063 } else if pair_state.range.start.to_offset(buffer) > range.end {
5064 break;
5065 } else {
5066 if pair_state.selection_id == selection.id {
5067 enclosing = Some(pair_state);
5068 }
5069 i += 1;
5070 }
5071 }
5072
5073 (selection, enclosing)
5074 })
5075 }
5076
5077 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5078 fn invalidate_autoclose_regions(
5079 &mut self,
5080 mut selections: &[Selection<Anchor>],
5081 buffer: &MultiBufferSnapshot,
5082 ) {
5083 self.autoclose_regions.retain(|state| {
5084 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5085 return false;
5086 }
5087
5088 let mut i = 0;
5089 while let Some(selection) = selections.get(i) {
5090 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5091 selections = &selections[1..];
5092 continue;
5093 }
5094 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5095 break;
5096 }
5097 if selection.id == state.selection_id {
5098 return true;
5099 } else {
5100 i += 1;
5101 }
5102 }
5103 false
5104 });
5105 }
5106
5107 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5108 let offset = position.to_offset(buffer);
5109 let (word_range, kind) =
5110 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5111 if offset > word_range.start && kind == Some(CharKind::Word) {
5112 Some(
5113 buffer
5114 .text_for_range(word_range.start..offset)
5115 .collect::<String>(),
5116 )
5117 } else {
5118 None
5119 }
5120 }
5121
5122 pub fn toggle_inline_values(
5123 &mut self,
5124 _: &ToggleInlineValues,
5125 _: &mut Window,
5126 cx: &mut Context<Self>,
5127 ) {
5128 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5129
5130 self.refresh_inline_values(cx);
5131 }
5132
5133 pub fn toggle_inlay_hints(
5134 &mut self,
5135 _: &ToggleInlayHints,
5136 _: &mut Window,
5137 cx: &mut Context<Self>,
5138 ) {
5139 self.refresh_inlay_hints(
5140 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5141 cx,
5142 );
5143 }
5144
5145 pub fn inlay_hints_enabled(&self) -> bool {
5146 self.inlay_hint_cache.enabled
5147 }
5148
5149 pub fn inline_values_enabled(&self) -> bool {
5150 self.inline_value_cache.enabled
5151 }
5152
5153 #[cfg(any(test, feature = "test-support"))]
5154 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5155 self.display_map
5156 .read(cx)
5157 .current_inlays()
5158 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5159 .cloned()
5160 .collect()
5161 }
5162
5163 #[cfg(any(test, feature = "test-support"))]
5164 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5165 self.display_map
5166 .read(cx)
5167 .current_inlays()
5168 .cloned()
5169 .collect()
5170 }
5171
5172 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5173 if self.semantics_provider.is_none() || !self.mode.is_full() {
5174 return;
5175 }
5176
5177 let reason_description = reason.description();
5178 let ignore_debounce = matches!(
5179 reason,
5180 InlayHintRefreshReason::SettingsChange(_)
5181 | InlayHintRefreshReason::Toggle(_)
5182 | InlayHintRefreshReason::ExcerptsRemoved(_)
5183 | InlayHintRefreshReason::ModifiersChanged(_)
5184 );
5185 let (invalidate_cache, required_languages) = match reason {
5186 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5187 match self.inlay_hint_cache.modifiers_override(enabled) {
5188 Some(enabled) => {
5189 if enabled {
5190 (InvalidationStrategy::RefreshRequested, None)
5191 } else {
5192 self.splice_inlays(
5193 &self
5194 .visible_inlay_hints(cx)
5195 .iter()
5196 .map(|inlay| inlay.id)
5197 .collect::<Vec<InlayId>>(),
5198 Vec::new(),
5199 cx,
5200 );
5201 return;
5202 }
5203 }
5204 None => return,
5205 }
5206 }
5207 InlayHintRefreshReason::Toggle(enabled) => {
5208 if self.inlay_hint_cache.toggle(enabled) {
5209 if enabled {
5210 (InvalidationStrategy::RefreshRequested, None)
5211 } else {
5212 self.splice_inlays(
5213 &self
5214 .visible_inlay_hints(cx)
5215 .iter()
5216 .map(|inlay| inlay.id)
5217 .collect::<Vec<InlayId>>(),
5218 Vec::new(),
5219 cx,
5220 );
5221 return;
5222 }
5223 } else {
5224 return;
5225 }
5226 }
5227 InlayHintRefreshReason::SettingsChange(new_settings) => {
5228 match self.inlay_hint_cache.update_settings(
5229 &self.buffer,
5230 new_settings,
5231 self.visible_inlay_hints(cx),
5232 cx,
5233 ) {
5234 ControlFlow::Break(Some(InlaySplice {
5235 to_remove,
5236 to_insert,
5237 })) => {
5238 self.splice_inlays(&to_remove, to_insert, cx);
5239 return;
5240 }
5241 ControlFlow::Break(None) => return,
5242 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5243 }
5244 }
5245 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5246 if let Some(InlaySplice {
5247 to_remove,
5248 to_insert,
5249 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5250 {
5251 self.splice_inlays(&to_remove, to_insert, cx);
5252 }
5253 self.display_map.update(cx, |display_map, _| {
5254 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5255 });
5256 return;
5257 }
5258 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5259 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5260 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5261 }
5262 InlayHintRefreshReason::RefreshRequested => {
5263 (InvalidationStrategy::RefreshRequested, None)
5264 }
5265 };
5266
5267 if let Some(InlaySplice {
5268 to_remove,
5269 to_insert,
5270 }) = self.inlay_hint_cache.spawn_hint_refresh(
5271 reason_description,
5272 self.visible_excerpts(required_languages.as_ref(), cx),
5273 invalidate_cache,
5274 ignore_debounce,
5275 cx,
5276 ) {
5277 self.splice_inlays(&to_remove, to_insert, cx);
5278 }
5279 }
5280
5281 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5282 self.display_map
5283 .read(cx)
5284 .current_inlays()
5285 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5286 .cloned()
5287 .collect()
5288 }
5289
5290 pub fn visible_excerpts(
5291 &self,
5292 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5293 cx: &mut Context<Editor>,
5294 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5295 let Some(project) = self.project() else {
5296 return HashMap::default();
5297 };
5298 let project = project.read(cx);
5299 let multi_buffer = self.buffer().read(cx);
5300 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5301 let multi_buffer_visible_start = self
5302 .scroll_manager
5303 .anchor()
5304 .anchor
5305 .to_point(&multi_buffer_snapshot);
5306 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5307 multi_buffer_visible_start
5308 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5309 Bias::Left,
5310 );
5311 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5312 multi_buffer_snapshot
5313 .range_to_buffer_ranges(multi_buffer_visible_range)
5314 .into_iter()
5315 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5316 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5317 let buffer_file = project::File::from_dyn(buffer.file())?;
5318 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5319 let worktree_entry = buffer_worktree
5320 .read(cx)
5321 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5322 if worktree_entry.is_ignored {
5323 return None;
5324 }
5325
5326 let language = buffer.language()?;
5327 if let Some(restrict_to_languages) = restrict_to_languages
5328 && !restrict_to_languages.contains(language)
5329 {
5330 return None;
5331 }
5332 Some((
5333 excerpt_id,
5334 (
5335 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5336 buffer.version().clone(),
5337 excerpt_visible_range,
5338 ),
5339 ))
5340 })
5341 .collect()
5342 }
5343
5344 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5345 TextLayoutDetails {
5346 text_system: window.text_system().clone(),
5347 editor_style: self.style.clone().unwrap(),
5348 rem_size: window.rem_size(),
5349 scroll_anchor: self.scroll_manager.anchor(),
5350 visible_rows: self.visible_line_count(),
5351 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5352 }
5353 }
5354
5355 pub fn splice_inlays(
5356 &self,
5357 to_remove: &[InlayId],
5358 to_insert: Vec<Inlay>,
5359 cx: &mut Context<Self>,
5360 ) {
5361 self.display_map.update(cx, |display_map, cx| {
5362 display_map.splice_inlays(to_remove, to_insert, cx)
5363 });
5364 cx.notify();
5365 }
5366
5367 fn trigger_on_type_formatting(
5368 &self,
5369 input: String,
5370 window: &mut Window,
5371 cx: &mut Context<Self>,
5372 ) -> Option<Task<Result<()>>> {
5373 if input.len() != 1 {
5374 return None;
5375 }
5376
5377 let project = self.project()?;
5378 let position = self.selections.newest_anchor().head();
5379 let (buffer, buffer_position) = self
5380 .buffer
5381 .read(cx)
5382 .text_anchor_for_position(position, cx)?;
5383
5384 let settings = language_settings::language_settings(
5385 buffer
5386 .read(cx)
5387 .language_at(buffer_position)
5388 .map(|l| l.name()),
5389 buffer.read(cx).file(),
5390 cx,
5391 );
5392 if !settings.use_on_type_format {
5393 return None;
5394 }
5395
5396 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5397 // hence we do LSP request & edit on host side only — add formats to host's history.
5398 let push_to_lsp_host_history = true;
5399 // If this is not the host, append its history with new edits.
5400 let push_to_client_history = project.read(cx).is_via_collab();
5401
5402 let on_type_formatting = project.update(cx, |project, cx| {
5403 project.on_type_format(
5404 buffer.clone(),
5405 buffer_position,
5406 input,
5407 push_to_lsp_host_history,
5408 cx,
5409 )
5410 });
5411 Some(cx.spawn_in(window, async move |editor, cx| {
5412 if let Some(transaction) = on_type_formatting.await? {
5413 if push_to_client_history {
5414 buffer
5415 .update(cx, |buffer, _| {
5416 buffer.push_transaction(transaction, Instant::now());
5417 buffer.finalize_last_transaction();
5418 })
5419 .ok();
5420 }
5421 editor.update(cx, |editor, cx| {
5422 editor.refresh_document_highlights(cx);
5423 })?;
5424 }
5425 Ok(())
5426 }))
5427 }
5428
5429 pub fn show_word_completions(
5430 &mut self,
5431 _: &ShowWordCompletions,
5432 window: &mut Window,
5433 cx: &mut Context<Self>,
5434 ) {
5435 self.open_or_update_completions_menu(
5436 Some(CompletionsMenuSource::Words {
5437 ignore_threshold: true,
5438 }),
5439 None,
5440 window,
5441 cx,
5442 );
5443 }
5444
5445 pub fn show_completions(
5446 &mut self,
5447 options: &ShowCompletions,
5448 window: &mut Window,
5449 cx: &mut Context<Self>,
5450 ) {
5451 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5452 }
5453
5454 fn open_or_update_completions_menu(
5455 &mut self,
5456 requested_source: Option<CompletionsMenuSource>,
5457 trigger: Option<&str>,
5458 window: &mut Window,
5459 cx: &mut Context<Self>,
5460 ) {
5461 if self.pending_rename.is_some() {
5462 return;
5463 }
5464
5465 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5466
5467 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5468 // inserted and selected. To handle that case, the start of the selection is used so that
5469 // the menu starts with all choices.
5470 let position = self
5471 .selections
5472 .newest_anchor()
5473 .start
5474 .bias_right(&multibuffer_snapshot);
5475 if position.diff_base_anchor.is_some() {
5476 return;
5477 }
5478 let buffer_position = multibuffer_snapshot.anchor_before(position);
5479 let Some(buffer) = buffer_position
5480 .buffer_id
5481 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5482 else {
5483 return;
5484 };
5485 let buffer_snapshot = buffer.read(cx).snapshot();
5486
5487 let query: Option<Arc<String>> =
5488 Self::completion_query(&multibuffer_snapshot, buffer_position)
5489 .map(|query| query.into());
5490
5491 drop(multibuffer_snapshot);
5492
5493 // Hide the current completions menu when query is empty. Without this, cached
5494 // completions from before the trigger char may be reused (#32774).
5495 if query.is_none() {
5496 let menu_is_open = matches!(
5497 self.context_menu.borrow().as_ref(),
5498 Some(CodeContextMenu::Completions(_))
5499 );
5500 if menu_is_open {
5501 self.hide_context_menu(window, cx);
5502 }
5503 }
5504
5505 let mut ignore_word_threshold = false;
5506 let provider = match requested_source {
5507 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5508 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5509 ignore_word_threshold = ignore_threshold;
5510 None
5511 }
5512 Some(CompletionsMenuSource::SnippetChoices) => {
5513 log::error!("bug: SnippetChoices requested_source is not handled");
5514 None
5515 }
5516 };
5517
5518 let sort_completions = provider
5519 .as_ref()
5520 .is_some_and(|provider| provider.sort_completions());
5521
5522 let filter_completions = provider
5523 .as_ref()
5524 .is_none_or(|provider| provider.filter_completions());
5525
5526 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5527 if filter_completions {
5528 menu.filter(query.clone(), provider.clone(), window, cx);
5529 }
5530 // When `is_incomplete` is false, no need to re-query completions when the current query
5531 // is a suffix of the initial query.
5532 if !menu.is_incomplete {
5533 // If the new query is a suffix of the old query (typing more characters) and
5534 // the previous result was complete, the existing completions can be filtered.
5535 //
5536 // Note that this is always true for snippet completions.
5537 let query_matches = match (&menu.initial_query, &query) {
5538 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5539 (None, _) => true,
5540 _ => false,
5541 };
5542 if query_matches {
5543 let position_matches = if menu.initial_position == position {
5544 true
5545 } else {
5546 let snapshot = self.buffer.read(cx).read(cx);
5547 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5548 };
5549 if position_matches {
5550 return;
5551 }
5552 }
5553 }
5554 };
5555
5556 let trigger_kind = match trigger {
5557 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5558 CompletionTriggerKind::TRIGGER_CHARACTER
5559 }
5560 _ => CompletionTriggerKind::INVOKED,
5561 };
5562 let completion_context = CompletionContext {
5563 trigger_character: trigger.and_then(|trigger| {
5564 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5565 Some(String::from(trigger))
5566 } else {
5567 None
5568 }
5569 }),
5570 trigger_kind,
5571 };
5572
5573 let Anchor {
5574 excerpt_id: buffer_excerpt_id,
5575 text_anchor: buffer_position,
5576 ..
5577 } = buffer_position;
5578
5579 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5580 buffer_snapshot.surrounding_word(buffer_position, None)
5581 {
5582 let word_to_exclude = buffer_snapshot
5583 .text_for_range(word_range.clone())
5584 .collect::<String>();
5585 (
5586 buffer_snapshot.anchor_before(word_range.start)
5587 ..buffer_snapshot.anchor_after(buffer_position),
5588 Some(word_to_exclude),
5589 )
5590 } else {
5591 (buffer_position..buffer_position, None)
5592 };
5593
5594 let language = buffer_snapshot
5595 .language_at(buffer_position)
5596 .map(|language| language.name());
5597
5598 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5599 .completions
5600 .clone();
5601
5602 let show_completion_documentation = buffer_snapshot
5603 .settings_at(buffer_position, cx)
5604 .show_completion_documentation;
5605
5606 // The document can be large, so stay in reasonable bounds when searching for words,
5607 // otherwise completion pop-up might be slow to appear.
5608 const WORD_LOOKUP_ROWS: u32 = 5_000;
5609 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5610 let min_word_search = buffer_snapshot.clip_point(
5611 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5612 Bias::Left,
5613 );
5614 let max_word_search = buffer_snapshot.clip_point(
5615 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5616 Bias::Right,
5617 );
5618 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5619 ..buffer_snapshot.point_to_offset(max_word_search);
5620
5621 let skip_digits = query
5622 .as_ref()
5623 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5624
5625 let omit_word_completions = !self.word_completions_enabled
5626 || (!ignore_word_threshold
5627 && match &query {
5628 Some(query) => query.chars().count() < completion_settings.words_min_length,
5629 None => completion_settings.words_min_length != 0,
5630 });
5631
5632 let (mut words, provider_responses) = match &provider {
5633 Some(provider) => {
5634 let provider_responses = provider.completions(
5635 buffer_excerpt_id,
5636 &buffer,
5637 buffer_position,
5638 completion_context,
5639 window,
5640 cx,
5641 );
5642
5643 let words = match (omit_word_completions, completion_settings.words) {
5644 (true, _) | (_, WordsCompletionMode::Disabled) => {
5645 Task::ready(BTreeMap::default())
5646 }
5647 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5648 .background_spawn(async move {
5649 buffer_snapshot.words_in_range(WordsQuery {
5650 fuzzy_contents: None,
5651 range: word_search_range,
5652 skip_digits,
5653 })
5654 }),
5655 };
5656
5657 (words, provider_responses)
5658 }
5659 None => {
5660 let words = if omit_word_completions {
5661 Task::ready(BTreeMap::default())
5662 } else {
5663 cx.background_spawn(async move {
5664 buffer_snapshot.words_in_range(WordsQuery {
5665 fuzzy_contents: None,
5666 range: word_search_range,
5667 skip_digits,
5668 })
5669 })
5670 };
5671 (words, Task::ready(Ok(Vec::new())))
5672 }
5673 };
5674
5675 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5676
5677 let id = post_inc(&mut self.next_completion_id);
5678 let task = cx.spawn_in(window, async move |editor, cx| {
5679 let Ok(()) = editor.update(cx, |this, _| {
5680 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5681 }) else {
5682 return;
5683 };
5684
5685 // TODO: Ideally completions from different sources would be selectively re-queried, so
5686 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5687 let mut completions = Vec::new();
5688 let mut is_incomplete = false;
5689 let mut display_options: Option<CompletionDisplayOptions> = None;
5690 if let Some(provider_responses) = provider_responses.await.log_err()
5691 && !provider_responses.is_empty()
5692 {
5693 for response in provider_responses {
5694 completions.extend(response.completions);
5695 is_incomplete = is_incomplete || response.is_incomplete;
5696 match display_options.as_mut() {
5697 None => {
5698 display_options = Some(response.display_options);
5699 }
5700 Some(options) => options.merge(&response.display_options),
5701 }
5702 }
5703 if completion_settings.words == WordsCompletionMode::Fallback {
5704 words = Task::ready(BTreeMap::default());
5705 }
5706 }
5707 let display_options = display_options.unwrap_or_default();
5708
5709 let mut words = words.await;
5710 if let Some(word_to_exclude) = &word_to_exclude {
5711 words.remove(word_to_exclude);
5712 }
5713 for lsp_completion in &completions {
5714 words.remove(&lsp_completion.new_text);
5715 }
5716 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5717 replace_range: word_replace_range.clone(),
5718 new_text: word.clone(),
5719 label: CodeLabel::plain(word, None),
5720 icon_path: None,
5721 documentation: None,
5722 source: CompletionSource::BufferWord {
5723 word_range,
5724 resolved: false,
5725 },
5726 insert_text_mode: Some(InsertTextMode::AS_IS),
5727 confirm: None,
5728 }));
5729
5730 let menu = if completions.is_empty() {
5731 None
5732 } else {
5733 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5734 let languages = editor
5735 .workspace
5736 .as_ref()
5737 .and_then(|(workspace, _)| workspace.upgrade())
5738 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5739 let menu = CompletionsMenu::new(
5740 id,
5741 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5742 sort_completions,
5743 show_completion_documentation,
5744 position,
5745 query.clone(),
5746 is_incomplete,
5747 buffer.clone(),
5748 completions.into(),
5749 display_options,
5750 snippet_sort_order,
5751 languages,
5752 language,
5753 cx,
5754 );
5755
5756 let query = if filter_completions { query } else { None };
5757 let matches_task = if let Some(query) = query {
5758 menu.do_async_filtering(query, cx)
5759 } else {
5760 Task::ready(menu.unfiltered_matches())
5761 };
5762 (menu, matches_task)
5763 }) else {
5764 return;
5765 };
5766
5767 let matches = matches_task.await;
5768
5769 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5770 // Newer menu already set, so exit.
5771 if let Some(CodeContextMenu::Completions(prev_menu)) =
5772 editor.context_menu.borrow().as_ref()
5773 && prev_menu.id > id
5774 {
5775 return;
5776 };
5777
5778 // Only valid to take prev_menu because it the new menu is immediately set
5779 // below, or the menu is hidden.
5780 if let Some(CodeContextMenu::Completions(prev_menu)) =
5781 editor.context_menu.borrow_mut().take()
5782 {
5783 let position_matches =
5784 if prev_menu.initial_position == menu.initial_position {
5785 true
5786 } else {
5787 let snapshot = editor.buffer.read(cx).read(cx);
5788 prev_menu.initial_position.to_offset(&snapshot)
5789 == menu.initial_position.to_offset(&snapshot)
5790 };
5791 if position_matches {
5792 // Preserve markdown cache before `set_filter_results` because it will
5793 // try to populate the documentation cache.
5794 menu.preserve_markdown_cache(prev_menu);
5795 }
5796 };
5797
5798 menu.set_filter_results(matches, provider, window, cx);
5799 }) else {
5800 return;
5801 };
5802
5803 menu.visible().then_some(menu)
5804 };
5805
5806 editor
5807 .update_in(cx, |editor, window, cx| {
5808 if editor.focus_handle.is_focused(window)
5809 && let Some(menu) = menu
5810 {
5811 *editor.context_menu.borrow_mut() =
5812 Some(CodeContextMenu::Completions(menu));
5813
5814 crate::hover_popover::hide_hover(editor, cx);
5815 if editor.show_edit_predictions_in_menu() {
5816 editor.update_visible_edit_prediction(window, cx);
5817 } else {
5818 editor.discard_edit_prediction(false, cx);
5819 }
5820
5821 cx.notify();
5822 return;
5823 }
5824
5825 if editor.completion_tasks.len() <= 1 {
5826 // If there are no more completion tasks and the last menu was empty, we should hide it.
5827 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5828 // If it was already hidden and we don't show edit predictions in the menu,
5829 // we should also show the edit prediction when available.
5830 if was_hidden && editor.show_edit_predictions_in_menu() {
5831 editor.update_visible_edit_prediction(window, cx);
5832 }
5833 }
5834 })
5835 .ok();
5836 });
5837
5838 self.completion_tasks.push((id, task));
5839 }
5840
5841 #[cfg(feature = "test-support")]
5842 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5843 let menu = self.context_menu.borrow();
5844 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5845 let completions = menu.completions.borrow();
5846 Some(completions.to_vec())
5847 } else {
5848 None
5849 }
5850 }
5851
5852 pub fn with_completions_menu_matching_id<R>(
5853 &self,
5854 id: CompletionId,
5855 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5856 ) -> R {
5857 let mut context_menu = self.context_menu.borrow_mut();
5858 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5859 return f(None);
5860 };
5861 if completions_menu.id != id {
5862 return f(None);
5863 }
5864 f(Some(completions_menu))
5865 }
5866
5867 pub fn confirm_completion(
5868 &mut self,
5869 action: &ConfirmCompletion,
5870 window: &mut Window,
5871 cx: &mut Context<Self>,
5872 ) -> Option<Task<Result<()>>> {
5873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5874 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5875 }
5876
5877 pub fn confirm_completion_insert(
5878 &mut self,
5879 _: &ConfirmCompletionInsert,
5880 window: &mut Window,
5881 cx: &mut Context<Self>,
5882 ) -> Option<Task<Result<()>>> {
5883 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5884 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5885 }
5886
5887 pub fn confirm_completion_replace(
5888 &mut self,
5889 _: &ConfirmCompletionReplace,
5890 window: &mut Window,
5891 cx: &mut Context<Self>,
5892 ) -> Option<Task<Result<()>>> {
5893 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5894 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5895 }
5896
5897 pub fn compose_completion(
5898 &mut self,
5899 action: &ComposeCompletion,
5900 window: &mut Window,
5901 cx: &mut Context<Self>,
5902 ) -> Option<Task<Result<()>>> {
5903 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5904 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5905 }
5906
5907 fn do_completion(
5908 &mut self,
5909 item_ix: Option<usize>,
5910 intent: CompletionIntent,
5911 window: &mut Window,
5912 cx: &mut Context<Editor>,
5913 ) -> Option<Task<Result<()>>> {
5914 use language::ToOffset as _;
5915
5916 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5917 else {
5918 return None;
5919 };
5920
5921 let candidate_id = {
5922 let entries = completions_menu.entries.borrow();
5923 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5924 if self.show_edit_predictions_in_menu() {
5925 self.discard_edit_prediction(true, cx);
5926 }
5927 mat.candidate_id
5928 };
5929
5930 let completion = completions_menu
5931 .completions
5932 .borrow()
5933 .get(candidate_id)?
5934 .clone();
5935 cx.stop_propagation();
5936
5937 let buffer_handle = completions_menu.buffer.clone();
5938
5939 let CompletionEdit {
5940 new_text,
5941 snippet,
5942 replace_range,
5943 } = process_completion_for_edit(
5944 &completion,
5945 intent,
5946 &buffer_handle,
5947 &completions_menu.initial_position.text_anchor,
5948 cx,
5949 );
5950
5951 let buffer = buffer_handle.read(cx);
5952 let snapshot = self.buffer.read(cx).snapshot(cx);
5953 let newest_anchor = self.selections.newest_anchor();
5954 let replace_range_multibuffer = {
5955 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5956 let multibuffer_anchor = snapshot
5957 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5958 .unwrap()
5959 ..snapshot
5960 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5961 .unwrap();
5962 multibuffer_anchor.start.to_offset(&snapshot)
5963 ..multibuffer_anchor.end.to_offset(&snapshot)
5964 };
5965 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5966 return None;
5967 }
5968
5969 let old_text = buffer
5970 .text_for_range(replace_range.clone())
5971 .collect::<String>();
5972 let lookbehind = newest_anchor
5973 .start
5974 .text_anchor
5975 .to_offset(buffer)
5976 .saturating_sub(replace_range.start);
5977 let lookahead = replace_range
5978 .end
5979 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5980 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5981 let suffix = &old_text[lookbehind.min(old_text.len())..];
5982
5983 let selections = self.selections.all::<usize>(cx);
5984 let mut ranges = Vec::new();
5985 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5986
5987 for selection in &selections {
5988 let range = if selection.id == newest_anchor.id {
5989 replace_range_multibuffer.clone()
5990 } else {
5991 let mut range = selection.range();
5992
5993 // if prefix is present, don't duplicate it
5994 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5995 range.start = range.start.saturating_sub(lookbehind);
5996
5997 // if suffix is also present, mimic the newest cursor and replace it
5998 if selection.id != newest_anchor.id
5999 && snapshot.contains_str_at(range.end, suffix)
6000 {
6001 range.end += lookahead;
6002 }
6003 }
6004 range
6005 };
6006
6007 ranges.push(range.clone());
6008
6009 if !self.linked_edit_ranges.is_empty() {
6010 let start_anchor = snapshot.anchor_before(range.start);
6011 let end_anchor = snapshot.anchor_after(range.end);
6012 if let Some(ranges) = self
6013 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6014 {
6015 for (buffer, edits) in ranges {
6016 linked_edits
6017 .entry(buffer.clone())
6018 .or_default()
6019 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6020 }
6021 }
6022 }
6023 }
6024
6025 let common_prefix_len = old_text
6026 .chars()
6027 .zip(new_text.chars())
6028 .take_while(|(a, b)| a == b)
6029 .map(|(a, _)| a.len_utf8())
6030 .sum::<usize>();
6031
6032 cx.emit(EditorEvent::InputHandled {
6033 utf16_range_to_replace: None,
6034 text: new_text[common_prefix_len..].into(),
6035 });
6036
6037 self.transact(window, cx, |editor, window, cx| {
6038 if let Some(mut snippet) = snippet {
6039 snippet.text = new_text.to_string();
6040 editor
6041 .insert_snippet(&ranges, snippet, window, cx)
6042 .log_err();
6043 } else {
6044 editor.buffer.update(cx, |multi_buffer, cx| {
6045 let auto_indent = match completion.insert_text_mode {
6046 Some(InsertTextMode::AS_IS) => None,
6047 _ => editor.autoindent_mode.clone(),
6048 };
6049 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6050 multi_buffer.edit(edits, auto_indent, cx);
6051 });
6052 }
6053 for (buffer, edits) in linked_edits {
6054 buffer.update(cx, |buffer, cx| {
6055 let snapshot = buffer.snapshot();
6056 let edits = edits
6057 .into_iter()
6058 .map(|(range, text)| {
6059 use text::ToPoint as TP;
6060 let end_point = TP::to_point(&range.end, &snapshot);
6061 let start_point = TP::to_point(&range.start, &snapshot);
6062 (start_point..end_point, text)
6063 })
6064 .sorted_by_key(|(range, _)| range.start);
6065 buffer.edit(edits, None, cx);
6066 })
6067 }
6068
6069 editor.refresh_edit_prediction(true, false, window, cx);
6070 });
6071 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6072
6073 let show_new_completions_on_confirm = completion
6074 .confirm
6075 .as_ref()
6076 .is_some_and(|confirm| confirm(intent, window, cx));
6077 if show_new_completions_on_confirm {
6078 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6079 }
6080
6081 let provider = self.completion_provider.as_ref()?;
6082 drop(completion);
6083 let apply_edits = provider.apply_additional_edits_for_completion(
6084 buffer_handle,
6085 completions_menu.completions.clone(),
6086 candidate_id,
6087 true,
6088 cx,
6089 );
6090
6091 let editor_settings = EditorSettings::get_global(cx);
6092 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6093 // After the code completion is finished, users often want to know what signatures are needed.
6094 // so we should automatically call signature_help
6095 self.show_signature_help(&ShowSignatureHelp, window, cx);
6096 }
6097
6098 Some(cx.foreground_executor().spawn(async move {
6099 apply_edits.await?;
6100 Ok(())
6101 }))
6102 }
6103
6104 pub fn toggle_code_actions(
6105 &mut self,
6106 action: &ToggleCodeActions,
6107 window: &mut Window,
6108 cx: &mut Context<Self>,
6109 ) {
6110 let quick_launch = action.quick_launch;
6111 let mut context_menu = self.context_menu.borrow_mut();
6112 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6113 if code_actions.deployed_from == action.deployed_from {
6114 // Toggle if we're selecting the same one
6115 *context_menu = None;
6116 cx.notify();
6117 return;
6118 } else {
6119 // Otherwise, clear it and start a new one
6120 *context_menu = None;
6121 cx.notify();
6122 }
6123 }
6124 drop(context_menu);
6125 let snapshot = self.snapshot(window, cx);
6126 let deployed_from = action.deployed_from.clone();
6127 let action = action.clone();
6128 self.completion_tasks.clear();
6129 self.discard_edit_prediction(false, cx);
6130
6131 let multibuffer_point = match &action.deployed_from {
6132 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6133 DisplayPoint::new(*row, 0).to_point(&snapshot)
6134 }
6135 _ => self.selections.newest::<Point>(cx).head(),
6136 };
6137 let Some((buffer, buffer_row)) = snapshot
6138 .buffer_snapshot
6139 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6140 .and_then(|(buffer_snapshot, range)| {
6141 self.buffer()
6142 .read(cx)
6143 .buffer(buffer_snapshot.remote_id())
6144 .map(|buffer| (buffer, range.start.row))
6145 })
6146 else {
6147 return;
6148 };
6149 let buffer_id = buffer.read(cx).remote_id();
6150 let tasks = self
6151 .tasks
6152 .get(&(buffer_id, buffer_row))
6153 .map(|t| Arc::new(t.to_owned()));
6154
6155 if !self.focus_handle.is_focused(window) {
6156 return;
6157 }
6158 let project = self.project.clone();
6159
6160 let code_actions_task = match deployed_from {
6161 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6162 _ => self.code_actions(buffer_row, window, cx),
6163 };
6164
6165 let runnable_task = match deployed_from {
6166 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6167 _ => {
6168 let mut task_context_task = Task::ready(None);
6169 if let Some(tasks) = &tasks
6170 && let Some(project) = project
6171 {
6172 task_context_task =
6173 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6174 }
6175
6176 cx.spawn_in(window, {
6177 let buffer = buffer.clone();
6178 async move |editor, cx| {
6179 let task_context = task_context_task.await;
6180
6181 let resolved_tasks =
6182 tasks
6183 .zip(task_context.clone())
6184 .map(|(tasks, task_context)| ResolvedTasks {
6185 templates: tasks.resolve(&task_context).collect(),
6186 position: snapshot.buffer_snapshot.anchor_before(Point::new(
6187 multibuffer_point.row,
6188 tasks.column,
6189 )),
6190 });
6191 let debug_scenarios = editor
6192 .update(cx, |editor, cx| {
6193 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6194 })?
6195 .await;
6196 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6197 }
6198 })
6199 }
6200 };
6201
6202 cx.spawn_in(window, async move |editor, cx| {
6203 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6204 let code_actions = code_actions_task.await;
6205 let spawn_straight_away = quick_launch
6206 && resolved_tasks
6207 .as_ref()
6208 .is_some_and(|tasks| tasks.templates.len() == 1)
6209 && code_actions
6210 .as_ref()
6211 .is_none_or(|actions| actions.is_empty())
6212 && debug_scenarios.is_empty();
6213
6214 editor.update_in(cx, |editor, window, cx| {
6215 crate::hover_popover::hide_hover(editor, cx);
6216 let actions = CodeActionContents::new(
6217 resolved_tasks,
6218 code_actions,
6219 debug_scenarios,
6220 task_context.unwrap_or_default(),
6221 );
6222
6223 // Don't show the menu if there are no actions available
6224 if actions.is_empty() {
6225 cx.notify();
6226 return Task::ready(Ok(()));
6227 }
6228
6229 *editor.context_menu.borrow_mut() =
6230 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6231 buffer,
6232 actions,
6233 selected_item: Default::default(),
6234 scroll_handle: UniformListScrollHandle::default(),
6235 deployed_from,
6236 }));
6237 cx.notify();
6238 if spawn_straight_away
6239 && let Some(task) = editor.confirm_code_action(
6240 &ConfirmCodeAction { item_ix: Some(0) },
6241 window,
6242 cx,
6243 )
6244 {
6245 return task;
6246 }
6247
6248 Task::ready(Ok(()))
6249 })
6250 })
6251 .detach_and_log_err(cx);
6252 }
6253
6254 fn debug_scenarios(
6255 &mut self,
6256 resolved_tasks: &Option<ResolvedTasks>,
6257 buffer: &Entity<Buffer>,
6258 cx: &mut App,
6259 ) -> Task<Vec<task::DebugScenario>> {
6260 maybe!({
6261 let project = self.project()?;
6262 let dap_store = project.read(cx).dap_store();
6263 let mut scenarios = vec![];
6264 let resolved_tasks = resolved_tasks.as_ref()?;
6265 let buffer = buffer.read(cx);
6266 let language = buffer.language()?;
6267 let file = buffer.file();
6268 let debug_adapter = language_settings(language.name().into(), file, cx)
6269 .debuggers
6270 .first()
6271 .map(SharedString::from)
6272 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6273
6274 dap_store.update(cx, |dap_store, cx| {
6275 for (_, task) in &resolved_tasks.templates {
6276 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6277 task.original_task().clone(),
6278 debug_adapter.clone().into(),
6279 task.display_label().to_owned().into(),
6280 cx,
6281 );
6282 scenarios.push(maybe_scenario);
6283 }
6284 });
6285 Some(cx.background_spawn(async move {
6286 futures::future::join_all(scenarios)
6287 .await
6288 .into_iter()
6289 .flatten()
6290 .collect::<Vec<_>>()
6291 }))
6292 })
6293 .unwrap_or_else(|| Task::ready(vec![]))
6294 }
6295
6296 fn code_actions(
6297 &mut self,
6298 buffer_row: u32,
6299 window: &mut Window,
6300 cx: &mut Context<Self>,
6301 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6302 let mut task = self.code_actions_task.take();
6303 cx.spawn_in(window, async move |editor, cx| {
6304 while let Some(prev_task) = task {
6305 prev_task.await.log_err();
6306 task = editor
6307 .update(cx, |this, _| this.code_actions_task.take())
6308 .ok()?;
6309 }
6310
6311 editor
6312 .update(cx, |editor, cx| {
6313 editor
6314 .available_code_actions
6315 .clone()
6316 .and_then(|(location, code_actions)| {
6317 let snapshot = location.buffer.read(cx).snapshot();
6318 let point_range = location.range.to_point(&snapshot);
6319 let point_range = point_range.start.row..=point_range.end.row;
6320 if point_range.contains(&buffer_row) {
6321 Some(code_actions)
6322 } else {
6323 None
6324 }
6325 })
6326 })
6327 .ok()
6328 .flatten()
6329 })
6330 }
6331
6332 pub fn confirm_code_action(
6333 &mut self,
6334 action: &ConfirmCodeAction,
6335 window: &mut Window,
6336 cx: &mut Context<Self>,
6337 ) -> Option<Task<Result<()>>> {
6338 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6339
6340 let actions_menu =
6341 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6342 menu
6343 } else {
6344 return None;
6345 };
6346
6347 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6348 let action = actions_menu.actions.get(action_ix)?;
6349 let title = action.label();
6350 let buffer = actions_menu.buffer;
6351 let workspace = self.workspace()?;
6352
6353 match action {
6354 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6355 workspace.update(cx, |workspace, cx| {
6356 workspace.schedule_resolved_task(
6357 task_source_kind,
6358 resolved_task,
6359 false,
6360 window,
6361 cx,
6362 );
6363
6364 Some(Task::ready(Ok(())))
6365 })
6366 }
6367 CodeActionsItem::CodeAction {
6368 excerpt_id,
6369 action,
6370 provider,
6371 } => {
6372 let apply_code_action =
6373 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6374 let workspace = workspace.downgrade();
6375 Some(cx.spawn_in(window, async move |editor, cx| {
6376 let project_transaction = apply_code_action.await?;
6377 Self::open_project_transaction(
6378 &editor,
6379 workspace,
6380 project_transaction,
6381 title,
6382 cx,
6383 )
6384 .await
6385 }))
6386 }
6387 CodeActionsItem::DebugScenario(scenario) => {
6388 let context = actions_menu.actions.context;
6389
6390 workspace.update(cx, |workspace, cx| {
6391 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6392 workspace.start_debug_session(
6393 scenario,
6394 context,
6395 Some(buffer),
6396 None,
6397 window,
6398 cx,
6399 );
6400 });
6401 Some(Task::ready(Ok(())))
6402 }
6403 }
6404 }
6405
6406 pub async fn open_project_transaction(
6407 editor: &WeakEntity<Editor>,
6408 workspace: WeakEntity<Workspace>,
6409 transaction: ProjectTransaction,
6410 title: String,
6411 cx: &mut AsyncWindowContext,
6412 ) -> Result<()> {
6413 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6414 cx.update(|_, cx| {
6415 entries.sort_unstable_by_key(|(buffer, _)| {
6416 buffer.read(cx).file().map(|f| f.path().clone())
6417 });
6418 })?;
6419
6420 // If the project transaction's edits are all contained within this editor, then
6421 // avoid opening a new editor to display them.
6422
6423 if let Some((buffer, transaction)) = entries.first() {
6424 if entries.len() == 1 {
6425 let excerpt = editor.update(cx, |editor, cx| {
6426 editor
6427 .buffer()
6428 .read(cx)
6429 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6430 })?;
6431 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6432 && excerpted_buffer == *buffer
6433 {
6434 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6435 let excerpt_range = excerpt_range.to_offset(buffer);
6436 buffer
6437 .edited_ranges_for_transaction::<usize>(transaction)
6438 .all(|range| {
6439 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6440 })
6441 })?;
6442
6443 if all_edits_within_excerpt {
6444 return Ok(());
6445 }
6446 }
6447 }
6448 } else {
6449 return Ok(());
6450 }
6451
6452 let mut ranges_to_highlight = Vec::new();
6453 let excerpt_buffer = cx.new(|cx| {
6454 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6455 for (buffer_handle, transaction) in &entries {
6456 let edited_ranges = buffer_handle
6457 .read(cx)
6458 .edited_ranges_for_transaction::<Point>(transaction)
6459 .collect::<Vec<_>>();
6460 let (ranges, _) = multibuffer.set_excerpts_for_path(
6461 PathKey::for_buffer(buffer_handle, cx),
6462 buffer_handle.clone(),
6463 edited_ranges,
6464 multibuffer_context_lines(cx),
6465 cx,
6466 );
6467
6468 ranges_to_highlight.extend(ranges);
6469 }
6470 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6471 multibuffer
6472 })?;
6473
6474 workspace.update_in(cx, |workspace, window, cx| {
6475 let project = workspace.project().clone();
6476 let editor =
6477 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6478 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6479 editor.update(cx, |editor, cx| {
6480 editor.highlight_background::<Self>(
6481 &ranges_to_highlight,
6482 |theme| theme.colors().editor_highlighted_line_background,
6483 cx,
6484 );
6485 });
6486 })?;
6487
6488 Ok(())
6489 }
6490
6491 pub fn clear_code_action_providers(&mut self) {
6492 self.code_action_providers.clear();
6493 self.available_code_actions.take();
6494 }
6495
6496 pub fn add_code_action_provider(
6497 &mut self,
6498 provider: Rc<dyn CodeActionProvider>,
6499 window: &mut Window,
6500 cx: &mut Context<Self>,
6501 ) {
6502 if self
6503 .code_action_providers
6504 .iter()
6505 .any(|existing_provider| existing_provider.id() == provider.id())
6506 {
6507 return;
6508 }
6509
6510 self.code_action_providers.push(provider);
6511 self.refresh_code_actions(window, cx);
6512 }
6513
6514 pub fn remove_code_action_provider(
6515 &mut self,
6516 id: Arc<str>,
6517 window: &mut Window,
6518 cx: &mut Context<Self>,
6519 ) {
6520 self.code_action_providers
6521 .retain(|provider| provider.id() != id);
6522 self.refresh_code_actions(window, cx);
6523 }
6524
6525 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6526 !self.code_action_providers.is_empty()
6527 && EditorSettings::get_global(cx).toolbar.code_actions
6528 }
6529
6530 pub fn has_available_code_actions(&self) -> bool {
6531 self.available_code_actions
6532 .as_ref()
6533 .is_some_and(|(_, actions)| !actions.is_empty())
6534 }
6535
6536 fn render_inline_code_actions(
6537 &self,
6538 icon_size: ui::IconSize,
6539 display_row: DisplayRow,
6540 is_active: bool,
6541 cx: &mut Context<Self>,
6542 ) -> AnyElement {
6543 let show_tooltip = !self.context_menu_visible();
6544 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6545 .icon_size(icon_size)
6546 .shape(ui::IconButtonShape::Square)
6547 .icon_color(ui::Color::Hidden)
6548 .toggle_state(is_active)
6549 .when(show_tooltip, |this| {
6550 this.tooltip({
6551 let focus_handle = self.focus_handle.clone();
6552 move |window, cx| {
6553 Tooltip::for_action_in(
6554 "Toggle Code Actions",
6555 &ToggleCodeActions {
6556 deployed_from: None,
6557 quick_launch: false,
6558 },
6559 &focus_handle,
6560 window,
6561 cx,
6562 )
6563 }
6564 })
6565 })
6566 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6567 window.focus(&editor.focus_handle(cx));
6568 editor.toggle_code_actions(
6569 &crate::actions::ToggleCodeActions {
6570 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6571 display_row,
6572 )),
6573 quick_launch: false,
6574 },
6575 window,
6576 cx,
6577 );
6578 }))
6579 .into_any_element()
6580 }
6581
6582 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6583 &self.context_menu
6584 }
6585
6586 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6587 let newest_selection = self.selections.newest_anchor().clone();
6588 let newest_selection_adjusted = self.selections.newest_adjusted(cx);
6589 let buffer = self.buffer.read(cx);
6590 if newest_selection.head().diff_base_anchor.is_some() {
6591 return None;
6592 }
6593 let (start_buffer, start) =
6594 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6595 let (end_buffer, end) =
6596 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6597 if start_buffer != end_buffer {
6598 return None;
6599 }
6600
6601 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6602 cx.background_executor()
6603 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6604 .await;
6605
6606 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6607 let providers = this.code_action_providers.clone();
6608 let tasks = this
6609 .code_action_providers
6610 .iter()
6611 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6612 .collect::<Vec<_>>();
6613 (providers, tasks)
6614 })?;
6615
6616 let mut actions = Vec::new();
6617 for (provider, provider_actions) in
6618 providers.into_iter().zip(future::join_all(tasks).await)
6619 {
6620 if let Some(provider_actions) = provider_actions.log_err() {
6621 actions.extend(provider_actions.into_iter().map(|action| {
6622 AvailableCodeAction {
6623 excerpt_id: newest_selection.start.excerpt_id,
6624 action,
6625 provider: provider.clone(),
6626 }
6627 }));
6628 }
6629 }
6630
6631 this.update(cx, |this, cx| {
6632 this.available_code_actions = if actions.is_empty() {
6633 None
6634 } else {
6635 Some((
6636 Location {
6637 buffer: start_buffer,
6638 range: start..end,
6639 },
6640 actions.into(),
6641 ))
6642 };
6643 cx.notify();
6644 })
6645 }));
6646 None
6647 }
6648
6649 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6650 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6651 self.show_git_blame_inline = false;
6652
6653 self.show_git_blame_inline_delay_task =
6654 Some(cx.spawn_in(window, async move |this, cx| {
6655 cx.background_executor().timer(delay).await;
6656
6657 this.update(cx, |this, cx| {
6658 this.show_git_blame_inline = true;
6659 cx.notify();
6660 })
6661 .log_err();
6662 }));
6663 }
6664 }
6665
6666 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6667 let snapshot = self.snapshot(window, cx);
6668 let cursor = self.selections.newest::<Point>(cx).head();
6669 let Some((buffer, point, _)) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)
6670 else {
6671 return;
6672 };
6673
6674 let Some(blame) = self.blame.as_ref() else {
6675 return;
6676 };
6677
6678 let row_info = RowInfo {
6679 buffer_id: Some(buffer.remote_id()),
6680 buffer_row: Some(point.row),
6681 ..Default::default()
6682 };
6683 let Some((buffer, blame_entry)) = blame
6684 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6685 .flatten()
6686 else {
6687 return;
6688 };
6689
6690 let anchor = self.selections.newest_anchor().head();
6691 let position = self.to_pixel_point(anchor, &snapshot, window);
6692 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6693 self.show_blame_popover(
6694 buffer,
6695 &blame_entry,
6696 position + last_bounds.origin,
6697 true,
6698 cx,
6699 );
6700 };
6701 }
6702
6703 fn show_blame_popover(
6704 &mut self,
6705 buffer: BufferId,
6706 blame_entry: &BlameEntry,
6707 position: gpui::Point<Pixels>,
6708 ignore_timeout: bool,
6709 cx: &mut Context<Self>,
6710 ) {
6711 if let Some(state) = &mut self.inline_blame_popover {
6712 state.hide_task.take();
6713 } else {
6714 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
6715 let blame_entry = blame_entry.clone();
6716 let show_task = cx.spawn(async move |editor, cx| {
6717 if !ignore_timeout {
6718 cx.background_executor()
6719 .timer(std::time::Duration::from_millis(blame_popover_delay))
6720 .await;
6721 }
6722 editor
6723 .update(cx, |editor, cx| {
6724 editor.inline_blame_popover_show_task.take();
6725 let Some(blame) = editor.blame.as_ref() else {
6726 return;
6727 };
6728 let blame = blame.read(cx);
6729 let details = blame.details_for_entry(buffer, &blame_entry);
6730 let markdown = cx.new(|cx| {
6731 Markdown::new(
6732 details
6733 .as_ref()
6734 .map(|message| message.message.clone())
6735 .unwrap_or_default(),
6736 None,
6737 None,
6738 cx,
6739 )
6740 });
6741 editor.inline_blame_popover = Some(InlineBlamePopover {
6742 position,
6743 hide_task: None,
6744 popover_bounds: None,
6745 popover_state: InlineBlamePopoverState {
6746 scroll_handle: ScrollHandle::new(),
6747 commit_message: details,
6748 markdown,
6749 },
6750 keyboard_grace: ignore_timeout,
6751 });
6752 cx.notify();
6753 })
6754 .ok();
6755 });
6756 self.inline_blame_popover_show_task = Some(show_task);
6757 }
6758 }
6759
6760 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6761 self.inline_blame_popover_show_task.take();
6762 if let Some(state) = &mut self.inline_blame_popover {
6763 let hide_task = cx.spawn(async move |editor, cx| {
6764 cx.background_executor()
6765 .timer(std::time::Duration::from_millis(100))
6766 .await;
6767 editor
6768 .update(cx, |editor, cx| {
6769 editor.inline_blame_popover.take();
6770 cx.notify();
6771 })
6772 .ok();
6773 });
6774 state.hide_task = Some(hide_task);
6775 }
6776 }
6777
6778 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6779 if self.pending_rename.is_some() {
6780 return None;
6781 }
6782
6783 let provider = self.semantics_provider.clone()?;
6784 let buffer = self.buffer.read(cx);
6785 let newest_selection = self.selections.newest_anchor().clone();
6786 let cursor_position = newest_selection.head();
6787 let (cursor_buffer, cursor_buffer_position) =
6788 buffer.text_anchor_for_position(cursor_position, cx)?;
6789 let (tail_buffer, tail_buffer_position) =
6790 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6791 if cursor_buffer != tail_buffer {
6792 return None;
6793 }
6794
6795 let snapshot = cursor_buffer.read(cx).snapshot();
6796 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6797 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6798 if start_word_range != end_word_range {
6799 self.document_highlights_task.take();
6800 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6801 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6802 return None;
6803 }
6804
6805 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6806 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6807 cx.background_executor()
6808 .timer(Duration::from_millis(debounce))
6809 .await;
6810
6811 let highlights = if let Some(highlights) = cx
6812 .update(|cx| {
6813 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6814 })
6815 .ok()
6816 .flatten()
6817 {
6818 highlights.await.log_err()
6819 } else {
6820 None
6821 };
6822
6823 if let Some(highlights) = highlights {
6824 this.update(cx, |this, cx| {
6825 if this.pending_rename.is_some() {
6826 return;
6827 }
6828
6829 let buffer = this.buffer.read(cx);
6830 if buffer
6831 .text_anchor_for_position(cursor_position, cx)
6832 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6833 {
6834 return;
6835 }
6836
6837 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6838 let mut write_ranges = Vec::new();
6839 let mut read_ranges = Vec::new();
6840 for highlight in highlights {
6841 let buffer_id = cursor_buffer.read(cx).remote_id();
6842 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6843 {
6844 let start = highlight
6845 .range
6846 .start
6847 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6848 let end = highlight
6849 .range
6850 .end
6851 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6852 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6853 continue;
6854 }
6855
6856 let range = Anchor {
6857 buffer_id: Some(buffer_id),
6858 excerpt_id,
6859 text_anchor: start,
6860 diff_base_anchor: None,
6861 }..Anchor {
6862 buffer_id: Some(buffer_id),
6863 excerpt_id,
6864 text_anchor: end,
6865 diff_base_anchor: None,
6866 };
6867 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6868 write_ranges.push(range);
6869 } else {
6870 read_ranges.push(range);
6871 }
6872 }
6873 }
6874
6875 this.highlight_background::<DocumentHighlightRead>(
6876 &read_ranges,
6877 |theme| theme.colors().editor_document_highlight_read_background,
6878 cx,
6879 );
6880 this.highlight_background::<DocumentHighlightWrite>(
6881 &write_ranges,
6882 |theme| theme.colors().editor_document_highlight_write_background,
6883 cx,
6884 );
6885 cx.notify();
6886 })
6887 .log_err();
6888 }
6889 }));
6890 None
6891 }
6892
6893 fn prepare_highlight_query_from_selection(
6894 &mut self,
6895 cx: &mut Context<Editor>,
6896 ) -> Option<(String, Range<Anchor>)> {
6897 if matches!(self.mode, EditorMode::SingleLine) {
6898 return None;
6899 }
6900 if !EditorSettings::get_global(cx).selection_highlight {
6901 return None;
6902 }
6903 if self.selections.count() != 1 || self.selections.line_mode {
6904 return None;
6905 }
6906 let selection = self.selections.newest::<Point>(cx);
6907 if selection.is_empty() || selection.start.row != selection.end.row {
6908 return None;
6909 }
6910 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6911 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6912 let query = multi_buffer_snapshot
6913 .text_for_range(selection_anchor_range.clone())
6914 .collect::<String>();
6915 if query.trim().is_empty() {
6916 return None;
6917 }
6918 Some((query, selection_anchor_range))
6919 }
6920
6921 fn update_selection_occurrence_highlights(
6922 &mut self,
6923 query_text: String,
6924 query_range: Range<Anchor>,
6925 multi_buffer_range_to_query: Range<Point>,
6926 use_debounce: bool,
6927 window: &mut Window,
6928 cx: &mut Context<Editor>,
6929 ) -> Task<()> {
6930 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6931 cx.spawn_in(window, async move |editor, cx| {
6932 if use_debounce {
6933 cx.background_executor()
6934 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6935 .await;
6936 }
6937 let match_task = cx.background_spawn(async move {
6938 let buffer_ranges = multi_buffer_snapshot
6939 .range_to_buffer_ranges(multi_buffer_range_to_query)
6940 .into_iter()
6941 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6942 let mut match_ranges = Vec::new();
6943 let Ok(regex) = project::search::SearchQuery::text(
6944 query_text.clone(),
6945 false,
6946 false,
6947 false,
6948 Default::default(),
6949 Default::default(),
6950 false,
6951 None,
6952 ) else {
6953 return Vec::default();
6954 };
6955 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6956 match_ranges.extend(
6957 regex
6958 .search(buffer_snapshot, Some(search_range.clone()))
6959 .await
6960 .into_iter()
6961 .filter_map(|match_range| {
6962 let match_start = buffer_snapshot
6963 .anchor_after(search_range.start + match_range.start);
6964 let match_end = buffer_snapshot
6965 .anchor_before(search_range.start + match_range.end);
6966 let match_anchor_range = Anchor::range_in_buffer(
6967 excerpt_id,
6968 buffer_snapshot.remote_id(),
6969 match_start..match_end,
6970 );
6971 (match_anchor_range != query_range).then_some(match_anchor_range)
6972 }),
6973 );
6974 }
6975 match_ranges
6976 });
6977 let match_ranges = match_task.await;
6978 editor
6979 .update_in(cx, |editor, _, cx| {
6980 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6981 if !match_ranges.is_empty() {
6982 editor.highlight_background::<SelectedTextHighlight>(
6983 &match_ranges,
6984 |theme| theme.colors().editor_document_highlight_bracket_background,
6985 cx,
6986 )
6987 }
6988 })
6989 .log_err();
6990 })
6991 }
6992
6993 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6994 struct NewlineFold;
6995 let type_id = std::any::TypeId::of::<NewlineFold>();
6996 if !self.mode.is_single_line() {
6997 return;
6998 }
6999 let snapshot = self.snapshot(window, cx);
7000 if snapshot.buffer_snapshot.max_point().row == 0 {
7001 return;
7002 }
7003 let task = cx.background_spawn(async move {
7004 let new_newlines = snapshot
7005 .buffer_chars_at(0)
7006 .filter_map(|(c, i)| {
7007 if c == '\n' {
7008 Some(
7009 snapshot.buffer_snapshot.anchor_after(i)
7010 ..snapshot.buffer_snapshot.anchor_before(i + 1),
7011 )
7012 } else {
7013 None
7014 }
7015 })
7016 .collect::<Vec<_>>();
7017 let existing_newlines = snapshot
7018 .folds_in_range(0..snapshot.buffer_snapshot.len())
7019 .filter_map(|fold| {
7020 if fold.placeholder.type_tag == Some(type_id) {
7021 Some(fold.range.start..fold.range.end)
7022 } else {
7023 None
7024 }
7025 })
7026 .collect::<Vec<_>>();
7027
7028 (new_newlines, existing_newlines)
7029 });
7030 self.folding_newlines = cx.spawn(async move |this, cx| {
7031 let (new_newlines, existing_newlines) = task.await;
7032 if new_newlines == existing_newlines {
7033 return;
7034 }
7035 let placeholder = FoldPlaceholder {
7036 render: Arc::new(move |_, _, cx| {
7037 div()
7038 .bg(cx.theme().status().hint_background)
7039 .border_b_1()
7040 .size_full()
7041 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7042 .border_color(cx.theme().status().hint)
7043 .child("\\n")
7044 .into_any()
7045 }),
7046 constrain_width: false,
7047 merge_adjacent: false,
7048 type_tag: Some(type_id),
7049 };
7050 let creases = new_newlines
7051 .into_iter()
7052 .map(|range| Crease::simple(range, placeholder.clone()))
7053 .collect();
7054 this.update(cx, |this, cx| {
7055 this.display_map.update(cx, |display_map, cx| {
7056 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7057 display_map.fold(creases, cx);
7058 });
7059 })
7060 .ok();
7061 });
7062 }
7063
7064 fn refresh_selected_text_highlights(
7065 &mut self,
7066 on_buffer_edit: bool,
7067 window: &mut Window,
7068 cx: &mut Context<Editor>,
7069 ) {
7070 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
7071 else {
7072 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7073 self.quick_selection_highlight_task.take();
7074 self.debounced_selection_highlight_task.take();
7075 return;
7076 };
7077 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7078 if on_buffer_edit
7079 || self
7080 .quick_selection_highlight_task
7081 .as_ref()
7082 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7083 {
7084 let multi_buffer_visible_start = self
7085 .scroll_manager
7086 .anchor()
7087 .anchor
7088 .to_point(&multi_buffer_snapshot);
7089 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7090 multi_buffer_visible_start
7091 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7092 Bias::Left,
7093 );
7094 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7095 self.quick_selection_highlight_task = Some((
7096 query_range.clone(),
7097 self.update_selection_occurrence_highlights(
7098 query_text.clone(),
7099 query_range.clone(),
7100 multi_buffer_visible_range,
7101 false,
7102 window,
7103 cx,
7104 ),
7105 ));
7106 }
7107 if on_buffer_edit
7108 || self
7109 .debounced_selection_highlight_task
7110 .as_ref()
7111 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7112 {
7113 let multi_buffer_start = multi_buffer_snapshot
7114 .anchor_before(0)
7115 .to_point(&multi_buffer_snapshot);
7116 let multi_buffer_end = multi_buffer_snapshot
7117 .anchor_after(multi_buffer_snapshot.len())
7118 .to_point(&multi_buffer_snapshot);
7119 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7120 self.debounced_selection_highlight_task = Some((
7121 query_range.clone(),
7122 self.update_selection_occurrence_highlights(
7123 query_text,
7124 query_range,
7125 multi_buffer_full_range,
7126 true,
7127 window,
7128 cx,
7129 ),
7130 ));
7131 }
7132 }
7133
7134 pub fn refresh_edit_prediction(
7135 &mut self,
7136 debounce: bool,
7137 user_requested: bool,
7138 window: &mut Window,
7139 cx: &mut Context<Self>,
7140 ) -> Option<()> {
7141 if DisableAiSettings::get_global(cx).disable_ai {
7142 return None;
7143 }
7144
7145 let provider = self.edit_prediction_provider()?;
7146 let cursor = self.selections.newest_anchor().head();
7147 let (buffer, cursor_buffer_position) =
7148 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7149
7150 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7151 self.discard_edit_prediction(false, cx);
7152 return None;
7153 }
7154
7155 if !user_requested
7156 && (!self.should_show_edit_predictions()
7157 || !self.is_focused(window)
7158 || buffer.read(cx).is_empty())
7159 {
7160 self.discard_edit_prediction(false, cx);
7161 return None;
7162 }
7163
7164 self.update_visible_edit_prediction(window, cx);
7165 provider.refresh(
7166 self.project.clone(),
7167 buffer,
7168 cursor_buffer_position,
7169 debounce,
7170 cx,
7171 );
7172 Some(())
7173 }
7174
7175 fn show_edit_predictions_in_menu(&self) -> bool {
7176 match self.edit_prediction_settings {
7177 EditPredictionSettings::Disabled => false,
7178 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7179 }
7180 }
7181
7182 pub fn edit_predictions_enabled(&self) -> bool {
7183 match self.edit_prediction_settings {
7184 EditPredictionSettings::Disabled => false,
7185 EditPredictionSettings::Enabled { .. } => true,
7186 }
7187 }
7188
7189 fn edit_prediction_requires_modifier(&self) -> bool {
7190 match self.edit_prediction_settings {
7191 EditPredictionSettings::Disabled => false,
7192 EditPredictionSettings::Enabled {
7193 preview_requires_modifier,
7194 ..
7195 } => preview_requires_modifier,
7196 }
7197 }
7198
7199 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7200 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7201 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7202 self.discard_edit_prediction(false, cx);
7203 } else {
7204 let selection = self.selections.newest_anchor();
7205 let cursor = selection.head();
7206
7207 if let Some((buffer, cursor_buffer_position)) =
7208 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7209 {
7210 self.edit_prediction_settings =
7211 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7212 }
7213 }
7214 }
7215
7216 fn edit_prediction_settings_at_position(
7217 &self,
7218 buffer: &Entity<Buffer>,
7219 buffer_position: language::Anchor,
7220 cx: &App,
7221 ) -> EditPredictionSettings {
7222 if !self.mode.is_full()
7223 || !self.show_edit_predictions_override.unwrap_or(true)
7224 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7225 {
7226 return EditPredictionSettings::Disabled;
7227 }
7228
7229 let buffer = buffer.read(cx);
7230
7231 let file = buffer.file();
7232
7233 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7234 return EditPredictionSettings::Disabled;
7235 };
7236
7237 let by_provider = matches!(
7238 self.menu_edit_predictions_policy,
7239 MenuEditPredictionsPolicy::ByProvider
7240 );
7241
7242 let show_in_menu = by_provider
7243 && self
7244 .edit_prediction_provider
7245 .as_ref()
7246 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7247
7248 let preview_requires_modifier =
7249 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7250
7251 EditPredictionSettings::Enabled {
7252 show_in_menu,
7253 preview_requires_modifier,
7254 }
7255 }
7256
7257 fn should_show_edit_predictions(&self) -> bool {
7258 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7259 }
7260
7261 pub fn edit_prediction_preview_is_active(&self) -> bool {
7262 matches!(
7263 self.edit_prediction_preview,
7264 EditPredictionPreview::Active { .. }
7265 )
7266 }
7267
7268 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7269 let cursor = self.selections.newest_anchor().head();
7270 if let Some((buffer, cursor_position)) =
7271 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7272 {
7273 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7274 } else {
7275 false
7276 }
7277 }
7278
7279 pub fn supports_minimap(&self, cx: &App) -> bool {
7280 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7281 }
7282
7283 fn edit_predictions_enabled_in_buffer(
7284 &self,
7285 buffer: &Entity<Buffer>,
7286 buffer_position: language::Anchor,
7287 cx: &App,
7288 ) -> bool {
7289 maybe!({
7290 if self.read_only(cx) {
7291 return Some(false);
7292 }
7293 let provider = self.edit_prediction_provider()?;
7294 if !provider.is_enabled(buffer, buffer_position, cx) {
7295 return Some(false);
7296 }
7297 let buffer = buffer.read(cx);
7298 let Some(file) = buffer.file() else {
7299 return Some(true);
7300 };
7301 let settings = all_language_settings(Some(file), cx);
7302 Some(settings.edit_predictions_enabled_for_file(file, cx))
7303 })
7304 .unwrap_or(false)
7305 }
7306
7307 fn cycle_edit_prediction(
7308 &mut self,
7309 direction: Direction,
7310 window: &mut Window,
7311 cx: &mut Context<Self>,
7312 ) -> Option<()> {
7313 let provider = self.edit_prediction_provider()?;
7314 let cursor = self.selections.newest_anchor().head();
7315 let (buffer, cursor_buffer_position) =
7316 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7317 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7318 return None;
7319 }
7320
7321 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7322 self.update_visible_edit_prediction(window, cx);
7323
7324 Some(())
7325 }
7326
7327 pub fn show_edit_prediction(
7328 &mut self,
7329 _: &ShowEditPrediction,
7330 window: &mut Window,
7331 cx: &mut Context<Self>,
7332 ) {
7333 if !self.has_active_edit_prediction() {
7334 self.refresh_edit_prediction(false, true, window, cx);
7335 return;
7336 }
7337
7338 self.update_visible_edit_prediction(window, cx);
7339 }
7340
7341 pub fn display_cursor_names(
7342 &mut self,
7343 _: &DisplayCursorNames,
7344 window: &mut Window,
7345 cx: &mut Context<Self>,
7346 ) {
7347 self.show_cursor_names(window, cx);
7348 }
7349
7350 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7351 self.show_cursor_names = true;
7352 cx.notify();
7353 cx.spawn_in(window, async move |this, cx| {
7354 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7355 this.update(cx, |this, cx| {
7356 this.show_cursor_names = false;
7357 cx.notify()
7358 })
7359 .ok()
7360 })
7361 .detach();
7362 }
7363
7364 pub fn next_edit_prediction(
7365 &mut self,
7366 _: &NextEditPrediction,
7367 window: &mut Window,
7368 cx: &mut Context<Self>,
7369 ) {
7370 if self.has_active_edit_prediction() {
7371 self.cycle_edit_prediction(Direction::Next, window, cx);
7372 } else {
7373 let is_copilot_disabled = self
7374 .refresh_edit_prediction(false, true, window, cx)
7375 .is_none();
7376 if is_copilot_disabled {
7377 cx.propagate();
7378 }
7379 }
7380 }
7381
7382 pub fn previous_edit_prediction(
7383 &mut self,
7384 _: &PreviousEditPrediction,
7385 window: &mut Window,
7386 cx: &mut Context<Self>,
7387 ) {
7388 if self.has_active_edit_prediction() {
7389 self.cycle_edit_prediction(Direction::Prev, window, cx);
7390 } else {
7391 let is_copilot_disabled = self
7392 .refresh_edit_prediction(false, true, window, cx)
7393 .is_none();
7394 if is_copilot_disabled {
7395 cx.propagate();
7396 }
7397 }
7398 }
7399
7400 pub fn accept_edit_prediction(
7401 &mut self,
7402 _: &AcceptEditPrediction,
7403 window: &mut Window,
7404 cx: &mut Context<Self>,
7405 ) {
7406 if self.show_edit_predictions_in_menu() {
7407 self.hide_context_menu(window, cx);
7408 }
7409
7410 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7411 return;
7412 };
7413
7414 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7415
7416 match &active_edit_prediction.completion {
7417 EditPrediction::Move { target, .. } => {
7418 let target = *target;
7419
7420 if let Some(position_map) = &self.last_position_map {
7421 if position_map
7422 .visible_row_range
7423 .contains(&target.to_display_point(&position_map.snapshot).row())
7424 || !self.edit_prediction_requires_modifier()
7425 {
7426 self.unfold_ranges(&[target..target], true, false, cx);
7427 // Note that this is also done in vim's handler of the Tab action.
7428 self.change_selections(
7429 SelectionEffects::scroll(Autoscroll::newest()),
7430 window,
7431 cx,
7432 |selections| {
7433 selections.select_anchor_ranges([target..target]);
7434 },
7435 );
7436 self.clear_row_highlights::<EditPredictionPreview>();
7437
7438 self.edit_prediction_preview
7439 .set_previous_scroll_position(None);
7440 } else {
7441 self.edit_prediction_preview
7442 .set_previous_scroll_position(Some(
7443 position_map.snapshot.scroll_anchor,
7444 ));
7445
7446 self.highlight_rows::<EditPredictionPreview>(
7447 target..target,
7448 cx.theme().colors().editor_highlighted_line_background,
7449 RowHighlightOptions {
7450 autoscroll: true,
7451 ..Default::default()
7452 },
7453 cx,
7454 );
7455 self.request_autoscroll(Autoscroll::fit(), cx);
7456 }
7457 }
7458 }
7459 EditPrediction::Edit { edits, .. } => {
7460 if let Some(provider) = self.edit_prediction_provider() {
7461 provider.accept(cx);
7462 }
7463
7464 // Store the transaction ID and selections before applying the edit
7465 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7466
7467 let snapshot = self.buffer.read(cx).snapshot(cx);
7468 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7469
7470 self.buffer.update(cx, |buffer, cx| {
7471 buffer.edit(edits.iter().cloned(), None, cx)
7472 });
7473
7474 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7475 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7476 });
7477
7478 let selections = self.selections.disjoint_anchors_arc();
7479 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7480 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7481 if has_new_transaction {
7482 self.selection_history
7483 .insert_transaction(transaction_id_now, selections);
7484 }
7485 }
7486
7487 self.update_visible_edit_prediction(window, cx);
7488 if self.active_edit_prediction.is_none() {
7489 self.refresh_edit_prediction(true, true, window, cx);
7490 }
7491
7492 cx.notify();
7493 }
7494 }
7495
7496 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7497 }
7498
7499 pub fn accept_partial_edit_prediction(
7500 &mut self,
7501 _: &AcceptPartialEditPrediction,
7502 window: &mut Window,
7503 cx: &mut Context<Self>,
7504 ) {
7505 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7506 return;
7507 };
7508 if self.selections.count() != 1 {
7509 return;
7510 }
7511
7512 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7513
7514 match &active_edit_prediction.completion {
7515 EditPrediction::Move { target, .. } => {
7516 let target = *target;
7517 self.change_selections(
7518 SelectionEffects::scroll(Autoscroll::newest()),
7519 window,
7520 cx,
7521 |selections| {
7522 selections.select_anchor_ranges([target..target]);
7523 },
7524 );
7525 }
7526 EditPrediction::Edit { edits, .. } => {
7527 // Find an insertion that starts at the cursor position.
7528 let snapshot = self.buffer.read(cx).snapshot(cx);
7529 let cursor_offset = self.selections.newest::<usize>(cx).head();
7530 let insertion = edits.iter().find_map(|(range, text)| {
7531 let range = range.to_offset(&snapshot);
7532 if range.is_empty() && range.start == cursor_offset {
7533 Some(text)
7534 } else {
7535 None
7536 }
7537 });
7538
7539 if let Some(text) = insertion {
7540 let mut partial_completion = text
7541 .chars()
7542 .by_ref()
7543 .take_while(|c| c.is_alphabetic())
7544 .collect::<String>();
7545 if partial_completion.is_empty() {
7546 partial_completion = text
7547 .chars()
7548 .by_ref()
7549 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7550 .collect::<String>();
7551 }
7552
7553 cx.emit(EditorEvent::InputHandled {
7554 utf16_range_to_replace: None,
7555 text: partial_completion.clone().into(),
7556 });
7557
7558 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7559
7560 self.refresh_edit_prediction(true, true, window, cx);
7561 cx.notify();
7562 } else {
7563 self.accept_edit_prediction(&Default::default(), window, cx);
7564 }
7565 }
7566 }
7567 }
7568
7569 fn discard_edit_prediction(
7570 &mut self,
7571 should_report_edit_prediction_event: bool,
7572 cx: &mut Context<Self>,
7573 ) -> bool {
7574 if should_report_edit_prediction_event {
7575 let completion_id = self
7576 .active_edit_prediction
7577 .as_ref()
7578 .and_then(|active_completion| active_completion.completion_id.clone());
7579
7580 self.report_edit_prediction_event(completion_id, false, cx);
7581 }
7582
7583 if let Some(provider) = self.edit_prediction_provider() {
7584 provider.discard(cx);
7585 }
7586
7587 self.take_active_edit_prediction(cx)
7588 }
7589
7590 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7591 let Some(provider) = self.edit_prediction_provider() else {
7592 return;
7593 };
7594
7595 let Some((_, buffer, _)) = self
7596 .buffer
7597 .read(cx)
7598 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7599 else {
7600 return;
7601 };
7602
7603 let extension = buffer
7604 .read(cx)
7605 .file()
7606 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7607
7608 let event_type = match accepted {
7609 true => "Edit Prediction Accepted",
7610 false => "Edit Prediction Discarded",
7611 };
7612 telemetry::event!(
7613 event_type,
7614 provider = provider.name(),
7615 prediction_id = id,
7616 suggestion_accepted = accepted,
7617 file_extension = extension,
7618 );
7619 }
7620
7621 pub fn has_active_edit_prediction(&self) -> bool {
7622 self.active_edit_prediction.is_some()
7623 }
7624
7625 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7626 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7627 return false;
7628 };
7629
7630 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7631 self.clear_highlights::<EditPredictionHighlight>(cx);
7632 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7633 true
7634 }
7635
7636 /// Returns true when we're displaying the edit prediction popover below the cursor
7637 /// like we are not previewing and the LSP autocomplete menu is visible
7638 /// or we are in `when_holding_modifier` mode.
7639 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7640 if self.edit_prediction_preview_is_active()
7641 || !self.show_edit_predictions_in_menu()
7642 || !self.edit_predictions_enabled()
7643 {
7644 return false;
7645 }
7646
7647 if self.has_visible_completions_menu() {
7648 return true;
7649 }
7650
7651 has_completion && self.edit_prediction_requires_modifier()
7652 }
7653
7654 fn handle_modifiers_changed(
7655 &mut self,
7656 modifiers: Modifiers,
7657 position_map: &PositionMap,
7658 window: &mut Window,
7659 cx: &mut Context<Self>,
7660 ) {
7661 if self.show_edit_predictions_in_menu() {
7662 self.update_edit_prediction_preview(&modifiers, window, cx);
7663 }
7664
7665 self.update_selection_mode(&modifiers, position_map, window, cx);
7666
7667 let mouse_position = window.mouse_position();
7668 if !position_map.text_hitbox.is_hovered(window) {
7669 return;
7670 }
7671
7672 self.update_hovered_link(
7673 position_map.point_for_position(mouse_position),
7674 &position_map.snapshot,
7675 modifiers,
7676 window,
7677 cx,
7678 )
7679 }
7680
7681 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7682 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7683 if invert {
7684 match multi_cursor_setting {
7685 MultiCursorModifier::Alt => modifiers.alt,
7686 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7687 }
7688 } else {
7689 match multi_cursor_setting {
7690 MultiCursorModifier::Alt => modifiers.secondary(),
7691 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7692 }
7693 }
7694 }
7695
7696 fn columnar_selection_mode(
7697 modifiers: &Modifiers,
7698 cx: &mut Context<Self>,
7699 ) -> Option<ColumnarMode> {
7700 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7701 if Self::multi_cursor_modifier(false, modifiers, cx) {
7702 Some(ColumnarMode::FromMouse)
7703 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7704 Some(ColumnarMode::FromSelection)
7705 } else {
7706 None
7707 }
7708 } else {
7709 None
7710 }
7711 }
7712
7713 fn update_selection_mode(
7714 &mut self,
7715 modifiers: &Modifiers,
7716 position_map: &PositionMap,
7717 window: &mut Window,
7718 cx: &mut Context<Self>,
7719 ) {
7720 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7721 return;
7722 };
7723 if self.selections.pending_anchor().is_none() {
7724 return;
7725 }
7726
7727 let mouse_position = window.mouse_position();
7728 let point_for_position = position_map.point_for_position(mouse_position);
7729 let position = point_for_position.previous_valid;
7730
7731 self.select(
7732 SelectPhase::BeginColumnar {
7733 position,
7734 reset: false,
7735 mode,
7736 goal_column: point_for_position.exact_unclipped.column(),
7737 },
7738 window,
7739 cx,
7740 );
7741 }
7742
7743 fn update_edit_prediction_preview(
7744 &mut self,
7745 modifiers: &Modifiers,
7746 window: &mut Window,
7747 cx: &mut Context<Self>,
7748 ) {
7749 let mut modifiers_held = false;
7750 if let Some(accept_keystroke) = self
7751 .accept_edit_prediction_keybind(false, window, cx)
7752 .keystroke()
7753 {
7754 modifiers_held = modifiers_held
7755 || (accept_keystroke.modifiers() == modifiers
7756 && accept_keystroke.modifiers().modified());
7757 };
7758 if let Some(accept_partial_keystroke) = self
7759 .accept_edit_prediction_keybind(true, window, cx)
7760 .keystroke()
7761 {
7762 modifiers_held = modifiers_held
7763 || (accept_partial_keystroke.modifiers() == modifiers
7764 && accept_partial_keystroke.modifiers().modified());
7765 }
7766
7767 if modifiers_held {
7768 if matches!(
7769 self.edit_prediction_preview,
7770 EditPredictionPreview::Inactive { .. }
7771 ) {
7772 self.edit_prediction_preview = EditPredictionPreview::Active {
7773 previous_scroll_position: None,
7774 since: Instant::now(),
7775 };
7776
7777 self.update_visible_edit_prediction(window, cx);
7778 cx.notify();
7779 }
7780 } else if let EditPredictionPreview::Active {
7781 previous_scroll_position,
7782 since,
7783 } = self.edit_prediction_preview
7784 {
7785 if let (Some(previous_scroll_position), Some(position_map)) =
7786 (previous_scroll_position, self.last_position_map.as_ref())
7787 {
7788 self.set_scroll_position(
7789 previous_scroll_position
7790 .scroll_position(&position_map.snapshot.display_snapshot),
7791 window,
7792 cx,
7793 );
7794 }
7795
7796 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7797 released_too_fast: since.elapsed() < Duration::from_millis(200),
7798 };
7799 self.clear_row_highlights::<EditPredictionPreview>();
7800 self.update_visible_edit_prediction(window, cx);
7801 cx.notify();
7802 }
7803 }
7804
7805 fn update_visible_edit_prediction(
7806 &mut self,
7807 _window: &mut Window,
7808 cx: &mut Context<Self>,
7809 ) -> Option<()> {
7810 if DisableAiSettings::get_global(cx).disable_ai {
7811 return None;
7812 }
7813
7814 if self.ime_transaction.is_some() {
7815 self.discard_edit_prediction(false, cx);
7816 return None;
7817 }
7818
7819 let selection = self.selections.newest_anchor();
7820 let cursor = selection.head();
7821 let multibuffer = self.buffer.read(cx).snapshot(cx);
7822 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7823 let excerpt_id = cursor.excerpt_id;
7824
7825 let show_in_menu = self.show_edit_predictions_in_menu();
7826 let completions_menu_has_precedence = !show_in_menu
7827 && (self.context_menu.borrow().is_some()
7828 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7829
7830 if completions_menu_has_precedence
7831 || !offset_selection.is_empty()
7832 || self
7833 .active_edit_prediction
7834 .as_ref()
7835 .is_some_and(|completion| {
7836 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7837 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7838 !invalidation_range.contains(&offset_selection.head())
7839 })
7840 {
7841 self.discard_edit_prediction(false, cx);
7842 return None;
7843 }
7844
7845 self.take_active_edit_prediction(cx);
7846 let Some(provider) = self.edit_prediction_provider() else {
7847 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7848 return None;
7849 };
7850
7851 let (buffer, cursor_buffer_position) =
7852 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7853
7854 self.edit_prediction_settings =
7855 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7856
7857 if let EditPredictionSettings::Disabled = self.edit_prediction_settings {
7858 self.discard_edit_prediction(false, cx);
7859 return None;
7860 };
7861
7862 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7863
7864 if self.edit_prediction_indent_conflict {
7865 let cursor_point = cursor.to_point(&multibuffer);
7866
7867 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7868
7869 if let Some((_, indent)) = indents.iter().next()
7870 && indent.len == cursor_point.column
7871 {
7872 self.edit_prediction_indent_conflict = false;
7873 }
7874 }
7875
7876 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7877 let edits = edit_prediction
7878 .edits
7879 .into_iter()
7880 .flat_map(|(range, new_text)| {
7881 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7882 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7883 Some((start..end, new_text))
7884 })
7885 .collect::<Vec<_>>();
7886 if edits.is_empty() {
7887 return None;
7888 }
7889
7890 let first_edit_start = edits.first().unwrap().0.start;
7891 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7892 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7893
7894 let last_edit_end = edits.last().unwrap().0.end;
7895 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7896 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7897
7898 let cursor_row = cursor.to_point(&multibuffer).row;
7899
7900 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7901
7902 let mut inlay_ids = Vec::new();
7903 let invalidation_row_range;
7904 let move_invalidation_row_range = if cursor_row < edit_start_row {
7905 Some(cursor_row..edit_end_row)
7906 } else if cursor_row > edit_end_row {
7907 Some(edit_start_row..cursor_row)
7908 } else {
7909 None
7910 };
7911 let supports_jump = self
7912 .edit_prediction_provider
7913 .as_ref()
7914 .map(|provider| provider.provider.supports_jump_to_edit())
7915 .unwrap_or(true);
7916
7917 let is_move = supports_jump
7918 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7919 let completion = if is_move {
7920 invalidation_row_range =
7921 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7922 let target = first_edit_start;
7923 EditPrediction::Move { target, snapshot }
7924 } else {
7925 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7926 && !self.edit_predictions_hidden_for_vim_mode;
7927
7928 if show_completions_in_buffer {
7929 if edits
7930 .iter()
7931 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7932 {
7933 let mut inlays = Vec::new();
7934 for (range, new_text) in &edits {
7935 let inlay = Inlay::edit_prediction(
7936 post_inc(&mut self.next_inlay_id),
7937 range.start,
7938 new_text.as_str(),
7939 );
7940 inlay_ids.push(inlay.id);
7941 inlays.push(inlay);
7942 }
7943
7944 self.splice_inlays(&[], inlays, cx);
7945 } else {
7946 let background_color = cx.theme().status().deleted_background;
7947 self.highlight_text::<EditPredictionHighlight>(
7948 edits.iter().map(|(range, _)| range.clone()).collect(),
7949 HighlightStyle {
7950 background_color: Some(background_color),
7951 ..Default::default()
7952 },
7953 cx,
7954 );
7955 }
7956 }
7957
7958 invalidation_row_range = edit_start_row..edit_end_row;
7959
7960 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7961 if provider.show_tab_accept_marker() {
7962 EditDisplayMode::TabAccept
7963 } else {
7964 EditDisplayMode::Inline
7965 }
7966 } else {
7967 EditDisplayMode::DiffPopover
7968 };
7969
7970 EditPrediction::Edit {
7971 edits,
7972 edit_preview: edit_prediction.edit_preview,
7973 display_mode,
7974 snapshot,
7975 }
7976 };
7977
7978 let invalidation_range = multibuffer
7979 .anchor_before(Point::new(invalidation_row_range.start, 0))
7980 ..multibuffer.anchor_after(Point::new(
7981 invalidation_row_range.end,
7982 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7983 ));
7984
7985 self.stale_edit_prediction_in_menu = None;
7986 self.active_edit_prediction = Some(EditPredictionState {
7987 inlay_ids,
7988 completion,
7989 completion_id: edit_prediction.id,
7990 invalidation_range,
7991 });
7992
7993 cx.notify();
7994
7995 Some(())
7996 }
7997
7998 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7999 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8000 }
8001
8002 fn clear_tasks(&mut self) {
8003 self.tasks.clear()
8004 }
8005
8006 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8007 if self.tasks.insert(key, value).is_some() {
8008 // This case should hopefully be rare, but just in case...
8009 log::error!(
8010 "multiple different run targets found on a single line, only the last target will be rendered"
8011 )
8012 }
8013 }
8014
8015 /// Get all display points of breakpoints that will be rendered within editor
8016 ///
8017 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8018 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8019 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8020 fn active_breakpoints(
8021 &self,
8022 range: Range<DisplayRow>,
8023 window: &mut Window,
8024 cx: &mut Context<Self>,
8025 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8026 let mut breakpoint_display_points = HashMap::default();
8027
8028 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8029 return breakpoint_display_points;
8030 };
8031
8032 let snapshot = self.snapshot(window, cx);
8033
8034 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8035 let Some(project) = self.project() else {
8036 return breakpoint_display_points;
8037 };
8038
8039 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8040 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8041
8042 for (buffer_snapshot, range, excerpt_id) in
8043 multi_buffer_snapshot.range_to_buffer_ranges(range)
8044 {
8045 let Some(buffer) = project
8046 .read(cx)
8047 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8048 else {
8049 continue;
8050 };
8051 let breakpoints = breakpoint_store.read(cx).breakpoints(
8052 &buffer,
8053 Some(
8054 buffer_snapshot.anchor_before(range.start)
8055 ..buffer_snapshot.anchor_after(range.end),
8056 ),
8057 buffer_snapshot,
8058 cx,
8059 );
8060 for (breakpoint, state) in breakpoints {
8061 let multi_buffer_anchor =
8062 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8063 let position = multi_buffer_anchor
8064 .to_point(multi_buffer_snapshot)
8065 .to_display_point(&snapshot);
8066
8067 breakpoint_display_points.insert(
8068 position.row(),
8069 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8070 );
8071 }
8072 }
8073
8074 breakpoint_display_points
8075 }
8076
8077 fn breakpoint_context_menu(
8078 &self,
8079 anchor: Anchor,
8080 window: &mut Window,
8081 cx: &mut Context<Self>,
8082 ) -> Entity<ui::ContextMenu> {
8083 let weak_editor = cx.weak_entity();
8084 let focus_handle = self.focus_handle(cx);
8085
8086 let row = self
8087 .buffer
8088 .read(cx)
8089 .snapshot(cx)
8090 .summary_for_anchor::<Point>(&anchor)
8091 .row;
8092
8093 let breakpoint = self
8094 .breakpoint_at_row(row, window, cx)
8095 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8096
8097 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8098 "Edit Log Breakpoint"
8099 } else {
8100 "Set Log Breakpoint"
8101 };
8102
8103 let condition_breakpoint_msg = if breakpoint
8104 .as_ref()
8105 .is_some_and(|bp| bp.1.condition.is_some())
8106 {
8107 "Edit Condition Breakpoint"
8108 } else {
8109 "Set Condition Breakpoint"
8110 };
8111
8112 let hit_condition_breakpoint_msg = if breakpoint
8113 .as_ref()
8114 .is_some_and(|bp| bp.1.hit_condition.is_some())
8115 {
8116 "Edit Hit Condition Breakpoint"
8117 } else {
8118 "Set Hit Condition Breakpoint"
8119 };
8120
8121 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8122 "Unset Breakpoint"
8123 } else {
8124 "Set Breakpoint"
8125 };
8126
8127 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8128
8129 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8130 BreakpointState::Enabled => Some("Disable"),
8131 BreakpointState::Disabled => Some("Enable"),
8132 });
8133
8134 let (anchor, breakpoint) =
8135 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8136
8137 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8138 menu.on_blur_subscription(Subscription::new(|| {}))
8139 .context(focus_handle)
8140 .when(run_to_cursor, |this| {
8141 let weak_editor = weak_editor.clone();
8142 this.entry("Run to cursor", None, move |window, cx| {
8143 weak_editor
8144 .update(cx, |editor, cx| {
8145 editor.change_selections(
8146 SelectionEffects::no_scroll(),
8147 window,
8148 cx,
8149 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8150 );
8151 })
8152 .ok();
8153
8154 window.dispatch_action(Box::new(RunToCursor), cx);
8155 })
8156 .separator()
8157 })
8158 .when_some(toggle_state_msg, |this, msg| {
8159 this.entry(msg, None, {
8160 let weak_editor = weak_editor.clone();
8161 let breakpoint = breakpoint.clone();
8162 move |_window, cx| {
8163 weak_editor
8164 .update(cx, |this, cx| {
8165 this.edit_breakpoint_at_anchor(
8166 anchor,
8167 breakpoint.as_ref().clone(),
8168 BreakpointEditAction::InvertState,
8169 cx,
8170 );
8171 })
8172 .log_err();
8173 }
8174 })
8175 })
8176 .entry(set_breakpoint_msg, None, {
8177 let weak_editor = weak_editor.clone();
8178 let breakpoint = breakpoint.clone();
8179 move |_window, cx| {
8180 weak_editor
8181 .update(cx, |this, cx| {
8182 this.edit_breakpoint_at_anchor(
8183 anchor,
8184 breakpoint.as_ref().clone(),
8185 BreakpointEditAction::Toggle,
8186 cx,
8187 );
8188 })
8189 .log_err();
8190 }
8191 })
8192 .entry(log_breakpoint_msg, None, {
8193 let breakpoint = breakpoint.clone();
8194 let weak_editor = weak_editor.clone();
8195 move |window, cx| {
8196 weak_editor
8197 .update(cx, |this, cx| {
8198 this.add_edit_breakpoint_block(
8199 anchor,
8200 breakpoint.as_ref(),
8201 BreakpointPromptEditAction::Log,
8202 window,
8203 cx,
8204 );
8205 })
8206 .log_err();
8207 }
8208 })
8209 .entry(condition_breakpoint_msg, None, {
8210 let breakpoint = breakpoint.clone();
8211 let weak_editor = weak_editor.clone();
8212 move |window, cx| {
8213 weak_editor
8214 .update(cx, |this, cx| {
8215 this.add_edit_breakpoint_block(
8216 anchor,
8217 breakpoint.as_ref(),
8218 BreakpointPromptEditAction::Condition,
8219 window,
8220 cx,
8221 );
8222 })
8223 .log_err();
8224 }
8225 })
8226 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8227 weak_editor
8228 .update(cx, |this, cx| {
8229 this.add_edit_breakpoint_block(
8230 anchor,
8231 breakpoint.as_ref(),
8232 BreakpointPromptEditAction::HitCondition,
8233 window,
8234 cx,
8235 );
8236 })
8237 .log_err();
8238 })
8239 })
8240 }
8241
8242 fn render_breakpoint(
8243 &self,
8244 position: Anchor,
8245 row: DisplayRow,
8246 breakpoint: &Breakpoint,
8247 state: Option<BreakpointSessionState>,
8248 cx: &mut Context<Self>,
8249 ) -> IconButton {
8250 let is_rejected = state.is_some_and(|s| !s.verified);
8251 // Is it a breakpoint that shows up when hovering over gutter?
8252 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8253 (false, false),
8254 |PhantomBreakpointIndicator {
8255 is_active,
8256 display_row,
8257 collides_with_existing_breakpoint,
8258 }| {
8259 (
8260 is_active && display_row == row,
8261 collides_with_existing_breakpoint,
8262 )
8263 },
8264 );
8265
8266 let (color, icon) = {
8267 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8268 (false, false) => ui::IconName::DebugBreakpoint,
8269 (true, false) => ui::IconName::DebugLogBreakpoint,
8270 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8271 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8272 };
8273
8274 let color = if is_phantom {
8275 Color::Hint
8276 } else if is_rejected {
8277 Color::Disabled
8278 } else {
8279 Color::Debugger
8280 };
8281
8282 (color, icon)
8283 };
8284
8285 let breakpoint = Arc::from(breakpoint.clone());
8286
8287 let alt_as_text = gpui::Keystroke {
8288 modifiers: Modifiers::secondary_key(),
8289 ..Default::default()
8290 };
8291 let primary_action_text = if breakpoint.is_disabled() {
8292 "Enable breakpoint"
8293 } else if is_phantom && !collides_with_existing {
8294 "Set breakpoint"
8295 } else {
8296 "Unset breakpoint"
8297 };
8298 let focus_handle = self.focus_handle.clone();
8299
8300 let meta = if is_rejected {
8301 SharedString::from("No executable code is associated with this line.")
8302 } else if collides_with_existing && !breakpoint.is_disabled() {
8303 SharedString::from(format!(
8304 "{alt_as_text}-click to disable,\nright-click for more options."
8305 ))
8306 } else {
8307 SharedString::from("Right-click for more options.")
8308 };
8309 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8310 .icon_size(IconSize::XSmall)
8311 .size(ui::ButtonSize::None)
8312 .when(is_rejected, |this| {
8313 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8314 })
8315 .icon_color(color)
8316 .style(ButtonStyle::Transparent)
8317 .on_click(cx.listener({
8318 move |editor, event: &ClickEvent, window, cx| {
8319 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8320 BreakpointEditAction::InvertState
8321 } else {
8322 BreakpointEditAction::Toggle
8323 };
8324
8325 window.focus(&editor.focus_handle(cx));
8326 editor.edit_breakpoint_at_anchor(
8327 position,
8328 breakpoint.as_ref().clone(),
8329 edit_action,
8330 cx,
8331 );
8332 }
8333 }))
8334 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8335 editor.set_breakpoint_context_menu(
8336 row,
8337 Some(position),
8338 event.position(),
8339 window,
8340 cx,
8341 );
8342 }))
8343 .tooltip(move |window, cx| {
8344 Tooltip::with_meta_in(
8345 primary_action_text,
8346 Some(&ToggleBreakpoint),
8347 meta.clone(),
8348 &focus_handle,
8349 window,
8350 cx,
8351 )
8352 })
8353 }
8354
8355 fn build_tasks_context(
8356 project: &Entity<Project>,
8357 buffer: &Entity<Buffer>,
8358 buffer_row: u32,
8359 tasks: &Arc<RunnableTasks>,
8360 cx: &mut Context<Self>,
8361 ) -> Task<Option<task::TaskContext>> {
8362 let position = Point::new(buffer_row, tasks.column);
8363 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8364 let location = Location {
8365 buffer: buffer.clone(),
8366 range: range_start..range_start,
8367 };
8368 // Fill in the environmental variables from the tree-sitter captures
8369 let mut captured_task_variables = TaskVariables::default();
8370 for (capture_name, value) in tasks.extra_variables.clone() {
8371 captured_task_variables.insert(
8372 task::VariableName::Custom(capture_name.into()),
8373 value.clone(),
8374 );
8375 }
8376 project.update(cx, |project, cx| {
8377 project.task_store().update(cx, |task_store, cx| {
8378 task_store.task_context_for_location(captured_task_variables, location, cx)
8379 })
8380 })
8381 }
8382
8383 pub fn spawn_nearest_task(
8384 &mut self,
8385 action: &SpawnNearestTask,
8386 window: &mut Window,
8387 cx: &mut Context<Self>,
8388 ) {
8389 let Some((workspace, _)) = self.workspace.clone() else {
8390 return;
8391 };
8392 let Some(project) = self.project.clone() else {
8393 return;
8394 };
8395
8396 // Try to find a closest, enclosing node using tree-sitter that has a task
8397 let Some((buffer, buffer_row, tasks)) = self
8398 .find_enclosing_node_task(cx)
8399 // Or find the task that's closest in row-distance.
8400 .or_else(|| self.find_closest_task(cx))
8401 else {
8402 return;
8403 };
8404
8405 let reveal_strategy = action.reveal;
8406 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8407 cx.spawn_in(window, async move |_, cx| {
8408 let context = task_context.await?;
8409 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8410
8411 let resolved = &mut resolved_task.resolved;
8412 resolved.reveal = reveal_strategy;
8413
8414 workspace
8415 .update_in(cx, |workspace, window, cx| {
8416 workspace.schedule_resolved_task(
8417 task_source_kind,
8418 resolved_task,
8419 false,
8420 window,
8421 cx,
8422 );
8423 })
8424 .ok()
8425 })
8426 .detach();
8427 }
8428
8429 fn find_closest_task(
8430 &mut self,
8431 cx: &mut Context<Self>,
8432 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8433 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8434
8435 let ((buffer_id, row), tasks) = self
8436 .tasks
8437 .iter()
8438 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8439
8440 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8441 let tasks = Arc::new(tasks.to_owned());
8442 Some((buffer, *row, tasks))
8443 }
8444
8445 fn find_enclosing_node_task(
8446 &mut self,
8447 cx: &mut Context<Self>,
8448 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8449 let snapshot = self.buffer.read(cx).snapshot(cx);
8450 let offset = self.selections.newest::<usize>(cx).head();
8451 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8452 let buffer_id = excerpt.buffer().remote_id();
8453
8454 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8455 let mut cursor = layer.node().walk();
8456
8457 while cursor.goto_first_child_for_byte(offset).is_some() {
8458 if cursor.node().end_byte() == offset {
8459 cursor.goto_next_sibling();
8460 }
8461 }
8462
8463 // Ascend to the smallest ancestor that contains the range and has a task.
8464 loop {
8465 let node = cursor.node();
8466 let node_range = node.byte_range();
8467 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8468
8469 // Check if this node contains our offset
8470 if node_range.start <= offset && node_range.end >= offset {
8471 // If it contains offset, check for task
8472 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8473 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8474 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8475 }
8476 }
8477
8478 if !cursor.goto_parent() {
8479 break;
8480 }
8481 }
8482 None
8483 }
8484
8485 fn render_run_indicator(
8486 &self,
8487 _style: &EditorStyle,
8488 is_active: bool,
8489 row: DisplayRow,
8490 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8491 cx: &mut Context<Self>,
8492 ) -> IconButton {
8493 let color = Color::Muted;
8494 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8495
8496 IconButton::new(
8497 ("run_indicator", row.0 as usize),
8498 ui::IconName::PlayOutlined,
8499 )
8500 .shape(ui::IconButtonShape::Square)
8501 .icon_size(IconSize::XSmall)
8502 .icon_color(color)
8503 .toggle_state(is_active)
8504 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8505 let quick_launch = match e {
8506 ClickEvent::Keyboard(_) => true,
8507 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8508 };
8509
8510 window.focus(&editor.focus_handle(cx));
8511 editor.toggle_code_actions(
8512 &ToggleCodeActions {
8513 deployed_from: Some(CodeActionSource::RunMenu(row)),
8514 quick_launch,
8515 },
8516 window,
8517 cx,
8518 );
8519 }))
8520 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8521 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8522 }))
8523 }
8524
8525 pub fn context_menu_visible(&self) -> bool {
8526 !self.edit_prediction_preview_is_active()
8527 && self
8528 .context_menu
8529 .borrow()
8530 .as_ref()
8531 .is_some_and(|menu| menu.visible())
8532 }
8533
8534 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8535 self.context_menu
8536 .borrow()
8537 .as_ref()
8538 .map(|menu| menu.origin())
8539 }
8540
8541 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8542 self.context_menu_options = Some(options);
8543 }
8544
8545 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8546 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8547
8548 fn render_edit_prediction_popover(
8549 &mut self,
8550 text_bounds: &Bounds<Pixels>,
8551 content_origin: gpui::Point<Pixels>,
8552 right_margin: Pixels,
8553 editor_snapshot: &EditorSnapshot,
8554 visible_row_range: Range<DisplayRow>,
8555 scroll_top: f32,
8556 scroll_bottom: f32,
8557 line_layouts: &[LineWithInvisibles],
8558 line_height: Pixels,
8559 scroll_pixel_position: gpui::Point<Pixels>,
8560 newest_selection_head: Option<DisplayPoint>,
8561 editor_width: Pixels,
8562 style: &EditorStyle,
8563 window: &mut Window,
8564 cx: &mut App,
8565 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8566 if self.mode().is_minimap() {
8567 return None;
8568 }
8569 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8570
8571 if self.edit_prediction_visible_in_cursor_popover(true) {
8572 return None;
8573 }
8574
8575 match &active_edit_prediction.completion {
8576 EditPrediction::Move { target, .. } => {
8577 let target_display_point = target.to_display_point(editor_snapshot);
8578
8579 if self.edit_prediction_requires_modifier() {
8580 if !self.edit_prediction_preview_is_active() {
8581 return None;
8582 }
8583
8584 self.render_edit_prediction_modifier_jump_popover(
8585 text_bounds,
8586 content_origin,
8587 visible_row_range,
8588 line_layouts,
8589 line_height,
8590 scroll_pixel_position,
8591 newest_selection_head,
8592 target_display_point,
8593 window,
8594 cx,
8595 )
8596 } else {
8597 self.render_edit_prediction_eager_jump_popover(
8598 text_bounds,
8599 content_origin,
8600 editor_snapshot,
8601 visible_row_range,
8602 scroll_top,
8603 scroll_bottom,
8604 line_height,
8605 scroll_pixel_position,
8606 target_display_point,
8607 editor_width,
8608 window,
8609 cx,
8610 )
8611 }
8612 }
8613 EditPrediction::Edit {
8614 display_mode: EditDisplayMode::Inline,
8615 ..
8616 } => None,
8617 EditPrediction::Edit {
8618 display_mode: EditDisplayMode::TabAccept,
8619 edits,
8620 ..
8621 } => {
8622 let range = &edits.first()?.0;
8623 let target_display_point = range.end.to_display_point(editor_snapshot);
8624
8625 self.render_edit_prediction_end_of_line_popover(
8626 "Accept",
8627 editor_snapshot,
8628 visible_row_range,
8629 target_display_point,
8630 line_height,
8631 scroll_pixel_position,
8632 content_origin,
8633 editor_width,
8634 window,
8635 cx,
8636 )
8637 }
8638 EditPrediction::Edit {
8639 edits,
8640 edit_preview,
8641 display_mode: EditDisplayMode::DiffPopover,
8642 snapshot,
8643 } => self.render_edit_prediction_diff_popover(
8644 text_bounds,
8645 content_origin,
8646 right_margin,
8647 editor_snapshot,
8648 visible_row_range,
8649 line_layouts,
8650 line_height,
8651 scroll_pixel_position,
8652 newest_selection_head,
8653 editor_width,
8654 style,
8655 edits,
8656 edit_preview,
8657 snapshot,
8658 window,
8659 cx,
8660 ),
8661 }
8662 }
8663
8664 fn render_edit_prediction_modifier_jump_popover(
8665 &mut self,
8666 text_bounds: &Bounds<Pixels>,
8667 content_origin: gpui::Point<Pixels>,
8668 visible_row_range: Range<DisplayRow>,
8669 line_layouts: &[LineWithInvisibles],
8670 line_height: Pixels,
8671 scroll_pixel_position: gpui::Point<Pixels>,
8672 newest_selection_head: Option<DisplayPoint>,
8673 target_display_point: DisplayPoint,
8674 window: &mut Window,
8675 cx: &mut App,
8676 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8677 let scrolled_content_origin =
8678 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8679
8680 const SCROLL_PADDING_Y: Pixels = px(12.);
8681
8682 if target_display_point.row() < visible_row_range.start {
8683 return self.render_edit_prediction_scroll_popover(
8684 |_| SCROLL_PADDING_Y,
8685 IconName::ArrowUp,
8686 visible_row_range,
8687 line_layouts,
8688 newest_selection_head,
8689 scrolled_content_origin,
8690 window,
8691 cx,
8692 );
8693 } else if target_display_point.row() >= visible_row_range.end {
8694 return self.render_edit_prediction_scroll_popover(
8695 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8696 IconName::ArrowDown,
8697 visible_row_range,
8698 line_layouts,
8699 newest_selection_head,
8700 scrolled_content_origin,
8701 window,
8702 cx,
8703 );
8704 }
8705
8706 const POLE_WIDTH: Pixels = px(2.);
8707
8708 let line_layout =
8709 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8710 let target_column = target_display_point.column() as usize;
8711
8712 let target_x = line_layout.x_for_index(target_column);
8713 let target_y =
8714 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8715
8716 let flag_on_right = target_x < text_bounds.size.width / 2.;
8717
8718 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8719 border_color.l += 0.001;
8720
8721 let mut element = v_flex()
8722 .items_end()
8723 .when(flag_on_right, |el| el.items_start())
8724 .child(if flag_on_right {
8725 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8726 .rounded_bl(px(0.))
8727 .rounded_tl(px(0.))
8728 .border_l_2()
8729 .border_color(border_color)
8730 } else {
8731 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8732 .rounded_br(px(0.))
8733 .rounded_tr(px(0.))
8734 .border_r_2()
8735 .border_color(border_color)
8736 })
8737 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8738 .into_any();
8739
8740 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8741
8742 let mut origin = scrolled_content_origin + point(target_x, target_y)
8743 - point(
8744 if flag_on_right {
8745 POLE_WIDTH
8746 } else {
8747 size.width - POLE_WIDTH
8748 },
8749 size.height - line_height,
8750 );
8751
8752 origin.x = origin.x.max(content_origin.x);
8753
8754 element.prepaint_at(origin, window, cx);
8755
8756 Some((element, origin))
8757 }
8758
8759 fn render_edit_prediction_scroll_popover(
8760 &mut self,
8761 to_y: impl Fn(Size<Pixels>) -> Pixels,
8762 scroll_icon: IconName,
8763 visible_row_range: Range<DisplayRow>,
8764 line_layouts: &[LineWithInvisibles],
8765 newest_selection_head: Option<DisplayPoint>,
8766 scrolled_content_origin: gpui::Point<Pixels>,
8767 window: &mut Window,
8768 cx: &mut App,
8769 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8770 let mut element = self
8771 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8772 .into_any();
8773
8774 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8775
8776 let cursor = newest_selection_head?;
8777 let cursor_row_layout =
8778 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8779 let cursor_column = cursor.column() as usize;
8780
8781 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8782
8783 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8784
8785 element.prepaint_at(origin, window, cx);
8786 Some((element, origin))
8787 }
8788
8789 fn render_edit_prediction_eager_jump_popover(
8790 &mut self,
8791 text_bounds: &Bounds<Pixels>,
8792 content_origin: gpui::Point<Pixels>,
8793 editor_snapshot: &EditorSnapshot,
8794 visible_row_range: Range<DisplayRow>,
8795 scroll_top: f32,
8796 scroll_bottom: f32,
8797 line_height: Pixels,
8798 scroll_pixel_position: gpui::Point<Pixels>,
8799 target_display_point: DisplayPoint,
8800 editor_width: Pixels,
8801 window: &mut Window,
8802 cx: &mut App,
8803 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8804 if target_display_point.row().as_f32() < scroll_top {
8805 let mut element = self
8806 .render_edit_prediction_line_popover(
8807 "Jump to Edit",
8808 Some(IconName::ArrowUp),
8809 window,
8810 cx,
8811 )?
8812 .into_any();
8813
8814 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8815 let offset = point(
8816 (text_bounds.size.width - size.width) / 2.,
8817 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8818 );
8819
8820 let origin = text_bounds.origin + offset;
8821 element.prepaint_at(origin, window, cx);
8822 Some((element, origin))
8823 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8824 let mut element = self
8825 .render_edit_prediction_line_popover(
8826 "Jump to Edit",
8827 Some(IconName::ArrowDown),
8828 window,
8829 cx,
8830 )?
8831 .into_any();
8832
8833 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8834 let offset = point(
8835 (text_bounds.size.width - size.width) / 2.,
8836 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8837 );
8838
8839 let origin = text_bounds.origin + offset;
8840 element.prepaint_at(origin, window, cx);
8841 Some((element, origin))
8842 } else {
8843 self.render_edit_prediction_end_of_line_popover(
8844 "Jump to Edit",
8845 editor_snapshot,
8846 visible_row_range,
8847 target_display_point,
8848 line_height,
8849 scroll_pixel_position,
8850 content_origin,
8851 editor_width,
8852 window,
8853 cx,
8854 )
8855 }
8856 }
8857
8858 fn render_edit_prediction_end_of_line_popover(
8859 self: &mut Editor,
8860 label: &'static str,
8861 editor_snapshot: &EditorSnapshot,
8862 visible_row_range: Range<DisplayRow>,
8863 target_display_point: DisplayPoint,
8864 line_height: Pixels,
8865 scroll_pixel_position: gpui::Point<Pixels>,
8866 content_origin: gpui::Point<Pixels>,
8867 editor_width: Pixels,
8868 window: &mut Window,
8869 cx: &mut App,
8870 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8871 let target_line_end = DisplayPoint::new(
8872 target_display_point.row(),
8873 editor_snapshot.line_len(target_display_point.row()),
8874 );
8875
8876 let mut element = self
8877 .render_edit_prediction_line_popover(label, None, window, cx)?
8878 .into_any();
8879
8880 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8881
8882 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8883
8884 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8885 let mut origin = start_point
8886 + line_origin
8887 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8888 origin.x = origin.x.max(content_origin.x);
8889
8890 let max_x = content_origin.x + editor_width - size.width;
8891
8892 if origin.x > max_x {
8893 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8894
8895 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8896 origin.y += offset;
8897 IconName::ArrowUp
8898 } else {
8899 origin.y -= offset;
8900 IconName::ArrowDown
8901 };
8902
8903 element = self
8904 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8905 .into_any();
8906
8907 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8908
8909 origin.x = content_origin.x + editor_width - size.width - px(2.);
8910 }
8911
8912 element.prepaint_at(origin, window, cx);
8913 Some((element, origin))
8914 }
8915
8916 fn render_edit_prediction_diff_popover(
8917 self: &Editor,
8918 text_bounds: &Bounds<Pixels>,
8919 content_origin: gpui::Point<Pixels>,
8920 right_margin: Pixels,
8921 editor_snapshot: &EditorSnapshot,
8922 visible_row_range: Range<DisplayRow>,
8923 line_layouts: &[LineWithInvisibles],
8924 line_height: Pixels,
8925 scroll_pixel_position: gpui::Point<Pixels>,
8926 newest_selection_head: Option<DisplayPoint>,
8927 editor_width: Pixels,
8928 style: &EditorStyle,
8929 edits: &Vec<(Range<Anchor>, String)>,
8930 edit_preview: &Option<language::EditPreview>,
8931 snapshot: &language::BufferSnapshot,
8932 window: &mut Window,
8933 cx: &mut App,
8934 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8935 let edit_start = edits
8936 .first()
8937 .unwrap()
8938 .0
8939 .start
8940 .to_display_point(editor_snapshot);
8941 let edit_end = edits
8942 .last()
8943 .unwrap()
8944 .0
8945 .end
8946 .to_display_point(editor_snapshot);
8947
8948 let is_visible = visible_row_range.contains(&edit_start.row())
8949 || visible_row_range.contains(&edit_end.row());
8950 if !is_visible {
8951 return None;
8952 }
8953
8954 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8955 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8956 } else {
8957 // Fallback for providers without edit_preview
8958 crate::edit_prediction_fallback_text(edits, cx)
8959 };
8960
8961 let styled_text = highlighted_edits.to_styled_text(&style.text);
8962 let line_count = highlighted_edits.text.lines().count();
8963
8964 const BORDER_WIDTH: Pixels = px(1.);
8965
8966 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8967 let has_keybind = keybind.is_some();
8968
8969 let mut element = h_flex()
8970 .items_start()
8971 .child(
8972 h_flex()
8973 .bg(cx.theme().colors().editor_background)
8974 .border(BORDER_WIDTH)
8975 .shadow_xs()
8976 .border_color(cx.theme().colors().border)
8977 .rounded_l_lg()
8978 .when(line_count > 1, |el| el.rounded_br_lg())
8979 .pr_1()
8980 .child(styled_text),
8981 )
8982 .child(
8983 h_flex()
8984 .h(line_height + BORDER_WIDTH * 2.)
8985 .px_1p5()
8986 .gap_1()
8987 // Workaround: For some reason, there's a gap if we don't do this
8988 .ml(-BORDER_WIDTH)
8989 .shadow(vec![gpui::BoxShadow {
8990 color: gpui::black().opacity(0.05),
8991 offset: point(px(1.), px(1.)),
8992 blur_radius: px(2.),
8993 spread_radius: px(0.),
8994 }])
8995 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8996 .border(BORDER_WIDTH)
8997 .border_color(cx.theme().colors().border)
8998 .rounded_r_lg()
8999 .id("edit_prediction_diff_popover_keybind")
9000 .when(!has_keybind, |el| {
9001 let status_colors = cx.theme().status();
9002
9003 el.bg(status_colors.error_background)
9004 .border_color(status_colors.error.opacity(0.6))
9005 .child(Icon::new(IconName::Info).color(Color::Error))
9006 .cursor_default()
9007 .hoverable_tooltip(move |_window, cx| {
9008 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9009 })
9010 })
9011 .children(keybind),
9012 )
9013 .into_any();
9014
9015 let longest_row =
9016 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9017 let longest_line_width = if visible_row_range.contains(&longest_row) {
9018 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9019 } else {
9020 layout_line(
9021 longest_row,
9022 editor_snapshot,
9023 style,
9024 editor_width,
9025 |_| false,
9026 window,
9027 cx,
9028 )
9029 .width
9030 };
9031
9032 let viewport_bounds =
9033 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9034 right: -right_margin,
9035 ..Default::default()
9036 });
9037
9038 let x_after_longest =
9039 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9040 - scroll_pixel_position.x;
9041
9042 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9043
9044 // Fully visible if it can be displayed within the window (allow overlapping other
9045 // panes). However, this is only allowed if the popover starts within text_bounds.
9046 let can_position_to_the_right = x_after_longest < text_bounds.right()
9047 && x_after_longest + element_bounds.width < viewport_bounds.right();
9048
9049 let mut origin = if can_position_to_the_right {
9050 point(
9051 x_after_longest,
9052 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9053 - scroll_pixel_position.y,
9054 )
9055 } else {
9056 let cursor_row = newest_selection_head.map(|head| head.row());
9057 let above_edit = edit_start
9058 .row()
9059 .0
9060 .checked_sub(line_count as u32)
9061 .map(DisplayRow);
9062 let below_edit = Some(edit_end.row() + 1);
9063 let above_cursor =
9064 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9065 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9066
9067 // Place the edit popover adjacent to the edit if there is a location
9068 // available that is onscreen and does not obscure the cursor. Otherwise,
9069 // place it adjacent to the cursor.
9070 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9071 .into_iter()
9072 .flatten()
9073 .find(|&start_row| {
9074 let end_row = start_row + line_count as u32;
9075 visible_row_range.contains(&start_row)
9076 && visible_row_range.contains(&end_row)
9077 && cursor_row
9078 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9079 })?;
9080
9081 content_origin
9082 + point(
9083 -scroll_pixel_position.x,
9084 row_target.as_f32() * line_height - scroll_pixel_position.y,
9085 )
9086 };
9087
9088 origin.x -= BORDER_WIDTH;
9089
9090 window.defer_draw(element, origin, 1);
9091
9092 // Do not return an element, since it will already be drawn due to defer_draw.
9093 None
9094 }
9095
9096 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9097 px(30.)
9098 }
9099
9100 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9101 if self.read_only(cx) {
9102 cx.theme().players().read_only()
9103 } else {
9104 self.style.as_ref().unwrap().local_player
9105 }
9106 }
9107
9108 fn render_edit_prediction_accept_keybind(
9109 &self,
9110 window: &mut Window,
9111 cx: &App,
9112 ) -> Option<AnyElement> {
9113 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9114 let accept_keystroke = accept_binding.keystroke()?;
9115
9116 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9117
9118 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9119 Color::Accent
9120 } else {
9121 Color::Muted
9122 };
9123
9124 h_flex()
9125 .px_0p5()
9126 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9127 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9128 .text_size(TextSize::XSmall.rems(cx))
9129 .child(h_flex().children(ui::render_modifiers(
9130 accept_keystroke.modifiers(),
9131 PlatformStyle::platform(),
9132 Some(modifiers_color),
9133 Some(IconSize::XSmall.rems().into()),
9134 true,
9135 )))
9136 .when(is_platform_style_mac, |parent| {
9137 parent.child(accept_keystroke.key().to_string())
9138 })
9139 .when(!is_platform_style_mac, |parent| {
9140 parent.child(
9141 Key::new(
9142 util::capitalize(accept_keystroke.key()),
9143 Some(Color::Default),
9144 )
9145 .size(Some(IconSize::XSmall.rems().into())),
9146 )
9147 })
9148 .into_any()
9149 .into()
9150 }
9151
9152 fn render_edit_prediction_line_popover(
9153 &self,
9154 label: impl Into<SharedString>,
9155 icon: Option<IconName>,
9156 window: &mut Window,
9157 cx: &App,
9158 ) -> Option<Stateful<Div>> {
9159 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9160
9161 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9162 let has_keybind = keybind.is_some();
9163
9164 let result = h_flex()
9165 .id("ep-line-popover")
9166 .py_0p5()
9167 .pl_1()
9168 .pr(padding_right)
9169 .gap_1()
9170 .rounded_md()
9171 .border_1()
9172 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9173 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9174 .shadow_xs()
9175 .when(!has_keybind, |el| {
9176 let status_colors = cx.theme().status();
9177
9178 el.bg(status_colors.error_background)
9179 .border_color(status_colors.error.opacity(0.6))
9180 .pl_2()
9181 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9182 .cursor_default()
9183 .hoverable_tooltip(move |_window, cx| {
9184 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9185 })
9186 })
9187 .children(keybind)
9188 .child(
9189 Label::new(label)
9190 .size(LabelSize::Small)
9191 .when(!has_keybind, |el| {
9192 el.color(cx.theme().status().error.into()).strikethrough()
9193 }),
9194 )
9195 .when(!has_keybind, |el| {
9196 el.child(
9197 h_flex().ml_1().child(
9198 Icon::new(IconName::Info)
9199 .size(IconSize::Small)
9200 .color(cx.theme().status().error.into()),
9201 ),
9202 )
9203 })
9204 .when_some(icon, |element, icon| {
9205 element.child(
9206 div()
9207 .mt(px(1.5))
9208 .child(Icon::new(icon).size(IconSize::Small)),
9209 )
9210 });
9211
9212 Some(result)
9213 }
9214
9215 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9216 let accent_color = cx.theme().colors().text_accent;
9217 let editor_bg_color = cx.theme().colors().editor_background;
9218 editor_bg_color.blend(accent_color.opacity(0.1))
9219 }
9220
9221 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9222 let accent_color = cx.theme().colors().text_accent;
9223 let editor_bg_color = cx.theme().colors().editor_background;
9224 editor_bg_color.blend(accent_color.opacity(0.6))
9225 }
9226 fn get_prediction_provider_icon_name(
9227 provider: &Option<RegisteredEditPredictionProvider>,
9228 ) -> IconName {
9229 match provider {
9230 Some(provider) => match provider.provider.name() {
9231 "copilot" => IconName::Copilot,
9232 "supermaven" => IconName::Supermaven,
9233 _ => IconName::ZedPredict,
9234 },
9235 None => IconName::ZedPredict,
9236 }
9237 }
9238
9239 fn render_edit_prediction_cursor_popover(
9240 &self,
9241 min_width: Pixels,
9242 max_width: Pixels,
9243 cursor_point: Point,
9244 style: &EditorStyle,
9245 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9246 _window: &Window,
9247 cx: &mut Context<Editor>,
9248 ) -> Option<AnyElement> {
9249 let provider = self.edit_prediction_provider.as_ref()?;
9250 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9251
9252 let is_refreshing = provider.provider.is_refreshing(cx);
9253
9254 fn pending_completion_container(icon: IconName) -> Div {
9255 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9256 }
9257
9258 let completion = match &self.active_edit_prediction {
9259 Some(prediction) => {
9260 if !self.has_visible_completions_menu() {
9261 const RADIUS: Pixels = px(6.);
9262 const BORDER_WIDTH: Pixels = px(1.);
9263
9264 return Some(
9265 h_flex()
9266 .elevation_2(cx)
9267 .border(BORDER_WIDTH)
9268 .border_color(cx.theme().colors().border)
9269 .when(accept_keystroke.is_none(), |el| {
9270 el.border_color(cx.theme().status().error)
9271 })
9272 .rounded(RADIUS)
9273 .rounded_tl(px(0.))
9274 .overflow_hidden()
9275 .child(div().px_1p5().child(match &prediction.completion {
9276 EditPrediction::Move { target, snapshot } => {
9277 use text::ToPoint as _;
9278 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9279 {
9280 Icon::new(IconName::ZedPredictDown)
9281 } else {
9282 Icon::new(IconName::ZedPredictUp)
9283 }
9284 }
9285 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9286 }))
9287 .child(
9288 h_flex()
9289 .gap_1()
9290 .py_1()
9291 .px_2()
9292 .rounded_r(RADIUS - BORDER_WIDTH)
9293 .border_l_1()
9294 .border_color(cx.theme().colors().border)
9295 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9296 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9297 el.child(
9298 Label::new("Hold")
9299 .size(LabelSize::Small)
9300 .when(accept_keystroke.is_none(), |el| {
9301 el.strikethrough()
9302 })
9303 .line_height_style(LineHeightStyle::UiLabel),
9304 )
9305 })
9306 .id("edit_prediction_cursor_popover_keybind")
9307 .when(accept_keystroke.is_none(), |el| {
9308 let status_colors = cx.theme().status();
9309
9310 el.bg(status_colors.error_background)
9311 .border_color(status_colors.error.opacity(0.6))
9312 .child(Icon::new(IconName::Info).color(Color::Error))
9313 .cursor_default()
9314 .hoverable_tooltip(move |_window, cx| {
9315 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9316 .into()
9317 })
9318 })
9319 .when_some(
9320 accept_keystroke.as_ref(),
9321 |el, accept_keystroke| {
9322 el.child(h_flex().children(ui::render_modifiers(
9323 accept_keystroke.modifiers(),
9324 PlatformStyle::platform(),
9325 Some(Color::Default),
9326 Some(IconSize::XSmall.rems().into()),
9327 false,
9328 )))
9329 },
9330 ),
9331 )
9332 .into_any(),
9333 );
9334 }
9335
9336 self.render_edit_prediction_cursor_popover_preview(
9337 prediction,
9338 cursor_point,
9339 style,
9340 cx,
9341 )?
9342 }
9343
9344 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9345 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9346 stale_completion,
9347 cursor_point,
9348 style,
9349 cx,
9350 )?,
9351
9352 None => pending_completion_container(provider_icon)
9353 .child(Label::new("...").size(LabelSize::Small)),
9354 },
9355
9356 None => pending_completion_container(provider_icon)
9357 .child(Label::new("...").size(LabelSize::Small)),
9358 };
9359
9360 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9361 completion
9362 .with_animation(
9363 "loading-completion",
9364 Animation::new(Duration::from_secs(2))
9365 .repeat()
9366 .with_easing(pulsating_between(0.4, 0.8)),
9367 |label, delta| label.opacity(delta),
9368 )
9369 .into_any_element()
9370 } else {
9371 completion.into_any_element()
9372 };
9373
9374 let has_completion = self.active_edit_prediction.is_some();
9375
9376 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9377 Some(
9378 h_flex()
9379 .min_w(min_width)
9380 .max_w(max_width)
9381 .flex_1()
9382 .elevation_2(cx)
9383 .border_color(cx.theme().colors().border)
9384 .child(
9385 div()
9386 .flex_1()
9387 .py_1()
9388 .px_2()
9389 .overflow_hidden()
9390 .child(completion),
9391 )
9392 .when_some(accept_keystroke, |el, accept_keystroke| {
9393 if !accept_keystroke.modifiers().modified() {
9394 return el;
9395 }
9396
9397 el.child(
9398 h_flex()
9399 .h_full()
9400 .border_l_1()
9401 .rounded_r_lg()
9402 .border_color(cx.theme().colors().border)
9403 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9404 .gap_1()
9405 .py_1()
9406 .px_2()
9407 .child(
9408 h_flex()
9409 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9410 .when(is_platform_style_mac, |parent| parent.gap_1())
9411 .child(h_flex().children(ui::render_modifiers(
9412 accept_keystroke.modifiers(),
9413 PlatformStyle::platform(),
9414 Some(if !has_completion {
9415 Color::Muted
9416 } else {
9417 Color::Default
9418 }),
9419 None,
9420 false,
9421 ))),
9422 )
9423 .child(Label::new("Preview").into_any_element())
9424 .opacity(if has_completion { 1.0 } else { 0.4 }),
9425 )
9426 })
9427 .into_any(),
9428 )
9429 }
9430
9431 fn render_edit_prediction_cursor_popover_preview(
9432 &self,
9433 completion: &EditPredictionState,
9434 cursor_point: Point,
9435 style: &EditorStyle,
9436 cx: &mut Context<Editor>,
9437 ) -> Option<Div> {
9438 use text::ToPoint as _;
9439
9440 fn render_relative_row_jump(
9441 prefix: impl Into<String>,
9442 current_row: u32,
9443 target_row: u32,
9444 ) -> Div {
9445 let (row_diff, arrow) = if target_row < current_row {
9446 (current_row - target_row, IconName::ArrowUp)
9447 } else {
9448 (target_row - current_row, IconName::ArrowDown)
9449 };
9450
9451 h_flex()
9452 .child(
9453 Label::new(format!("{}{}", prefix.into(), row_diff))
9454 .color(Color::Muted)
9455 .size(LabelSize::Small),
9456 )
9457 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9458 }
9459
9460 let supports_jump = self
9461 .edit_prediction_provider
9462 .as_ref()
9463 .map(|provider| provider.provider.supports_jump_to_edit())
9464 .unwrap_or(true);
9465
9466 match &completion.completion {
9467 EditPrediction::Move {
9468 target, snapshot, ..
9469 } => {
9470 if !supports_jump {
9471 return None;
9472 }
9473
9474 Some(
9475 h_flex()
9476 .px_2()
9477 .gap_2()
9478 .flex_1()
9479 .child(
9480 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9481 Icon::new(IconName::ZedPredictDown)
9482 } else {
9483 Icon::new(IconName::ZedPredictUp)
9484 },
9485 )
9486 .child(Label::new("Jump to Edit")),
9487 )
9488 }
9489
9490 EditPrediction::Edit {
9491 edits,
9492 edit_preview,
9493 snapshot,
9494 display_mode: _,
9495 } => {
9496 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9497
9498 let (highlighted_edits, has_more_lines) =
9499 if let Some(edit_preview) = edit_preview.as_ref() {
9500 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9501 .first_line_preview()
9502 } else {
9503 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9504 };
9505
9506 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9507 .with_default_highlights(&style.text, highlighted_edits.highlights);
9508
9509 let preview = h_flex()
9510 .gap_1()
9511 .min_w_16()
9512 .child(styled_text)
9513 .when(has_more_lines, |parent| parent.child("…"));
9514
9515 let left = if supports_jump && first_edit_row != cursor_point.row {
9516 render_relative_row_jump("", cursor_point.row, first_edit_row)
9517 .into_any_element()
9518 } else {
9519 let icon_name =
9520 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9521 Icon::new(icon_name).into_any_element()
9522 };
9523
9524 Some(
9525 h_flex()
9526 .h_full()
9527 .flex_1()
9528 .gap_2()
9529 .pr_1()
9530 .overflow_x_hidden()
9531 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9532 .child(left)
9533 .child(preview),
9534 )
9535 }
9536 }
9537 }
9538
9539 pub fn render_context_menu(
9540 &self,
9541 style: &EditorStyle,
9542 max_height_in_lines: u32,
9543 window: &mut Window,
9544 cx: &mut Context<Editor>,
9545 ) -> Option<AnyElement> {
9546 let menu = self.context_menu.borrow();
9547 let menu = menu.as_ref()?;
9548 if !menu.visible() {
9549 return None;
9550 };
9551 Some(menu.render(style, max_height_in_lines, window, cx))
9552 }
9553
9554 fn render_context_menu_aside(
9555 &mut self,
9556 max_size: Size<Pixels>,
9557 window: &mut Window,
9558 cx: &mut Context<Editor>,
9559 ) -> Option<AnyElement> {
9560 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9561 if menu.visible() {
9562 menu.render_aside(max_size, window, cx)
9563 } else {
9564 None
9565 }
9566 })
9567 }
9568
9569 fn hide_context_menu(
9570 &mut self,
9571 window: &mut Window,
9572 cx: &mut Context<Self>,
9573 ) -> Option<CodeContextMenu> {
9574 cx.notify();
9575 self.completion_tasks.clear();
9576 let context_menu = self.context_menu.borrow_mut().take();
9577 self.stale_edit_prediction_in_menu.take();
9578 self.update_visible_edit_prediction(window, cx);
9579 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9580 && let Some(completion_provider) = &self.completion_provider
9581 {
9582 completion_provider.selection_changed(None, window, cx);
9583 }
9584 context_menu
9585 }
9586
9587 fn show_snippet_choices(
9588 &mut self,
9589 choices: &Vec<String>,
9590 selection: Range<Anchor>,
9591 cx: &mut Context<Self>,
9592 ) {
9593 let Some((_, buffer, _)) = self
9594 .buffer()
9595 .read(cx)
9596 .excerpt_containing(selection.start, cx)
9597 else {
9598 return;
9599 };
9600 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9601 else {
9602 return;
9603 };
9604 if buffer != end_buffer {
9605 log::error!("expected anchor range to have matching buffer IDs");
9606 return;
9607 }
9608
9609 let id = post_inc(&mut self.next_completion_id);
9610 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9611 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9612 CompletionsMenu::new_snippet_choices(
9613 id,
9614 true,
9615 choices,
9616 selection,
9617 buffer,
9618 snippet_sort_order,
9619 ),
9620 ));
9621 }
9622
9623 pub fn insert_snippet(
9624 &mut self,
9625 insertion_ranges: &[Range<usize>],
9626 snippet: Snippet,
9627 window: &mut Window,
9628 cx: &mut Context<Self>,
9629 ) -> Result<()> {
9630 struct Tabstop<T> {
9631 is_end_tabstop: bool,
9632 ranges: Vec<Range<T>>,
9633 choices: Option<Vec<String>>,
9634 }
9635
9636 let tabstops = self.buffer.update(cx, |buffer, cx| {
9637 let snippet_text: Arc<str> = snippet.text.clone().into();
9638 let edits = insertion_ranges
9639 .iter()
9640 .cloned()
9641 .map(|range| (range, snippet_text.clone()));
9642 let autoindent_mode = AutoindentMode::Block {
9643 original_indent_columns: Vec::new(),
9644 };
9645 buffer.edit(edits, Some(autoindent_mode), cx);
9646
9647 let snapshot = &*buffer.read(cx);
9648 let snippet = &snippet;
9649 snippet
9650 .tabstops
9651 .iter()
9652 .map(|tabstop| {
9653 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9654 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9655 });
9656 let mut tabstop_ranges = tabstop
9657 .ranges
9658 .iter()
9659 .flat_map(|tabstop_range| {
9660 let mut delta = 0_isize;
9661 insertion_ranges.iter().map(move |insertion_range| {
9662 let insertion_start = insertion_range.start as isize + delta;
9663 delta +=
9664 snippet.text.len() as isize - insertion_range.len() as isize;
9665
9666 let start = ((insertion_start + tabstop_range.start) as usize)
9667 .min(snapshot.len());
9668 let end = ((insertion_start + tabstop_range.end) as usize)
9669 .min(snapshot.len());
9670 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9671 })
9672 })
9673 .collect::<Vec<_>>();
9674 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9675
9676 Tabstop {
9677 is_end_tabstop,
9678 ranges: tabstop_ranges,
9679 choices: tabstop.choices.clone(),
9680 }
9681 })
9682 .collect::<Vec<_>>()
9683 });
9684 if let Some(tabstop) = tabstops.first() {
9685 self.change_selections(Default::default(), window, cx, |s| {
9686 // Reverse order so that the first range is the newest created selection.
9687 // Completions will use it and autoscroll will prioritize it.
9688 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9689 });
9690
9691 if let Some(choices) = &tabstop.choices
9692 && let Some(selection) = tabstop.ranges.first()
9693 {
9694 self.show_snippet_choices(choices, selection.clone(), cx)
9695 }
9696
9697 // If we're already at the last tabstop and it's at the end of the snippet,
9698 // we're done, we don't need to keep the state around.
9699 if !tabstop.is_end_tabstop {
9700 let choices = tabstops
9701 .iter()
9702 .map(|tabstop| tabstop.choices.clone())
9703 .collect();
9704
9705 let ranges = tabstops
9706 .into_iter()
9707 .map(|tabstop| tabstop.ranges)
9708 .collect::<Vec<_>>();
9709
9710 self.snippet_stack.push(SnippetState {
9711 active_index: 0,
9712 ranges,
9713 choices,
9714 });
9715 }
9716
9717 // Check whether the just-entered snippet ends with an auto-closable bracket.
9718 if self.autoclose_regions.is_empty() {
9719 let snapshot = self.buffer.read(cx).snapshot(cx);
9720 let mut all_selections = self.selections.all::<Point>(cx);
9721 for selection in &mut all_selections {
9722 let selection_head = selection.head();
9723 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9724 continue;
9725 };
9726
9727 let mut bracket_pair = None;
9728 let max_lookup_length = scope
9729 .brackets()
9730 .map(|(pair, _)| {
9731 pair.start
9732 .as_str()
9733 .chars()
9734 .count()
9735 .max(pair.end.as_str().chars().count())
9736 })
9737 .max();
9738 if let Some(max_lookup_length) = max_lookup_length {
9739 let next_text = snapshot
9740 .chars_at(selection_head)
9741 .take(max_lookup_length)
9742 .collect::<String>();
9743 let prev_text = snapshot
9744 .reversed_chars_at(selection_head)
9745 .take(max_lookup_length)
9746 .collect::<String>();
9747
9748 for (pair, enabled) in scope.brackets() {
9749 if enabled
9750 && pair.close
9751 && prev_text.starts_with(pair.start.as_str())
9752 && next_text.starts_with(pair.end.as_str())
9753 {
9754 bracket_pair = Some(pair.clone());
9755 break;
9756 }
9757 }
9758 }
9759
9760 if let Some(pair) = bracket_pair {
9761 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9762 let autoclose_enabled =
9763 self.use_autoclose && snapshot_settings.use_autoclose;
9764 if autoclose_enabled {
9765 let start = snapshot.anchor_after(selection_head);
9766 let end = snapshot.anchor_after(selection_head);
9767 self.autoclose_regions.push(AutocloseRegion {
9768 selection_id: selection.id,
9769 range: start..end,
9770 pair,
9771 });
9772 }
9773 }
9774 }
9775 }
9776 }
9777 Ok(())
9778 }
9779
9780 pub fn move_to_next_snippet_tabstop(
9781 &mut self,
9782 window: &mut Window,
9783 cx: &mut Context<Self>,
9784 ) -> bool {
9785 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9786 }
9787
9788 pub fn move_to_prev_snippet_tabstop(
9789 &mut self,
9790 window: &mut Window,
9791 cx: &mut Context<Self>,
9792 ) -> bool {
9793 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9794 }
9795
9796 pub fn move_to_snippet_tabstop(
9797 &mut self,
9798 bias: Bias,
9799 window: &mut Window,
9800 cx: &mut Context<Self>,
9801 ) -> bool {
9802 if let Some(mut snippet) = self.snippet_stack.pop() {
9803 match bias {
9804 Bias::Left => {
9805 if snippet.active_index > 0 {
9806 snippet.active_index -= 1;
9807 } else {
9808 self.snippet_stack.push(snippet);
9809 return false;
9810 }
9811 }
9812 Bias::Right => {
9813 if snippet.active_index + 1 < snippet.ranges.len() {
9814 snippet.active_index += 1;
9815 } else {
9816 self.snippet_stack.push(snippet);
9817 return false;
9818 }
9819 }
9820 }
9821 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9822 self.change_selections(Default::default(), window, cx, |s| {
9823 // Reverse order so that the first range is the newest created selection.
9824 // Completions will use it and autoscroll will prioritize it.
9825 s.select_ranges(current_ranges.iter().rev().cloned())
9826 });
9827
9828 if let Some(choices) = &snippet.choices[snippet.active_index]
9829 && let Some(selection) = current_ranges.first()
9830 {
9831 self.show_snippet_choices(choices, selection.clone(), cx);
9832 }
9833
9834 // If snippet state is not at the last tabstop, push it back on the stack
9835 if snippet.active_index + 1 < snippet.ranges.len() {
9836 self.snippet_stack.push(snippet);
9837 }
9838 return true;
9839 }
9840 }
9841
9842 false
9843 }
9844
9845 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9846 self.transact(window, cx, |this, window, cx| {
9847 this.select_all(&SelectAll, window, cx);
9848 this.insert("", window, cx);
9849 });
9850 }
9851
9852 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9853 if self.read_only(cx) {
9854 return;
9855 }
9856 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9857 self.transact(window, cx, |this, window, cx| {
9858 this.select_autoclose_pair(window, cx);
9859 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9860 if !this.linked_edit_ranges.is_empty() {
9861 let selections = this.selections.all::<MultiBufferPoint>(cx);
9862 let snapshot = this.buffer.read(cx).snapshot(cx);
9863
9864 for selection in selections.iter() {
9865 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9866 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9867 if selection_start.buffer_id != selection_end.buffer_id {
9868 continue;
9869 }
9870 if let Some(ranges) =
9871 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9872 {
9873 for (buffer, entries) in ranges {
9874 linked_ranges.entry(buffer).or_default().extend(entries);
9875 }
9876 }
9877 }
9878 }
9879
9880 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9881 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9882 for selection in &mut selections {
9883 if selection.is_empty() {
9884 let old_head = selection.head();
9885 let mut new_head =
9886 movement::left(&display_map, old_head.to_display_point(&display_map))
9887 .to_point(&display_map);
9888 if let Some((buffer, line_buffer_range)) = display_map
9889 .buffer_snapshot
9890 .buffer_line_for_row(MultiBufferRow(old_head.row))
9891 {
9892 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9893 let indent_len = match indent_size.kind {
9894 IndentKind::Space => {
9895 buffer.settings_at(line_buffer_range.start, cx).tab_size
9896 }
9897 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9898 };
9899 if old_head.column <= indent_size.len && old_head.column > 0 {
9900 let indent_len = indent_len.get();
9901 new_head = cmp::min(
9902 new_head,
9903 MultiBufferPoint::new(
9904 old_head.row,
9905 ((old_head.column - 1) / indent_len) * indent_len,
9906 ),
9907 );
9908 }
9909 }
9910
9911 selection.set_head(new_head, SelectionGoal::None);
9912 }
9913 }
9914
9915 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9916 this.insert("", window, cx);
9917 let empty_str: Arc<str> = Arc::from("");
9918 for (buffer, edits) in linked_ranges {
9919 let snapshot = buffer.read(cx).snapshot();
9920 use text::ToPoint as TP;
9921
9922 let edits = edits
9923 .into_iter()
9924 .map(|range| {
9925 let end_point = TP::to_point(&range.end, &snapshot);
9926 let mut start_point = TP::to_point(&range.start, &snapshot);
9927
9928 if end_point == start_point {
9929 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9930 .saturating_sub(1);
9931 start_point =
9932 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9933 };
9934
9935 (start_point..end_point, empty_str.clone())
9936 })
9937 .sorted_by_key(|(range, _)| range.start)
9938 .collect::<Vec<_>>();
9939 buffer.update(cx, |this, cx| {
9940 this.edit(edits, None, cx);
9941 })
9942 }
9943 this.refresh_edit_prediction(true, false, window, cx);
9944 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9945 });
9946 }
9947
9948 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9949 if self.read_only(cx) {
9950 return;
9951 }
9952 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9953 self.transact(window, cx, |this, window, cx| {
9954 this.change_selections(Default::default(), window, cx, |s| {
9955 s.move_with(|map, selection| {
9956 if selection.is_empty() {
9957 let cursor = movement::right(map, selection.head());
9958 selection.end = cursor;
9959 selection.reversed = true;
9960 selection.goal = SelectionGoal::None;
9961 }
9962 })
9963 });
9964 this.insert("", window, cx);
9965 this.refresh_edit_prediction(true, false, window, cx);
9966 });
9967 }
9968
9969 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9970 if self.mode.is_single_line() {
9971 cx.propagate();
9972 return;
9973 }
9974
9975 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9976 if self.move_to_prev_snippet_tabstop(window, cx) {
9977 return;
9978 }
9979 self.outdent(&Outdent, window, cx);
9980 }
9981
9982 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9983 if self.mode.is_single_line() {
9984 cx.propagate();
9985 return;
9986 }
9987
9988 if self.move_to_next_snippet_tabstop(window, cx) {
9989 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9990 return;
9991 }
9992 if self.read_only(cx) {
9993 return;
9994 }
9995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9996 let mut selections = self.selections.all_adjusted(cx);
9997 let buffer = self.buffer.read(cx);
9998 let snapshot = buffer.snapshot(cx);
9999 let rows_iter = selections.iter().map(|s| s.head().row);
10000 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10001
10002 let has_some_cursor_in_whitespace = selections
10003 .iter()
10004 .filter(|selection| selection.is_empty())
10005 .any(|selection| {
10006 let cursor = selection.head();
10007 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10008 cursor.column < current_indent.len
10009 });
10010
10011 let mut edits = Vec::new();
10012 let mut prev_edited_row = 0;
10013 let mut row_delta = 0;
10014 for selection in &mut selections {
10015 if selection.start.row != prev_edited_row {
10016 row_delta = 0;
10017 }
10018 prev_edited_row = selection.end.row;
10019
10020 // If the selection is non-empty, then increase the indentation of the selected lines.
10021 if !selection.is_empty() {
10022 row_delta =
10023 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10024 continue;
10025 }
10026
10027 let cursor = selection.head();
10028 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10029 if let Some(suggested_indent) =
10030 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10031 {
10032 // Don't do anything if already at suggested indent
10033 // and there is any other cursor which is not
10034 if has_some_cursor_in_whitespace
10035 && cursor.column == current_indent.len
10036 && current_indent.len == suggested_indent.len
10037 {
10038 continue;
10039 }
10040
10041 // Adjust line and move cursor to suggested indent
10042 // if cursor is not at suggested indent
10043 if cursor.column < suggested_indent.len
10044 && cursor.column <= current_indent.len
10045 && current_indent.len <= suggested_indent.len
10046 {
10047 selection.start = Point::new(cursor.row, suggested_indent.len);
10048 selection.end = selection.start;
10049 if row_delta == 0 {
10050 edits.extend(Buffer::edit_for_indent_size_adjustment(
10051 cursor.row,
10052 current_indent,
10053 suggested_indent,
10054 ));
10055 row_delta = suggested_indent.len - current_indent.len;
10056 }
10057 continue;
10058 }
10059
10060 // If current indent is more than suggested indent
10061 // only move cursor to current indent and skip indent
10062 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10063 selection.start = Point::new(cursor.row, current_indent.len);
10064 selection.end = selection.start;
10065 continue;
10066 }
10067 }
10068
10069 // Otherwise, insert a hard or soft tab.
10070 let settings = buffer.language_settings_at(cursor, cx);
10071 let tab_size = if settings.hard_tabs {
10072 IndentSize::tab()
10073 } else {
10074 let tab_size = settings.tab_size.get();
10075 let indent_remainder = snapshot
10076 .text_for_range(Point::new(cursor.row, 0)..cursor)
10077 .flat_map(str::chars)
10078 .fold(row_delta % tab_size, |counter: u32, c| {
10079 if c == '\t' {
10080 0
10081 } else {
10082 (counter + 1) % tab_size
10083 }
10084 });
10085
10086 let chars_to_next_tab_stop = tab_size - indent_remainder;
10087 IndentSize::spaces(chars_to_next_tab_stop)
10088 };
10089 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10090 selection.end = selection.start;
10091 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10092 row_delta += tab_size.len;
10093 }
10094
10095 self.transact(window, cx, |this, window, cx| {
10096 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10097 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10098 this.refresh_edit_prediction(true, false, window, cx);
10099 });
10100 }
10101
10102 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10103 if self.read_only(cx) {
10104 return;
10105 }
10106 if self.mode.is_single_line() {
10107 cx.propagate();
10108 return;
10109 }
10110
10111 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10112 let mut selections = self.selections.all::<Point>(cx);
10113 let mut prev_edited_row = 0;
10114 let mut row_delta = 0;
10115 let mut edits = Vec::new();
10116 let buffer = self.buffer.read(cx);
10117 let snapshot = buffer.snapshot(cx);
10118 for selection in &mut selections {
10119 if selection.start.row != prev_edited_row {
10120 row_delta = 0;
10121 }
10122 prev_edited_row = selection.end.row;
10123
10124 row_delta =
10125 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10126 }
10127
10128 self.transact(window, cx, |this, window, cx| {
10129 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10130 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10131 });
10132 }
10133
10134 fn indent_selection(
10135 buffer: &MultiBuffer,
10136 snapshot: &MultiBufferSnapshot,
10137 selection: &mut Selection<Point>,
10138 edits: &mut Vec<(Range<Point>, String)>,
10139 delta_for_start_row: u32,
10140 cx: &App,
10141 ) -> u32 {
10142 let settings = buffer.language_settings_at(selection.start, cx);
10143 let tab_size = settings.tab_size.get();
10144 let indent_kind = if settings.hard_tabs {
10145 IndentKind::Tab
10146 } else {
10147 IndentKind::Space
10148 };
10149 let mut start_row = selection.start.row;
10150 let mut end_row = selection.end.row + 1;
10151
10152 // If a selection ends at the beginning of a line, don't indent
10153 // that last line.
10154 if selection.end.column == 0 && selection.end.row > selection.start.row {
10155 end_row -= 1;
10156 }
10157
10158 // Avoid re-indenting a row that has already been indented by a
10159 // previous selection, but still update this selection's column
10160 // to reflect that indentation.
10161 if delta_for_start_row > 0 {
10162 start_row += 1;
10163 selection.start.column += delta_for_start_row;
10164 if selection.end.row == selection.start.row {
10165 selection.end.column += delta_for_start_row;
10166 }
10167 }
10168
10169 let mut delta_for_end_row = 0;
10170 let has_multiple_rows = start_row + 1 != end_row;
10171 for row in start_row..end_row {
10172 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10173 let indent_delta = match (current_indent.kind, indent_kind) {
10174 (IndentKind::Space, IndentKind::Space) => {
10175 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10176 IndentSize::spaces(columns_to_next_tab_stop)
10177 }
10178 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10179 (_, IndentKind::Tab) => IndentSize::tab(),
10180 };
10181
10182 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10183 0
10184 } else {
10185 selection.start.column
10186 };
10187 let row_start = Point::new(row, start);
10188 edits.push((
10189 row_start..row_start,
10190 indent_delta.chars().collect::<String>(),
10191 ));
10192
10193 // Update this selection's endpoints to reflect the indentation.
10194 if row == selection.start.row {
10195 selection.start.column += indent_delta.len;
10196 }
10197 if row == selection.end.row {
10198 selection.end.column += indent_delta.len;
10199 delta_for_end_row = indent_delta.len;
10200 }
10201 }
10202
10203 if selection.start.row == selection.end.row {
10204 delta_for_start_row + delta_for_end_row
10205 } else {
10206 delta_for_end_row
10207 }
10208 }
10209
10210 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10211 if self.read_only(cx) {
10212 return;
10213 }
10214 if self.mode.is_single_line() {
10215 cx.propagate();
10216 return;
10217 }
10218
10219 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10220 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10221 let selections = self.selections.all::<Point>(cx);
10222 let mut deletion_ranges = Vec::new();
10223 let mut last_outdent = None;
10224 {
10225 let buffer = self.buffer.read(cx);
10226 let snapshot = buffer.snapshot(cx);
10227 for selection in &selections {
10228 let settings = buffer.language_settings_at(selection.start, cx);
10229 let tab_size = settings.tab_size.get();
10230 let mut rows = selection.spanned_rows(false, &display_map);
10231
10232 // Avoid re-outdenting a row that has already been outdented by a
10233 // previous selection.
10234 if let Some(last_row) = last_outdent
10235 && last_row == rows.start
10236 {
10237 rows.start = rows.start.next_row();
10238 }
10239 let has_multiple_rows = rows.len() > 1;
10240 for row in rows.iter_rows() {
10241 let indent_size = snapshot.indent_size_for_line(row);
10242 if indent_size.len > 0 {
10243 let deletion_len = match indent_size.kind {
10244 IndentKind::Space => {
10245 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10246 if columns_to_prev_tab_stop == 0 {
10247 tab_size
10248 } else {
10249 columns_to_prev_tab_stop
10250 }
10251 }
10252 IndentKind::Tab => 1,
10253 };
10254 let start = if has_multiple_rows
10255 || deletion_len > selection.start.column
10256 || indent_size.len < selection.start.column
10257 {
10258 0
10259 } else {
10260 selection.start.column - deletion_len
10261 };
10262 deletion_ranges.push(
10263 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10264 );
10265 last_outdent = Some(row);
10266 }
10267 }
10268 }
10269 }
10270
10271 self.transact(window, cx, |this, window, cx| {
10272 this.buffer.update(cx, |buffer, cx| {
10273 let empty_str: Arc<str> = Arc::default();
10274 buffer.edit(
10275 deletion_ranges
10276 .into_iter()
10277 .map(|range| (range, empty_str.clone())),
10278 None,
10279 cx,
10280 );
10281 });
10282 let selections = this.selections.all::<usize>(cx);
10283 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10284 });
10285 }
10286
10287 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10288 if self.read_only(cx) {
10289 return;
10290 }
10291 if self.mode.is_single_line() {
10292 cx.propagate();
10293 return;
10294 }
10295
10296 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10297 let selections = self
10298 .selections
10299 .all::<usize>(cx)
10300 .into_iter()
10301 .map(|s| s.range());
10302
10303 self.transact(window, cx, |this, window, cx| {
10304 this.buffer.update(cx, |buffer, cx| {
10305 buffer.autoindent_ranges(selections, cx);
10306 });
10307 let selections = this.selections.all::<usize>(cx);
10308 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10309 });
10310 }
10311
10312 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10313 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10315 let selections = self.selections.all::<Point>(cx);
10316
10317 let mut new_cursors = Vec::new();
10318 let mut edit_ranges = Vec::new();
10319 let mut selections = selections.iter().peekable();
10320 while let Some(selection) = selections.next() {
10321 let mut rows = selection.spanned_rows(false, &display_map);
10322 let goal_display_column = selection.head().to_display_point(&display_map).column();
10323
10324 // Accumulate contiguous regions of rows that we want to delete.
10325 while let Some(next_selection) = selections.peek() {
10326 let next_rows = next_selection.spanned_rows(false, &display_map);
10327 if next_rows.start <= rows.end {
10328 rows.end = next_rows.end;
10329 selections.next().unwrap();
10330 } else {
10331 break;
10332 }
10333 }
10334
10335 let buffer = &display_map.buffer_snapshot;
10336 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10337 let edit_end;
10338 let cursor_buffer_row;
10339 if buffer.max_point().row >= rows.end.0 {
10340 // If there's a line after the range, delete the \n from the end of the row range
10341 // and position the cursor on the next line.
10342 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10343 cursor_buffer_row = rows.end;
10344 } else {
10345 // If there isn't a line after the range, delete the \n from the line before the
10346 // start of the row range and position the cursor there.
10347 edit_start = edit_start.saturating_sub(1);
10348 edit_end = buffer.len();
10349 cursor_buffer_row = rows.start.previous_row();
10350 }
10351
10352 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10353 *cursor.column_mut() =
10354 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10355
10356 new_cursors.push((
10357 selection.id,
10358 buffer.anchor_after(cursor.to_point(&display_map)),
10359 ));
10360 edit_ranges.push(edit_start..edit_end);
10361 }
10362
10363 self.transact(window, cx, |this, window, cx| {
10364 let buffer = this.buffer.update(cx, |buffer, cx| {
10365 let empty_str: Arc<str> = Arc::default();
10366 buffer.edit(
10367 edit_ranges
10368 .into_iter()
10369 .map(|range| (range, empty_str.clone())),
10370 None,
10371 cx,
10372 );
10373 buffer.snapshot(cx)
10374 });
10375 let new_selections = new_cursors
10376 .into_iter()
10377 .map(|(id, cursor)| {
10378 let cursor = cursor.to_point(&buffer);
10379 Selection {
10380 id,
10381 start: cursor,
10382 end: cursor,
10383 reversed: false,
10384 goal: SelectionGoal::None,
10385 }
10386 })
10387 .collect();
10388
10389 this.change_selections(Default::default(), window, cx, |s| {
10390 s.select(new_selections);
10391 });
10392 });
10393 }
10394
10395 pub fn join_lines_impl(
10396 &mut self,
10397 insert_whitespace: bool,
10398 window: &mut Window,
10399 cx: &mut Context<Self>,
10400 ) {
10401 if self.read_only(cx) {
10402 return;
10403 }
10404 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10405 for selection in self.selections.all::<Point>(cx) {
10406 let start = MultiBufferRow(selection.start.row);
10407 // Treat single line selections as if they include the next line. Otherwise this action
10408 // would do nothing for single line selections individual cursors.
10409 let end = if selection.start.row == selection.end.row {
10410 MultiBufferRow(selection.start.row + 1)
10411 } else {
10412 MultiBufferRow(selection.end.row)
10413 };
10414
10415 if let Some(last_row_range) = row_ranges.last_mut()
10416 && start <= last_row_range.end
10417 {
10418 last_row_range.end = end;
10419 continue;
10420 }
10421 row_ranges.push(start..end);
10422 }
10423
10424 let snapshot = self.buffer.read(cx).snapshot(cx);
10425 let mut cursor_positions = Vec::new();
10426 for row_range in &row_ranges {
10427 let anchor = snapshot.anchor_before(Point::new(
10428 row_range.end.previous_row().0,
10429 snapshot.line_len(row_range.end.previous_row()),
10430 ));
10431 cursor_positions.push(anchor..anchor);
10432 }
10433
10434 self.transact(window, cx, |this, window, cx| {
10435 for row_range in row_ranges.into_iter().rev() {
10436 for row in row_range.iter_rows().rev() {
10437 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10438 let next_line_row = row.next_row();
10439 let indent = snapshot.indent_size_for_line(next_line_row);
10440 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10441
10442 let replace =
10443 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10444 " "
10445 } else {
10446 ""
10447 };
10448
10449 this.buffer.update(cx, |buffer, cx| {
10450 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10451 });
10452 }
10453 }
10454
10455 this.change_selections(Default::default(), window, cx, |s| {
10456 s.select_anchor_ranges(cursor_positions)
10457 });
10458 });
10459 }
10460
10461 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10462 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10463 self.join_lines_impl(true, window, cx);
10464 }
10465
10466 pub fn sort_lines_case_sensitive(
10467 &mut self,
10468 _: &SortLinesCaseSensitive,
10469 window: &mut Window,
10470 cx: &mut Context<Self>,
10471 ) {
10472 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10473 }
10474
10475 pub fn sort_lines_by_length(
10476 &mut self,
10477 _: &SortLinesByLength,
10478 window: &mut Window,
10479 cx: &mut Context<Self>,
10480 ) {
10481 self.manipulate_immutable_lines(window, cx, |lines| {
10482 lines.sort_by_key(|&line| line.chars().count())
10483 })
10484 }
10485
10486 pub fn sort_lines_case_insensitive(
10487 &mut self,
10488 _: &SortLinesCaseInsensitive,
10489 window: &mut Window,
10490 cx: &mut Context<Self>,
10491 ) {
10492 self.manipulate_immutable_lines(window, cx, |lines| {
10493 lines.sort_by_key(|line| line.to_lowercase())
10494 })
10495 }
10496
10497 pub fn unique_lines_case_insensitive(
10498 &mut self,
10499 _: &UniqueLinesCaseInsensitive,
10500 window: &mut Window,
10501 cx: &mut Context<Self>,
10502 ) {
10503 self.manipulate_immutable_lines(window, cx, |lines| {
10504 let mut seen = HashSet::default();
10505 lines.retain(|line| seen.insert(line.to_lowercase()));
10506 })
10507 }
10508
10509 pub fn unique_lines_case_sensitive(
10510 &mut self,
10511 _: &UniqueLinesCaseSensitive,
10512 window: &mut Window,
10513 cx: &mut Context<Self>,
10514 ) {
10515 self.manipulate_immutable_lines(window, cx, |lines| {
10516 let mut seen = HashSet::default();
10517 lines.retain(|line| seen.insert(*line));
10518 })
10519 }
10520
10521 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10522 let snapshot = self.buffer.read(cx).snapshot(cx);
10523 for selection in self.selections.disjoint_anchors_arc().iter() {
10524 if snapshot
10525 .language_at(selection.start)
10526 .and_then(|lang| lang.config().wrap_characters.as_ref())
10527 .is_some()
10528 {
10529 return true;
10530 }
10531 }
10532 false
10533 }
10534
10535 fn wrap_selections_in_tag(
10536 &mut self,
10537 _: &WrapSelectionsInTag,
10538 window: &mut Window,
10539 cx: &mut Context<Self>,
10540 ) {
10541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10542
10543 let snapshot = self.buffer.read(cx).snapshot(cx);
10544
10545 let mut edits = Vec::new();
10546 let mut boundaries = Vec::new();
10547
10548 for selection in self.selections.all::<Point>(cx).iter() {
10549 let Some(wrap_config) = snapshot
10550 .language_at(selection.start)
10551 .and_then(|lang| lang.config().wrap_characters.clone())
10552 else {
10553 continue;
10554 };
10555
10556 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10557 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10558
10559 let start_before = snapshot.anchor_before(selection.start);
10560 let end_after = snapshot.anchor_after(selection.end);
10561
10562 edits.push((start_before..start_before, open_tag));
10563 edits.push((end_after..end_after, close_tag));
10564
10565 boundaries.push((
10566 start_before,
10567 end_after,
10568 wrap_config.start_prefix.len(),
10569 wrap_config.end_suffix.len(),
10570 ));
10571 }
10572
10573 if edits.is_empty() {
10574 return;
10575 }
10576
10577 self.transact(window, cx, |this, window, cx| {
10578 let buffer = this.buffer.update(cx, |buffer, cx| {
10579 buffer.edit(edits, None, cx);
10580 buffer.snapshot(cx)
10581 });
10582
10583 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10584 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10585 boundaries.into_iter()
10586 {
10587 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10588 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10589 new_selections.push(open_offset..open_offset);
10590 new_selections.push(close_offset..close_offset);
10591 }
10592
10593 this.change_selections(Default::default(), window, cx, |s| {
10594 s.select_ranges(new_selections);
10595 });
10596
10597 this.request_autoscroll(Autoscroll::fit(), cx);
10598 });
10599 }
10600
10601 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10602 let Some(project) = self.project.clone() else {
10603 return;
10604 };
10605 self.reload(project, window, cx)
10606 .detach_and_notify_err(window, cx);
10607 }
10608
10609 pub fn restore_file(
10610 &mut self,
10611 _: &::git::RestoreFile,
10612 window: &mut Window,
10613 cx: &mut Context<Self>,
10614 ) {
10615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10616 let mut buffer_ids = HashSet::default();
10617 let snapshot = self.buffer().read(cx).snapshot(cx);
10618 for selection in self.selections.all::<usize>(cx) {
10619 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10620 }
10621
10622 let buffer = self.buffer().read(cx);
10623 let ranges = buffer_ids
10624 .into_iter()
10625 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10626 .collect::<Vec<_>>();
10627
10628 self.restore_hunks_in_ranges(ranges, window, cx);
10629 }
10630
10631 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10633 let selections = self
10634 .selections
10635 .all(cx)
10636 .into_iter()
10637 .map(|s| s.range())
10638 .collect();
10639 self.restore_hunks_in_ranges(selections, window, cx);
10640 }
10641
10642 pub fn restore_hunks_in_ranges(
10643 &mut self,
10644 ranges: Vec<Range<Point>>,
10645 window: &mut Window,
10646 cx: &mut Context<Editor>,
10647 ) {
10648 let mut revert_changes = HashMap::default();
10649 let chunk_by = self
10650 .snapshot(window, cx)
10651 .hunks_for_ranges(ranges)
10652 .into_iter()
10653 .chunk_by(|hunk| hunk.buffer_id);
10654 for (buffer_id, hunks) in &chunk_by {
10655 let hunks = hunks.collect::<Vec<_>>();
10656 for hunk in &hunks {
10657 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10658 }
10659 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10660 }
10661 drop(chunk_by);
10662 if !revert_changes.is_empty() {
10663 self.transact(window, cx, |editor, window, cx| {
10664 editor.restore(revert_changes, window, cx);
10665 });
10666 }
10667 }
10668
10669 pub fn open_active_item_in_terminal(
10670 &mut self,
10671 _: &OpenInTerminal,
10672 window: &mut Window,
10673 cx: &mut Context<Self>,
10674 ) {
10675 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10676 let project_path = buffer.read(cx).project_path(cx)?;
10677 let project = self.project()?.read(cx);
10678 let entry = project.entry_for_path(&project_path, cx)?;
10679 let parent = match &entry.canonical_path {
10680 Some(canonical_path) => canonical_path.to_path_buf(),
10681 None => project.absolute_path(&project_path, cx)?,
10682 }
10683 .parent()?
10684 .to_path_buf();
10685 Some(parent)
10686 }) {
10687 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10688 }
10689 }
10690
10691 fn set_breakpoint_context_menu(
10692 &mut self,
10693 display_row: DisplayRow,
10694 position: Option<Anchor>,
10695 clicked_point: gpui::Point<Pixels>,
10696 window: &mut Window,
10697 cx: &mut Context<Self>,
10698 ) {
10699 let source = self
10700 .buffer
10701 .read(cx)
10702 .snapshot(cx)
10703 .anchor_before(Point::new(display_row.0, 0u32));
10704
10705 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10706
10707 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10708 self,
10709 source,
10710 clicked_point,
10711 context_menu,
10712 window,
10713 cx,
10714 );
10715 }
10716
10717 fn add_edit_breakpoint_block(
10718 &mut self,
10719 anchor: Anchor,
10720 breakpoint: &Breakpoint,
10721 edit_action: BreakpointPromptEditAction,
10722 window: &mut Window,
10723 cx: &mut Context<Self>,
10724 ) {
10725 let weak_editor = cx.weak_entity();
10726 let bp_prompt = cx.new(|cx| {
10727 BreakpointPromptEditor::new(
10728 weak_editor,
10729 anchor,
10730 breakpoint.clone(),
10731 edit_action,
10732 window,
10733 cx,
10734 )
10735 });
10736
10737 let height = bp_prompt.update(cx, |this, cx| {
10738 this.prompt
10739 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10740 });
10741 let cloned_prompt = bp_prompt.clone();
10742 let blocks = vec![BlockProperties {
10743 style: BlockStyle::Sticky,
10744 placement: BlockPlacement::Above(anchor),
10745 height: Some(height),
10746 render: Arc::new(move |cx| {
10747 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10748 cloned_prompt.clone().into_any_element()
10749 }),
10750 priority: 0,
10751 }];
10752
10753 let focus_handle = bp_prompt.focus_handle(cx);
10754 window.focus(&focus_handle);
10755
10756 let block_ids = self.insert_blocks(blocks, None, cx);
10757 bp_prompt.update(cx, |prompt, _| {
10758 prompt.add_block_ids(block_ids);
10759 });
10760 }
10761
10762 pub(crate) fn breakpoint_at_row(
10763 &self,
10764 row: u32,
10765 window: &mut Window,
10766 cx: &mut Context<Self>,
10767 ) -> Option<(Anchor, Breakpoint)> {
10768 let snapshot = self.snapshot(window, cx);
10769 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10770
10771 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10772 }
10773
10774 pub(crate) fn breakpoint_at_anchor(
10775 &self,
10776 breakpoint_position: Anchor,
10777 snapshot: &EditorSnapshot,
10778 cx: &mut Context<Self>,
10779 ) -> Option<(Anchor, Breakpoint)> {
10780 let buffer = self
10781 .buffer
10782 .read(cx)
10783 .buffer_for_anchor(breakpoint_position, cx)?;
10784
10785 let enclosing_excerpt = breakpoint_position.excerpt_id;
10786 let buffer_snapshot = buffer.read(cx).snapshot();
10787
10788 let row = buffer_snapshot
10789 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10790 .row;
10791
10792 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10793 let anchor_end = snapshot
10794 .buffer_snapshot
10795 .anchor_after(Point::new(row, line_len));
10796
10797 self.breakpoint_store
10798 .as_ref()?
10799 .read_with(cx, |breakpoint_store, cx| {
10800 breakpoint_store
10801 .breakpoints(
10802 &buffer,
10803 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10804 &buffer_snapshot,
10805 cx,
10806 )
10807 .next()
10808 .and_then(|(bp, _)| {
10809 let breakpoint_row = buffer_snapshot
10810 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10811 .row;
10812
10813 if breakpoint_row == row {
10814 snapshot
10815 .buffer_snapshot
10816 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10817 .map(|position| (position, bp.bp.clone()))
10818 } else {
10819 None
10820 }
10821 })
10822 })
10823 }
10824
10825 pub fn edit_log_breakpoint(
10826 &mut self,
10827 _: &EditLogBreakpoint,
10828 window: &mut Window,
10829 cx: &mut Context<Self>,
10830 ) {
10831 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10832 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10833 message: None,
10834 state: BreakpointState::Enabled,
10835 condition: None,
10836 hit_condition: None,
10837 });
10838
10839 self.add_edit_breakpoint_block(
10840 anchor,
10841 &breakpoint,
10842 BreakpointPromptEditAction::Log,
10843 window,
10844 cx,
10845 );
10846 }
10847 }
10848
10849 fn breakpoints_at_cursors(
10850 &self,
10851 window: &mut Window,
10852 cx: &mut Context<Self>,
10853 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10854 let snapshot = self.snapshot(window, cx);
10855 let cursors = self
10856 .selections
10857 .disjoint_anchors_arc()
10858 .iter()
10859 .map(|selection| {
10860 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10861
10862 let breakpoint_position = self
10863 .breakpoint_at_row(cursor_position.row, window, cx)
10864 .map(|bp| bp.0)
10865 .unwrap_or_else(|| {
10866 snapshot
10867 .display_snapshot
10868 .buffer_snapshot
10869 .anchor_after(Point::new(cursor_position.row, 0))
10870 });
10871
10872 let breakpoint = self
10873 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10874 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10875
10876 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10877 })
10878 // 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.
10879 .collect::<HashMap<Anchor, _>>();
10880
10881 cursors.into_iter().collect()
10882 }
10883
10884 pub fn enable_breakpoint(
10885 &mut self,
10886 _: &crate::actions::EnableBreakpoint,
10887 window: &mut Window,
10888 cx: &mut Context<Self>,
10889 ) {
10890 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10891 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10892 continue;
10893 };
10894 self.edit_breakpoint_at_anchor(
10895 anchor,
10896 breakpoint,
10897 BreakpointEditAction::InvertState,
10898 cx,
10899 );
10900 }
10901 }
10902
10903 pub fn disable_breakpoint(
10904 &mut self,
10905 _: &crate::actions::DisableBreakpoint,
10906 window: &mut Window,
10907 cx: &mut Context<Self>,
10908 ) {
10909 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10910 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10911 continue;
10912 };
10913 self.edit_breakpoint_at_anchor(
10914 anchor,
10915 breakpoint,
10916 BreakpointEditAction::InvertState,
10917 cx,
10918 );
10919 }
10920 }
10921
10922 pub fn toggle_breakpoint(
10923 &mut self,
10924 _: &crate::actions::ToggleBreakpoint,
10925 window: &mut Window,
10926 cx: &mut Context<Self>,
10927 ) {
10928 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10929 if let Some(breakpoint) = breakpoint {
10930 self.edit_breakpoint_at_anchor(
10931 anchor,
10932 breakpoint,
10933 BreakpointEditAction::Toggle,
10934 cx,
10935 );
10936 } else {
10937 self.edit_breakpoint_at_anchor(
10938 anchor,
10939 Breakpoint::new_standard(),
10940 BreakpointEditAction::Toggle,
10941 cx,
10942 );
10943 }
10944 }
10945 }
10946
10947 pub fn edit_breakpoint_at_anchor(
10948 &mut self,
10949 breakpoint_position: Anchor,
10950 breakpoint: Breakpoint,
10951 edit_action: BreakpointEditAction,
10952 cx: &mut Context<Self>,
10953 ) {
10954 let Some(breakpoint_store) = &self.breakpoint_store else {
10955 return;
10956 };
10957
10958 let Some(buffer) = self
10959 .buffer
10960 .read(cx)
10961 .buffer_for_anchor(breakpoint_position, cx)
10962 else {
10963 return;
10964 };
10965
10966 breakpoint_store.update(cx, |breakpoint_store, cx| {
10967 breakpoint_store.toggle_breakpoint(
10968 buffer,
10969 BreakpointWithPosition {
10970 position: breakpoint_position.text_anchor,
10971 bp: breakpoint,
10972 },
10973 edit_action,
10974 cx,
10975 );
10976 });
10977
10978 cx.notify();
10979 }
10980
10981 #[cfg(any(test, feature = "test-support"))]
10982 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10983 self.breakpoint_store.clone()
10984 }
10985
10986 pub fn prepare_restore_change(
10987 &self,
10988 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10989 hunk: &MultiBufferDiffHunk,
10990 cx: &mut App,
10991 ) -> Option<()> {
10992 if hunk.is_created_file() {
10993 return None;
10994 }
10995 let buffer = self.buffer.read(cx);
10996 let diff = buffer.diff_for(hunk.buffer_id)?;
10997 let buffer = buffer.buffer(hunk.buffer_id)?;
10998 let buffer = buffer.read(cx);
10999 let original_text = diff
11000 .read(cx)
11001 .base_text()
11002 .as_rope()
11003 .slice(hunk.diff_base_byte_range.clone());
11004 let buffer_snapshot = buffer.snapshot();
11005 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11006 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11007 probe
11008 .0
11009 .start
11010 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11011 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11012 }) {
11013 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11014 Some(())
11015 } else {
11016 None
11017 }
11018 }
11019
11020 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11021 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11022 }
11023
11024 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11025 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11026 }
11027
11028 fn manipulate_lines<M>(
11029 &mut self,
11030 window: &mut Window,
11031 cx: &mut Context<Self>,
11032 mut manipulate: M,
11033 ) where
11034 M: FnMut(&str) -> LineManipulationResult,
11035 {
11036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11037
11038 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11039 let buffer = self.buffer.read(cx).snapshot(cx);
11040
11041 let mut edits = Vec::new();
11042
11043 let selections = self.selections.all::<Point>(cx);
11044 let mut selections = selections.iter().peekable();
11045 let mut contiguous_row_selections = Vec::new();
11046 let mut new_selections = Vec::new();
11047 let mut added_lines = 0;
11048 let mut removed_lines = 0;
11049
11050 while let Some(selection) = selections.next() {
11051 let (start_row, end_row) = consume_contiguous_rows(
11052 &mut contiguous_row_selections,
11053 selection,
11054 &display_map,
11055 &mut selections,
11056 );
11057
11058 let start_point = Point::new(start_row.0, 0);
11059 let end_point = Point::new(
11060 end_row.previous_row().0,
11061 buffer.line_len(end_row.previous_row()),
11062 );
11063 let text = buffer
11064 .text_for_range(start_point..end_point)
11065 .collect::<String>();
11066
11067 let LineManipulationResult {
11068 new_text,
11069 line_count_before,
11070 line_count_after,
11071 } = manipulate(&text);
11072
11073 edits.push((start_point..end_point, new_text));
11074
11075 // Selections must change based on added and removed line count
11076 let start_row =
11077 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11078 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11079 new_selections.push(Selection {
11080 id: selection.id,
11081 start: start_row,
11082 end: end_row,
11083 goal: SelectionGoal::None,
11084 reversed: selection.reversed,
11085 });
11086
11087 if line_count_after > line_count_before {
11088 added_lines += line_count_after - line_count_before;
11089 } else if line_count_before > line_count_after {
11090 removed_lines += line_count_before - line_count_after;
11091 }
11092 }
11093
11094 self.transact(window, cx, |this, window, cx| {
11095 let buffer = this.buffer.update(cx, |buffer, cx| {
11096 buffer.edit(edits, None, cx);
11097 buffer.snapshot(cx)
11098 });
11099
11100 // Recalculate offsets on newly edited buffer
11101 let new_selections = new_selections
11102 .iter()
11103 .map(|s| {
11104 let start_point = Point::new(s.start.0, 0);
11105 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11106 Selection {
11107 id: s.id,
11108 start: buffer.point_to_offset(start_point),
11109 end: buffer.point_to_offset(end_point),
11110 goal: s.goal,
11111 reversed: s.reversed,
11112 }
11113 })
11114 .collect();
11115
11116 this.change_selections(Default::default(), window, cx, |s| {
11117 s.select(new_selections);
11118 });
11119
11120 this.request_autoscroll(Autoscroll::fit(), cx);
11121 });
11122 }
11123
11124 fn manipulate_immutable_lines<Fn>(
11125 &mut self,
11126 window: &mut Window,
11127 cx: &mut Context<Self>,
11128 mut callback: Fn,
11129 ) where
11130 Fn: FnMut(&mut Vec<&str>),
11131 {
11132 self.manipulate_lines(window, cx, |text| {
11133 let mut lines: Vec<&str> = text.split('\n').collect();
11134 let line_count_before = lines.len();
11135
11136 callback(&mut lines);
11137
11138 LineManipulationResult {
11139 new_text: lines.join("\n"),
11140 line_count_before,
11141 line_count_after: lines.len(),
11142 }
11143 });
11144 }
11145
11146 fn manipulate_mutable_lines<Fn>(
11147 &mut self,
11148 window: &mut Window,
11149 cx: &mut Context<Self>,
11150 mut callback: Fn,
11151 ) where
11152 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11153 {
11154 self.manipulate_lines(window, cx, |text| {
11155 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11156 let line_count_before = lines.len();
11157
11158 callback(&mut lines);
11159
11160 LineManipulationResult {
11161 new_text: lines.join("\n"),
11162 line_count_before,
11163 line_count_after: lines.len(),
11164 }
11165 });
11166 }
11167
11168 pub fn convert_indentation_to_spaces(
11169 &mut self,
11170 _: &ConvertIndentationToSpaces,
11171 window: &mut Window,
11172 cx: &mut Context<Self>,
11173 ) {
11174 let settings = self.buffer.read(cx).language_settings(cx);
11175 let tab_size = settings.tab_size.get() as usize;
11176
11177 self.manipulate_mutable_lines(window, cx, |lines| {
11178 // Allocates a reasonably sized scratch buffer once for the whole loop
11179 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11180 // Avoids recomputing spaces that could be inserted many times
11181 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11182 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11183 .collect();
11184
11185 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11186 let mut chars = line.as_ref().chars();
11187 let mut col = 0;
11188 let mut changed = false;
11189
11190 for ch in chars.by_ref() {
11191 match ch {
11192 ' ' => {
11193 reindented_line.push(' ');
11194 col += 1;
11195 }
11196 '\t' => {
11197 // \t are converted to spaces depending on the current column
11198 let spaces_len = tab_size - (col % tab_size);
11199 reindented_line.extend(&space_cache[spaces_len - 1]);
11200 col += spaces_len;
11201 changed = true;
11202 }
11203 _ => {
11204 // If we dont append before break, the character is consumed
11205 reindented_line.push(ch);
11206 break;
11207 }
11208 }
11209 }
11210
11211 if !changed {
11212 reindented_line.clear();
11213 continue;
11214 }
11215 // Append the rest of the line and replace old reference with new one
11216 reindented_line.extend(chars);
11217 *line = Cow::Owned(reindented_line.clone());
11218 reindented_line.clear();
11219 }
11220 });
11221 }
11222
11223 pub fn convert_indentation_to_tabs(
11224 &mut self,
11225 _: &ConvertIndentationToTabs,
11226 window: &mut Window,
11227 cx: &mut Context<Self>,
11228 ) {
11229 let settings = self.buffer.read(cx).language_settings(cx);
11230 let tab_size = settings.tab_size.get() as usize;
11231
11232 self.manipulate_mutable_lines(window, cx, |lines| {
11233 // Allocates a reasonably sized buffer once for the whole loop
11234 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11235 // Avoids recomputing spaces that could be inserted many times
11236 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11237 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11238 .collect();
11239
11240 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11241 let mut chars = line.chars();
11242 let mut spaces_count = 0;
11243 let mut first_non_indent_char = None;
11244 let mut changed = false;
11245
11246 for ch in chars.by_ref() {
11247 match ch {
11248 ' ' => {
11249 // Keep track of spaces. Append \t when we reach tab_size
11250 spaces_count += 1;
11251 changed = true;
11252 if spaces_count == tab_size {
11253 reindented_line.push('\t');
11254 spaces_count = 0;
11255 }
11256 }
11257 '\t' => {
11258 reindented_line.push('\t');
11259 spaces_count = 0;
11260 }
11261 _ => {
11262 // Dont append it yet, we might have remaining spaces
11263 first_non_indent_char = Some(ch);
11264 break;
11265 }
11266 }
11267 }
11268
11269 if !changed {
11270 reindented_line.clear();
11271 continue;
11272 }
11273 // Remaining spaces that didn't make a full tab stop
11274 if spaces_count > 0 {
11275 reindented_line.extend(&space_cache[spaces_count - 1]);
11276 }
11277 // If we consume an extra character that was not indentation, add it back
11278 if let Some(extra_char) = first_non_indent_char {
11279 reindented_line.push(extra_char);
11280 }
11281 // Append the rest of the line and replace old reference with new one
11282 reindented_line.extend(chars);
11283 *line = Cow::Owned(reindented_line.clone());
11284 reindented_line.clear();
11285 }
11286 });
11287 }
11288
11289 pub fn convert_to_upper_case(
11290 &mut self,
11291 _: &ConvertToUpperCase,
11292 window: &mut Window,
11293 cx: &mut Context<Self>,
11294 ) {
11295 self.manipulate_text(window, cx, |text| text.to_uppercase())
11296 }
11297
11298 pub fn convert_to_lower_case(
11299 &mut self,
11300 _: &ConvertToLowerCase,
11301 window: &mut Window,
11302 cx: &mut Context<Self>,
11303 ) {
11304 self.manipulate_text(window, cx, |text| text.to_lowercase())
11305 }
11306
11307 pub fn convert_to_title_case(
11308 &mut self,
11309 _: &ConvertToTitleCase,
11310 window: &mut Window,
11311 cx: &mut Context<Self>,
11312 ) {
11313 self.manipulate_text(window, cx, |text| {
11314 text.split('\n')
11315 .map(|line| line.to_case(Case::Title))
11316 .join("\n")
11317 })
11318 }
11319
11320 pub fn convert_to_snake_case(
11321 &mut self,
11322 _: &ConvertToSnakeCase,
11323 window: &mut Window,
11324 cx: &mut Context<Self>,
11325 ) {
11326 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11327 }
11328
11329 pub fn convert_to_kebab_case(
11330 &mut self,
11331 _: &ConvertToKebabCase,
11332 window: &mut Window,
11333 cx: &mut Context<Self>,
11334 ) {
11335 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11336 }
11337
11338 pub fn convert_to_upper_camel_case(
11339 &mut self,
11340 _: &ConvertToUpperCamelCase,
11341 window: &mut Window,
11342 cx: &mut Context<Self>,
11343 ) {
11344 self.manipulate_text(window, cx, |text| {
11345 text.split('\n')
11346 .map(|line| line.to_case(Case::UpperCamel))
11347 .join("\n")
11348 })
11349 }
11350
11351 pub fn convert_to_lower_camel_case(
11352 &mut self,
11353 _: &ConvertToLowerCamelCase,
11354 window: &mut Window,
11355 cx: &mut Context<Self>,
11356 ) {
11357 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11358 }
11359
11360 pub fn convert_to_opposite_case(
11361 &mut self,
11362 _: &ConvertToOppositeCase,
11363 window: &mut Window,
11364 cx: &mut Context<Self>,
11365 ) {
11366 self.manipulate_text(window, cx, |text| {
11367 text.chars()
11368 .fold(String::with_capacity(text.len()), |mut t, c| {
11369 if c.is_uppercase() {
11370 t.extend(c.to_lowercase());
11371 } else {
11372 t.extend(c.to_uppercase());
11373 }
11374 t
11375 })
11376 })
11377 }
11378
11379 pub fn convert_to_sentence_case(
11380 &mut self,
11381 _: &ConvertToSentenceCase,
11382 window: &mut Window,
11383 cx: &mut Context<Self>,
11384 ) {
11385 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11386 }
11387
11388 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11389 self.manipulate_text(window, cx, |text| {
11390 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11391 if has_upper_case_characters {
11392 text.to_lowercase()
11393 } else {
11394 text.to_uppercase()
11395 }
11396 })
11397 }
11398
11399 pub fn convert_to_rot13(
11400 &mut self,
11401 _: &ConvertToRot13,
11402 window: &mut Window,
11403 cx: &mut Context<Self>,
11404 ) {
11405 self.manipulate_text(window, cx, |text| {
11406 text.chars()
11407 .map(|c| match c {
11408 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11409 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11410 _ => c,
11411 })
11412 .collect()
11413 })
11414 }
11415
11416 pub fn convert_to_rot47(
11417 &mut self,
11418 _: &ConvertToRot47,
11419 window: &mut Window,
11420 cx: &mut Context<Self>,
11421 ) {
11422 self.manipulate_text(window, cx, |text| {
11423 text.chars()
11424 .map(|c| {
11425 let code_point = c as u32;
11426 if code_point >= 33 && code_point <= 126 {
11427 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11428 }
11429 c
11430 })
11431 .collect()
11432 })
11433 }
11434
11435 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11436 where
11437 Fn: FnMut(&str) -> String,
11438 {
11439 let buffer = self.buffer.read(cx).snapshot(cx);
11440
11441 let mut new_selections = Vec::new();
11442 let mut edits = Vec::new();
11443 let mut selection_adjustment = 0i32;
11444
11445 for selection in self.selections.all_adjusted(cx) {
11446 let selection_is_empty = selection.is_empty();
11447
11448 let (start, end) = if selection_is_empty {
11449 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11450 (word_range.start, word_range.end)
11451 } else {
11452 (
11453 buffer.point_to_offset(selection.start),
11454 buffer.point_to_offset(selection.end),
11455 )
11456 };
11457
11458 let text = buffer.text_for_range(start..end).collect::<String>();
11459 let old_length = text.len() as i32;
11460 let text = callback(&text);
11461
11462 new_selections.push(Selection {
11463 start: (start as i32 - selection_adjustment) as usize,
11464 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11465 goal: SelectionGoal::None,
11466 id: selection.id,
11467 reversed: selection.reversed,
11468 });
11469
11470 selection_adjustment += old_length - text.len() as i32;
11471
11472 edits.push((start..end, text));
11473 }
11474
11475 self.transact(window, cx, |this, window, cx| {
11476 this.buffer.update(cx, |buffer, cx| {
11477 buffer.edit(edits, None, cx);
11478 });
11479
11480 this.change_selections(Default::default(), window, cx, |s| {
11481 s.select(new_selections);
11482 });
11483
11484 this.request_autoscroll(Autoscroll::fit(), cx);
11485 });
11486 }
11487
11488 pub fn move_selection_on_drop(
11489 &mut self,
11490 selection: &Selection<Anchor>,
11491 target: DisplayPoint,
11492 is_cut: bool,
11493 window: &mut Window,
11494 cx: &mut Context<Self>,
11495 ) {
11496 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11497 let buffer = &display_map.buffer_snapshot;
11498 let mut edits = Vec::new();
11499 let insert_point = display_map
11500 .clip_point(target, Bias::Left)
11501 .to_point(&display_map);
11502 let text = buffer
11503 .text_for_range(selection.start..selection.end)
11504 .collect::<String>();
11505 if is_cut {
11506 edits.push(((selection.start..selection.end), String::new()));
11507 }
11508 let insert_anchor = buffer.anchor_before(insert_point);
11509 edits.push(((insert_anchor..insert_anchor), text));
11510 let last_edit_start = insert_anchor.bias_left(buffer);
11511 let last_edit_end = insert_anchor.bias_right(buffer);
11512 self.transact(window, cx, |this, window, cx| {
11513 this.buffer.update(cx, |buffer, cx| {
11514 buffer.edit(edits, None, cx);
11515 });
11516 this.change_selections(Default::default(), window, cx, |s| {
11517 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11518 });
11519 });
11520 }
11521
11522 pub fn clear_selection_drag_state(&mut self) {
11523 self.selection_drag_state = SelectionDragState::None;
11524 }
11525
11526 pub fn duplicate(
11527 &mut self,
11528 upwards: bool,
11529 whole_lines: bool,
11530 window: &mut Window,
11531 cx: &mut Context<Self>,
11532 ) {
11533 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11534
11535 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11536 let buffer = &display_map.buffer_snapshot;
11537 let selections = self.selections.all::<Point>(cx);
11538
11539 let mut edits = Vec::new();
11540 let mut selections_iter = selections.iter().peekable();
11541 while let Some(selection) = selections_iter.next() {
11542 let mut rows = selection.spanned_rows(false, &display_map);
11543 // duplicate line-wise
11544 if whole_lines || selection.start == selection.end {
11545 // Avoid duplicating the same lines twice.
11546 while let Some(next_selection) = selections_iter.peek() {
11547 let next_rows = next_selection.spanned_rows(false, &display_map);
11548 if next_rows.start < rows.end {
11549 rows.end = next_rows.end;
11550 selections_iter.next().unwrap();
11551 } else {
11552 break;
11553 }
11554 }
11555
11556 // Copy the text from the selected row region and splice it either at the start
11557 // or end of the region.
11558 let start = Point::new(rows.start.0, 0);
11559 let end = Point::new(
11560 rows.end.previous_row().0,
11561 buffer.line_len(rows.end.previous_row()),
11562 );
11563 let text = buffer
11564 .text_for_range(start..end)
11565 .chain(Some("\n"))
11566 .collect::<String>();
11567 let insert_location = if upwards {
11568 Point::new(rows.end.0, 0)
11569 } else {
11570 start
11571 };
11572 edits.push((insert_location..insert_location, text));
11573 } else {
11574 // duplicate character-wise
11575 let start = selection.start;
11576 let end = selection.end;
11577 let text = buffer.text_for_range(start..end).collect::<String>();
11578 edits.push((selection.end..selection.end, text));
11579 }
11580 }
11581
11582 self.transact(window, cx, |this, _, cx| {
11583 this.buffer.update(cx, |buffer, cx| {
11584 buffer.edit(edits, None, cx);
11585 });
11586
11587 this.request_autoscroll(Autoscroll::fit(), cx);
11588 });
11589 }
11590
11591 pub fn duplicate_line_up(
11592 &mut self,
11593 _: &DuplicateLineUp,
11594 window: &mut Window,
11595 cx: &mut Context<Self>,
11596 ) {
11597 self.duplicate(true, true, window, cx);
11598 }
11599
11600 pub fn duplicate_line_down(
11601 &mut self,
11602 _: &DuplicateLineDown,
11603 window: &mut Window,
11604 cx: &mut Context<Self>,
11605 ) {
11606 self.duplicate(false, true, window, cx);
11607 }
11608
11609 pub fn duplicate_selection(
11610 &mut self,
11611 _: &DuplicateSelection,
11612 window: &mut Window,
11613 cx: &mut Context<Self>,
11614 ) {
11615 self.duplicate(false, false, window, cx);
11616 }
11617
11618 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11619 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11620 if self.mode.is_single_line() {
11621 cx.propagate();
11622 return;
11623 }
11624
11625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11626 let buffer = self.buffer.read(cx).snapshot(cx);
11627
11628 let mut edits = Vec::new();
11629 let mut unfold_ranges = Vec::new();
11630 let mut refold_creases = Vec::new();
11631
11632 let selections = self.selections.all::<Point>(cx);
11633 let mut selections = selections.iter().peekable();
11634 let mut contiguous_row_selections = Vec::new();
11635 let mut new_selections = Vec::new();
11636
11637 while let Some(selection) = selections.next() {
11638 // Find all the selections that span a contiguous row range
11639 let (start_row, end_row) = consume_contiguous_rows(
11640 &mut contiguous_row_selections,
11641 selection,
11642 &display_map,
11643 &mut selections,
11644 );
11645
11646 // Move the text spanned by the row range to be before the line preceding the row range
11647 if start_row.0 > 0 {
11648 let range_to_move = Point::new(
11649 start_row.previous_row().0,
11650 buffer.line_len(start_row.previous_row()),
11651 )
11652 ..Point::new(
11653 end_row.previous_row().0,
11654 buffer.line_len(end_row.previous_row()),
11655 );
11656 let insertion_point = display_map
11657 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11658 .0;
11659
11660 // Don't move lines across excerpts
11661 if buffer
11662 .excerpt_containing(insertion_point..range_to_move.end)
11663 .is_some()
11664 {
11665 let text = buffer
11666 .text_for_range(range_to_move.clone())
11667 .flat_map(|s| s.chars())
11668 .skip(1)
11669 .chain(['\n'])
11670 .collect::<String>();
11671
11672 edits.push((
11673 buffer.anchor_after(range_to_move.start)
11674 ..buffer.anchor_before(range_to_move.end),
11675 String::new(),
11676 ));
11677 let insertion_anchor = buffer.anchor_after(insertion_point);
11678 edits.push((insertion_anchor..insertion_anchor, text));
11679
11680 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11681
11682 // Move selections up
11683 new_selections.extend(contiguous_row_selections.drain(..).map(
11684 |mut selection| {
11685 selection.start.row -= row_delta;
11686 selection.end.row -= row_delta;
11687 selection
11688 },
11689 ));
11690
11691 // Move folds up
11692 unfold_ranges.push(range_to_move.clone());
11693 for fold in display_map.folds_in_range(
11694 buffer.anchor_before(range_to_move.start)
11695 ..buffer.anchor_after(range_to_move.end),
11696 ) {
11697 let mut start = fold.range.start.to_point(&buffer);
11698 let mut end = fold.range.end.to_point(&buffer);
11699 start.row -= row_delta;
11700 end.row -= row_delta;
11701 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11702 }
11703 }
11704 }
11705
11706 // If we didn't move line(s), preserve the existing selections
11707 new_selections.append(&mut contiguous_row_selections);
11708 }
11709
11710 self.transact(window, cx, |this, window, cx| {
11711 this.unfold_ranges(&unfold_ranges, true, true, cx);
11712 this.buffer.update(cx, |buffer, cx| {
11713 for (range, text) in edits {
11714 buffer.edit([(range, text)], None, cx);
11715 }
11716 });
11717 this.fold_creases(refold_creases, true, window, cx);
11718 this.change_selections(Default::default(), window, cx, |s| {
11719 s.select(new_selections);
11720 })
11721 });
11722 }
11723
11724 pub fn move_line_down(
11725 &mut self,
11726 _: &MoveLineDown,
11727 window: &mut Window,
11728 cx: &mut Context<Self>,
11729 ) {
11730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11731 if self.mode.is_single_line() {
11732 cx.propagate();
11733 return;
11734 }
11735
11736 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11737 let buffer = self.buffer.read(cx).snapshot(cx);
11738
11739 let mut edits = Vec::new();
11740 let mut unfold_ranges = Vec::new();
11741 let mut refold_creases = Vec::new();
11742
11743 let selections = self.selections.all::<Point>(cx);
11744 let mut selections = selections.iter().peekable();
11745 let mut contiguous_row_selections = Vec::new();
11746 let mut new_selections = Vec::new();
11747
11748 while let Some(selection) = selections.next() {
11749 // Find all the selections that span a contiguous row range
11750 let (start_row, end_row) = consume_contiguous_rows(
11751 &mut contiguous_row_selections,
11752 selection,
11753 &display_map,
11754 &mut selections,
11755 );
11756
11757 // Move the text spanned by the row range to be after the last line of the row range
11758 if end_row.0 <= buffer.max_point().row {
11759 let range_to_move =
11760 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11761 let insertion_point = display_map
11762 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11763 .0;
11764
11765 // Don't move lines across excerpt boundaries
11766 if buffer
11767 .excerpt_containing(range_to_move.start..insertion_point)
11768 .is_some()
11769 {
11770 let mut text = String::from("\n");
11771 text.extend(buffer.text_for_range(range_to_move.clone()));
11772 text.pop(); // Drop trailing newline
11773 edits.push((
11774 buffer.anchor_after(range_to_move.start)
11775 ..buffer.anchor_before(range_to_move.end),
11776 String::new(),
11777 ));
11778 let insertion_anchor = buffer.anchor_after(insertion_point);
11779 edits.push((insertion_anchor..insertion_anchor, text));
11780
11781 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11782
11783 // Move selections down
11784 new_selections.extend(contiguous_row_selections.drain(..).map(
11785 |mut selection| {
11786 selection.start.row += row_delta;
11787 selection.end.row += row_delta;
11788 selection
11789 },
11790 ));
11791
11792 // Move folds down
11793 unfold_ranges.push(range_to_move.clone());
11794 for fold in display_map.folds_in_range(
11795 buffer.anchor_before(range_to_move.start)
11796 ..buffer.anchor_after(range_to_move.end),
11797 ) {
11798 let mut start = fold.range.start.to_point(&buffer);
11799 let mut end = fold.range.end.to_point(&buffer);
11800 start.row += row_delta;
11801 end.row += row_delta;
11802 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11803 }
11804 }
11805 }
11806
11807 // If we didn't move line(s), preserve the existing selections
11808 new_selections.append(&mut contiguous_row_selections);
11809 }
11810
11811 self.transact(window, cx, |this, window, cx| {
11812 this.unfold_ranges(&unfold_ranges, true, true, cx);
11813 this.buffer.update(cx, |buffer, cx| {
11814 for (range, text) in edits {
11815 buffer.edit([(range, text)], None, cx);
11816 }
11817 });
11818 this.fold_creases(refold_creases, true, window, cx);
11819 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11820 });
11821 }
11822
11823 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11825 let text_layout_details = &self.text_layout_details(window);
11826 self.transact(window, cx, |this, window, cx| {
11827 let edits = this.change_selections(Default::default(), window, cx, |s| {
11828 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11829 s.move_with(|display_map, selection| {
11830 if !selection.is_empty() {
11831 return;
11832 }
11833
11834 let mut head = selection.head();
11835 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11836 if head.column() == display_map.line_len(head.row()) {
11837 transpose_offset = display_map
11838 .buffer_snapshot
11839 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11840 }
11841
11842 if transpose_offset == 0 {
11843 return;
11844 }
11845
11846 *head.column_mut() += 1;
11847 head = display_map.clip_point(head, Bias::Right);
11848 let goal = SelectionGoal::HorizontalPosition(
11849 display_map
11850 .x_for_display_point(head, text_layout_details)
11851 .into(),
11852 );
11853 selection.collapse_to(head, goal);
11854
11855 let transpose_start = display_map
11856 .buffer_snapshot
11857 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11858 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11859 let transpose_end = display_map
11860 .buffer_snapshot
11861 .clip_offset(transpose_offset + 1, Bias::Right);
11862 if let Some(ch) =
11863 display_map.buffer_snapshot.chars_at(transpose_start).next()
11864 {
11865 edits.push((transpose_start..transpose_offset, String::new()));
11866 edits.push((transpose_end..transpose_end, ch.to_string()));
11867 }
11868 }
11869 });
11870 edits
11871 });
11872 this.buffer
11873 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11874 let selections = this.selections.all::<usize>(cx);
11875 this.change_selections(Default::default(), window, cx, |s| {
11876 s.select(selections);
11877 });
11878 });
11879 }
11880
11881 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11883 if self.mode.is_single_line() {
11884 cx.propagate();
11885 return;
11886 }
11887
11888 self.rewrap_impl(RewrapOptions::default(), cx)
11889 }
11890
11891 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11892 let buffer = self.buffer.read(cx).snapshot(cx);
11893 let selections = self.selections.all::<Point>(cx);
11894
11895 #[derive(Clone, Debug, PartialEq)]
11896 enum CommentFormat {
11897 /// single line comment, with prefix for line
11898 Line(String),
11899 /// single line within a block comment, with prefix for line
11900 BlockLine(String),
11901 /// a single line of a block comment that includes the initial delimiter
11902 BlockCommentWithStart(BlockCommentConfig),
11903 /// a single line of a block comment that includes the ending delimiter
11904 BlockCommentWithEnd(BlockCommentConfig),
11905 }
11906
11907 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11908 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11909 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11910 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11911 .peekable();
11912
11913 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11914 row
11915 } else {
11916 return Vec::new();
11917 };
11918
11919 let language_settings = buffer.language_settings_at(selection.head(), cx);
11920 let language_scope = buffer.language_scope_at(selection.head());
11921
11922 let indent_and_prefix_for_row =
11923 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11924 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11925 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11926 &language_scope
11927 {
11928 let indent_end = Point::new(row, indent.len);
11929 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11930 let line_text_after_indent = buffer
11931 .text_for_range(indent_end..line_end)
11932 .collect::<String>();
11933
11934 let is_within_comment_override = buffer
11935 .language_scope_at(indent_end)
11936 .is_some_and(|scope| scope.override_name() == Some("comment"));
11937 let comment_delimiters = if is_within_comment_override {
11938 // we are within a comment syntax node, but we don't
11939 // yet know what kind of comment: block, doc or line
11940 match (
11941 language_scope.documentation_comment(),
11942 language_scope.block_comment(),
11943 ) {
11944 (Some(config), _) | (_, Some(config))
11945 if buffer.contains_str_at(indent_end, &config.start) =>
11946 {
11947 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11948 }
11949 (Some(config), _) | (_, Some(config))
11950 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11951 {
11952 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11953 }
11954 (Some(config), _) | (_, Some(config))
11955 if buffer.contains_str_at(indent_end, &config.prefix) =>
11956 {
11957 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11958 }
11959 (_, _) => language_scope
11960 .line_comment_prefixes()
11961 .iter()
11962 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11963 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11964 }
11965 } else {
11966 // we not in an overridden comment node, but we may
11967 // be within a non-overridden line comment node
11968 language_scope
11969 .line_comment_prefixes()
11970 .iter()
11971 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11972 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11973 };
11974
11975 let rewrap_prefix = language_scope
11976 .rewrap_prefixes()
11977 .iter()
11978 .find_map(|prefix_regex| {
11979 prefix_regex.find(&line_text_after_indent).map(|mat| {
11980 if mat.start() == 0 {
11981 Some(mat.as_str().to_string())
11982 } else {
11983 None
11984 }
11985 })
11986 })
11987 .flatten();
11988 (comment_delimiters, rewrap_prefix)
11989 } else {
11990 (None, None)
11991 };
11992 (indent, comment_prefix, rewrap_prefix)
11993 };
11994
11995 let mut ranges = Vec::new();
11996 let from_empty_selection = selection.is_empty();
11997
11998 let mut current_range_start = first_row;
11999 let mut prev_row = first_row;
12000 let (
12001 mut current_range_indent,
12002 mut current_range_comment_delimiters,
12003 mut current_range_rewrap_prefix,
12004 ) = indent_and_prefix_for_row(first_row);
12005
12006 for row in non_blank_rows_iter.skip(1) {
12007 let has_paragraph_break = row > prev_row + 1;
12008
12009 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12010 indent_and_prefix_for_row(row);
12011
12012 let has_indent_change = row_indent != current_range_indent;
12013 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12014
12015 let has_boundary_change = has_comment_change
12016 || row_rewrap_prefix.is_some()
12017 || (has_indent_change && current_range_comment_delimiters.is_some());
12018
12019 if has_paragraph_break || has_boundary_change {
12020 ranges.push((
12021 language_settings.clone(),
12022 Point::new(current_range_start, 0)
12023 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12024 current_range_indent,
12025 current_range_comment_delimiters.clone(),
12026 current_range_rewrap_prefix.clone(),
12027 from_empty_selection,
12028 ));
12029 current_range_start = row;
12030 current_range_indent = row_indent;
12031 current_range_comment_delimiters = row_comment_delimiters;
12032 current_range_rewrap_prefix = row_rewrap_prefix;
12033 }
12034 prev_row = row;
12035 }
12036
12037 ranges.push((
12038 language_settings.clone(),
12039 Point::new(current_range_start, 0)
12040 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12041 current_range_indent,
12042 current_range_comment_delimiters,
12043 current_range_rewrap_prefix,
12044 from_empty_selection,
12045 ));
12046
12047 ranges
12048 });
12049
12050 let mut edits = Vec::new();
12051 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12052
12053 for (
12054 language_settings,
12055 wrap_range,
12056 mut indent_size,
12057 comment_prefix,
12058 rewrap_prefix,
12059 from_empty_selection,
12060 ) in wrap_ranges
12061 {
12062 let mut start_row = wrap_range.start.row;
12063 let mut end_row = wrap_range.end.row;
12064
12065 // Skip selections that overlap with a range that has already been rewrapped.
12066 let selection_range = start_row..end_row;
12067 if rewrapped_row_ranges
12068 .iter()
12069 .any(|range| range.overlaps(&selection_range))
12070 {
12071 continue;
12072 }
12073
12074 let tab_size = language_settings.tab_size;
12075
12076 let (line_prefix, inside_comment) = match &comment_prefix {
12077 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12078 (Some(prefix.as_str()), true)
12079 }
12080 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12081 (Some(prefix.as_ref()), true)
12082 }
12083 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12084 start: _,
12085 end: _,
12086 prefix,
12087 tab_size,
12088 })) => {
12089 indent_size.len += tab_size;
12090 (Some(prefix.as_ref()), true)
12091 }
12092 None => (None, false),
12093 };
12094 let indent_prefix = indent_size.chars().collect::<String>();
12095 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12096
12097 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12098 RewrapBehavior::InComments => inside_comment,
12099 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12100 RewrapBehavior::Anywhere => true,
12101 };
12102
12103 let should_rewrap = options.override_language_settings
12104 || allow_rewrap_based_on_language
12105 || self.hard_wrap.is_some();
12106 if !should_rewrap {
12107 continue;
12108 }
12109
12110 if from_empty_selection {
12111 'expand_upwards: while start_row > 0 {
12112 let prev_row = start_row - 1;
12113 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12114 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12115 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12116 {
12117 start_row = prev_row;
12118 } else {
12119 break 'expand_upwards;
12120 }
12121 }
12122
12123 'expand_downwards: while end_row < buffer.max_point().row {
12124 let next_row = end_row + 1;
12125 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12126 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12127 && !buffer.is_line_blank(MultiBufferRow(next_row))
12128 {
12129 end_row = next_row;
12130 } else {
12131 break 'expand_downwards;
12132 }
12133 }
12134 }
12135
12136 let start = Point::new(start_row, 0);
12137 let start_offset = start.to_offset(&buffer);
12138 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12139 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12140 let mut first_line_delimiter = None;
12141 let mut last_line_delimiter = None;
12142 let Some(lines_without_prefixes) = selection_text
12143 .lines()
12144 .enumerate()
12145 .map(|(ix, line)| {
12146 let line_trimmed = line.trim_start();
12147 if rewrap_prefix.is_some() && ix > 0 {
12148 Ok(line_trimmed)
12149 } else if let Some(
12150 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12151 start,
12152 prefix,
12153 end,
12154 tab_size,
12155 })
12156 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12157 start,
12158 prefix,
12159 end,
12160 tab_size,
12161 }),
12162 ) = &comment_prefix
12163 {
12164 let line_trimmed = line_trimmed
12165 .strip_prefix(start.as_ref())
12166 .map(|s| {
12167 let mut indent_size = indent_size;
12168 indent_size.len -= tab_size;
12169 let indent_prefix: String = indent_size.chars().collect();
12170 first_line_delimiter = Some((indent_prefix, start));
12171 s.trim_start()
12172 })
12173 .unwrap_or(line_trimmed);
12174 let line_trimmed = line_trimmed
12175 .strip_suffix(end.as_ref())
12176 .map(|s| {
12177 last_line_delimiter = Some(end);
12178 s.trim_end()
12179 })
12180 .unwrap_or(line_trimmed);
12181 let line_trimmed = line_trimmed
12182 .strip_prefix(prefix.as_ref())
12183 .unwrap_or(line_trimmed);
12184 Ok(line_trimmed)
12185 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12186 line_trimmed.strip_prefix(prefix).with_context(|| {
12187 format!("line did not start with prefix {prefix:?}: {line:?}")
12188 })
12189 } else {
12190 line_trimmed
12191 .strip_prefix(&line_prefix.trim_start())
12192 .with_context(|| {
12193 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12194 })
12195 }
12196 })
12197 .collect::<Result<Vec<_>, _>>()
12198 .log_err()
12199 else {
12200 continue;
12201 };
12202
12203 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12204 buffer
12205 .language_settings_at(Point::new(start_row, 0), cx)
12206 .preferred_line_length as usize
12207 });
12208
12209 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12210 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12211 } else {
12212 line_prefix.clone()
12213 };
12214
12215 let wrapped_text = {
12216 let mut wrapped_text = wrap_with_prefix(
12217 line_prefix,
12218 subsequent_lines_prefix,
12219 lines_without_prefixes.join("\n"),
12220 wrap_column,
12221 tab_size,
12222 options.preserve_existing_whitespace,
12223 );
12224
12225 if let Some((indent, delimiter)) = first_line_delimiter {
12226 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12227 }
12228 if let Some(last_line) = last_line_delimiter {
12229 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12230 }
12231
12232 wrapped_text
12233 };
12234
12235 // TODO: should always use char-based diff while still supporting cursor behavior that
12236 // matches vim.
12237 let mut diff_options = DiffOptions::default();
12238 if options.override_language_settings {
12239 diff_options.max_word_diff_len = 0;
12240 diff_options.max_word_diff_line_count = 0;
12241 } else {
12242 diff_options.max_word_diff_len = usize::MAX;
12243 diff_options.max_word_diff_line_count = usize::MAX;
12244 }
12245
12246 for (old_range, new_text) in
12247 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12248 {
12249 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12250 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12251 edits.push((edit_start..edit_end, new_text));
12252 }
12253
12254 rewrapped_row_ranges.push(start_row..=end_row);
12255 }
12256
12257 self.buffer
12258 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12259 }
12260
12261 pub fn cut_common(
12262 &mut self,
12263 cut_no_selection_line: bool,
12264 window: &mut Window,
12265 cx: &mut Context<Self>,
12266 ) -> ClipboardItem {
12267 let mut text = String::new();
12268 let buffer = self.buffer.read(cx).snapshot(cx);
12269 let mut selections = self.selections.all::<Point>(cx);
12270 let mut clipboard_selections = Vec::with_capacity(selections.len());
12271 {
12272 let max_point = buffer.max_point();
12273 let mut is_first = true;
12274 for selection in &mut selections {
12275 let is_entire_line =
12276 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode;
12277 if is_entire_line {
12278 selection.start = Point::new(selection.start.row, 0);
12279 if !selection.is_empty() && selection.end.column == 0 {
12280 selection.end = cmp::min(max_point, selection.end);
12281 } else {
12282 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12283 }
12284 selection.goal = SelectionGoal::None;
12285 }
12286 if is_first {
12287 is_first = false;
12288 } else {
12289 text += "\n";
12290 }
12291 let mut len = 0;
12292 for chunk in buffer.text_for_range(selection.start..selection.end) {
12293 text.push_str(chunk);
12294 len += chunk.len();
12295 }
12296 clipboard_selections.push(ClipboardSelection {
12297 len,
12298 is_entire_line,
12299 first_line_indent: buffer
12300 .indent_size_for_line(MultiBufferRow(selection.start.row))
12301 .len,
12302 });
12303 }
12304 }
12305
12306 self.transact(window, cx, |this, window, cx| {
12307 this.change_selections(Default::default(), window, cx, |s| {
12308 s.select(selections);
12309 });
12310 this.insert("", window, cx);
12311 });
12312 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12313 }
12314
12315 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12316 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12317 let item = self.cut_common(true, window, cx);
12318 cx.write_to_clipboard(item);
12319 }
12320
12321 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12322 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12323 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12324 s.move_with(|snapshot, sel| {
12325 if sel.is_empty() {
12326 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12327 }
12328 if sel.is_empty() {
12329 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12330 }
12331 });
12332 });
12333 let item = self.cut_common(true, window, cx);
12334 cx.set_global(KillRing(item))
12335 }
12336
12337 pub fn kill_ring_yank(
12338 &mut self,
12339 _: &KillRingYank,
12340 window: &mut Window,
12341 cx: &mut Context<Self>,
12342 ) {
12343 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12344 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12345 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12346 (kill_ring.text().to_string(), kill_ring.metadata_json())
12347 } else {
12348 return;
12349 }
12350 } else {
12351 return;
12352 };
12353 self.do_paste(&text, metadata, false, window, cx);
12354 }
12355
12356 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12357 self.do_copy(true, cx);
12358 }
12359
12360 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12361 self.do_copy(false, cx);
12362 }
12363
12364 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12365 let selections = self.selections.all::<Point>(cx);
12366 let buffer = self.buffer.read(cx).read(cx);
12367 let mut text = String::new();
12368
12369 let mut clipboard_selections = Vec::with_capacity(selections.len());
12370 {
12371 let max_point = buffer.max_point();
12372 let mut is_first = true;
12373 for selection in &selections {
12374 let mut start = selection.start;
12375 let mut end = selection.end;
12376 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12377 if is_entire_line {
12378 start = Point::new(start.row, 0);
12379 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12380 }
12381
12382 let mut trimmed_selections = Vec::new();
12383 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12384 let row = MultiBufferRow(start.row);
12385 let first_indent = buffer.indent_size_for_line(row);
12386 if first_indent.len == 0 || start.column > first_indent.len {
12387 trimmed_selections.push(start..end);
12388 } else {
12389 trimmed_selections.push(
12390 Point::new(row.0, first_indent.len)
12391 ..Point::new(row.0, buffer.line_len(row)),
12392 );
12393 for row in start.row + 1..=end.row {
12394 let mut line_len = buffer.line_len(MultiBufferRow(row));
12395 if row == end.row {
12396 line_len = end.column;
12397 }
12398 if line_len == 0 {
12399 trimmed_selections
12400 .push(Point::new(row, 0)..Point::new(row, line_len));
12401 continue;
12402 }
12403 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12404 if row_indent_size.len >= first_indent.len {
12405 trimmed_selections.push(
12406 Point::new(row, first_indent.len)..Point::new(row, line_len),
12407 );
12408 } else {
12409 trimmed_selections.clear();
12410 trimmed_selections.push(start..end);
12411 break;
12412 }
12413 }
12414 }
12415 } else {
12416 trimmed_selections.push(start..end);
12417 }
12418
12419 for trimmed_range in trimmed_selections {
12420 if is_first {
12421 is_first = false;
12422 } else {
12423 text += "\n";
12424 }
12425 let mut len = 0;
12426 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12427 text.push_str(chunk);
12428 len += chunk.len();
12429 }
12430 clipboard_selections.push(ClipboardSelection {
12431 len,
12432 is_entire_line,
12433 first_line_indent: buffer
12434 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12435 .len,
12436 });
12437 }
12438 }
12439 }
12440
12441 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12442 text,
12443 clipboard_selections,
12444 ));
12445 }
12446
12447 pub fn do_paste(
12448 &mut self,
12449 text: &String,
12450 clipboard_selections: Option<Vec<ClipboardSelection>>,
12451 handle_entire_lines: bool,
12452 window: &mut Window,
12453 cx: &mut Context<Self>,
12454 ) {
12455 if self.read_only(cx) {
12456 return;
12457 }
12458
12459 let clipboard_text = Cow::Borrowed(text.as_str());
12460
12461 self.transact(window, cx, |this, window, cx| {
12462 let had_active_edit_prediction = this.has_active_edit_prediction();
12463 let old_selections = this.selections.all::<usize>(cx);
12464 let cursor_offset = this.selections.last::<usize>(cx).head();
12465
12466 if let Some(mut clipboard_selections) = clipboard_selections {
12467 let all_selections_were_entire_line =
12468 clipboard_selections.iter().all(|s| s.is_entire_line);
12469 let first_selection_indent_column =
12470 clipboard_selections.first().map(|s| s.first_line_indent);
12471 if clipboard_selections.len() != old_selections.len() {
12472 clipboard_selections.drain(..);
12473 }
12474 let mut auto_indent_on_paste = true;
12475
12476 this.buffer.update(cx, |buffer, cx| {
12477 let snapshot = buffer.read(cx);
12478 auto_indent_on_paste = snapshot
12479 .language_settings_at(cursor_offset, cx)
12480 .auto_indent_on_paste;
12481
12482 let mut start_offset = 0;
12483 let mut edits = Vec::new();
12484 let mut original_indent_columns = Vec::new();
12485 for (ix, selection) in old_selections.iter().enumerate() {
12486 let to_insert;
12487 let entire_line;
12488 let original_indent_column;
12489 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12490 let end_offset = start_offset + clipboard_selection.len;
12491 to_insert = &clipboard_text[start_offset..end_offset];
12492 entire_line = clipboard_selection.is_entire_line;
12493 start_offset = end_offset + 1;
12494 original_indent_column = Some(clipboard_selection.first_line_indent);
12495 } else {
12496 to_insert = &*clipboard_text;
12497 entire_line = all_selections_were_entire_line;
12498 original_indent_column = first_selection_indent_column
12499 }
12500
12501 let (range, to_insert) =
12502 if selection.is_empty() && handle_entire_lines && entire_line {
12503 // If the corresponding selection was empty when this slice of the
12504 // clipboard text was written, then the entire line containing the
12505 // selection was copied. If this selection is also currently empty,
12506 // then paste the line before the current line of the buffer.
12507 let column = selection.start.to_point(&snapshot).column as usize;
12508 let line_start = selection.start - column;
12509 (line_start..line_start, Cow::Borrowed(to_insert))
12510 } else {
12511 let language = snapshot.language_at(selection.head());
12512 let range = selection.range();
12513 if let Some(language) = language
12514 && language.name() == "Markdown".into()
12515 {
12516 edit_for_markdown_paste(
12517 &snapshot,
12518 range,
12519 to_insert,
12520 url::Url::parse(to_insert).ok(),
12521 )
12522 } else {
12523 (range, Cow::Borrowed(to_insert))
12524 }
12525 };
12526
12527 edits.push((range, to_insert));
12528 original_indent_columns.push(original_indent_column);
12529 }
12530 drop(snapshot);
12531
12532 buffer.edit(
12533 edits,
12534 if auto_indent_on_paste {
12535 Some(AutoindentMode::Block {
12536 original_indent_columns,
12537 })
12538 } else {
12539 None
12540 },
12541 cx,
12542 );
12543 });
12544
12545 let selections = this.selections.all::<usize>(cx);
12546 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12547 } else {
12548 let url = url::Url::parse(&clipboard_text).ok();
12549
12550 let auto_indent_mode = if !clipboard_text.is_empty() {
12551 Some(AutoindentMode::Block {
12552 original_indent_columns: Vec::new(),
12553 })
12554 } else {
12555 None
12556 };
12557
12558 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12559 let snapshot = buffer.snapshot(cx);
12560
12561 let anchors = old_selections
12562 .iter()
12563 .map(|s| {
12564 let anchor = snapshot.anchor_after(s.head());
12565 s.map(|_| anchor)
12566 })
12567 .collect::<Vec<_>>();
12568
12569 let mut edits = Vec::new();
12570
12571 for selection in old_selections.iter() {
12572 let language = snapshot.language_at(selection.head());
12573 let range = selection.range();
12574
12575 let (edit_range, edit_text) = if let Some(language) = language
12576 && language.name() == "Markdown".into()
12577 {
12578 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12579 } else {
12580 (range, clipboard_text.clone())
12581 };
12582
12583 edits.push((edit_range, edit_text));
12584 }
12585
12586 drop(snapshot);
12587 buffer.edit(edits, auto_indent_mode, cx);
12588
12589 anchors
12590 });
12591
12592 this.change_selections(Default::default(), window, cx, |s| {
12593 s.select_anchors(selection_anchors);
12594 });
12595 }
12596
12597 let trigger_in_words =
12598 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12599
12600 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12601 });
12602 }
12603
12604 pub fn diff_clipboard_with_selection(
12605 &mut self,
12606 _: &DiffClipboardWithSelection,
12607 window: &mut Window,
12608 cx: &mut Context<Self>,
12609 ) {
12610 let selections = self.selections.all::<usize>(cx);
12611
12612 if selections.is_empty() {
12613 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12614 return;
12615 };
12616
12617 let clipboard_text = match cx.read_from_clipboard() {
12618 Some(item) => match item.entries().first() {
12619 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12620 _ => None,
12621 },
12622 None => None,
12623 };
12624
12625 let Some(clipboard_text) = clipboard_text else {
12626 log::warn!("Clipboard doesn't contain text.");
12627 return;
12628 };
12629
12630 window.dispatch_action(
12631 Box::new(DiffClipboardWithSelectionData {
12632 clipboard_text,
12633 editor: cx.entity(),
12634 }),
12635 cx,
12636 );
12637 }
12638
12639 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12640 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12641 if let Some(item) = cx.read_from_clipboard() {
12642 let entries = item.entries();
12643
12644 match entries.first() {
12645 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12646 // of all the pasted entries.
12647 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12648 .do_paste(
12649 clipboard_string.text(),
12650 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12651 true,
12652 window,
12653 cx,
12654 ),
12655 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12656 }
12657 }
12658 }
12659
12660 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12661 if self.read_only(cx) {
12662 return;
12663 }
12664
12665 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12666
12667 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12668 if let Some((selections, _)) =
12669 self.selection_history.transaction(transaction_id).cloned()
12670 {
12671 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12672 s.select_anchors(selections.to_vec());
12673 });
12674 } else {
12675 log::error!(
12676 "No entry in selection_history found for undo. \
12677 This may correspond to a bug where undo does not update the selection. \
12678 If this is occurring, please add details to \
12679 https://github.com/zed-industries/zed/issues/22692"
12680 );
12681 }
12682 self.request_autoscroll(Autoscroll::fit(), cx);
12683 self.unmark_text(window, cx);
12684 self.refresh_edit_prediction(true, false, window, cx);
12685 cx.emit(EditorEvent::Edited { transaction_id });
12686 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12687 }
12688 }
12689
12690 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12691 if self.read_only(cx) {
12692 return;
12693 }
12694
12695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12696
12697 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12698 if let Some((_, Some(selections))) =
12699 self.selection_history.transaction(transaction_id).cloned()
12700 {
12701 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12702 s.select_anchors(selections.to_vec());
12703 });
12704 } else {
12705 log::error!(
12706 "No entry in selection_history found for redo. \
12707 This may correspond to a bug where undo does not update the selection. \
12708 If this is occurring, please add details to \
12709 https://github.com/zed-industries/zed/issues/22692"
12710 );
12711 }
12712 self.request_autoscroll(Autoscroll::fit(), cx);
12713 self.unmark_text(window, cx);
12714 self.refresh_edit_prediction(true, false, window, cx);
12715 cx.emit(EditorEvent::Edited { transaction_id });
12716 }
12717 }
12718
12719 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12720 self.buffer
12721 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12722 }
12723
12724 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12725 self.buffer
12726 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12727 }
12728
12729 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12730 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12731 self.change_selections(Default::default(), window, cx, |s| {
12732 s.move_with(|map, selection| {
12733 let cursor = if selection.is_empty() {
12734 movement::left(map, selection.start)
12735 } else {
12736 selection.start
12737 };
12738 selection.collapse_to(cursor, SelectionGoal::None);
12739 });
12740 })
12741 }
12742
12743 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12744 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12745 self.change_selections(Default::default(), window, cx, |s| {
12746 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12747 })
12748 }
12749
12750 pub fn move_right(&mut self, _: &MoveRight, 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_with(|map, selection| {
12754 let cursor = if selection.is_empty() {
12755 movement::right(map, selection.end)
12756 } else {
12757 selection.end
12758 };
12759 selection.collapse_to(cursor, SelectionGoal::None)
12760 });
12761 })
12762 }
12763
12764 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12765 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12766 self.change_selections(Default::default(), window, cx, |s| {
12767 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12768 })
12769 }
12770
12771 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12772 if self.take_rename(true, window, cx).is_some() {
12773 return;
12774 }
12775
12776 if self.mode.is_single_line() {
12777 cx.propagate();
12778 return;
12779 }
12780
12781 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12782
12783 let text_layout_details = &self.text_layout_details(window);
12784 let selection_count = self.selections.count();
12785 let first_selection = self.selections.first_anchor();
12786
12787 self.change_selections(Default::default(), window, cx, |s| {
12788 s.move_with(|map, selection| {
12789 if !selection.is_empty() {
12790 selection.goal = SelectionGoal::None;
12791 }
12792 let (cursor, goal) = movement::up(
12793 map,
12794 selection.start,
12795 selection.goal,
12796 false,
12797 text_layout_details,
12798 );
12799 selection.collapse_to(cursor, goal);
12800 });
12801 });
12802
12803 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12804 {
12805 cx.propagate();
12806 }
12807 }
12808
12809 pub fn move_up_by_lines(
12810 &mut self,
12811 action: &MoveUpByLines,
12812 window: &mut Window,
12813 cx: &mut Context<Self>,
12814 ) {
12815 if self.take_rename(true, window, cx).is_some() {
12816 return;
12817 }
12818
12819 if self.mode.is_single_line() {
12820 cx.propagate();
12821 return;
12822 }
12823
12824 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12825
12826 let text_layout_details = &self.text_layout_details(window);
12827
12828 self.change_selections(Default::default(), window, cx, |s| {
12829 s.move_with(|map, selection| {
12830 if !selection.is_empty() {
12831 selection.goal = SelectionGoal::None;
12832 }
12833 let (cursor, goal) = movement::up_by_rows(
12834 map,
12835 selection.start,
12836 action.lines,
12837 selection.goal,
12838 false,
12839 text_layout_details,
12840 );
12841 selection.collapse_to(cursor, goal);
12842 });
12843 })
12844 }
12845
12846 pub fn move_down_by_lines(
12847 &mut self,
12848 action: &MoveDownByLines,
12849 window: &mut Window,
12850 cx: &mut Context<Self>,
12851 ) {
12852 if self.take_rename(true, window, cx).is_some() {
12853 return;
12854 }
12855
12856 if self.mode.is_single_line() {
12857 cx.propagate();
12858 return;
12859 }
12860
12861 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12862
12863 let text_layout_details = &self.text_layout_details(window);
12864
12865 self.change_selections(Default::default(), window, cx, |s| {
12866 s.move_with(|map, selection| {
12867 if !selection.is_empty() {
12868 selection.goal = SelectionGoal::None;
12869 }
12870 let (cursor, goal) = movement::down_by_rows(
12871 map,
12872 selection.start,
12873 action.lines,
12874 selection.goal,
12875 false,
12876 text_layout_details,
12877 );
12878 selection.collapse_to(cursor, goal);
12879 });
12880 })
12881 }
12882
12883 pub fn select_down_by_lines(
12884 &mut self,
12885 action: &SelectDownByLines,
12886 window: &mut Window,
12887 cx: &mut Context<Self>,
12888 ) {
12889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12890 let text_layout_details = &self.text_layout_details(window);
12891 self.change_selections(Default::default(), window, cx, |s| {
12892 s.move_heads_with(|map, head, goal| {
12893 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12894 })
12895 })
12896 }
12897
12898 pub fn select_up_by_lines(
12899 &mut self,
12900 action: &SelectUpByLines,
12901 window: &mut Window,
12902 cx: &mut Context<Self>,
12903 ) {
12904 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12905 let text_layout_details = &self.text_layout_details(window);
12906 self.change_selections(Default::default(), window, cx, |s| {
12907 s.move_heads_with(|map, head, goal| {
12908 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12909 })
12910 })
12911 }
12912
12913 pub fn select_page_up(
12914 &mut self,
12915 _: &SelectPageUp,
12916 window: &mut Window,
12917 cx: &mut Context<Self>,
12918 ) {
12919 let Some(row_count) = self.visible_row_count() else {
12920 return;
12921 };
12922
12923 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12924
12925 let text_layout_details = &self.text_layout_details(window);
12926
12927 self.change_selections(Default::default(), window, cx, |s| {
12928 s.move_heads_with(|map, head, goal| {
12929 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12930 })
12931 })
12932 }
12933
12934 pub fn move_page_up(
12935 &mut self,
12936 action: &MovePageUp,
12937 window: &mut Window,
12938 cx: &mut Context<Self>,
12939 ) {
12940 if self.take_rename(true, window, cx).is_some() {
12941 return;
12942 }
12943
12944 if self
12945 .context_menu
12946 .borrow_mut()
12947 .as_mut()
12948 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12949 .unwrap_or(false)
12950 {
12951 return;
12952 }
12953
12954 if matches!(self.mode, EditorMode::SingleLine) {
12955 cx.propagate();
12956 return;
12957 }
12958
12959 let Some(row_count) = self.visible_row_count() else {
12960 return;
12961 };
12962
12963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12964
12965 let effects = if action.center_cursor {
12966 SelectionEffects::scroll(Autoscroll::center())
12967 } else {
12968 SelectionEffects::default()
12969 };
12970
12971 let text_layout_details = &self.text_layout_details(window);
12972
12973 self.change_selections(effects, window, cx, |s| {
12974 s.move_with(|map, selection| {
12975 if !selection.is_empty() {
12976 selection.goal = SelectionGoal::None;
12977 }
12978 let (cursor, goal) = movement::up_by_rows(
12979 map,
12980 selection.end,
12981 row_count,
12982 selection.goal,
12983 false,
12984 text_layout_details,
12985 );
12986 selection.collapse_to(cursor, goal);
12987 });
12988 });
12989 }
12990
12991 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12992 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12993 let text_layout_details = &self.text_layout_details(window);
12994 self.change_selections(Default::default(), window, cx, |s| {
12995 s.move_heads_with(|map, head, goal| {
12996 movement::up(map, head, goal, false, text_layout_details)
12997 })
12998 })
12999 }
13000
13001 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13002 self.take_rename(true, window, cx);
13003
13004 if self.mode.is_single_line() {
13005 cx.propagate();
13006 return;
13007 }
13008
13009 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13010
13011 let text_layout_details = &self.text_layout_details(window);
13012 let selection_count = self.selections.count();
13013 let first_selection = self.selections.first_anchor();
13014
13015 self.change_selections(Default::default(), window, cx, |s| {
13016 s.move_with(|map, selection| {
13017 if !selection.is_empty() {
13018 selection.goal = SelectionGoal::None;
13019 }
13020 let (cursor, goal) = movement::down(
13021 map,
13022 selection.end,
13023 selection.goal,
13024 false,
13025 text_layout_details,
13026 );
13027 selection.collapse_to(cursor, goal);
13028 });
13029 });
13030
13031 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13032 {
13033 cx.propagate();
13034 }
13035 }
13036
13037 pub fn select_page_down(
13038 &mut self,
13039 _: &SelectPageDown,
13040 window: &mut Window,
13041 cx: &mut Context<Self>,
13042 ) {
13043 let Some(row_count) = self.visible_row_count() else {
13044 return;
13045 };
13046
13047 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13048
13049 let text_layout_details = &self.text_layout_details(window);
13050
13051 self.change_selections(Default::default(), window, cx, |s| {
13052 s.move_heads_with(|map, head, goal| {
13053 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13054 })
13055 })
13056 }
13057
13058 pub fn move_page_down(
13059 &mut self,
13060 action: &MovePageDown,
13061 window: &mut Window,
13062 cx: &mut Context<Self>,
13063 ) {
13064 if self.take_rename(true, window, cx).is_some() {
13065 return;
13066 }
13067
13068 if self
13069 .context_menu
13070 .borrow_mut()
13071 .as_mut()
13072 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13073 .unwrap_or(false)
13074 {
13075 return;
13076 }
13077
13078 if matches!(self.mode, EditorMode::SingleLine) {
13079 cx.propagate();
13080 return;
13081 }
13082
13083 let Some(row_count) = self.visible_row_count() else {
13084 return;
13085 };
13086
13087 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13088
13089 let effects = if action.center_cursor {
13090 SelectionEffects::scroll(Autoscroll::center())
13091 } else {
13092 SelectionEffects::default()
13093 };
13094
13095 let text_layout_details = &self.text_layout_details(window);
13096 self.change_selections(effects, window, cx, |s| {
13097 s.move_with(|map, selection| {
13098 if !selection.is_empty() {
13099 selection.goal = SelectionGoal::None;
13100 }
13101 let (cursor, goal) = movement::down_by_rows(
13102 map,
13103 selection.end,
13104 row_count,
13105 selection.goal,
13106 false,
13107 text_layout_details,
13108 );
13109 selection.collapse_to(cursor, goal);
13110 });
13111 });
13112 }
13113
13114 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13116 let text_layout_details = &self.text_layout_details(window);
13117 self.change_selections(Default::default(), window, cx, |s| {
13118 s.move_heads_with(|map, head, goal| {
13119 movement::down(map, head, goal, false, text_layout_details)
13120 })
13121 });
13122 }
13123
13124 pub fn context_menu_first(
13125 &mut self,
13126 _: &ContextMenuFirst,
13127 window: &mut Window,
13128 cx: &mut Context<Self>,
13129 ) {
13130 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13131 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13132 }
13133 }
13134
13135 pub fn context_menu_prev(
13136 &mut self,
13137 _: &ContextMenuPrevious,
13138 window: &mut Window,
13139 cx: &mut Context<Self>,
13140 ) {
13141 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13142 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13143 }
13144 }
13145
13146 pub fn context_menu_next(
13147 &mut self,
13148 _: &ContextMenuNext,
13149 window: &mut Window,
13150 cx: &mut Context<Self>,
13151 ) {
13152 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13153 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13154 }
13155 }
13156
13157 pub fn context_menu_last(
13158 &mut self,
13159 _: &ContextMenuLast,
13160 window: &mut Window,
13161 cx: &mut Context<Self>,
13162 ) {
13163 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13164 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13165 }
13166 }
13167
13168 pub fn signature_help_prev(
13169 &mut self,
13170 _: &SignatureHelpPrevious,
13171 _: &mut Window,
13172 cx: &mut Context<Self>,
13173 ) {
13174 if let Some(popover) = self.signature_help_state.popover_mut() {
13175 if popover.current_signature == 0 {
13176 popover.current_signature = popover.signatures.len() - 1;
13177 } else {
13178 popover.current_signature -= 1;
13179 }
13180 cx.notify();
13181 }
13182 }
13183
13184 pub fn signature_help_next(
13185 &mut self,
13186 _: &SignatureHelpNext,
13187 _: &mut Window,
13188 cx: &mut Context<Self>,
13189 ) {
13190 if let Some(popover) = self.signature_help_state.popover_mut() {
13191 if popover.current_signature + 1 == popover.signatures.len() {
13192 popover.current_signature = 0;
13193 } else {
13194 popover.current_signature += 1;
13195 }
13196 cx.notify();
13197 }
13198 }
13199
13200 pub fn move_to_previous_word_start(
13201 &mut self,
13202 _: &MoveToPreviousWordStart,
13203 window: &mut Window,
13204 cx: &mut Context<Self>,
13205 ) {
13206 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13207 self.change_selections(Default::default(), window, cx, |s| {
13208 s.move_cursors_with(|map, head, _| {
13209 (
13210 movement::previous_word_start(map, head),
13211 SelectionGoal::None,
13212 )
13213 });
13214 })
13215 }
13216
13217 pub fn move_to_previous_subword_start(
13218 &mut self,
13219 _: &MoveToPreviousSubwordStart,
13220 window: &mut Window,
13221 cx: &mut Context<Self>,
13222 ) {
13223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13224 self.change_selections(Default::default(), window, cx, |s| {
13225 s.move_cursors_with(|map, head, _| {
13226 (
13227 movement::previous_subword_start(map, head),
13228 SelectionGoal::None,
13229 )
13230 });
13231 })
13232 }
13233
13234 pub fn select_to_previous_word_start(
13235 &mut self,
13236 _: &SelectToPreviousWordStart,
13237 window: &mut Window,
13238 cx: &mut Context<Self>,
13239 ) {
13240 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13241 self.change_selections(Default::default(), window, cx, |s| {
13242 s.move_heads_with(|map, head, _| {
13243 (
13244 movement::previous_word_start(map, head),
13245 SelectionGoal::None,
13246 )
13247 });
13248 })
13249 }
13250
13251 pub fn select_to_previous_subword_start(
13252 &mut self,
13253 _: &SelectToPreviousSubwordStart,
13254 window: &mut Window,
13255 cx: &mut Context<Self>,
13256 ) {
13257 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13258 self.change_selections(Default::default(), window, cx, |s| {
13259 s.move_heads_with(|map, head, _| {
13260 (
13261 movement::previous_subword_start(map, head),
13262 SelectionGoal::None,
13263 )
13264 });
13265 })
13266 }
13267
13268 pub fn delete_to_previous_word_start(
13269 &mut self,
13270 action: &DeleteToPreviousWordStart,
13271 window: &mut Window,
13272 cx: &mut Context<Self>,
13273 ) {
13274 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13275 self.transact(window, cx, |this, window, cx| {
13276 this.select_autoclose_pair(window, cx);
13277 this.change_selections(Default::default(), window, cx, |s| {
13278 s.move_with(|map, selection| {
13279 if selection.is_empty() {
13280 let mut cursor = if action.ignore_newlines {
13281 movement::previous_word_start(map, selection.head())
13282 } else {
13283 movement::previous_word_start_or_newline(map, selection.head())
13284 };
13285 cursor = movement::adjust_greedy_deletion(
13286 map,
13287 selection.head(),
13288 cursor,
13289 action.ignore_brackets,
13290 );
13291 selection.set_head(cursor, SelectionGoal::None);
13292 }
13293 });
13294 });
13295 this.insert("", window, cx);
13296 });
13297 }
13298
13299 pub fn delete_to_previous_subword_start(
13300 &mut self,
13301 _: &DeleteToPreviousSubwordStart,
13302 window: &mut Window,
13303 cx: &mut Context<Self>,
13304 ) {
13305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13306 self.transact(window, cx, |this, window, cx| {
13307 this.select_autoclose_pair(window, cx);
13308 this.change_selections(Default::default(), window, cx, |s| {
13309 s.move_with(|map, selection| {
13310 if selection.is_empty() {
13311 let mut cursor = movement::previous_subword_start(map, selection.head());
13312 cursor =
13313 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13314 selection.set_head(cursor, SelectionGoal::None);
13315 }
13316 });
13317 });
13318 this.insert("", window, cx);
13319 });
13320 }
13321
13322 pub fn move_to_next_word_end(
13323 &mut self,
13324 _: &MoveToNextWordEnd,
13325 window: &mut Window,
13326 cx: &mut Context<Self>,
13327 ) {
13328 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13329 self.change_selections(Default::default(), window, cx, |s| {
13330 s.move_cursors_with(|map, head, _| {
13331 (movement::next_word_end(map, head), SelectionGoal::None)
13332 });
13333 })
13334 }
13335
13336 pub fn move_to_next_subword_end(
13337 &mut self,
13338 _: &MoveToNextSubwordEnd,
13339 window: &mut Window,
13340 cx: &mut Context<Self>,
13341 ) {
13342 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13343 self.change_selections(Default::default(), window, cx, |s| {
13344 s.move_cursors_with(|map, head, _| {
13345 (movement::next_subword_end(map, head), SelectionGoal::None)
13346 });
13347 })
13348 }
13349
13350 pub fn select_to_next_word_end(
13351 &mut self,
13352 _: &SelectToNextWordEnd,
13353 window: &mut Window,
13354 cx: &mut Context<Self>,
13355 ) {
13356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13357 self.change_selections(Default::default(), window, cx, |s| {
13358 s.move_heads_with(|map, head, _| {
13359 (movement::next_word_end(map, head), SelectionGoal::None)
13360 });
13361 })
13362 }
13363
13364 pub fn select_to_next_subword_end(
13365 &mut self,
13366 _: &SelectToNextSubwordEnd,
13367 window: &mut Window,
13368 cx: &mut Context<Self>,
13369 ) {
13370 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13371 self.change_selections(Default::default(), window, cx, |s| {
13372 s.move_heads_with(|map, head, _| {
13373 (movement::next_subword_end(map, head), SelectionGoal::None)
13374 });
13375 })
13376 }
13377
13378 pub fn delete_to_next_word_end(
13379 &mut self,
13380 action: &DeleteToNextWordEnd,
13381 window: &mut Window,
13382 cx: &mut Context<Self>,
13383 ) {
13384 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13385 self.transact(window, cx, |this, window, cx| {
13386 this.change_selections(Default::default(), window, cx, |s| {
13387 s.move_with(|map, selection| {
13388 if selection.is_empty() {
13389 let mut cursor = if action.ignore_newlines {
13390 movement::next_word_end(map, selection.head())
13391 } else {
13392 movement::next_word_end_or_newline(map, selection.head())
13393 };
13394 cursor = movement::adjust_greedy_deletion(
13395 map,
13396 selection.head(),
13397 cursor,
13398 action.ignore_brackets,
13399 );
13400 selection.set_head(cursor, SelectionGoal::None);
13401 }
13402 });
13403 });
13404 this.insert("", window, cx);
13405 });
13406 }
13407
13408 pub fn delete_to_next_subword_end(
13409 &mut self,
13410 _: &DeleteToNextSubwordEnd,
13411 window: &mut Window,
13412 cx: &mut Context<Self>,
13413 ) {
13414 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13415 self.transact(window, cx, |this, window, cx| {
13416 this.change_selections(Default::default(), window, cx, |s| {
13417 s.move_with(|map, selection| {
13418 if selection.is_empty() {
13419 let mut cursor = movement::next_subword_end(map, selection.head());
13420 cursor =
13421 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13422 selection.set_head(cursor, SelectionGoal::None);
13423 }
13424 });
13425 });
13426 this.insert("", window, cx);
13427 });
13428 }
13429
13430 pub fn move_to_beginning_of_line(
13431 &mut self,
13432 action: &MoveToBeginningOfLine,
13433 window: &mut Window,
13434 cx: &mut Context<Self>,
13435 ) {
13436 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13437 self.change_selections(Default::default(), window, cx, |s| {
13438 s.move_cursors_with(|map, head, _| {
13439 (
13440 movement::indented_line_beginning(
13441 map,
13442 head,
13443 action.stop_at_soft_wraps,
13444 action.stop_at_indent,
13445 ),
13446 SelectionGoal::None,
13447 )
13448 });
13449 })
13450 }
13451
13452 pub fn select_to_beginning_of_line(
13453 &mut self,
13454 action: &SelectToBeginningOfLine,
13455 window: &mut Window,
13456 cx: &mut Context<Self>,
13457 ) {
13458 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13459 self.change_selections(Default::default(), window, cx, |s| {
13460 s.move_heads_with(|map, head, _| {
13461 (
13462 movement::indented_line_beginning(
13463 map,
13464 head,
13465 action.stop_at_soft_wraps,
13466 action.stop_at_indent,
13467 ),
13468 SelectionGoal::None,
13469 )
13470 });
13471 });
13472 }
13473
13474 pub fn delete_to_beginning_of_line(
13475 &mut self,
13476 action: &DeleteToBeginningOfLine,
13477 window: &mut Window,
13478 cx: &mut Context<Self>,
13479 ) {
13480 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13481 self.transact(window, cx, |this, window, cx| {
13482 this.change_selections(Default::default(), window, cx, |s| {
13483 s.move_with(|_, selection| {
13484 selection.reversed = true;
13485 });
13486 });
13487
13488 this.select_to_beginning_of_line(
13489 &SelectToBeginningOfLine {
13490 stop_at_soft_wraps: false,
13491 stop_at_indent: action.stop_at_indent,
13492 },
13493 window,
13494 cx,
13495 );
13496 this.backspace(&Backspace, window, cx);
13497 });
13498 }
13499
13500 pub fn move_to_end_of_line(
13501 &mut self,
13502 action: &MoveToEndOfLine,
13503 window: &mut Window,
13504 cx: &mut Context<Self>,
13505 ) {
13506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13507 self.change_selections(Default::default(), window, cx, |s| {
13508 s.move_cursors_with(|map, head, _| {
13509 (
13510 movement::line_end(map, head, action.stop_at_soft_wraps),
13511 SelectionGoal::None,
13512 )
13513 });
13514 })
13515 }
13516
13517 pub fn select_to_end_of_line(
13518 &mut self,
13519 action: &SelectToEndOfLine,
13520 window: &mut Window,
13521 cx: &mut Context<Self>,
13522 ) {
13523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13524 self.change_selections(Default::default(), window, cx, |s| {
13525 s.move_heads_with(|map, head, _| {
13526 (
13527 movement::line_end(map, head, action.stop_at_soft_wraps),
13528 SelectionGoal::None,
13529 )
13530 });
13531 })
13532 }
13533
13534 pub fn delete_to_end_of_line(
13535 &mut self,
13536 _: &DeleteToEndOfLine,
13537 window: &mut Window,
13538 cx: &mut Context<Self>,
13539 ) {
13540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13541 self.transact(window, cx, |this, window, cx| {
13542 this.select_to_end_of_line(
13543 &SelectToEndOfLine {
13544 stop_at_soft_wraps: false,
13545 },
13546 window,
13547 cx,
13548 );
13549 this.delete(&Delete, window, cx);
13550 });
13551 }
13552
13553 pub fn cut_to_end_of_line(
13554 &mut self,
13555 action: &CutToEndOfLine,
13556 window: &mut Window,
13557 cx: &mut Context<Self>,
13558 ) {
13559 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13560 self.transact(window, cx, |this, window, cx| {
13561 this.select_to_end_of_line(
13562 &SelectToEndOfLine {
13563 stop_at_soft_wraps: false,
13564 },
13565 window,
13566 cx,
13567 );
13568 if !action.stop_at_newlines {
13569 this.change_selections(Default::default(), window, cx, |s| {
13570 s.move_with(|_, sel| {
13571 if sel.is_empty() {
13572 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13573 }
13574 });
13575 });
13576 }
13577 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13578 let item = this.cut_common(false, window, cx);
13579 cx.write_to_clipboard(item);
13580 });
13581 }
13582
13583 pub fn move_to_start_of_paragraph(
13584 &mut self,
13585 _: &MoveToStartOfParagraph,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 if matches!(self.mode, EditorMode::SingleLine) {
13590 cx.propagate();
13591 return;
13592 }
13593 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13594 self.change_selections(Default::default(), window, cx, |s| {
13595 s.move_with(|map, selection| {
13596 selection.collapse_to(
13597 movement::start_of_paragraph(map, selection.head(), 1),
13598 SelectionGoal::None,
13599 )
13600 });
13601 })
13602 }
13603
13604 pub fn move_to_end_of_paragraph(
13605 &mut self,
13606 _: &MoveToEndOfParagraph,
13607 window: &mut Window,
13608 cx: &mut Context<Self>,
13609 ) {
13610 if matches!(self.mode, EditorMode::SingleLine) {
13611 cx.propagate();
13612 return;
13613 }
13614 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13615 self.change_selections(Default::default(), window, cx, |s| {
13616 s.move_with(|map, selection| {
13617 selection.collapse_to(
13618 movement::end_of_paragraph(map, selection.head(), 1),
13619 SelectionGoal::None,
13620 )
13621 });
13622 })
13623 }
13624
13625 pub fn select_to_start_of_paragraph(
13626 &mut self,
13627 _: &SelectToStartOfParagraph,
13628 window: &mut Window,
13629 cx: &mut Context<Self>,
13630 ) {
13631 if matches!(self.mode, EditorMode::SingleLine) {
13632 cx.propagate();
13633 return;
13634 }
13635 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13636 self.change_selections(Default::default(), window, cx, |s| {
13637 s.move_heads_with(|map, head, _| {
13638 (
13639 movement::start_of_paragraph(map, head, 1),
13640 SelectionGoal::None,
13641 )
13642 });
13643 })
13644 }
13645
13646 pub fn select_to_end_of_paragraph(
13647 &mut self,
13648 _: &SelectToEndOfParagraph,
13649 window: &mut Window,
13650 cx: &mut Context<Self>,
13651 ) {
13652 if matches!(self.mode, EditorMode::SingleLine) {
13653 cx.propagate();
13654 return;
13655 }
13656 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13657 self.change_selections(Default::default(), window, cx, |s| {
13658 s.move_heads_with(|map, head, _| {
13659 (
13660 movement::end_of_paragraph(map, head, 1),
13661 SelectionGoal::None,
13662 )
13663 });
13664 })
13665 }
13666
13667 pub fn move_to_start_of_excerpt(
13668 &mut self,
13669 _: &MoveToStartOfExcerpt,
13670 window: &mut Window,
13671 cx: &mut Context<Self>,
13672 ) {
13673 if matches!(self.mode, EditorMode::SingleLine) {
13674 cx.propagate();
13675 return;
13676 }
13677 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13678 self.change_selections(Default::default(), window, cx, |s| {
13679 s.move_with(|map, selection| {
13680 selection.collapse_to(
13681 movement::start_of_excerpt(
13682 map,
13683 selection.head(),
13684 workspace::searchable::Direction::Prev,
13685 ),
13686 SelectionGoal::None,
13687 )
13688 });
13689 })
13690 }
13691
13692 pub fn move_to_start_of_next_excerpt(
13693 &mut self,
13694 _: &MoveToStartOfNextExcerpt,
13695 window: &mut Window,
13696 cx: &mut Context<Self>,
13697 ) {
13698 if matches!(self.mode, EditorMode::SingleLine) {
13699 cx.propagate();
13700 return;
13701 }
13702
13703 self.change_selections(Default::default(), window, cx, |s| {
13704 s.move_with(|map, selection| {
13705 selection.collapse_to(
13706 movement::start_of_excerpt(
13707 map,
13708 selection.head(),
13709 workspace::searchable::Direction::Next,
13710 ),
13711 SelectionGoal::None,
13712 )
13713 });
13714 })
13715 }
13716
13717 pub fn move_to_end_of_excerpt(
13718 &mut self,
13719 _: &MoveToEndOfExcerpt,
13720 window: &mut Window,
13721 cx: &mut Context<Self>,
13722 ) {
13723 if matches!(self.mode, EditorMode::SingleLine) {
13724 cx.propagate();
13725 return;
13726 }
13727 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13728 self.change_selections(Default::default(), window, cx, |s| {
13729 s.move_with(|map, selection| {
13730 selection.collapse_to(
13731 movement::end_of_excerpt(
13732 map,
13733 selection.head(),
13734 workspace::searchable::Direction::Next,
13735 ),
13736 SelectionGoal::None,
13737 )
13738 });
13739 })
13740 }
13741
13742 pub fn move_to_end_of_previous_excerpt(
13743 &mut self,
13744 _: &MoveToEndOfPreviousExcerpt,
13745 window: &mut Window,
13746 cx: &mut Context<Self>,
13747 ) {
13748 if matches!(self.mode, EditorMode::SingleLine) {
13749 cx.propagate();
13750 return;
13751 }
13752 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13753 self.change_selections(Default::default(), window, cx, |s| {
13754 s.move_with(|map, selection| {
13755 selection.collapse_to(
13756 movement::end_of_excerpt(
13757 map,
13758 selection.head(),
13759 workspace::searchable::Direction::Prev,
13760 ),
13761 SelectionGoal::None,
13762 )
13763 });
13764 })
13765 }
13766
13767 pub fn select_to_start_of_excerpt(
13768 &mut self,
13769 _: &SelectToStartOfExcerpt,
13770 window: &mut Window,
13771 cx: &mut Context<Self>,
13772 ) {
13773 if matches!(self.mode, EditorMode::SingleLine) {
13774 cx.propagate();
13775 return;
13776 }
13777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13778 self.change_selections(Default::default(), window, cx, |s| {
13779 s.move_heads_with(|map, head, _| {
13780 (
13781 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13782 SelectionGoal::None,
13783 )
13784 });
13785 })
13786 }
13787
13788 pub fn select_to_start_of_next_excerpt(
13789 &mut self,
13790 _: &SelectToStartOfNextExcerpt,
13791 window: &mut Window,
13792 cx: &mut Context<Self>,
13793 ) {
13794 if matches!(self.mode, EditorMode::SingleLine) {
13795 cx.propagate();
13796 return;
13797 }
13798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13799 self.change_selections(Default::default(), window, cx, |s| {
13800 s.move_heads_with(|map, head, _| {
13801 (
13802 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13803 SelectionGoal::None,
13804 )
13805 });
13806 })
13807 }
13808
13809 pub fn select_to_end_of_excerpt(
13810 &mut self,
13811 _: &SelectToEndOfExcerpt,
13812 window: &mut Window,
13813 cx: &mut Context<Self>,
13814 ) {
13815 if matches!(self.mode, EditorMode::SingleLine) {
13816 cx.propagate();
13817 return;
13818 }
13819 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13820 self.change_selections(Default::default(), window, cx, |s| {
13821 s.move_heads_with(|map, head, _| {
13822 (
13823 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13824 SelectionGoal::None,
13825 )
13826 });
13827 })
13828 }
13829
13830 pub fn select_to_end_of_previous_excerpt(
13831 &mut self,
13832 _: &SelectToEndOfPreviousExcerpt,
13833 window: &mut Window,
13834 cx: &mut Context<Self>,
13835 ) {
13836 if matches!(self.mode, EditorMode::SingleLine) {
13837 cx.propagate();
13838 return;
13839 }
13840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13841 self.change_selections(Default::default(), window, cx, |s| {
13842 s.move_heads_with(|map, head, _| {
13843 (
13844 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13845 SelectionGoal::None,
13846 )
13847 });
13848 })
13849 }
13850
13851 pub fn move_to_beginning(
13852 &mut self,
13853 _: &MoveToBeginning,
13854 window: &mut Window,
13855 cx: &mut Context<Self>,
13856 ) {
13857 if matches!(self.mode, EditorMode::SingleLine) {
13858 cx.propagate();
13859 return;
13860 }
13861 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13862 self.change_selections(Default::default(), window, cx, |s| {
13863 s.select_ranges(vec![0..0]);
13864 });
13865 }
13866
13867 pub fn select_to_beginning(
13868 &mut self,
13869 _: &SelectToBeginning,
13870 window: &mut Window,
13871 cx: &mut Context<Self>,
13872 ) {
13873 let mut selection = self.selections.last::<Point>(cx);
13874 selection.set_head(Point::zero(), SelectionGoal::None);
13875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13876 self.change_selections(Default::default(), window, cx, |s| {
13877 s.select(vec![selection]);
13878 });
13879 }
13880
13881 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13882 if matches!(self.mode, EditorMode::SingleLine) {
13883 cx.propagate();
13884 return;
13885 }
13886 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13887 let cursor = self.buffer.read(cx).read(cx).len();
13888 self.change_selections(Default::default(), window, cx, |s| {
13889 s.select_ranges(vec![cursor..cursor])
13890 });
13891 }
13892
13893 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13894 self.nav_history = nav_history;
13895 }
13896
13897 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13898 self.nav_history.as_ref()
13899 }
13900
13901 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13902 self.push_to_nav_history(
13903 self.selections.newest_anchor().head(),
13904 None,
13905 false,
13906 true,
13907 cx,
13908 );
13909 }
13910
13911 fn push_to_nav_history(
13912 &mut self,
13913 cursor_anchor: Anchor,
13914 new_position: Option<Point>,
13915 is_deactivate: bool,
13916 always: bool,
13917 cx: &mut Context<Self>,
13918 ) {
13919 if let Some(nav_history) = self.nav_history.as_mut() {
13920 let buffer = self.buffer.read(cx).read(cx);
13921 let cursor_position = cursor_anchor.to_point(&buffer);
13922 let scroll_state = self.scroll_manager.anchor();
13923 let scroll_top_row = scroll_state.top_row(&buffer);
13924 drop(buffer);
13925
13926 if let Some(new_position) = new_position {
13927 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13928 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13929 return;
13930 }
13931 }
13932
13933 nav_history.push(
13934 Some(NavigationData {
13935 cursor_anchor,
13936 cursor_position,
13937 scroll_anchor: scroll_state,
13938 scroll_top_row,
13939 }),
13940 cx,
13941 );
13942 cx.emit(EditorEvent::PushedToNavHistory {
13943 anchor: cursor_anchor,
13944 is_deactivate,
13945 })
13946 }
13947 }
13948
13949 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13950 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13951 let buffer = self.buffer.read(cx).snapshot(cx);
13952 let mut selection = self.selections.first::<usize>(cx);
13953 selection.set_head(buffer.len(), SelectionGoal::None);
13954 self.change_selections(Default::default(), window, cx, |s| {
13955 s.select(vec![selection]);
13956 });
13957 }
13958
13959 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13961 let end = self.buffer.read(cx).read(cx).len();
13962 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13963 s.select_ranges(vec![0..end]);
13964 });
13965 }
13966
13967 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13968 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13969 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13970 let mut selections = self.selections.all::<Point>(cx);
13971 let max_point = display_map.buffer_snapshot.max_point();
13972 for selection in &mut selections {
13973 let rows = selection.spanned_rows(true, &display_map);
13974 selection.start = Point::new(rows.start.0, 0);
13975 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13976 selection.reversed = false;
13977 }
13978 self.change_selections(Default::default(), window, cx, |s| {
13979 s.select(selections);
13980 });
13981 }
13982
13983 pub fn split_selection_into_lines(
13984 &mut self,
13985 action: &SplitSelectionIntoLines,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) {
13989 let selections = self
13990 .selections
13991 .all::<Point>(cx)
13992 .into_iter()
13993 .map(|selection| selection.start..selection.end)
13994 .collect::<Vec<_>>();
13995 self.unfold_ranges(&selections, true, true, cx);
13996
13997 let mut new_selection_ranges = Vec::new();
13998 {
13999 let buffer = self.buffer.read(cx).read(cx);
14000 for selection in selections {
14001 for row in selection.start.row..selection.end.row {
14002 let line_start = Point::new(row, 0);
14003 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14004
14005 if action.keep_selections {
14006 // Keep the selection range for each line
14007 let selection_start = if row == selection.start.row {
14008 selection.start
14009 } else {
14010 line_start
14011 };
14012 new_selection_ranges.push(selection_start..line_end);
14013 } else {
14014 // Collapse to cursor at end of line
14015 new_selection_ranges.push(line_end..line_end);
14016 }
14017 }
14018
14019 let is_multiline_selection = selection.start.row != selection.end.row;
14020 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14021 // so this action feels more ergonomic when paired with other selection operations
14022 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14023 if !should_skip_last {
14024 if action.keep_selections {
14025 if is_multiline_selection {
14026 let line_start = Point::new(selection.end.row, 0);
14027 new_selection_ranges.push(line_start..selection.end);
14028 } else {
14029 new_selection_ranges.push(selection.start..selection.end);
14030 }
14031 } else {
14032 new_selection_ranges.push(selection.end..selection.end);
14033 }
14034 }
14035 }
14036 }
14037 self.change_selections(Default::default(), window, cx, |s| {
14038 s.select_ranges(new_selection_ranges);
14039 });
14040 }
14041
14042 pub fn add_selection_above(
14043 &mut self,
14044 _: &AddSelectionAbove,
14045 window: &mut Window,
14046 cx: &mut Context<Self>,
14047 ) {
14048 self.add_selection(true, window, cx);
14049 }
14050
14051 pub fn add_selection_below(
14052 &mut self,
14053 _: &AddSelectionBelow,
14054 window: &mut Window,
14055 cx: &mut Context<Self>,
14056 ) {
14057 self.add_selection(false, window, cx);
14058 }
14059
14060 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14061 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14062
14063 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14064 let all_selections = self.selections.all::<Point>(cx);
14065 let text_layout_details = self.text_layout_details(window);
14066
14067 let (mut columnar_selections, new_selections_to_columnarize) = {
14068 if let Some(state) = self.add_selections_state.as_ref() {
14069 let columnar_selection_ids: HashSet<_> = state
14070 .groups
14071 .iter()
14072 .flat_map(|group| group.stack.iter())
14073 .copied()
14074 .collect();
14075
14076 all_selections
14077 .into_iter()
14078 .partition(|s| columnar_selection_ids.contains(&s.id))
14079 } else {
14080 (Vec::new(), all_selections)
14081 }
14082 };
14083
14084 let mut state = self
14085 .add_selections_state
14086 .take()
14087 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14088
14089 for selection in new_selections_to_columnarize {
14090 let range = selection.display_range(&display_map).sorted();
14091 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14092 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14093 let positions = start_x.min(end_x)..start_x.max(end_x);
14094 let mut stack = Vec::new();
14095 for row in range.start.row().0..=range.end.row().0 {
14096 if let Some(selection) = self.selections.build_columnar_selection(
14097 &display_map,
14098 DisplayRow(row),
14099 &positions,
14100 selection.reversed,
14101 &text_layout_details,
14102 ) {
14103 stack.push(selection.id);
14104 columnar_selections.push(selection);
14105 }
14106 }
14107 if !stack.is_empty() {
14108 if above {
14109 stack.reverse();
14110 }
14111 state.groups.push(AddSelectionsGroup { above, stack });
14112 }
14113 }
14114
14115 let mut final_selections = Vec::new();
14116 let end_row = if above {
14117 DisplayRow(0)
14118 } else {
14119 display_map.max_point().row()
14120 };
14121
14122 let mut last_added_item_per_group = HashMap::default();
14123 for group in state.groups.iter_mut() {
14124 if let Some(last_id) = group.stack.last() {
14125 last_added_item_per_group.insert(*last_id, group);
14126 }
14127 }
14128
14129 for selection in columnar_selections {
14130 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14131 if above == group.above {
14132 let range = selection.display_range(&display_map).sorted();
14133 debug_assert_eq!(range.start.row(), range.end.row());
14134 let mut row = range.start.row();
14135 let positions =
14136 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14137 px(start)..px(end)
14138 } else {
14139 let start_x =
14140 display_map.x_for_display_point(range.start, &text_layout_details);
14141 let end_x =
14142 display_map.x_for_display_point(range.end, &text_layout_details);
14143 start_x.min(end_x)..start_x.max(end_x)
14144 };
14145
14146 let mut maybe_new_selection = None;
14147 while row != end_row {
14148 if above {
14149 row.0 -= 1;
14150 } else {
14151 row.0 += 1;
14152 }
14153 if let Some(new_selection) = self.selections.build_columnar_selection(
14154 &display_map,
14155 row,
14156 &positions,
14157 selection.reversed,
14158 &text_layout_details,
14159 ) {
14160 maybe_new_selection = Some(new_selection);
14161 break;
14162 }
14163 }
14164
14165 if let Some(new_selection) = maybe_new_selection {
14166 group.stack.push(new_selection.id);
14167 if above {
14168 final_selections.push(new_selection);
14169 final_selections.push(selection);
14170 } else {
14171 final_selections.push(selection);
14172 final_selections.push(new_selection);
14173 }
14174 } else {
14175 final_selections.push(selection);
14176 }
14177 } else {
14178 group.stack.pop();
14179 }
14180 } else {
14181 final_selections.push(selection);
14182 }
14183 }
14184
14185 self.change_selections(Default::default(), window, cx, |s| {
14186 s.select(final_selections);
14187 });
14188
14189 let final_selection_ids: HashSet<_> = self
14190 .selections
14191 .all::<Point>(cx)
14192 .iter()
14193 .map(|s| s.id)
14194 .collect();
14195 state.groups.retain_mut(|group| {
14196 // selections might get merged above so we remove invalid items from stacks
14197 group.stack.retain(|id| final_selection_ids.contains(id));
14198
14199 // single selection in stack can be treated as initial state
14200 group.stack.len() > 1
14201 });
14202
14203 if !state.groups.is_empty() {
14204 self.add_selections_state = Some(state);
14205 }
14206 }
14207
14208 fn select_match_ranges(
14209 &mut self,
14210 range: Range<usize>,
14211 reversed: bool,
14212 replace_newest: bool,
14213 auto_scroll: Option<Autoscroll>,
14214 window: &mut Window,
14215 cx: &mut Context<Editor>,
14216 ) {
14217 self.unfold_ranges(
14218 std::slice::from_ref(&range),
14219 false,
14220 auto_scroll.is_some(),
14221 cx,
14222 );
14223 let effects = if let Some(scroll) = auto_scroll {
14224 SelectionEffects::scroll(scroll)
14225 } else {
14226 SelectionEffects::no_scroll()
14227 };
14228 self.change_selections(effects, window, cx, |s| {
14229 if replace_newest {
14230 s.delete(s.newest_anchor().id);
14231 }
14232 if reversed {
14233 s.insert_range(range.end..range.start);
14234 } else {
14235 s.insert_range(range);
14236 }
14237 });
14238 }
14239
14240 pub fn select_next_match_internal(
14241 &mut self,
14242 display_map: &DisplaySnapshot,
14243 replace_newest: bool,
14244 autoscroll: Option<Autoscroll>,
14245 window: &mut Window,
14246 cx: &mut Context<Self>,
14247 ) -> Result<()> {
14248 let buffer = &display_map.buffer_snapshot;
14249 let mut selections = self.selections.all::<usize>(cx);
14250 if let Some(mut select_next_state) = self.select_next_state.take() {
14251 let query = &select_next_state.query;
14252 if !select_next_state.done {
14253 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14254 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14255 let mut next_selected_range = None;
14256
14257 let bytes_after_last_selection =
14258 buffer.bytes_in_range(last_selection.end..buffer.len());
14259 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14260 let query_matches = query
14261 .stream_find_iter(bytes_after_last_selection)
14262 .map(|result| (last_selection.end, result))
14263 .chain(
14264 query
14265 .stream_find_iter(bytes_before_first_selection)
14266 .map(|result| (0, result)),
14267 );
14268
14269 for (start_offset, query_match) in query_matches {
14270 let query_match = query_match.unwrap(); // can only fail due to I/O
14271 let offset_range =
14272 start_offset + query_match.start()..start_offset + query_match.end();
14273
14274 if !select_next_state.wordwise
14275 || (!buffer.is_inside_word(offset_range.start, None)
14276 && !buffer.is_inside_word(offset_range.end, None))
14277 {
14278 // TODO: This is n^2, because we might check all the selections
14279 if !selections
14280 .iter()
14281 .any(|selection| selection.range().overlaps(&offset_range))
14282 {
14283 next_selected_range = Some(offset_range);
14284 break;
14285 }
14286 }
14287 }
14288
14289 if let Some(next_selected_range) = next_selected_range {
14290 self.select_match_ranges(
14291 next_selected_range,
14292 last_selection.reversed,
14293 replace_newest,
14294 autoscroll,
14295 window,
14296 cx,
14297 );
14298 } else {
14299 select_next_state.done = true;
14300 }
14301 }
14302
14303 self.select_next_state = Some(select_next_state);
14304 } else {
14305 let mut only_carets = true;
14306 let mut same_text_selected = true;
14307 let mut selected_text = None;
14308
14309 let mut selections_iter = selections.iter().peekable();
14310 while let Some(selection) = selections_iter.next() {
14311 if selection.start != selection.end {
14312 only_carets = false;
14313 }
14314
14315 if same_text_selected {
14316 if selected_text.is_none() {
14317 selected_text =
14318 Some(buffer.text_for_range(selection.range()).collect::<String>());
14319 }
14320
14321 if let Some(next_selection) = selections_iter.peek() {
14322 if next_selection.range().len() == selection.range().len() {
14323 let next_selected_text = buffer
14324 .text_for_range(next_selection.range())
14325 .collect::<String>();
14326 if Some(next_selected_text) != selected_text {
14327 same_text_selected = false;
14328 selected_text = None;
14329 }
14330 } else {
14331 same_text_selected = false;
14332 selected_text = None;
14333 }
14334 }
14335 }
14336 }
14337
14338 if only_carets {
14339 for selection in &mut selections {
14340 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14341 selection.start = word_range.start;
14342 selection.end = word_range.end;
14343 selection.goal = SelectionGoal::None;
14344 selection.reversed = false;
14345 self.select_match_ranges(
14346 selection.start..selection.end,
14347 selection.reversed,
14348 replace_newest,
14349 autoscroll,
14350 window,
14351 cx,
14352 );
14353 }
14354
14355 if selections.len() == 1 {
14356 let selection = selections
14357 .last()
14358 .expect("ensured that there's only one selection");
14359 let query = buffer
14360 .text_for_range(selection.start..selection.end)
14361 .collect::<String>();
14362 let is_empty = query.is_empty();
14363 let select_state = SelectNextState {
14364 query: AhoCorasick::new(&[query])?,
14365 wordwise: true,
14366 done: is_empty,
14367 };
14368 self.select_next_state = Some(select_state);
14369 } else {
14370 self.select_next_state = None;
14371 }
14372 } else if let Some(selected_text) = selected_text {
14373 self.select_next_state = Some(SelectNextState {
14374 query: AhoCorasick::new(&[selected_text])?,
14375 wordwise: false,
14376 done: false,
14377 });
14378 self.select_next_match_internal(
14379 display_map,
14380 replace_newest,
14381 autoscroll,
14382 window,
14383 cx,
14384 )?;
14385 }
14386 }
14387 Ok(())
14388 }
14389
14390 pub fn select_all_matches(
14391 &mut self,
14392 _action: &SelectAllMatches,
14393 window: &mut Window,
14394 cx: &mut Context<Self>,
14395 ) -> Result<()> {
14396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14397
14398 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14399
14400 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14401 let Some(select_next_state) = self.select_next_state.as_mut() else {
14402 return Ok(());
14403 };
14404 if select_next_state.done {
14405 return Ok(());
14406 }
14407
14408 let mut new_selections = Vec::new();
14409
14410 let reversed = self.selections.oldest::<usize>(cx).reversed;
14411 let buffer = &display_map.buffer_snapshot;
14412 let query_matches = select_next_state
14413 .query
14414 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14415
14416 for query_match in query_matches.into_iter() {
14417 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14418 let offset_range = if reversed {
14419 query_match.end()..query_match.start()
14420 } else {
14421 query_match.start()..query_match.end()
14422 };
14423
14424 if !select_next_state.wordwise
14425 || (!buffer.is_inside_word(offset_range.start, None)
14426 && !buffer.is_inside_word(offset_range.end, None))
14427 {
14428 new_selections.push(offset_range.start..offset_range.end);
14429 }
14430 }
14431
14432 select_next_state.done = true;
14433
14434 if new_selections.is_empty() {
14435 log::error!("bug: new_selections is empty in select_all_matches");
14436 return Ok(());
14437 }
14438
14439 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14440 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14441 selections.select_ranges(new_selections)
14442 });
14443
14444 Ok(())
14445 }
14446
14447 pub fn select_next(
14448 &mut self,
14449 action: &SelectNext,
14450 window: &mut Window,
14451 cx: &mut Context<Self>,
14452 ) -> Result<()> {
14453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14454 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14455 self.select_next_match_internal(
14456 &display_map,
14457 action.replace_newest,
14458 Some(Autoscroll::newest()),
14459 window,
14460 cx,
14461 )?;
14462 Ok(())
14463 }
14464
14465 pub fn select_previous(
14466 &mut self,
14467 action: &SelectPrevious,
14468 window: &mut Window,
14469 cx: &mut Context<Self>,
14470 ) -> Result<()> {
14471 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14472 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14473 let buffer = &display_map.buffer_snapshot;
14474 let mut selections = self.selections.all::<usize>(cx);
14475 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14476 let query = &select_prev_state.query;
14477 if !select_prev_state.done {
14478 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14479 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14480 let mut next_selected_range = None;
14481 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14482 let bytes_before_last_selection =
14483 buffer.reversed_bytes_in_range(0..last_selection.start);
14484 let bytes_after_first_selection =
14485 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14486 let query_matches = query
14487 .stream_find_iter(bytes_before_last_selection)
14488 .map(|result| (last_selection.start, result))
14489 .chain(
14490 query
14491 .stream_find_iter(bytes_after_first_selection)
14492 .map(|result| (buffer.len(), result)),
14493 );
14494 for (end_offset, query_match) in query_matches {
14495 let query_match = query_match.unwrap(); // can only fail due to I/O
14496 let offset_range =
14497 end_offset - query_match.end()..end_offset - query_match.start();
14498
14499 if !select_prev_state.wordwise
14500 || (!buffer.is_inside_word(offset_range.start, None)
14501 && !buffer.is_inside_word(offset_range.end, None))
14502 {
14503 next_selected_range = Some(offset_range);
14504 break;
14505 }
14506 }
14507
14508 if let Some(next_selected_range) = next_selected_range {
14509 self.select_match_ranges(
14510 next_selected_range,
14511 last_selection.reversed,
14512 action.replace_newest,
14513 Some(Autoscroll::newest()),
14514 window,
14515 cx,
14516 );
14517 } else {
14518 select_prev_state.done = true;
14519 }
14520 }
14521
14522 self.select_prev_state = Some(select_prev_state);
14523 } else {
14524 let mut only_carets = true;
14525 let mut same_text_selected = true;
14526 let mut selected_text = None;
14527
14528 let mut selections_iter = selections.iter().peekable();
14529 while let Some(selection) = selections_iter.next() {
14530 if selection.start != selection.end {
14531 only_carets = false;
14532 }
14533
14534 if same_text_selected {
14535 if selected_text.is_none() {
14536 selected_text =
14537 Some(buffer.text_for_range(selection.range()).collect::<String>());
14538 }
14539
14540 if let Some(next_selection) = selections_iter.peek() {
14541 if next_selection.range().len() == selection.range().len() {
14542 let next_selected_text = buffer
14543 .text_for_range(next_selection.range())
14544 .collect::<String>();
14545 if Some(next_selected_text) != selected_text {
14546 same_text_selected = false;
14547 selected_text = None;
14548 }
14549 } else {
14550 same_text_selected = false;
14551 selected_text = None;
14552 }
14553 }
14554 }
14555 }
14556
14557 if only_carets {
14558 for selection in &mut selections {
14559 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14560 selection.start = word_range.start;
14561 selection.end = word_range.end;
14562 selection.goal = SelectionGoal::None;
14563 selection.reversed = false;
14564 self.select_match_ranges(
14565 selection.start..selection.end,
14566 selection.reversed,
14567 action.replace_newest,
14568 Some(Autoscroll::newest()),
14569 window,
14570 cx,
14571 );
14572 }
14573 if selections.len() == 1 {
14574 let selection = selections
14575 .last()
14576 .expect("ensured that there's only one selection");
14577 let query = buffer
14578 .text_for_range(selection.start..selection.end)
14579 .collect::<String>();
14580 let is_empty = query.is_empty();
14581 let select_state = SelectNextState {
14582 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14583 wordwise: true,
14584 done: is_empty,
14585 };
14586 self.select_prev_state = Some(select_state);
14587 } else {
14588 self.select_prev_state = None;
14589 }
14590 } else if let Some(selected_text) = selected_text {
14591 self.select_prev_state = Some(SelectNextState {
14592 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14593 wordwise: false,
14594 done: false,
14595 });
14596 self.select_previous(action, window, cx)?;
14597 }
14598 }
14599 Ok(())
14600 }
14601
14602 pub fn find_next_match(
14603 &mut self,
14604 _: &FindNextMatch,
14605 window: &mut Window,
14606 cx: &mut Context<Self>,
14607 ) -> Result<()> {
14608 let selections = self.selections.disjoint_anchors_arc();
14609 match selections.first() {
14610 Some(first) if selections.len() >= 2 => {
14611 self.change_selections(Default::default(), window, cx, |s| {
14612 s.select_ranges([first.range()]);
14613 });
14614 }
14615 _ => self.select_next(
14616 &SelectNext {
14617 replace_newest: true,
14618 },
14619 window,
14620 cx,
14621 )?,
14622 }
14623 Ok(())
14624 }
14625
14626 pub fn find_previous_match(
14627 &mut self,
14628 _: &FindPreviousMatch,
14629 window: &mut Window,
14630 cx: &mut Context<Self>,
14631 ) -> Result<()> {
14632 let selections = self.selections.disjoint_anchors_arc();
14633 match selections.last() {
14634 Some(last) if selections.len() >= 2 => {
14635 self.change_selections(Default::default(), window, cx, |s| {
14636 s.select_ranges([last.range()]);
14637 });
14638 }
14639 _ => self.select_previous(
14640 &SelectPrevious {
14641 replace_newest: true,
14642 },
14643 window,
14644 cx,
14645 )?,
14646 }
14647 Ok(())
14648 }
14649
14650 pub fn toggle_comments(
14651 &mut self,
14652 action: &ToggleComments,
14653 window: &mut Window,
14654 cx: &mut Context<Self>,
14655 ) {
14656 if self.read_only(cx) {
14657 return;
14658 }
14659 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14660 let text_layout_details = &self.text_layout_details(window);
14661 self.transact(window, cx, |this, window, cx| {
14662 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14663 let mut edits = Vec::new();
14664 let mut selection_edit_ranges = Vec::new();
14665 let mut last_toggled_row = None;
14666 let snapshot = this.buffer.read(cx).read(cx);
14667 let empty_str: Arc<str> = Arc::default();
14668 let mut suffixes_inserted = Vec::new();
14669 let ignore_indent = action.ignore_indent;
14670
14671 fn comment_prefix_range(
14672 snapshot: &MultiBufferSnapshot,
14673 row: MultiBufferRow,
14674 comment_prefix: &str,
14675 comment_prefix_whitespace: &str,
14676 ignore_indent: bool,
14677 ) -> Range<Point> {
14678 let indent_size = if ignore_indent {
14679 0
14680 } else {
14681 snapshot.indent_size_for_line(row).len
14682 };
14683
14684 let start = Point::new(row.0, indent_size);
14685
14686 let mut line_bytes = snapshot
14687 .bytes_in_range(start..snapshot.max_point())
14688 .flatten()
14689 .copied();
14690
14691 // If this line currently begins with the line comment prefix, then record
14692 // the range containing the prefix.
14693 if line_bytes
14694 .by_ref()
14695 .take(comment_prefix.len())
14696 .eq(comment_prefix.bytes())
14697 {
14698 // Include any whitespace that matches the comment prefix.
14699 let matching_whitespace_len = line_bytes
14700 .zip(comment_prefix_whitespace.bytes())
14701 .take_while(|(a, b)| a == b)
14702 .count() as u32;
14703 let end = Point::new(
14704 start.row,
14705 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14706 );
14707 start..end
14708 } else {
14709 start..start
14710 }
14711 }
14712
14713 fn comment_suffix_range(
14714 snapshot: &MultiBufferSnapshot,
14715 row: MultiBufferRow,
14716 comment_suffix: &str,
14717 comment_suffix_has_leading_space: bool,
14718 ) -> Range<Point> {
14719 let end = Point::new(row.0, snapshot.line_len(row));
14720 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14721
14722 let mut line_end_bytes = snapshot
14723 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14724 .flatten()
14725 .copied();
14726
14727 let leading_space_len = if suffix_start_column > 0
14728 && line_end_bytes.next() == Some(b' ')
14729 && comment_suffix_has_leading_space
14730 {
14731 1
14732 } else {
14733 0
14734 };
14735
14736 // If this line currently begins with the line comment prefix, then record
14737 // the range containing the prefix.
14738 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14739 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14740 start..end
14741 } else {
14742 end..end
14743 }
14744 }
14745
14746 // TODO: Handle selections that cross excerpts
14747 for selection in &mut selections {
14748 let start_column = snapshot
14749 .indent_size_for_line(MultiBufferRow(selection.start.row))
14750 .len;
14751 let language = if let Some(language) =
14752 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14753 {
14754 language
14755 } else {
14756 continue;
14757 };
14758
14759 selection_edit_ranges.clear();
14760
14761 // If multiple selections contain a given row, avoid processing that
14762 // row more than once.
14763 let mut start_row = MultiBufferRow(selection.start.row);
14764 if last_toggled_row == Some(start_row) {
14765 start_row = start_row.next_row();
14766 }
14767 let end_row =
14768 if selection.end.row > selection.start.row && selection.end.column == 0 {
14769 MultiBufferRow(selection.end.row - 1)
14770 } else {
14771 MultiBufferRow(selection.end.row)
14772 };
14773 last_toggled_row = Some(end_row);
14774
14775 if start_row > end_row {
14776 continue;
14777 }
14778
14779 // If the language has line comments, toggle those.
14780 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14781
14782 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14783 if ignore_indent {
14784 full_comment_prefixes = full_comment_prefixes
14785 .into_iter()
14786 .map(|s| Arc::from(s.trim_end()))
14787 .collect();
14788 }
14789
14790 if !full_comment_prefixes.is_empty() {
14791 let first_prefix = full_comment_prefixes
14792 .first()
14793 .expect("prefixes is non-empty");
14794 let prefix_trimmed_lengths = full_comment_prefixes
14795 .iter()
14796 .map(|p| p.trim_end_matches(' ').len())
14797 .collect::<SmallVec<[usize; 4]>>();
14798
14799 let mut all_selection_lines_are_comments = true;
14800
14801 for row in start_row.0..=end_row.0 {
14802 let row = MultiBufferRow(row);
14803 if start_row < end_row && snapshot.is_line_blank(row) {
14804 continue;
14805 }
14806
14807 let prefix_range = full_comment_prefixes
14808 .iter()
14809 .zip(prefix_trimmed_lengths.iter().copied())
14810 .map(|(prefix, trimmed_prefix_len)| {
14811 comment_prefix_range(
14812 snapshot.deref(),
14813 row,
14814 &prefix[..trimmed_prefix_len],
14815 &prefix[trimmed_prefix_len..],
14816 ignore_indent,
14817 )
14818 })
14819 .max_by_key(|range| range.end.column - range.start.column)
14820 .expect("prefixes is non-empty");
14821
14822 if prefix_range.is_empty() {
14823 all_selection_lines_are_comments = false;
14824 }
14825
14826 selection_edit_ranges.push(prefix_range);
14827 }
14828
14829 if all_selection_lines_are_comments {
14830 edits.extend(
14831 selection_edit_ranges
14832 .iter()
14833 .cloned()
14834 .map(|range| (range, empty_str.clone())),
14835 );
14836 } else {
14837 let min_column = selection_edit_ranges
14838 .iter()
14839 .map(|range| range.start.column)
14840 .min()
14841 .unwrap_or(0);
14842 edits.extend(selection_edit_ranges.iter().map(|range| {
14843 let position = Point::new(range.start.row, min_column);
14844 (position..position, first_prefix.clone())
14845 }));
14846 }
14847 } else if let Some(BlockCommentConfig {
14848 start: full_comment_prefix,
14849 end: comment_suffix,
14850 ..
14851 }) = language.block_comment()
14852 {
14853 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14854 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14855 let prefix_range = comment_prefix_range(
14856 snapshot.deref(),
14857 start_row,
14858 comment_prefix,
14859 comment_prefix_whitespace,
14860 ignore_indent,
14861 );
14862 let suffix_range = comment_suffix_range(
14863 snapshot.deref(),
14864 end_row,
14865 comment_suffix.trim_start_matches(' '),
14866 comment_suffix.starts_with(' '),
14867 );
14868
14869 if prefix_range.is_empty() || suffix_range.is_empty() {
14870 edits.push((
14871 prefix_range.start..prefix_range.start,
14872 full_comment_prefix.clone(),
14873 ));
14874 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14875 suffixes_inserted.push((end_row, comment_suffix.len()));
14876 } else {
14877 edits.push((prefix_range, empty_str.clone()));
14878 edits.push((suffix_range, empty_str.clone()));
14879 }
14880 } else {
14881 continue;
14882 }
14883 }
14884
14885 drop(snapshot);
14886 this.buffer.update(cx, |buffer, cx| {
14887 buffer.edit(edits, None, cx);
14888 });
14889
14890 // Adjust selections so that they end before any comment suffixes that
14891 // were inserted.
14892 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14893 let mut selections = this.selections.all::<Point>(cx);
14894 let snapshot = this.buffer.read(cx).read(cx);
14895 for selection in &mut selections {
14896 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14897 match row.cmp(&MultiBufferRow(selection.end.row)) {
14898 Ordering::Less => {
14899 suffixes_inserted.next();
14900 continue;
14901 }
14902 Ordering::Greater => break,
14903 Ordering::Equal => {
14904 if selection.end.column == snapshot.line_len(row) {
14905 if selection.is_empty() {
14906 selection.start.column -= suffix_len as u32;
14907 }
14908 selection.end.column -= suffix_len as u32;
14909 }
14910 break;
14911 }
14912 }
14913 }
14914 }
14915
14916 drop(snapshot);
14917 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14918
14919 let selections = this.selections.all::<Point>(cx);
14920 let selections_on_single_row = selections.windows(2).all(|selections| {
14921 selections[0].start.row == selections[1].start.row
14922 && selections[0].end.row == selections[1].end.row
14923 && selections[0].start.row == selections[0].end.row
14924 });
14925 let selections_selecting = selections
14926 .iter()
14927 .any(|selection| selection.start != selection.end);
14928 let advance_downwards = action.advance_downwards
14929 && selections_on_single_row
14930 && !selections_selecting
14931 && !matches!(this.mode, EditorMode::SingleLine);
14932
14933 if advance_downwards {
14934 let snapshot = this.buffer.read(cx).snapshot(cx);
14935
14936 this.change_selections(Default::default(), window, cx, |s| {
14937 s.move_cursors_with(|display_snapshot, display_point, _| {
14938 let mut point = display_point.to_point(display_snapshot);
14939 point.row += 1;
14940 point = snapshot.clip_point(point, Bias::Left);
14941 let display_point = point.to_display_point(display_snapshot);
14942 let goal = SelectionGoal::HorizontalPosition(
14943 display_snapshot
14944 .x_for_display_point(display_point, text_layout_details)
14945 .into(),
14946 );
14947 (display_point, goal)
14948 })
14949 });
14950 }
14951 });
14952 }
14953
14954 pub fn select_enclosing_symbol(
14955 &mut self,
14956 _: &SelectEnclosingSymbol,
14957 window: &mut Window,
14958 cx: &mut Context<Self>,
14959 ) {
14960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14961
14962 let buffer = self.buffer.read(cx).snapshot(cx);
14963 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14964
14965 fn update_selection(
14966 selection: &Selection<usize>,
14967 buffer_snap: &MultiBufferSnapshot,
14968 ) -> Option<Selection<usize>> {
14969 let cursor = selection.head();
14970 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14971 for symbol in symbols.iter().rev() {
14972 let start = symbol.range.start.to_offset(buffer_snap);
14973 let end = symbol.range.end.to_offset(buffer_snap);
14974 let new_range = start..end;
14975 if start < selection.start || end > selection.end {
14976 return Some(Selection {
14977 id: selection.id,
14978 start: new_range.start,
14979 end: new_range.end,
14980 goal: SelectionGoal::None,
14981 reversed: selection.reversed,
14982 });
14983 }
14984 }
14985 None
14986 }
14987
14988 let mut selected_larger_symbol = false;
14989 let new_selections = old_selections
14990 .iter()
14991 .map(|selection| match update_selection(selection, &buffer) {
14992 Some(new_selection) => {
14993 if new_selection.range() != selection.range() {
14994 selected_larger_symbol = true;
14995 }
14996 new_selection
14997 }
14998 None => selection.clone(),
14999 })
15000 .collect::<Vec<_>>();
15001
15002 if selected_larger_symbol {
15003 self.change_selections(Default::default(), window, cx, |s| {
15004 s.select(new_selections);
15005 });
15006 }
15007 }
15008
15009 pub fn select_larger_syntax_node(
15010 &mut self,
15011 _: &SelectLargerSyntaxNode,
15012 window: &mut Window,
15013 cx: &mut Context<Self>,
15014 ) {
15015 let Some(visible_row_count) = self.visible_row_count() else {
15016 return;
15017 };
15018 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15019 if old_selections.is_empty() {
15020 return;
15021 }
15022
15023 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15024
15025 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15026 let buffer = self.buffer.read(cx).snapshot(cx);
15027
15028 let mut selected_larger_node = false;
15029 let mut new_selections = old_selections
15030 .iter()
15031 .map(|selection| {
15032 let old_range = selection.start..selection.end;
15033
15034 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15035 // manually select word at selection
15036 if ["string_content", "inline"].contains(&node.kind()) {
15037 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15038 // ignore if word is already selected
15039 if !word_range.is_empty() && old_range != word_range {
15040 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15041 // only select word if start and end point belongs to same word
15042 if word_range == last_word_range {
15043 selected_larger_node = true;
15044 return Selection {
15045 id: selection.id,
15046 start: word_range.start,
15047 end: word_range.end,
15048 goal: SelectionGoal::None,
15049 reversed: selection.reversed,
15050 };
15051 }
15052 }
15053 }
15054 }
15055
15056 let mut new_range = old_range.clone();
15057 while let Some((node, containing_range)) = buffer.syntax_ancestor(new_range.clone())
15058 {
15059 new_range = match containing_range {
15060 MultiOrSingleBufferOffsetRange::Single(_) => break,
15061 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15062 };
15063 if !node.is_named() {
15064 continue;
15065 }
15066 if !display_map.intersects_fold(new_range.start)
15067 && !display_map.intersects_fold(new_range.end)
15068 {
15069 break;
15070 }
15071 }
15072
15073 selected_larger_node |= new_range != old_range;
15074 Selection {
15075 id: selection.id,
15076 start: new_range.start,
15077 end: new_range.end,
15078 goal: SelectionGoal::None,
15079 reversed: selection.reversed,
15080 }
15081 })
15082 .collect::<Vec<_>>();
15083
15084 if !selected_larger_node {
15085 return; // don't put this call in the history
15086 }
15087
15088 // scroll based on transformation done to the last selection created by the user
15089 let (last_old, last_new) = old_selections
15090 .last()
15091 .zip(new_selections.last().cloned())
15092 .expect("old_selections isn't empty");
15093
15094 // revert selection
15095 let is_selection_reversed = {
15096 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15097 new_selections.last_mut().expect("checked above").reversed =
15098 should_newest_selection_be_reversed;
15099 should_newest_selection_be_reversed
15100 };
15101
15102 if selected_larger_node {
15103 self.select_syntax_node_history.disable_clearing = true;
15104 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15105 s.select(new_selections.clone());
15106 });
15107 self.select_syntax_node_history.disable_clearing = false;
15108 }
15109
15110 let start_row = last_new.start.to_display_point(&display_map).row().0;
15111 let end_row = last_new.end.to_display_point(&display_map).row().0;
15112 let selection_height = end_row - start_row + 1;
15113 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15114
15115 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15116 let scroll_behavior = if fits_on_the_screen {
15117 self.request_autoscroll(Autoscroll::fit(), cx);
15118 SelectSyntaxNodeScrollBehavior::FitSelection
15119 } else if is_selection_reversed {
15120 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15121 SelectSyntaxNodeScrollBehavior::CursorTop
15122 } else {
15123 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15124 SelectSyntaxNodeScrollBehavior::CursorBottom
15125 };
15126
15127 self.select_syntax_node_history.push((
15128 old_selections,
15129 scroll_behavior,
15130 is_selection_reversed,
15131 ));
15132 }
15133
15134 pub fn select_smaller_syntax_node(
15135 &mut self,
15136 _: &SelectSmallerSyntaxNode,
15137 window: &mut Window,
15138 cx: &mut Context<Self>,
15139 ) {
15140 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15141
15142 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15143 self.select_syntax_node_history.pop()
15144 {
15145 if let Some(selection) = selections.last_mut() {
15146 selection.reversed = is_selection_reversed;
15147 }
15148
15149 self.select_syntax_node_history.disable_clearing = true;
15150 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15151 s.select(selections.to_vec());
15152 });
15153 self.select_syntax_node_history.disable_clearing = false;
15154
15155 match scroll_behavior {
15156 SelectSyntaxNodeScrollBehavior::CursorTop => {
15157 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15158 }
15159 SelectSyntaxNodeScrollBehavior::FitSelection => {
15160 self.request_autoscroll(Autoscroll::fit(), cx);
15161 }
15162 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15163 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15164 }
15165 }
15166 }
15167 }
15168
15169 pub fn unwrap_syntax_node(
15170 &mut self,
15171 _: &UnwrapSyntaxNode,
15172 window: &mut Window,
15173 cx: &mut Context<Self>,
15174 ) {
15175 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15176
15177 let buffer = self.buffer.read(cx).snapshot(cx);
15178 let selections = self
15179 .selections
15180 .all::<usize>(cx)
15181 .into_iter()
15182 // subtracting the offset requires sorting
15183 .sorted_by_key(|i| i.start);
15184
15185 let full_edits = selections
15186 .into_iter()
15187 .filter_map(|selection| {
15188 let child = if selection.is_empty()
15189 && let Some((_, ancestor_range)) =
15190 buffer.syntax_ancestor(selection.start..selection.end)
15191 {
15192 match ancestor_range {
15193 MultiOrSingleBufferOffsetRange::Single(range) => range,
15194 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15195 }
15196 } else {
15197 selection.range()
15198 };
15199
15200 let mut parent = child.clone();
15201 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15202 parent = match ancestor_range {
15203 MultiOrSingleBufferOffsetRange::Single(range) => range,
15204 MultiOrSingleBufferOffsetRange::Multi(range) => range,
15205 };
15206 if parent.start < child.start || parent.end > child.end {
15207 break;
15208 }
15209 }
15210
15211 if parent == child {
15212 return None;
15213 }
15214 let text = buffer.text_for_range(child).collect::<String>();
15215 Some((selection.id, parent, text))
15216 })
15217 .collect::<Vec<_>>();
15218 if full_edits.is_empty() {
15219 return;
15220 }
15221
15222 self.transact(window, cx, |this, window, cx| {
15223 this.buffer.update(cx, |buffer, cx| {
15224 buffer.edit(
15225 full_edits
15226 .iter()
15227 .map(|(_, p, t)| (p.clone(), t.clone()))
15228 .collect::<Vec<_>>(),
15229 None,
15230 cx,
15231 );
15232 });
15233 this.change_selections(Default::default(), window, cx, |s| {
15234 let mut offset = 0;
15235 let mut selections = vec![];
15236 for (id, parent, text) in full_edits {
15237 let start = parent.start - offset;
15238 offset += parent.len() - text.len();
15239 selections.push(Selection {
15240 id,
15241 start,
15242 end: start + text.len(),
15243 reversed: false,
15244 goal: Default::default(),
15245 });
15246 }
15247 s.select(selections);
15248 });
15249 });
15250 }
15251
15252 pub fn select_next_syntax_node(
15253 &mut self,
15254 _: &SelectNextSyntaxNode,
15255 window: &mut Window,
15256 cx: &mut Context<Self>,
15257 ) {
15258 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15259 if old_selections.is_empty() {
15260 return;
15261 }
15262
15263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15264
15265 let buffer = self.buffer.read(cx).snapshot(cx);
15266 let mut selected_sibling = false;
15267
15268 let new_selections = old_selections
15269 .iter()
15270 .map(|selection| {
15271 let old_range = selection.start..selection.end;
15272
15273 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15274 let new_range = node.byte_range();
15275 selected_sibling = true;
15276 Selection {
15277 id: selection.id,
15278 start: new_range.start,
15279 end: new_range.end,
15280 goal: SelectionGoal::None,
15281 reversed: selection.reversed,
15282 }
15283 } else {
15284 selection.clone()
15285 }
15286 })
15287 .collect::<Vec<_>>();
15288
15289 if selected_sibling {
15290 self.change_selections(
15291 SelectionEffects::scroll(Autoscroll::fit()),
15292 window,
15293 cx,
15294 |s| {
15295 s.select(new_selections);
15296 },
15297 );
15298 }
15299 }
15300
15301 pub fn select_prev_syntax_node(
15302 &mut self,
15303 _: &SelectPreviousSyntaxNode,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) {
15307 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15308 if old_selections.is_empty() {
15309 return;
15310 }
15311
15312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15313
15314 let buffer = self.buffer.read(cx).snapshot(cx);
15315 let mut selected_sibling = false;
15316
15317 let new_selections = old_selections
15318 .iter()
15319 .map(|selection| {
15320 let old_range = selection.start..selection.end;
15321
15322 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15323 let new_range = node.byte_range();
15324 selected_sibling = true;
15325 Selection {
15326 id: selection.id,
15327 start: new_range.start,
15328 end: new_range.end,
15329 goal: SelectionGoal::None,
15330 reversed: selection.reversed,
15331 }
15332 } else {
15333 selection.clone()
15334 }
15335 })
15336 .collect::<Vec<_>>();
15337
15338 if selected_sibling {
15339 self.change_selections(
15340 SelectionEffects::scroll(Autoscroll::fit()),
15341 window,
15342 cx,
15343 |s| {
15344 s.select(new_selections);
15345 },
15346 );
15347 }
15348 }
15349
15350 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15351 if !EditorSettings::get_global(cx).gutter.runnables {
15352 self.clear_tasks();
15353 return Task::ready(());
15354 }
15355 let project = self.project().map(Entity::downgrade);
15356 let task_sources = self.lsp_task_sources(cx);
15357 let multi_buffer = self.buffer.downgrade();
15358 cx.spawn_in(window, async move |editor, cx| {
15359 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15360 let Some(project) = project.and_then(|p| p.upgrade()) else {
15361 return;
15362 };
15363 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15364 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15365 }) else {
15366 return;
15367 };
15368
15369 let hide_runnables = project
15370 .update(cx, |project, _| project.is_via_collab())
15371 .unwrap_or(true);
15372 if hide_runnables {
15373 return;
15374 }
15375 let new_rows =
15376 cx.background_spawn({
15377 let snapshot = display_snapshot.clone();
15378 async move {
15379 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15380 }
15381 })
15382 .await;
15383 let Ok(lsp_tasks) =
15384 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15385 else {
15386 return;
15387 };
15388 let lsp_tasks = lsp_tasks.await;
15389
15390 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15391 lsp_tasks
15392 .into_iter()
15393 .flat_map(|(kind, tasks)| {
15394 tasks.into_iter().filter_map(move |(location, task)| {
15395 Some((kind.clone(), location?, task))
15396 })
15397 })
15398 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15399 let buffer = location.target.buffer;
15400 let buffer_snapshot = buffer.read(cx).snapshot();
15401 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15402 |(excerpt_id, snapshot, _)| {
15403 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15404 display_snapshot
15405 .buffer_snapshot
15406 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15407 } else {
15408 None
15409 }
15410 },
15411 );
15412 if let Some(offset) = offset {
15413 let task_buffer_range =
15414 location.target.range.to_point(&buffer_snapshot);
15415 let context_buffer_range =
15416 task_buffer_range.to_offset(&buffer_snapshot);
15417 let context_range = BufferOffset(context_buffer_range.start)
15418 ..BufferOffset(context_buffer_range.end);
15419
15420 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15421 .or_insert_with(|| RunnableTasks {
15422 templates: Vec::new(),
15423 offset,
15424 column: task_buffer_range.start.column,
15425 extra_variables: HashMap::default(),
15426 context_range,
15427 })
15428 .templates
15429 .push((kind, task.original_task().clone()));
15430 }
15431
15432 acc
15433 })
15434 }) else {
15435 return;
15436 };
15437
15438 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15439 buffer.language_settings(cx).tasks.prefer_lsp
15440 }) else {
15441 return;
15442 };
15443
15444 let rows = Self::runnable_rows(
15445 project,
15446 display_snapshot,
15447 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15448 new_rows,
15449 cx.clone(),
15450 )
15451 .await;
15452 editor
15453 .update(cx, |editor, _| {
15454 editor.clear_tasks();
15455 for (key, mut value) in rows {
15456 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15457 value.templates.extend(lsp_tasks.templates);
15458 }
15459
15460 editor.insert_tasks(key, value);
15461 }
15462 for (key, value) in lsp_tasks_by_rows {
15463 editor.insert_tasks(key, value);
15464 }
15465 })
15466 .ok();
15467 })
15468 }
15469 fn fetch_runnable_ranges(
15470 snapshot: &DisplaySnapshot,
15471 range: Range<Anchor>,
15472 ) -> Vec<language::RunnableRange> {
15473 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15474 }
15475
15476 fn runnable_rows(
15477 project: Entity<Project>,
15478 snapshot: DisplaySnapshot,
15479 prefer_lsp: bool,
15480 runnable_ranges: Vec<RunnableRange>,
15481 cx: AsyncWindowContext,
15482 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15483 cx.spawn(async move |cx| {
15484 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15485 for mut runnable in runnable_ranges {
15486 let Some(tasks) = cx
15487 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15488 .ok()
15489 else {
15490 continue;
15491 };
15492 let mut tasks = tasks.await;
15493
15494 if prefer_lsp {
15495 tasks.retain(|(task_kind, _)| {
15496 !matches!(task_kind, TaskSourceKind::Language { .. })
15497 });
15498 }
15499 if tasks.is_empty() {
15500 continue;
15501 }
15502
15503 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15504 let Some(row) = snapshot
15505 .buffer_snapshot
15506 .buffer_line_for_row(MultiBufferRow(point.row))
15507 .map(|(_, range)| range.start.row)
15508 else {
15509 continue;
15510 };
15511
15512 let context_range =
15513 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15514 runnable_rows.push((
15515 (runnable.buffer_id, row),
15516 RunnableTasks {
15517 templates: tasks,
15518 offset: snapshot
15519 .buffer_snapshot
15520 .anchor_before(runnable.run_range.start),
15521 context_range,
15522 column: point.column,
15523 extra_variables: runnable.extra_captures,
15524 },
15525 ));
15526 }
15527 runnable_rows
15528 })
15529 }
15530
15531 fn templates_with_tags(
15532 project: &Entity<Project>,
15533 runnable: &mut Runnable,
15534 cx: &mut App,
15535 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15536 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15537 let (worktree_id, file) = project
15538 .buffer_for_id(runnable.buffer, cx)
15539 .and_then(|buffer| buffer.read(cx).file())
15540 .map(|file| (file.worktree_id(cx), file.clone()))
15541 .unzip();
15542
15543 (
15544 project.task_store().read(cx).task_inventory().cloned(),
15545 worktree_id,
15546 file,
15547 )
15548 });
15549
15550 let tags = mem::take(&mut runnable.tags);
15551 let language = runnable.language.clone();
15552 cx.spawn(async move |cx| {
15553 let mut templates_with_tags = Vec::new();
15554 if let Some(inventory) = inventory {
15555 for RunnableTag(tag) in tags {
15556 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15557 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15558 }) else {
15559 return templates_with_tags;
15560 };
15561 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15562 move |(_, template)| {
15563 template.tags.iter().any(|source_tag| source_tag == &tag)
15564 },
15565 ));
15566 }
15567 }
15568 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15569
15570 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15571 // Strongest source wins; if we have worktree tag binding, prefer that to
15572 // global and language bindings;
15573 // if we have a global binding, prefer that to language binding.
15574 let first_mismatch = templates_with_tags
15575 .iter()
15576 .position(|(tag_source, _)| tag_source != leading_tag_source);
15577 if let Some(index) = first_mismatch {
15578 templates_with_tags.truncate(index);
15579 }
15580 }
15581
15582 templates_with_tags
15583 })
15584 }
15585
15586 pub fn move_to_enclosing_bracket(
15587 &mut self,
15588 _: &MoveToEnclosingBracket,
15589 window: &mut Window,
15590 cx: &mut Context<Self>,
15591 ) {
15592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15593 self.change_selections(Default::default(), window, cx, |s| {
15594 s.move_offsets_with(|snapshot, selection| {
15595 let Some(enclosing_bracket_ranges) =
15596 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15597 else {
15598 return;
15599 };
15600
15601 let mut best_length = usize::MAX;
15602 let mut best_inside = false;
15603 let mut best_in_bracket_range = false;
15604 let mut best_destination = None;
15605 for (open, close) in enclosing_bracket_ranges {
15606 let close = close.to_inclusive();
15607 let length = close.end() - open.start;
15608 let inside = selection.start >= open.end && selection.end <= *close.start();
15609 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15610 || close.contains(&selection.head());
15611
15612 // If best is next to a bracket and current isn't, skip
15613 if !in_bracket_range && best_in_bracket_range {
15614 continue;
15615 }
15616
15617 // Prefer smaller lengths unless best is inside and current isn't
15618 if length > best_length && (best_inside || !inside) {
15619 continue;
15620 }
15621
15622 best_length = length;
15623 best_inside = inside;
15624 best_in_bracket_range = in_bracket_range;
15625 best_destination = Some(
15626 if close.contains(&selection.start) && close.contains(&selection.end) {
15627 if inside { open.end } else { open.start }
15628 } else if inside {
15629 *close.start()
15630 } else {
15631 *close.end()
15632 },
15633 );
15634 }
15635
15636 if let Some(destination) = best_destination {
15637 selection.collapse_to(destination, SelectionGoal::None);
15638 }
15639 })
15640 });
15641 }
15642
15643 pub fn undo_selection(
15644 &mut self,
15645 _: &UndoSelection,
15646 window: &mut Window,
15647 cx: &mut Context<Self>,
15648 ) {
15649 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15650 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15651 self.selection_history.mode = SelectionHistoryMode::Undoing;
15652 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15653 this.end_selection(window, cx);
15654 this.change_selections(
15655 SelectionEffects::scroll(Autoscroll::newest()),
15656 window,
15657 cx,
15658 |s| s.select_anchors(entry.selections.to_vec()),
15659 );
15660 });
15661 self.selection_history.mode = SelectionHistoryMode::Normal;
15662
15663 self.select_next_state = entry.select_next_state;
15664 self.select_prev_state = entry.select_prev_state;
15665 self.add_selections_state = entry.add_selections_state;
15666 }
15667 }
15668
15669 pub fn redo_selection(
15670 &mut self,
15671 _: &RedoSelection,
15672 window: &mut Window,
15673 cx: &mut Context<Self>,
15674 ) {
15675 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15676 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15677 self.selection_history.mode = SelectionHistoryMode::Redoing;
15678 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15679 this.end_selection(window, cx);
15680 this.change_selections(
15681 SelectionEffects::scroll(Autoscroll::newest()),
15682 window,
15683 cx,
15684 |s| s.select_anchors(entry.selections.to_vec()),
15685 );
15686 });
15687 self.selection_history.mode = SelectionHistoryMode::Normal;
15688
15689 self.select_next_state = entry.select_next_state;
15690 self.select_prev_state = entry.select_prev_state;
15691 self.add_selections_state = entry.add_selections_state;
15692 }
15693 }
15694
15695 pub fn expand_excerpts(
15696 &mut self,
15697 action: &ExpandExcerpts,
15698 _: &mut Window,
15699 cx: &mut Context<Self>,
15700 ) {
15701 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15702 }
15703
15704 pub fn expand_excerpts_down(
15705 &mut self,
15706 action: &ExpandExcerptsDown,
15707 _: &mut Window,
15708 cx: &mut Context<Self>,
15709 ) {
15710 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15711 }
15712
15713 pub fn expand_excerpts_up(
15714 &mut self,
15715 action: &ExpandExcerptsUp,
15716 _: &mut Window,
15717 cx: &mut Context<Self>,
15718 ) {
15719 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15720 }
15721
15722 pub fn expand_excerpts_for_direction(
15723 &mut self,
15724 lines: u32,
15725 direction: ExpandExcerptDirection,
15726
15727 cx: &mut Context<Self>,
15728 ) {
15729 let selections = self.selections.disjoint_anchors_arc();
15730
15731 let lines = if lines == 0 {
15732 EditorSettings::get_global(cx).expand_excerpt_lines
15733 } else {
15734 lines
15735 };
15736
15737 self.buffer.update(cx, |buffer, cx| {
15738 let snapshot = buffer.snapshot(cx);
15739 let mut excerpt_ids = selections
15740 .iter()
15741 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15742 .collect::<Vec<_>>();
15743 excerpt_ids.sort();
15744 excerpt_ids.dedup();
15745 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15746 })
15747 }
15748
15749 pub fn expand_excerpt(
15750 &mut self,
15751 excerpt: ExcerptId,
15752 direction: ExpandExcerptDirection,
15753 window: &mut Window,
15754 cx: &mut Context<Self>,
15755 ) {
15756 let current_scroll_position = self.scroll_position(cx);
15757 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15758 let mut should_scroll_up = false;
15759
15760 if direction == ExpandExcerptDirection::Down {
15761 let multi_buffer = self.buffer.read(cx);
15762 let snapshot = multi_buffer.snapshot(cx);
15763 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15764 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15765 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15766 {
15767 let buffer_snapshot = buffer.read(cx).snapshot();
15768 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15769 let last_row = buffer_snapshot.max_point().row;
15770 let lines_below = last_row.saturating_sub(excerpt_end_row);
15771 should_scroll_up = lines_below >= lines_to_expand;
15772 }
15773 }
15774
15775 self.buffer.update(cx, |buffer, cx| {
15776 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15777 });
15778
15779 if should_scroll_up {
15780 let new_scroll_position =
15781 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15782 self.set_scroll_position(new_scroll_position, window, cx);
15783 }
15784 }
15785
15786 pub fn go_to_singleton_buffer_point(
15787 &mut self,
15788 point: Point,
15789 window: &mut Window,
15790 cx: &mut Context<Self>,
15791 ) {
15792 self.go_to_singleton_buffer_range(point..point, window, cx);
15793 }
15794
15795 pub fn go_to_singleton_buffer_range(
15796 &mut self,
15797 range: Range<Point>,
15798 window: &mut Window,
15799 cx: &mut Context<Self>,
15800 ) {
15801 let multibuffer = self.buffer().read(cx);
15802 let Some(buffer) = multibuffer.as_singleton() else {
15803 return;
15804 };
15805 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15806 return;
15807 };
15808 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15809 return;
15810 };
15811 self.change_selections(
15812 SelectionEffects::default().nav_history(true),
15813 window,
15814 cx,
15815 |s| s.select_anchor_ranges([start..end]),
15816 );
15817 }
15818
15819 pub fn go_to_diagnostic(
15820 &mut self,
15821 action: &GoToDiagnostic,
15822 window: &mut Window,
15823 cx: &mut Context<Self>,
15824 ) {
15825 if !self.diagnostics_enabled() {
15826 return;
15827 }
15828 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15829 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15830 }
15831
15832 pub fn go_to_prev_diagnostic(
15833 &mut self,
15834 action: &GoToPreviousDiagnostic,
15835 window: &mut Window,
15836 cx: &mut Context<Self>,
15837 ) {
15838 if !self.diagnostics_enabled() {
15839 return;
15840 }
15841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15842 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15843 }
15844
15845 pub fn go_to_diagnostic_impl(
15846 &mut self,
15847 direction: Direction,
15848 severity: GoToDiagnosticSeverityFilter,
15849 window: &mut Window,
15850 cx: &mut Context<Self>,
15851 ) {
15852 let buffer = self.buffer.read(cx).snapshot(cx);
15853 let selection = self.selections.newest::<usize>(cx);
15854
15855 let mut active_group_id = None;
15856 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15857 && active_group.active_range.start.to_offset(&buffer) == selection.start
15858 {
15859 active_group_id = Some(active_group.group_id);
15860 }
15861
15862 fn filtered(
15863 snapshot: EditorSnapshot,
15864 severity: GoToDiagnosticSeverityFilter,
15865 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15866 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15867 diagnostics
15868 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15869 .filter(|entry| entry.range.start != entry.range.end)
15870 .filter(|entry| !entry.diagnostic.is_unnecessary)
15871 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15872 }
15873
15874 let snapshot = self.snapshot(window, cx);
15875 let before = filtered(
15876 snapshot.clone(),
15877 severity,
15878 buffer
15879 .diagnostics_in_range(0..selection.start)
15880 .filter(|entry| entry.range.start <= selection.start),
15881 );
15882 let after = filtered(
15883 snapshot,
15884 severity,
15885 buffer
15886 .diagnostics_in_range(selection.start..buffer.len())
15887 .filter(|entry| entry.range.start >= selection.start),
15888 );
15889
15890 let mut found: Option<DiagnosticEntry<usize>> = None;
15891 if direction == Direction::Prev {
15892 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15893 {
15894 for diagnostic in prev_diagnostics.into_iter().rev() {
15895 if diagnostic.range.start != selection.start
15896 || active_group_id
15897 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15898 {
15899 found = Some(diagnostic);
15900 break 'outer;
15901 }
15902 }
15903 }
15904 } else {
15905 for diagnostic in after.chain(before) {
15906 if diagnostic.range.start != selection.start
15907 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15908 {
15909 found = Some(diagnostic);
15910 break;
15911 }
15912 }
15913 }
15914 let Some(next_diagnostic) = found else {
15915 return;
15916 };
15917
15918 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15919 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15920 return;
15921 };
15922 self.change_selections(Default::default(), window, cx, |s| {
15923 s.select_ranges(vec![
15924 next_diagnostic.range.start..next_diagnostic.range.start,
15925 ])
15926 });
15927 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15928 self.refresh_edit_prediction(false, true, window, cx);
15929 }
15930
15931 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15933 let snapshot = self.snapshot(window, cx);
15934 let selection = self.selections.newest::<Point>(cx);
15935 self.go_to_hunk_before_or_after_position(
15936 &snapshot,
15937 selection.head(),
15938 Direction::Next,
15939 window,
15940 cx,
15941 );
15942 }
15943
15944 pub fn go_to_hunk_before_or_after_position(
15945 &mut self,
15946 snapshot: &EditorSnapshot,
15947 position: Point,
15948 direction: Direction,
15949 window: &mut Window,
15950 cx: &mut Context<Editor>,
15951 ) {
15952 let row = if direction == Direction::Next {
15953 self.hunk_after_position(snapshot, position)
15954 .map(|hunk| hunk.row_range.start)
15955 } else {
15956 self.hunk_before_position(snapshot, position)
15957 };
15958
15959 if let Some(row) = row {
15960 let destination = Point::new(row.0, 0);
15961 let autoscroll = Autoscroll::center();
15962
15963 self.unfold_ranges(&[destination..destination], false, false, cx);
15964 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15965 s.select_ranges([destination..destination]);
15966 });
15967 }
15968 }
15969
15970 fn hunk_after_position(
15971 &mut self,
15972 snapshot: &EditorSnapshot,
15973 position: Point,
15974 ) -> Option<MultiBufferDiffHunk> {
15975 snapshot
15976 .buffer_snapshot
15977 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15978 .find(|hunk| hunk.row_range.start.0 > position.row)
15979 .or_else(|| {
15980 snapshot
15981 .buffer_snapshot
15982 .diff_hunks_in_range(Point::zero()..position)
15983 .find(|hunk| hunk.row_range.end.0 < position.row)
15984 })
15985 }
15986
15987 fn go_to_prev_hunk(
15988 &mut self,
15989 _: &GoToPreviousHunk,
15990 window: &mut Window,
15991 cx: &mut Context<Self>,
15992 ) {
15993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15994 let snapshot = self.snapshot(window, cx);
15995 let selection = self.selections.newest::<Point>(cx);
15996 self.go_to_hunk_before_or_after_position(
15997 &snapshot,
15998 selection.head(),
15999 Direction::Prev,
16000 window,
16001 cx,
16002 );
16003 }
16004
16005 fn hunk_before_position(
16006 &mut self,
16007 snapshot: &EditorSnapshot,
16008 position: Point,
16009 ) -> Option<MultiBufferRow> {
16010 snapshot
16011 .buffer_snapshot
16012 .diff_hunk_before(position)
16013 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
16014 }
16015
16016 fn go_to_next_change(
16017 &mut self,
16018 _: &GoToNextChange,
16019 window: &mut Window,
16020 cx: &mut Context<Self>,
16021 ) {
16022 if let Some(selections) = self
16023 .change_list
16024 .next_change(1, Direction::Next)
16025 .map(|s| s.to_vec())
16026 {
16027 self.change_selections(Default::default(), window, cx, |s| {
16028 let map = s.display_map();
16029 s.select_display_ranges(selections.iter().map(|a| {
16030 let point = a.to_display_point(&map);
16031 point..point
16032 }))
16033 })
16034 }
16035 }
16036
16037 fn go_to_previous_change(
16038 &mut self,
16039 _: &GoToPreviousChange,
16040 window: &mut Window,
16041 cx: &mut Context<Self>,
16042 ) {
16043 if let Some(selections) = self
16044 .change_list
16045 .next_change(1, Direction::Prev)
16046 .map(|s| s.to_vec())
16047 {
16048 self.change_selections(Default::default(), window, cx, |s| {
16049 let map = s.display_map();
16050 s.select_display_ranges(selections.iter().map(|a| {
16051 let point = a.to_display_point(&map);
16052 point..point
16053 }))
16054 })
16055 }
16056 }
16057
16058 pub fn go_to_next_document_highlight(
16059 &mut self,
16060 _: &GoToNextDocumentHighlight,
16061 window: &mut Window,
16062 cx: &mut Context<Self>,
16063 ) {
16064 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16065 }
16066
16067 pub fn go_to_prev_document_highlight(
16068 &mut self,
16069 _: &GoToPreviousDocumentHighlight,
16070 window: &mut Window,
16071 cx: &mut Context<Self>,
16072 ) {
16073 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16074 }
16075
16076 pub fn go_to_document_highlight_before_or_after_position(
16077 &mut self,
16078 direction: Direction,
16079 window: &mut Window,
16080 cx: &mut Context<Editor>,
16081 ) {
16082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16083 let snapshot = self.snapshot(window, cx);
16084 let buffer = &snapshot.buffer_snapshot;
16085 let position = self.selections.newest::<Point>(cx).head();
16086 let anchor_position = buffer.anchor_after(position);
16087
16088 // Get all document highlights (both read and write)
16089 let mut all_highlights = Vec::new();
16090
16091 if let Some((_, read_highlights)) = self
16092 .background_highlights
16093 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16094 {
16095 all_highlights.extend(read_highlights.iter());
16096 }
16097
16098 if let Some((_, write_highlights)) = self
16099 .background_highlights
16100 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16101 {
16102 all_highlights.extend(write_highlights.iter());
16103 }
16104
16105 if all_highlights.is_empty() {
16106 return;
16107 }
16108
16109 // Sort highlights by position
16110 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16111
16112 let target_highlight = match direction {
16113 Direction::Next => {
16114 // Find the first highlight after the current position
16115 all_highlights
16116 .iter()
16117 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16118 }
16119 Direction::Prev => {
16120 // Find the last highlight before the current position
16121 all_highlights
16122 .iter()
16123 .rev()
16124 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16125 }
16126 };
16127
16128 if let Some(highlight) = target_highlight {
16129 let destination = highlight.start.to_point(buffer);
16130 let autoscroll = Autoscroll::center();
16131
16132 self.unfold_ranges(&[destination..destination], false, false, cx);
16133 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16134 s.select_ranges([destination..destination]);
16135 });
16136 }
16137 }
16138
16139 fn go_to_line<T: 'static>(
16140 &mut self,
16141 position: Anchor,
16142 highlight_color: Option<Hsla>,
16143 window: &mut Window,
16144 cx: &mut Context<Self>,
16145 ) {
16146 let snapshot = self.snapshot(window, cx).display_snapshot;
16147 let position = position.to_point(&snapshot.buffer_snapshot);
16148 let start = snapshot
16149 .buffer_snapshot
16150 .clip_point(Point::new(position.row, 0), Bias::Left);
16151 let end = start + Point::new(1, 0);
16152 let start = snapshot.buffer_snapshot.anchor_before(start);
16153 let end = snapshot.buffer_snapshot.anchor_before(end);
16154
16155 self.highlight_rows::<T>(
16156 start..end,
16157 highlight_color
16158 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16159 Default::default(),
16160 cx,
16161 );
16162
16163 if self.buffer.read(cx).is_singleton() {
16164 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16165 }
16166 }
16167
16168 pub fn go_to_definition(
16169 &mut self,
16170 _: &GoToDefinition,
16171 window: &mut Window,
16172 cx: &mut Context<Self>,
16173 ) -> Task<Result<Navigated>> {
16174 let definition =
16175 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16176 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16177 cx.spawn_in(window, async move |editor, cx| {
16178 if definition.await? == Navigated::Yes {
16179 return Ok(Navigated::Yes);
16180 }
16181 match fallback_strategy {
16182 GoToDefinitionFallback::None => Ok(Navigated::No),
16183 GoToDefinitionFallback::FindAllReferences => {
16184 match editor.update_in(cx, |editor, window, cx| {
16185 editor.find_all_references(&FindAllReferences, window, cx)
16186 })? {
16187 Some(references) => references.await,
16188 None => Ok(Navigated::No),
16189 }
16190 }
16191 }
16192 })
16193 }
16194
16195 pub fn go_to_declaration(
16196 &mut self,
16197 _: &GoToDeclaration,
16198 window: &mut Window,
16199 cx: &mut Context<Self>,
16200 ) -> Task<Result<Navigated>> {
16201 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16202 }
16203
16204 pub fn go_to_declaration_split(
16205 &mut self,
16206 _: &GoToDeclaration,
16207 window: &mut Window,
16208 cx: &mut Context<Self>,
16209 ) -> Task<Result<Navigated>> {
16210 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16211 }
16212
16213 pub fn go_to_implementation(
16214 &mut self,
16215 _: &GoToImplementation,
16216 window: &mut Window,
16217 cx: &mut Context<Self>,
16218 ) -> Task<Result<Navigated>> {
16219 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16220 }
16221
16222 pub fn go_to_implementation_split(
16223 &mut self,
16224 _: &GoToImplementationSplit,
16225 window: &mut Window,
16226 cx: &mut Context<Self>,
16227 ) -> Task<Result<Navigated>> {
16228 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16229 }
16230
16231 pub fn go_to_type_definition(
16232 &mut self,
16233 _: &GoToTypeDefinition,
16234 window: &mut Window,
16235 cx: &mut Context<Self>,
16236 ) -> Task<Result<Navigated>> {
16237 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16238 }
16239
16240 pub fn go_to_definition_split(
16241 &mut self,
16242 _: &GoToDefinitionSplit,
16243 window: &mut Window,
16244 cx: &mut Context<Self>,
16245 ) -> Task<Result<Navigated>> {
16246 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16247 }
16248
16249 pub fn go_to_type_definition_split(
16250 &mut self,
16251 _: &GoToTypeDefinitionSplit,
16252 window: &mut Window,
16253 cx: &mut Context<Self>,
16254 ) -> Task<Result<Navigated>> {
16255 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16256 }
16257
16258 fn go_to_definition_of_kind(
16259 &mut self,
16260 kind: GotoDefinitionKind,
16261 split: bool,
16262 window: &mut Window,
16263 cx: &mut Context<Self>,
16264 ) -> Task<Result<Navigated>> {
16265 let Some(provider) = self.semantics_provider.clone() else {
16266 return Task::ready(Ok(Navigated::No));
16267 };
16268 let head = self.selections.newest::<usize>(cx).head();
16269 let buffer = self.buffer.read(cx);
16270 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16271 return Task::ready(Ok(Navigated::No));
16272 };
16273 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16274 return Task::ready(Ok(Navigated::No));
16275 };
16276
16277 cx.spawn_in(window, async move |editor, cx| {
16278 let Some(definitions) = definitions.await? else {
16279 return Ok(Navigated::No);
16280 };
16281 let navigated = editor
16282 .update_in(cx, |editor, window, cx| {
16283 editor.navigate_to_hover_links(
16284 Some(kind),
16285 definitions
16286 .into_iter()
16287 .filter(|location| {
16288 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16289 })
16290 .map(HoverLink::Text)
16291 .collect::<Vec<_>>(),
16292 split,
16293 window,
16294 cx,
16295 )
16296 })?
16297 .await?;
16298 anyhow::Ok(navigated)
16299 })
16300 }
16301
16302 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16303 let selection = self.selections.newest_anchor();
16304 let head = selection.head();
16305 let tail = selection.tail();
16306
16307 let Some((buffer, start_position)) =
16308 self.buffer.read(cx).text_anchor_for_position(head, cx)
16309 else {
16310 return;
16311 };
16312
16313 let end_position = if head != tail {
16314 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16315 return;
16316 };
16317 Some(pos)
16318 } else {
16319 None
16320 };
16321
16322 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16323 let url = if let Some(end_pos) = end_position {
16324 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16325 } else {
16326 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16327 };
16328
16329 if let Some(url) = url {
16330 editor.update(cx, |_, cx| {
16331 cx.open_url(&url);
16332 })
16333 } else {
16334 Ok(())
16335 }
16336 });
16337
16338 url_finder.detach();
16339 }
16340
16341 pub fn open_selected_filename(
16342 &mut self,
16343 _: &OpenSelectedFilename,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) {
16347 let Some(workspace) = self.workspace() else {
16348 return;
16349 };
16350
16351 let position = self.selections.newest_anchor().head();
16352
16353 let Some((buffer, buffer_position)) =
16354 self.buffer.read(cx).text_anchor_for_position(position, cx)
16355 else {
16356 return;
16357 };
16358
16359 let project = self.project.clone();
16360
16361 cx.spawn_in(window, async move |_, cx| {
16362 let result = find_file(&buffer, project, buffer_position, cx).await;
16363
16364 if let Some((_, path)) = result {
16365 workspace
16366 .update_in(cx, |workspace, window, cx| {
16367 workspace.open_resolved_path(path, window, cx)
16368 })?
16369 .await?;
16370 }
16371 anyhow::Ok(())
16372 })
16373 .detach();
16374 }
16375
16376 pub(crate) fn navigate_to_hover_links(
16377 &mut self,
16378 kind: Option<GotoDefinitionKind>,
16379 definitions: Vec<HoverLink>,
16380 split: bool,
16381 window: &mut Window,
16382 cx: &mut Context<Editor>,
16383 ) -> Task<Result<Navigated>> {
16384 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16385 let mut first_url_or_file = None;
16386 let definitions: Vec<_> = definitions
16387 .into_iter()
16388 .filter_map(|def| match def {
16389 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16390 HoverLink::InlayHint(lsp_location, server_id) => {
16391 let computation =
16392 self.compute_target_location(lsp_location, server_id, window, cx);
16393 Some(cx.background_spawn(computation))
16394 }
16395 HoverLink::Url(url) => {
16396 first_url_or_file = Some(Either::Left(url));
16397 None
16398 }
16399 HoverLink::File(path) => {
16400 first_url_or_file = Some(Either::Right(path));
16401 None
16402 }
16403 })
16404 .collect();
16405
16406 let workspace = self.workspace();
16407
16408 cx.spawn_in(window, async move |editor, cx| {
16409 let locations: Vec<Location> = future::join_all(definitions)
16410 .await
16411 .into_iter()
16412 .filter_map(|location| location.transpose())
16413 .collect::<Result<_>>()
16414 .context("location tasks")?;
16415 let mut locations = cx.update(|_, cx| {
16416 locations
16417 .into_iter()
16418 .map(|location| {
16419 let buffer = location.buffer.read(cx);
16420 (location.buffer, location.range.to_point(buffer))
16421 })
16422 .into_group_map()
16423 })?;
16424 let mut num_locations = 0;
16425 for ranges in locations.values_mut() {
16426 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16427 ranges.dedup();
16428 num_locations += ranges.len();
16429 }
16430
16431 if num_locations > 1 {
16432 let Some(workspace) = workspace else {
16433 return Ok(Navigated::No);
16434 };
16435
16436 let tab_kind = match kind {
16437 Some(GotoDefinitionKind::Implementation) => "Implementations",
16438 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16439 Some(GotoDefinitionKind::Declaration) => "Declarations",
16440 Some(GotoDefinitionKind::Type) => "Types",
16441 };
16442 let title = editor
16443 .update_in(cx, |_, _, cx| {
16444 let target = locations
16445 .iter()
16446 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16447 .map(|(buffer, location)| {
16448 buffer
16449 .read(cx)
16450 .text_for_range(location.clone())
16451 .collect::<String>()
16452 })
16453 .filter(|text| !text.contains('\n'))
16454 .unique()
16455 .take(3)
16456 .join(", ");
16457 if target.is_empty() {
16458 tab_kind.to_owned()
16459 } else {
16460 format!("{tab_kind} for {target}")
16461 }
16462 })
16463 .context("buffer title")?;
16464
16465 let opened = workspace
16466 .update_in(cx, |workspace, window, cx| {
16467 Self::open_locations_in_multibuffer(
16468 workspace,
16469 locations,
16470 title,
16471 split,
16472 MultibufferSelectionMode::First,
16473 window,
16474 cx,
16475 )
16476 })
16477 .is_ok();
16478
16479 anyhow::Ok(Navigated::from_bool(opened))
16480 } else if num_locations == 0 {
16481 // If there is one url or file, open it directly
16482 match first_url_or_file {
16483 Some(Either::Left(url)) => {
16484 cx.update(|_, cx| cx.open_url(&url))?;
16485 Ok(Navigated::Yes)
16486 }
16487 Some(Either::Right(path)) => {
16488 let Some(workspace) = workspace else {
16489 return Ok(Navigated::No);
16490 };
16491
16492 workspace
16493 .update_in(cx, |workspace, window, cx| {
16494 workspace.open_resolved_path(path, window, cx)
16495 })?
16496 .await?;
16497 Ok(Navigated::Yes)
16498 }
16499 None => Ok(Navigated::No),
16500 }
16501 } else {
16502 let Some(workspace) = workspace else {
16503 return Ok(Navigated::No);
16504 };
16505
16506 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16507 let target_range = target_ranges.first().unwrap().clone();
16508
16509 editor.update_in(cx, |editor, window, cx| {
16510 let range = target_range.to_point(target_buffer.read(cx));
16511 let range = editor.range_for_match(&range);
16512 let range = collapse_multiline_range(range);
16513
16514 if !split
16515 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16516 {
16517 editor.go_to_singleton_buffer_range(range, window, cx);
16518 } else {
16519 let pane = workspace.read(cx).active_pane().clone();
16520 window.defer(cx, move |window, cx| {
16521 let target_editor: Entity<Self> =
16522 workspace.update(cx, |workspace, cx| {
16523 let pane = if split {
16524 workspace.adjacent_pane(window, cx)
16525 } else {
16526 workspace.active_pane().clone()
16527 };
16528
16529 workspace.open_project_item(
16530 pane,
16531 target_buffer.clone(),
16532 true,
16533 true,
16534 window,
16535 cx,
16536 )
16537 });
16538 target_editor.update(cx, |target_editor, cx| {
16539 // When selecting a definition in a different buffer, disable the nav history
16540 // to avoid creating a history entry at the previous cursor location.
16541 pane.update(cx, |pane, _| pane.disable_history());
16542 target_editor.go_to_singleton_buffer_range(range, window, cx);
16543 pane.update(cx, |pane, _| pane.enable_history());
16544 });
16545 });
16546 }
16547 Navigated::Yes
16548 })
16549 }
16550 })
16551 }
16552
16553 fn compute_target_location(
16554 &self,
16555 lsp_location: lsp::Location,
16556 server_id: LanguageServerId,
16557 window: &mut Window,
16558 cx: &mut Context<Self>,
16559 ) -> Task<anyhow::Result<Option<Location>>> {
16560 let Some(project) = self.project.clone() else {
16561 return Task::ready(Ok(None));
16562 };
16563
16564 cx.spawn_in(window, async move |editor, cx| {
16565 let location_task = editor.update(cx, |_, cx| {
16566 project.update(cx, |project, cx| {
16567 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16568 })
16569 })?;
16570 let location = Some({
16571 let target_buffer_handle = location_task.await.context("open local buffer")?;
16572 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16573 let target_start = target_buffer
16574 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16575 let target_end = target_buffer
16576 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16577 target_buffer.anchor_after(target_start)
16578 ..target_buffer.anchor_before(target_end)
16579 })?;
16580 Location {
16581 buffer: target_buffer_handle,
16582 range,
16583 }
16584 });
16585 Ok(location)
16586 })
16587 }
16588
16589 pub fn find_all_references(
16590 &mut self,
16591 _: &FindAllReferences,
16592 window: &mut Window,
16593 cx: &mut Context<Self>,
16594 ) -> Option<Task<Result<Navigated>>> {
16595 let selection = self.selections.newest::<usize>(cx);
16596 let multi_buffer = self.buffer.read(cx);
16597 let head = selection.head();
16598
16599 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16600 let head_anchor = multi_buffer_snapshot.anchor_at(
16601 head,
16602 if head < selection.tail() {
16603 Bias::Right
16604 } else {
16605 Bias::Left
16606 },
16607 );
16608
16609 match self
16610 .find_all_references_task_sources
16611 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16612 {
16613 Ok(_) => {
16614 log::info!(
16615 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16616 );
16617 return None;
16618 }
16619 Err(i) => {
16620 self.find_all_references_task_sources.insert(i, head_anchor);
16621 }
16622 }
16623
16624 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16625 let workspace = self.workspace()?;
16626 let project = workspace.read(cx).project().clone();
16627 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16628 Some(cx.spawn_in(window, async move |editor, cx| {
16629 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16630 if let Ok(i) = editor
16631 .find_all_references_task_sources
16632 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16633 {
16634 editor.find_all_references_task_sources.remove(i);
16635 }
16636 });
16637
16638 let Some(locations) = references.await? else {
16639 return anyhow::Ok(Navigated::No);
16640 };
16641 let mut locations = cx.update(|_, cx| {
16642 locations
16643 .into_iter()
16644 .map(|location| {
16645 let buffer = location.buffer.read(cx);
16646 (location.buffer, location.range.to_point(buffer))
16647 })
16648 .into_group_map()
16649 })?;
16650 if locations.is_empty() {
16651 return anyhow::Ok(Navigated::No);
16652 }
16653 for ranges in locations.values_mut() {
16654 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16655 ranges.dedup();
16656 }
16657
16658 workspace.update_in(cx, |workspace, window, cx| {
16659 let target = locations
16660 .iter()
16661 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16662 .map(|(buffer, location)| {
16663 buffer
16664 .read(cx)
16665 .text_for_range(location.clone())
16666 .collect::<String>()
16667 })
16668 .filter(|text| !text.contains('\n'))
16669 .unique()
16670 .take(3)
16671 .join(", ");
16672 let title = if target.is_empty() {
16673 "References".to_owned()
16674 } else {
16675 format!("References to {target}")
16676 };
16677 Self::open_locations_in_multibuffer(
16678 workspace,
16679 locations,
16680 title,
16681 false,
16682 MultibufferSelectionMode::First,
16683 window,
16684 cx,
16685 );
16686 Navigated::Yes
16687 })
16688 }))
16689 }
16690
16691 /// Opens a multibuffer with the given project locations in it
16692 pub fn open_locations_in_multibuffer(
16693 workspace: &mut Workspace,
16694 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16695 title: String,
16696 split: bool,
16697 multibuffer_selection_mode: MultibufferSelectionMode,
16698 window: &mut Window,
16699 cx: &mut Context<Workspace>,
16700 ) {
16701 if locations.is_empty() {
16702 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16703 return;
16704 }
16705
16706 let capability = workspace.project().read(cx).capability();
16707 let mut ranges = <Vec<Range<Anchor>>>::new();
16708
16709 // a key to find existing multibuffer editors with the same set of locations
16710 // to prevent us from opening more and more multibuffer tabs for searches and the like
16711 let mut key = (title.clone(), vec![]);
16712 let excerpt_buffer = cx.new(|cx| {
16713 let key = &mut key.1;
16714 let mut multibuffer = MultiBuffer::new(capability);
16715 for (buffer, mut ranges_for_buffer) in locations {
16716 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16717 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16718 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16719 PathKey::for_buffer(&buffer, cx),
16720 buffer.clone(),
16721 ranges_for_buffer,
16722 multibuffer_context_lines(cx),
16723 cx,
16724 );
16725 ranges.extend(new_ranges)
16726 }
16727
16728 multibuffer.with_title(title)
16729 });
16730 let existing = workspace.active_pane().update(cx, |pane, cx| {
16731 pane.items()
16732 .filter_map(|item| item.downcast::<Editor>())
16733 .find(|editor| {
16734 editor
16735 .read(cx)
16736 .lookup_key
16737 .as_ref()
16738 .and_then(|it| {
16739 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16740 })
16741 .is_some_and(|it| *it == key)
16742 })
16743 });
16744 let editor = existing.unwrap_or_else(|| {
16745 cx.new(|cx| {
16746 let mut editor = Editor::for_multibuffer(
16747 excerpt_buffer,
16748 Some(workspace.project().clone()),
16749 window,
16750 cx,
16751 );
16752 editor.lookup_key = Some(Box::new(key));
16753 editor
16754 })
16755 });
16756 editor.update(cx, |editor, cx| {
16757 match multibuffer_selection_mode {
16758 MultibufferSelectionMode::First => {
16759 if let Some(first_range) = ranges.first() {
16760 editor.change_selections(
16761 SelectionEffects::no_scroll(),
16762 window,
16763 cx,
16764 |selections| {
16765 selections.clear_disjoint();
16766 selections
16767 .select_anchor_ranges(std::iter::once(first_range.clone()));
16768 },
16769 );
16770 }
16771 editor.highlight_background::<Self>(
16772 &ranges,
16773 |theme| theme.colors().editor_highlighted_line_background,
16774 cx,
16775 );
16776 }
16777 MultibufferSelectionMode::All => {
16778 editor.change_selections(
16779 SelectionEffects::no_scroll(),
16780 window,
16781 cx,
16782 |selections| {
16783 selections.clear_disjoint();
16784 selections.select_anchor_ranges(ranges);
16785 },
16786 );
16787 }
16788 }
16789 editor.register_buffers_with_language_servers(cx);
16790 });
16791
16792 let item = Box::new(editor);
16793 let item_id = item.item_id();
16794
16795 if split {
16796 workspace.split_item(SplitDirection::Right, item, window, cx);
16797 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16798 let (preview_item_id, preview_item_idx) =
16799 workspace.active_pane().read_with(cx, |pane, _| {
16800 (pane.preview_item_id(), pane.preview_item_idx())
16801 });
16802
16803 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16804
16805 if let Some(preview_item_id) = preview_item_id {
16806 workspace.active_pane().update(cx, |pane, cx| {
16807 pane.remove_item(preview_item_id, false, false, window, cx);
16808 });
16809 }
16810 } else {
16811 workspace.add_item_to_active_pane(item, None, true, window, cx);
16812 }
16813 workspace.active_pane().update(cx, |pane, cx| {
16814 pane.set_preview_item_id(Some(item_id), cx);
16815 });
16816 }
16817
16818 pub fn rename(
16819 &mut self,
16820 _: &Rename,
16821 window: &mut Window,
16822 cx: &mut Context<Self>,
16823 ) -> Option<Task<Result<()>>> {
16824 use language::ToOffset as _;
16825
16826 let provider = self.semantics_provider.clone()?;
16827 let selection = self.selections.newest_anchor().clone();
16828 let (cursor_buffer, cursor_buffer_position) = self
16829 .buffer
16830 .read(cx)
16831 .text_anchor_for_position(selection.head(), cx)?;
16832 let (tail_buffer, cursor_buffer_position_end) = self
16833 .buffer
16834 .read(cx)
16835 .text_anchor_for_position(selection.tail(), cx)?;
16836 if tail_buffer != cursor_buffer {
16837 return None;
16838 }
16839
16840 let snapshot = cursor_buffer.read(cx).snapshot();
16841 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16842 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16843 let prepare_rename = provider
16844 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16845 .unwrap_or_else(|| Task::ready(Ok(None)));
16846 drop(snapshot);
16847
16848 Some(cx.spawn_in(window, async move |this, cx| {
16849 let rename_range = if let Some(range) = prepare_rename.await? {
16850 Some(range)
16851 } else {
16852 this.update(cx, |this, cx| {
16853 let buffer = this.buffer.read(cx).snapshot(cx);
16854 let mut buffer_highlights = this
16855 .document_highlights_for_position(selection.head(), &buffer)
16856 .filter(|highlight| {
16857 highlight.start.excerpt_id == selection.head().excerpt_id
16858 && highlight.end.excerpt_id == selection.head().excerpt_id
16859 });
16860 buffer_highlights
16861 .next()
16862 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16863 })?
16864 };
16865 if let Some(rename_range) = rename_range {
16866 this.update_in(cx, |this, window, cx| {
16867 let snapshot = cursor_buffer.read(cx).snapshot();
16868 let rename_buffer_range = rename_range.to_offset(&snapshot);
16869 let cursor_offset_in_rename_range =
16870 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16871 let cursor_offset_in_rename_range_end =
16872 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16873
16874 this.take_rename(false, window, cx);
16875 let buffer = this.buffer.read(cx).read(cx);
16876 let cursor_offset = selection.head().to_offset(&buffer);
16877 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16878 let rename_end = rename_start + rename_buffer_range.len();
16879 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16880 let mut old_highlight_id = None;
16881 let old_name: Arc<str> = buffer
16882 .chunks(rename_start..rename_end, true)
16883 .map(|chunk| {
16884 if old_highlight_id.is_none() {
16885 old_highlight_id = chunk.syntax_highlight_id;
16886 }
16887 chunk.text
16888 })
16889 .collect::<String>()
16890 .into();
16891
16892 drop(buffer);
16893
16894 // Position the selection in the rename editor so that it matches the current selection.
16895 this.show_local_selections = false;
16896 let rename_editor = cx.new(|cx| {
16897 let mut editor = Editor::single_line(window, cx);
16898 editor.buffer.update(cx, |buffer, cx| {
16899 buffer.edit([(0..0, old_name.clone())], None, cx)
16900 });
16901 let rename_selection_range = match cursor_offset_in_rename_range
16902 .cmp(&cursor_offset_in_rename_range_end)
16903 {
16904 Ordering::Equal => {
16905 editor.select_all(&SelectAll, window, cx);
16906 return editor;
16907 }
16908 Ordering::Less => {
16909 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16910 }
16911 Ordering::Greater => {
16912 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16913 }
16914 };
16915 if rename_selection_range.end > old_name.len() {
16916 editor.select_all(&SelectAll, window, cx);
16917 } else {
16918 editor.change_selections(Default::default(), window, cx, |s| {
16919 s.select_ranges([rename_selection_range]);
16920 });
16921 }
16922 editor
16923 });
16924 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16925 if e == &EditorEvent::Focused {
16926 cx.emit(EditorEvent::FocusedIn)
16927 }
16928 })
16929 .detach();
16930
16931 let write_highlights =
16932 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16933 let read_highlights =
16934 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16935 let ranges = write_highlights
16936 .iter()
16937 .flat_map(|(_, ranges)| ranges.iter())
16938 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16939 .cloned()
16940 .collect();
16941
16942 this.highlight_text::<Rename>(
16943 ranges,
16944 HighlightStyle {
16945 fade_out: Some(0.6),
16946 ..Default::default()
16947 },
16948 cx,
16949 );
16950 let rename_focus_handle = rename_editor.focus_handle(cx);
16951 window.focus(&rename_focus_handle);
16952 let block_id = this.insert_blocks(
16953 [BlockProperties {
16954 style: BlockStyle::Flex,
16955 placement: BlockPlacement::Below(range.start),
16956 height: Some(1),
16957 render: Arc::new({
16958 let rename_editor = rename_editor.clone();
16959 move |cx: &mut BlockContext| {
16960 let mut text_style = cx.editor_style.text.clone();
16961 if let Some(highlight_style) = old_highlight_id
16962 .and_then(|h| h.style(&cx.editor_style.syntax))
16963 {
16964 text_style = text_style.highlight(highlight_style);
16965 }
16966 div()
16967 .block_mouse_except_scroll()
16968 .pl(cx.anchor_x)
16969 .child(EditorElement::new(
16970 &rename_editor,
16971 EditorStyle {
16972 background: cx.theme().system().transparent,
16973 local_player: cx.editor_style.local_player,
16974 text: text_style,
16975 scrollbar_width: cx.editor_style.scrollbar_width,
16976 syntax: cx.editor_style.syntax.clone(),
16977 status: cx.editor_style.status.clone(),
16978 inlay_hints_style: HighlightStyle {
16979 font_weight: Some(FontWeight::BOLD),
16980 ..make_inlay_hints_style(cx.app)
16981 },
16982 edit_prediction_styles: make_suggestion_styles(
16983 cx.app,
16984 ),
16985 ..EditorStyle::default()
16986 },
16987 ))
16988 .into_any_element()
16989 }
16990 }),
16991 priority: 0,
16992 }],
16993 Some(Autoscroll::fit()),
16994 cx,
16995 )[0];
16996 this.pending_rename = Some(RenameState {
16997 range,
16998 old_name,
16999 editor: rename_editor,
17000 block_id,
17001 });
17002 })?;
17003 }
17004
17005 Ok(())
17006 }))
17007 }
17008
17009 pub fn confirm_rename(
17010 &mut self,
17011 _: &ConfirmRename,
17012 window: &mut Window,
17013 cx: &mut Context<Self>,
17014 ) -> Option<Task<Result<()>>> {
17015 let rename = self.take_rename(false, window, cx)?;
17016 let workspace = self.workspace()?.downgrade();
17017 let (buffer, start) = self
17018 .buffer
17019 .read(cx)
17020 .text_anchor_for_position(rename.range.start, cx)?;
17021 let (end_buffer, _) = self
17022 .buffer
17023 .read(cx)
17024 .text_anchor_for_position(rename.range.end, cx)?;
17025 if buffer != end_buffer {
17026 return None;
17027 }
17028
17029 let old_name = rename.old_name;
17030 let new_name = rename.editor.read(cx).text(cx);
17031
17032 let rename = self.semantics_provider.as_ref()?.perform_rename(
17033 &buffer,
17034 start,
17035 new_name.clone(),
17036 cx,
17037 )?;
17038
17039 Some(cx.spawn_in(window, async move |editor, cx| {
17040 let project_transaction = rename.await?;
17041 Self::open_project_transaction(
17042 &editor,
17043 workspace,
17044 project_transaction,
17045 format!("Rename: {} → {}", old_name, new_name),
17046 cx,
17047 )
17048 .await?;
17049
17050 editor.update(cx, |editor, cx| {
17051 editor.refresh_document_highlights(cx);
17052 })?;
17053 Ok(())
17054 }))
17055 }
17056
17057 fn take_rename(
17058 &mut self,
17059 moving_cursor: bool,
17060 window: &mut Window,
17061 cx: &mut Context<Self>,
17062 ) -> Option<RenameState> {
17063 let rename = self.pending_rename.take()?;
17064 if rename.editor.focus_handle(cx).is_focused(window) {
17065 window.focus(&self.focus_handle);
17066 }
17067
17068 self.remove_blocks(
17069 [rename.block_id].into_iter().collect(),
17070 Some(Autoscroll::fit()),
17071 cx,
17072 );
17073 self.clear_highlights::<Rename>(cx);
17074 self.show_local_selections = true;
17075
17076 if moving_cursor {
17077 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17078 editor.selections.newest::<usize>(cx).head()
17079 });
17080
17081 // Update the selection to match the position of the selection inside
17082 // the rename editor.
17083 let snapshot = self.buffer.read(cx).read(cx);
17084 let rename_range = rename.range.to_offset(&snapshot);
17085 let cursor_in_editor = snapshot
17086 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17087 .min(rename_range.end);
17088 drop(snapshot);
17089
17090 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17091 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17092 });
17093 } else {
17094 self.refresh_document_highlights(cx);
17095 }
17096
17097 Some(rename)
17098 }
17099
17100 pub fn pending_rename(&self) -> Option<&RenameState> {
17101 self.pending_rename.as_ref()
17102 }
17103
17104 fn format(
17105 &mut self,
17106 _: &Format,
17107 window: &mut Window,
17108 cx: &mut Context<Self>,
17109 ) -> Option<Task<Result<()>>> {
17110 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17111
17112 let project = match &self.project {
17113 Some(project) => project.clone(),
17114 None => return None,
17115 };
17116
17117 Some(self.perform_format(
17118 project,
17119 FormatTrigger::Manual,
17120 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17121 window,
17122 cx,
17123 ))
17124 }
17125
17126 fn format_selections(
17127 &mut self,
17128 _: &FormatSelections,
17129 window: &mut Window,
17130 cx: &mut Context<Self>,
17131 ) -> Option<Task<Result<()>>> {
17132 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17133
17134 let project = match &self.project {
17135 Some(project) => project.clone(),
17136 None => return None,
17137 };
17138
17139 let ranges = self
17140 .selections
17141 .all_adjusted(cx)
17142 .into_iter()
17143 .map(|selection| selection.range())
17144 .collect_vec();
17145
17146 Some(self.perform_format(
17147 project,
17148 FormatTrigger::Manual,
17149 FormatTarget::Ranges(ranges),
17150 window,
17151 cx,
17152 ))
17153 }
17154
17155 fn perform_format(
17156 &mut self,
17157 project: Entity<Project>,
17158 trigger: FormatTrigger,
17159 target: FormatTarget,
17160 window: &mut Window,
17161 cx: &mut Context<Self>,
17162 ) -> Task<Result<()>> {
17163 let buffer = self.buffer.clone();
17164 let (buffers, target) = match target {
17165 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17166 FormatTarget::Ranges(selection_ranges) => {
17167 let multi_buffer = buffer.read(cx);
17168 let snapshot = multi_buffer.read(cx);
17169 let mut buffers = HashSet::default();
17170 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17171 BTreeMap::new();
17172 for selection_range in selection_ranges {
17173 for (buffer, buffer_range, _) in
17174 snapshot.range_to_buffer_ranges(selection_range)
17175 {
17176 let buffer_id = buffer.remote_id();
17177 let start = buffer.anchor_before(buffer_range.start);
17178 let end = buffer.anchor_after(buffer_range.end);
17179 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17180 buffer_id_to_ranges
17181 .entry(buffer_id)
17182 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17183 .or_insert_with(|| vec![start..end]);
17184 }
17185 }
17186 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17187 }
17188 };
17189
17190 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17191 let selections_prev = transaction_id_prev
17192 .and_then(|transaction_id_prev| {
17193 // default to selections as they were after the last edit, if we have them,
17194 // instead of how they are now.
17195 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17196 // will take you back to where you made the last edit, instead of staying where you scrolled
17197 self.selection_history
17198 .transaction(transaction_id_prev)
17199 .map(|t| t.0.clone())
17200 })
17201 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17202
17203 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17204 let format = project.update(cx, |project, cx| {
17205 project.format(buffers, target, true, trigger, cx)
17206 });
17207
17208 cx.spawn_in(window, async move |editor, cx| {
17209 let transaction = futures::select_biased! {
17210 transaction = format.log_err().fuse() => transaction,
17211 () = timeout => {
17212 log::warn!("timed out waiting for formatting");
17213 None
17214 }
17215 };
17216
17217 buffer
17218 .update(cx, |buffer, cx| {
17219 if let Some(transaction) = transaction
17220 && !buffer.is_singleton()
17221 {
17222 buffer.push_transaction(&transaction.0, cx);
17223 }
17224 cx.notify();
17225 })
17226 .ok();
17227
17228 if let Some(transaction_id_now) =
17229 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17230 {
17231 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17232 if has_new_transaction {
17233 _ = editor.update(cx, |editor, _| {
17234 editor
17235 .selection_history
17236 .insert_transaction(transaction_id_now, selections_prev);
17237 });
17238 }
17239 }
17240
17241 Ok(())
17242 })
17243 }
17244
17245 fn organize_imports(
17246 &mut self,
17247 _: &OrganizeImports,
17248 window: &mut Window,
17249 cx: &mut Context<Self>,
17250 ) -> Option<Task<Result<()>>> {
17251 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17252 let project = match &self.project {
17253 Some(project) => project.clone(),
17254 None => return None,
17255 };
17256 Some(self.perform_code_action_kind(
17257 project,
17258 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17259 window,
17260 cx,
17261 ))
17262 }
17263
17264 fn perform_code_action_kind(
17265 &mut self,
17266 project: Entity<Project>,
17267 kind: CodeActionKind,
17268 window: &mut Window,
17269 cx: &mut Context<Self>,
17270 ) -> Task<Result<()>> {
17271 let buffer = self.buffer.clone();
17272 let buffers = buffer.read(cx).all_buffers();
17273 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17274 let apply_action = project.update(cx, |project, cx| {
17275 project.apply_code_action_kind(buffers, kind, true, cx)
17276 });
17277 cx.spawn_in(window, async move |_, cx| {
17278 let transaction = futures::select_biased! {
17279 () = timeout => {
17280 log::warn!("timed out waiting for executing code action");
17281 None
17282 }
17283 transaction = apply_action.log_err().fuse() => transaction,
17284 };
17285 buffer
17286 .update(cx, |buffer, cx| {
17287 // check if we need this
17288 if let Some(transaction) = transaction
17289 && !buffer.is_singleton()
17290 {
17291 buffer.push_transaction(&transaction.0, cx);
17292 }
17293 cx.notify();
17294 })
17295 .ok();
17296 Ok(())
17297 })
17298 }
17299
17300 pub fn restart_language_server(
17301 &mut self,
17302 _: &RestartLanguageServer,
17303 _: &mut Window,
17304 cx: &mut Context<Self>,
17305 ) {
17306 if let Some(project) = self.project.clone() {
17307 self.buffer.update(cx, |multi_buffer, cx| {
17308 project.update(cx, |project, cx| {
17309 project.restart_language_servers_for_buffers(
17310 multi_buffer.all_buffers().into_iter().collect(),
17311 HashSet::default(),
17312 cx,
17313 );
17314 });
17315 })
17316 }
17317 }
17318
17319 pub fn stop_language_server(
17320 &mut self,
17321 _: &StopLanguageServer,
17322 _: &mut Window,
17323 cx: &mut Context<Self>,
17324 ) {
17325 if let Some(project) = self.project.clone() {
17326 self.buffer.update(cx, |multi_buffer, cx| {
17327 project.update(cx, |project, cx| {
17328 project.stop_language_servers_for_buffers(
17329 multi_buffer.all_buffers().into_iter().collect(),
17330 HashSet::default(),
17331 cx,
17332 );
17333 cx.emit(project::Event::RefreshInlayHints);
17334 });
17335 });
17336 }
17337 }
17338
17339 fn cancel_language_server_work(
17340 workspace: &mut Workspace,
17341 _: &actions::CancelLanguageServerWork,
17342 _: &mut Window,
17343 cx: &mut Context<Workspace>,
17344 ) {
17345 let project = workspace.project();
17346 let buffers = workspace
17347 .active_item(cx)
17348 .and_then(|item| item.act_as::<Editor>(cx))
17349 .map_or(HashSet::default(), |editor| {
17350 editor.read(cx).buffer.read(cx).all_buffers()
17351 });
17352 project.update(cx, |project, cx| {
17353 project.cancel_language_server_work_for_buffers(buffers, cx);
17354 });
17355 }
17356
17357 fn show_character_palette(
17358 &mut self,
17359 _: &ShowCharacterPalette,
17360 window: &mut Window,
17361 _: &mut Context<Self>,
17362 ) {
17363 window.show_character_palette();
17364 }
17365
17366 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17367 if !self.diagnostics_enabled() {
17368 return;
17369 }
17370
17371 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17372 let buffer = self.buffer.read(cx).snapshot(cx);
17373 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17374 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17375 let is_valid = buffer
17376 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17377 .any(|entry| {
17378 entry.diagnostic.is_primary
17379 && !entry.range.is_empty()
17380 && entry.range.start == primary_range_start
17381 && entry.diagnostic.message == active_diagnostics.active_message
17382 });
17383
17384 if !is_valid {
17385 self.dismiss_diagnostics(cx);
17386 }
17387 }
17388 }
17389
17390 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17391 match &self.active_diagnostics {
17392 ActiveDiagnostic::Group(group) => Some(group),
17393 _ => None,
17394 }
17395 }
17396
17397 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17398 if !self.diagnostics_enabled() {
17399 return;
17400 }
17401 self.dismiss_diagnostics(cx);
17402 self.active_diagnostics = ActiveDiagnostic::All;
17403 }
17404
17405 fn activate_diagnostics(
17406 &mut self,
17407 buffer_id: BufferId,
17408 diagnostic: DiagnosticEntry<usize>,
17409 window: &mut Window,
17410 cx: &mut Context<Self>,
17411 ) {
17412 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17413 return;
17414 }
17415 self.dismiss_diagnostics(cx);
17416 let snapshot = self.snapshot(window, cx);
17417 let buffer = self.buffer.read(cx).snapshot(cx);
17418 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17419 return;
17420 };
17421
17422 let diagnostic_group = buffer
17423 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17424 .collect::<Vec<_>>();
17425
17426 let blocks =
17427 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17428
17429 let blocks = self.display_map.update(cx, |display_map, cx| {
17430 display_map.insert_blocks(blocks, cx).into_iter().collect()
17431 });
17432 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17433 active_range: buffer.anchor_before(diagnostic.range.start)
17434 ..buffer.anchor_after(diagnostic.range.end),
17435 active_message: diagnostic.diagnostic.message.clone(),
17436 group_id: diagnostic.diagnostic.group_id,
17437 blocks,
17438 });
17439 cx.notify();
17440 }
17441
17442 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17443 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17444 return;
17445 };
17446
17447 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17448 if let ActiveDiagnostic::Group(group) = prev {
17449 self.display_map.update(cx, |display_map, cx| {
17450 display_map.remove_blocks(group.blocks, cx);
17451 });
17452 cx.notify();
17453 }
17454 }
17455
17456 /// Disable inline diagnostics rendering for this editor.
17457 pub fn disable_inline_diagnostics(&mut self) {
17458 self.inline_diagnostics_enabled = false;
17459 self.inline_diagnostics_update = Task::ready(());
17460 self.inline_diagnostics.clear();
17461 }
17462
17463 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17464 self.diagnostics_enabled = false;
17465 self.dismiss_diagnostics(cx);
17466 self.inline_diagnostics_update = Task::ready(());
17467 self.inline_diagnostics.clear();
17468 }
17469
17470 pub fn disable_word_completions(&mut self) {
17471 self.word_completions_enabled = false;
17472 }
17473
17474 pub fn diagnostics_enabled(&self) -> bool {
17475 self.diagnostics_enabled && self.mode.is_full()
17476 }
17477
17478 pub fn inline_diagnostics_enabled(&self) -> bool {
17479 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17480 }
17481
17482 pub fn show_inline_diagnostics(&self) -> bool {
17483 self.show_inline_diagnostics
17484 }
17485
17486 pub fn toggle_inline_diagnostics(
17487 &mut self,
17488 _: &ToggleInlineDiagnostics,
17489 window: &mut Window,
17490 cx: &mut Context<Editor>,
17491 ) {
17492 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17493 self.refresh_inline_diagnostics(false, window, cx);
17494 }
17495
17496 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17497 self.diagnostics_max_severity = severity;
17498 self.display_map.update(cx, |display_map, _| {
17499 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17500 });
17501 }
17502
17503 pub fn toggle_diagnostics(
17504 &mut self,
17505 _: &ToggleDiagnostics,
17506 window: &mut Window,
17507 cx: &mut Context<Editor>,
17508 ) {
17509 if !self.diagnostics_enabled() {
17510 return;
17511 }
17512
17513 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17514 EditorSettings::get_global(cx)
17515 .diagnostics_max_severity
17516 .filter(|severity| severity != &DiagnosticSeverity::Off)
17517 .unwrap_or(DiagnosticSeverity::Hint)
17518 } else {
17519 DiagnosticSeverity::Off
17520 };
17521 self.set_max_diagnostics_severity(new_severity, cx);
17522 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17523 self.active_diagnostics = ActiveDiagnostic::None;
17524 self.inline_diagnostics_update = Task::ready(());
17525 self.inline_diagnostics.clear();
17526 } else {
17527 self.refresh_inline_diagnostics(false, window, cx);
17528 }
17529
17530 cx.notify();
17531 }
17532
17533 pub fn toggle_minimap(
17534 &mut self,
17535 _: &ToggleMinimap,
17536 window: &mut Window,
17537 cx: &mut Context<Editor>,
17538 ) {
17539 if self.supports_minimap(cx) {
17540 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17541 }
17542 }
17543
17544 fn refresh_inline_diagnostics(
17545 &mut self,
17546 debounce: bool,
17547 window: &mut Window,
17548 cx: &mut Context<Self>,
17549 ) {
17550 let max_severity = ProjectSettings::get_global(cx)
17551 .diagnostics
17552 .inline
17553 .max_severity
17554 .unwrap_or(self.diagnostics_max_severity);
17555
17556 if !self.inline_diagnostics_enabled()
17557 || !self.show_inline_diagnostics
17558 || max_severity == DiagnosticSeverity::Off
17559 {
17560 self.inline_diagnostics_update = Task::ready(());
17561 self.inline_diagnostics.clear();
17562 return;
17563 }
17564
17565 let debounce_ms = ProjectSettings::get_global(cx)
17566 .diagnostics
17567 .inline
17568 .update_debounce_ms;
17569 let debounce = if debounce && debounce_ms > 0 {
17570 Some(Duration::from_millis(debounce_ms))
17571 } else {
17572 None
17573 };
17574 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17575 if let Some(debounce) = debounce {
17576 cx.background_executor().timer(debounce).await;
17577 }
17578 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17579 editor
17580 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17581 .ok()
17582 }) else {
17583 return;
17584 };
17585
17586 let new_inline_diagnostics = cx
17587 .background_spawn(async move {
17588 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17589 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17590 let message = diagnostic_entry
17591 .diagnostic
17592 .message
17593 .split_once('\n')
17594 .map(|(line, _)| line)
17595 .map(SharedString::new)
17596 .unwrap_or_else(|| {
17597 SharedString::from(diagnostic_entry.diagnostic.message)
17598 });
17599 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17600 let (Ok(i) | Err(i)) = inline_diagnostics
17601 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17602 inline_diagnostics.insert(
17603 i,
17604 (
17605 start_anchor,
17606 InlineDiagnostic {
17607 message,
17608 group_id: diagnostic_entry.diagnostic.group_id,
17609 start: diagnostic_entry.range.start.to_point(&snapshot),
17610 is_primary: diagnostic_entry.diagnostic.is_primary,
17611 severity: diagnostic_entry.diagnostic.severity,
17612 },
17613 ),
17614 );
17615 }
17616 inline_diagnostics
17617 })
17618 .await;
17619
17620 editor
17621 .update(cx, |editor, cx| {
17622 editor.inline_diagnostics = new_inline_diagnostics;
17623 cx.notify();
17624 })
17625 .ok();
17626 });
17627 }
17628
17629 fn pull_diagnostics(
17630 &mut self,
17631 buffer_id: Option<BufferId>,
17632 window: &Window,
17633 cx: &mut Context<Self>,
17634 ) -> Option<()> {
17635 if !self.mode().is_full() {
17636 return None;
17637 }
17638 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17639 .diagnostics
17640 .lsp_pull_diagnostics;
17641 if !pull_diagnostics_settings.enabled {
17642 return None;
17643 }
17644 let project = self.project()?.downgrade();
17645 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17646 let mut buffers = self.buffer.read(cx).all_buffers();
17647 if let Some(buffer_id) = buffer_id {
17648 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17649 }
17650
17651 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17652 cx.background_executor().timer(debounce).await;
17653
17654 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17655 buffers
17656 .into_iter()
17657 .filter_map(|buffer| {
17658 project
17659 .update(cx, |project, cx| {
17660 project.lsp_store().update(cx, |lsp_store, cx| {
17661 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17662 })
17663 })
17664 .ok()
17665 })
17666 .collect::<FuturesUnordered<_>>()
17667 }) else {
17668 return;
17669 };
17670
17671 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17672 match pull_task {
17673 Ok(()) => {
17674 if editor
17675 .update_in(cx, |editor, window, cx| {
17676 editor.update_diagnostics_state(window, cx);
17677 })
17678 .is_err()
17679 {
17680 return;
17681 }
17682 }
17683 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17684 }
17685 }
17686 });
17687
17688 Some(())
17689 }
17690
17691 pub fn set_selections_from_remote(
17692 &mut self,
17693 selections: Vec<Selection<Anchor>>,
17694 pending_selection: Option<Selection<Anchor>>,
17695 window: &mut Window,
17696 cx: &mut Context<Self>,
17697 ) {
17698 let old_cursor_position = self.selections.newest_anchor().head();
17699 self.selections.change_with(cx, |s| {
17700 s.select_anchors(selections);
17701 if let Some(pending_selection) = pending_selection {
17702 s.set_pending(pending_selection, SelectMode::Character);
17703 } else {
17704 s.clear_pending();
17705 }
17706 });
17707 self.selections_did_change(
17708 false,
17709 &old_cursor_position,
17710 SelectionEffects::default(),
17711 window,
17712 cx,
17713 );
17714 }
17715
17716 pub fn transact(
17717 &mut self,
17718 window: &mut Window,
17719 cx: &mut Context<Self>,
17720 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17721 ) -> Option<TransactionId> {
17722 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17723 this.start_transaction_at(Instant::now(), window, cx);
17724 update(this, window, cx);
17725 this.end_transaction_at(Instant::now(), cx)
17726 })
17727 }
17728
17729 pub fn start_transaction_at(
17730 &mut self,
17731 now: Instant,
17732 window: &mut Window,
17733 cx: &mut Context<Self>,
17734 ) -> Option<TransactionId> {
17735 self.end_selection(window, cx);
17736 if let Some(tx_id) = self
17737 .buffer
17738 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17739 {
17740 self.selection_history
17741 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17742 cx.emit(EditorEvent::TransactionBegun {
17743 transaction_id: tx_id,
17744 });
17745 Some(tx_id)
17746 } else {
17747 None
17748 }
17749 }
17750
17751 pub fn end_transaction_at(
17752 &mut self,
17753 now: Instant,
17754 cx: &mut Context<Self>,
17755 ) -> Option<TransactionId> {
17756 if let Some(transaction_id) = self
17757 .buffer
17758 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17759 {
17760 if let Some((_, end_selections)) =
17761 self.selection_history.transaction_mut(transaction_id)
17762 {
17763 *end_selections = Some(self.selections.disjoint_anchors_arc());
17764 } else {
17765 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17766 }
17767
17768 cx.emit(EditorEvent::Edited { transaction_id });
17769 Some(transaction_id)
17770 } else {
17771 None
17772 }
17773 }
17774
17775 pub fn modify_transaction_selection_history(
17776 &mut self,
17777 transaction_id: TransactionId,
17778 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17779 ) -> bool {
17780 self.selection_history
17781 .transaction_mut(transaction_id)
17782 .map(modify)
17783 .is_some()
17784 }
17785
17786 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17787 if self.selection_mark_mode {
17788 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17789 s.move_with(|_, sel| {
17790 sel.collapse_to(sel.head(), SelectionGoal::None);
17791 });
17792 })
17793 }
17794 self.selection_mark_mode = true;
17795 cx.notify();
17796 }
17797
17798 pub fn swap_selection_ends(
17799 &mut self,
17800 _: &actions::SwapSelectionEnds,
17801 window: &mut Window,
17802 cx: &mut Context<Self>,
17803 ) {
17804 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17805 s.move_with(|_, sel| {
17806 if sel.start != sel.end {
17807 sel.reversed = !sel.reversed
17808 }
17809 });
17810 });
17811 self.request_autoscroll(Autoscroll::newest(), cx);
17812 cx.notify();
17813 }
17814
17815 pub fn toggle_focus(
17816 workspace: &mut Workspace,
17817 _: &actions::ToggleFocus,
17818 window: &mut Window,
17819 cx: &mut Context<Workspace>,
17820 ) {
17821 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17822 return;
17823 };
17824 workspace.activate_item(&item, true, true, window, cx);
17825 }
17826
17827 pub fn toggle_fold(
17828 &mut self,
17829 _: &actions::ToggleFold,
17830 window: &mut Window,
17831 cx: &mut Context<Self>,
17832 ) {
17833 if self.is_singleton(cx) {
17834 let selection = self.selections.newest::<Point>(cx);
17835
17836 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17837 let range = if selection.is_empty() {
17838 let point = selection.head().to_display_point(&display_map);
17839 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17840 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17841 .to_point(&display_map);
17842 start..end
17843 } else {
17844 selection.range()
17845 };
17846 if display_map.folds_in_range(range).next().is_some() {
17847 self.unfold_lines(&Default::default(), window, cx)
17848 } else {
17849 self.fold(&Default::default(), window, cx)
17850 }
17851 } else {
17852 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17853 let buffer_ids: HashSet<_> = self
17854 .selections
17855 .disjoint_anchor_ranges()
17856 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17857 .collect();
17858
17859 let should_unfold = buffer_ids
17860 .iter()
17861 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17862
17863 for buffer_id in buffer_ids {
17864 if should_unfold {
17865 self.unfold_buffer(buffer_id, cx);
17866 } else {
17867 self.fold_buffer(buffer_id, cx);
17868 }
17869 }
17870 }
17871 }
17872
17873 pub fn toggle_fold_recursive(
17874 &mut self,
17875 _: &actions::ToggleFoldRecursive,
17876 window: &mut Window,
17877 cx: &mut Context<Self>,
17878 ) {
17879 let selection = self.selections.newest::<Point>(cx);
17880
17881 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17882 let range = if selection.is_empty() {
17883 let point = selection.head().to_display_point(&display_map);
17884 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17885 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17886 .to_point(&display_map);
17887 start..end
17888 } else {
17889 selection.range()
17890 };
17891 if display_map.folds_in_range(range).next().is_some() {
17892 self.unfold_recursive(&Default::default(), window, cx)
17893 } else {
17894 self.fold_recursive(&Default::default(), window, cx)
17895 }
17896 }
17897
17898 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17899 if self.is_singleton(cx) {
17900 let mut to_fold = Vec::new();
17901 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17902 let selections = self.selections.all_adjusted(cx);
17903
17904 for selection in selections {
17905 let range = selection.range().sorted();
17906 let buffer_start_row = range.start.row;
17907
17908 if range.start.row != range.end.row {
17909 let mut found = false;
17910 let mut row = range.start.row;
17911 while row <= range.end.row {
17912 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17913 {
17914 found = true;
17915 row = crease.range().end.row + 1;
17916 to_fold.push(crease);
17917 } else {
17918 row += 1
17919 }
17920 }
17921 if found {
17922 continue;
17923 }
17924 }
17925
17926 for row in (0..=range.start.row).rev() {
17927 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17928 && crease.range().end.row >= buffer_start_row
17929 {
17930 to_fold.push(crease);
17931 if row <= range.start.row {
17932 break;
17933 }
17934 }
17935 }
17936 }
17937
17938 self.fold_creases(to_fold, true, window, cx);
17939 } else {
17940 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17941 let buffer_ids = self
17942 .selections
17943 .disjoint_anchor_ranges()
17944 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17945 .collect::<HashSet<_>>();
17946 for buffer_id in buffer_ids {
17947 self.fold_buffer(buffer_id, cx);
17948 }
17949 }
17950 }
17951
17952 pub fn toggle_fold_all(
17953 &mut self,
17954 _: &actions::ToggleFoldAll,
17955 window: &mut Window,
17956 cx: &mut Context<Self>,
17957 ) {
17958 if self.buffer.read(cx).is_singleton() {
17959 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17960 let has_folds = display_map
17961 .folds_in_range(0..display_map.buffer_snapshot.len())
17962 .next()
17963 .is_some();
17964
17965 if has_folds {
17966 self.unfold_all(&actions::UnfoldAll, window, cx);
17967 } else {
17968 self.fold_all(&actions::FoldAll, window, cx);
17969 }
17970 } else {
17971 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17972 let should_unfold = buffer_ids
17973 .iter()
17974 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17975
17976 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17977 editor
17978 .update_in(cx, |editor, _, cx| {
17979 for buffer_id in buffer_ids {
17980 if should_unfold {
17981 editor.unfold_buffer(buffer_id, cx);
17982 } else {
17983 editor.fold_buffer(buffer_id, cx);
17984 }
17985 }
17986 })
17987 .ok();
17988 });
17989 }
17990 }
17991
17992 fn fold_at_level(
17993 &mut self,
17994 fold_at: &FoldAtLevel,
17995 window: &mut Window,
17996 cx: &mut Context<Self>,
17997 ) {
17998 if !self.buffer.read(cx).is_singleton() {
17999 return;
18000 }
18001
18002 let fold_at_level = fold_at.0;
18003 let snapshot = self.buffer.read(cx).snapshot(cx);
18004 let mut to_fold = Vec::new();
18005 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18006
18007 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18008 while start_row < end_row {
18009 match self
18010 .snapshot(window, cx)
18011 .crease_for_buffer_row(MultiBufferRow(start_row))
18012 {
18013 Some(crease) => {
18014 let nested_start_row = crease.range().start.row + 1;
18015 let nested_end_row = crease.range().end.row;
18016
18017 if current_level < fold_at_level {
18018 stack.push((nested_start_row, nested_end_row, current_level + 1));
18019 } else if current_level == fold_at_level {
18020 to_fold.push(crease);
18021 }
18022
18023 start_row = nested_end_row + 1;
18024 }
18025 None => start_row += 1,
18026 }
18027 }
18028 }
18029
18030 self.fold_creases(to_fold, true, window, cx);
18031 }
18032
18033 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18034 if self.buffer.read(cx).is_singleton() {
18035 let mut fold_ranges = Vec::new();
18036 let snapshot = self.buffer.read(cx).snapshot(cx);
18037
18038 for row in 0..snapshot.max_row().0 {
18039 if let Some(foldable_range) = self
18040 .snapshot(window, cx)
18041 .crease_for_buffer_row(MultiBufferRow(row))
18042 {
18043 fold_ranges.push(foldable_range);
18044 }
18045 }
18046
18047 self.fold_creases(fold_ranges, true, window, cx);
18048 } else {
18049 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18050 editor
18051 .update_in(cx, |editor, _, cx| {
18052 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18053 editor.fold_buffer(buffer_id, cx);
18054 }
18055 })
18056 .ok();
18057 });
18058 }
18059 }
18060
18061 pub fn fold_function_bodies(
18062 &mut self,
18063 _: &actions::FoldFunctionBodies,
18064 window: &mut Window,
18065 cx: &mut Context<Self>,
18066 ) {
18067 let snapshot = self.buffer.read(cx).snapshot(cx);
18068
18069 let ranges = snapshot
18070 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18071 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18072 .collect::<Vec<_>>();
18073
18074 let creases = ranges
18075 .into_iter()
18076 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18077 .collect();
18078
18079 self.fold_creases(creases, true, window, cx);
18080 }
18081
18082 pub fn fold_recursive(
18083 &mut self,
18084 _: &actions::FoldRecursive,
18085 window: &mut Window,
18086 cx: &mut Context<Self>,
18087 ) {
18088 let mut to_fold = Vec::new();
18089 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18090 let selections = self.selections.all_adjusted(cx);
18091
18092 for selection in selections {
18093 let range = selection.range().sorted();
18094 let buffer_start_row = range.start.row;
18095
18096 if range.start.row != range.end.row {
18097 let mut found = false;
18098 for row in range.start.row..=range.end.row {
18099 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18100 found = true;
18101 to_fold.push(crease);
18102 }
18103 }
18104 if found {
18105 continue;
18106 }
18107 }
18108
18109 for row in (0..=range.start.row).rev() {
18110 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18111 if crease.range().end.row >= buffer_start_row {
18112 to_fold.push(crease);
18113 } else {
18114 break;
18115 }
18116 }
18117 }
18118 }
18119
18120 self.fold_creases(to_fold, true, window, cx);
18121 }
18122
18123 pub fn fold_at(
18124 &mut self,
18125 buffer_row: MultiBufferRow,
18126 window: &mut Window,
18127 cx: &mut Context<Self>,
18128 ) {
18129 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18130
18131 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18132 let autoscroll = self
18133 .selections
18134 .all::<Point>(cx)
18135 .iter()
18136 .any(|selection| crease.range().overlaps(&selection.range()));
18137
18138 self.fold_creases(vec![crease], autoscroll, window, cx);
18139 }
18140 }
18141
18142 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18143 if self.is_singleton(cx) {
18144 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18145 let buffer = &display_map.buffer_snapshot;
18146 let selections = self.selections.all::<Point>(cx);
18147 let ranges = selections
18148 .iter()
18149 .map(|s| {
18150 let range = s.display_range(&display_map).sorted();
18151 let mut start = range.start.to_point(&display_map);
18152 let mut end = range.end.to_point(&display_map);
18153 start.column = 0;
18154 end.column = buffer.line_len(MultiBufferRow(end.row));
18155 start..end
18156 })
18157 .collect::<Vec<_>>();
18158
18159 self.unfold_ranges(&ranges, true, true, cx);
18160 } else {
18161 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18162 let buffer_ids = self
18163 .selections
18164 .disjoint_anchor_ranges()
18165 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18166 .collect::<HashSet<_>>();
18167 for buffer_id in buffer_ids {
18168 self.unfold_buffer(buffer_id, cx);
18169 }
18170 }
18171 }
18172
18173 pub fn unfold_recursive(
18174 &mut self,
18175 _: &UnfoldRecursive,
18176 _window: &mut Window,
18177 cx: &mut Context<Self>,
18178 ) {
18179 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18180 let selections = self.selections.all::<Point>(cx);
18181 let ranges = selections
18182 .iter()
18183 .map(|s| {
18184 let mut range = s.display_range(&display_map).sorted();
18185 *range.start.column_mut() = 0;
18186 *range.end.column_mut() = display_map.line_len(range.end.row());
18187 let start = range.start.to_point(&display_map);
18188 let end = range.end.to_point(&display_map);
18189 start..end
18190 })
18191 .collect::<Vec<_>>();
18192
18193 self.unfold_ranges(&ranges, true, true, cx);
18194 }
18195
18196 pub fn unfold_at(
18197 &mut self,
18198 buffer_row: MultiBufferRow,
18199 _window: &mut Window,
18200 cx: &mut Context<Self>,
18201 ) {
18202 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18203
18204 let intersection_range = Point::new(buffer_row.0, 0)
18205 ..Point::new(
18206 buffer_row.0,
18207 display_map.buffer_snapshot.line_len(buffer_row),
18208 );
18209
18210 let autoscroll = self
18211 .selections
18212 .all::<Point>(cx)
18213 .iter()
18214 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18215
18216 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18217 }
18218
18219 pub fn unfold_all(
18220 &mut self,
18221 _: &actions::UnfoldAll,
18222 _window: &mut Window,
18223 cx: &mut Context<Self>,
18224 ) {
18225 if self.buffer.read(cx).is_singleton() {
18226 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18227 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18228 } else {
18229 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18230 editor
18231 .update(cx, |editor, cx| {
18232 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18233 editor.unfold_buffer(buffer_id, cx);
18234 }
18235 })
18236 .ok();
18237 });
18238 }
18239 }
18240
18241 pub fn fold_selected_ranges(
18242 &mut self,
18243 _: &FoldSelectedRanges,
18244 window: &mut Window,
18245 cx: &mut Context<Self>,
18246 ) {
18247 let selections = self.selections.all_adjusted(cx);
18248 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18249 let ranges = selections
18250 .into_iter()
18251 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18252 .collect::<Vec<_>>();
18253 self.fold_creases(ranges, true, window, cx);
18254 }
18255
18256 pub fn fold_ranges<T: ToOffset + Clone>(
18257 &mut self,
18258 ranges: Vec<Range<T>>,
18259 auto_scroll: bool,
18260 window: &mut Window,
18261 cx: &mut Context<Self>,
18262 ) {
18263 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18264 let ranges = ranges
18265 .into_iter()
18266 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18267 .collect::<Vec<_>>();
18268 self.fold_creases(ranges, auto_scroll, window, cx);
18269 }
18270
18271 pub fn fold_creases<T: ToOffset + Clone>(
18272 &mut self,
18273 creases: Vec<Crease<T>>,
18274 auto_scroll: bool,
18275 _window: &mut Window,
18276 cx: &mut Context<Self>,
18277 ) {
18278 if creases.is_empty() {
18279 return;
18280 }
18281
18282 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18283
18284 if auto_scroll {
18285 self.request_autoscroll(Autoscroll::fit(), cx);
18286 }
18287
18288 cx.notify();
18289
18290 self.scrollbar_marker_state.dirty = true;
18291 self.folds_did_change(cx);
18292 }
18293
18294 /// Removes any folds whose ranges intersect any of the given ranges.
18295 pub fn unfold_ranges<T: ToOffset + Clone>(
18296 &mut self,
18297 ranges: &[Range<T>],
18298 inclusive: bool,
18299 auto_scroll: bool,
18300 cx: &mut Context<Self>,
18301 ) {
18302 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18303 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18304 });
18305 self.folds_did_change(cx);
18306 }
18307
18308 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18309 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18310 return;
18311 }
18312 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18313 self.display_map.update(cx, |display_map, cx| {
18314 display_map.fold_buffers([buffer_id], cx)
18315 });
18316 cx.emit(EditorEvent::BufferFoldToggled {
18317 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18318 folded: true,
18319 });
18320 cx.notify();
18321 }
18322
18323 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18324 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18325 return;
18326 }
18327 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18328 self.display_map.update(cx, |display_map, cx| {
18329 display_map.unfold_buffers([buffer_id], cx);
18330 });
18331 cx.emit(EditorEvent::BufferFoldToggled {
18332 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18333 folded: false,
18334 });
18335 cx.notify();
18336 }
18337
18338 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18339 self.display_map.read(cx).is_buffer_folded(buffer)
18340 }
18341
18342 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18343 self.display_map.read(cx).folded_buffers()
18344 }
18345
18346 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18347 self.display_map.update(cx, |display_map, cx| {
18348 display_map.disable_header_for_buffer(buffer_id, cx);
18349 });
18350 cx.notify();
18351 }
18352
18353 /// Removes any folds with the given ranges.
18354 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18355 &mut self,
18356 ranges: &[Range<T>],
18357 type_id: TypeId,
18358 auto_scroll: bool,
18359 cx: &mut Context<Self>,
18360 ) {
18361 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18362 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18363 });
18364 self.folds_did_change(cx);
18365 }
18366
18367 fn remove_folds_with<T: ToOffset + Clone>(
18368 &mut self,
18369 ranges: &[Range<T>],
18370 auto_scroll: bool,
18371 cx: &mut Context<Self>,
18372 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18373 ) {
18374 if ranges.is_empty() {
18375 return;
18376 }
18377
18378 let mut buffers_affected = HashSet::default();
18379 let multi_buffer = self.buffer().read(cx);
18380 for range in ranges {
18381 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18382 buffers_affected.insert(buffer.read(cx).remote_id());
18383 };
18384 }
18385
18386 self.display_map.update(cx, update);
18387
18388 if auto_scroll {
18389 self.request_autoscroll(Autoscroll::fit(), cx);
18390 }
18391
18392 cx.notify();
18393 self.scrollbar_marker_state.dirty = true;
18394 self.active_indent_guides_state.dirty = true;
18395 }
18396
18397 pub fn update_renderer_widths(
18398 &mut self,
18399 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18400 cx: &mut Context<Self>,
18401 ) -> bool {
18402 self.display_map
18403 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18404 }
18405
18406 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18407 self.display_map.read(cx).fold_placeholder.clone()
18408 }
18409
18410 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18411 self.buffer.update(cx, |buffer, cx| {
18412 buffer.set_all_diff_hunks_expanded(cx);
18413 });
18414 }
18415
18416 pub fn expand_all_diff_hunks(
18417 &mut self,
18418 _: &ExpandAllDiffHunks,
18419 _window: &mut Window,
18420 cx: &mut Context<Self>,
18421 ) {
18422 self.buffer.update(cx, |buffer, cx| {
18423 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18424 });
18425 }
18426
18427 pub fn toggle_selected_diff_hunks(
18428 &mut self,
18429 _: &ToggleSelectedDiffHunks,
18430 _window: &mut Window,
18431 cx: &mut Context<Self>,
18432 ) {
18433 let ranges: Vec<_> = self
18434 .selections
18435 .disjoint_anchors()
18436 .iter()
18437 .map(|s| s.range())
18438 .collect();
18439 self.toggle_diff_hunks_in_ranges(ranges, cx);
18440 }
18441
18442 pub fn diff_hunks_in_ranges<'a>(
18443 &'a self,
18444 ranges: &'a [Range<Anchor>],
18445 buffer: &'a MultiBufferSnapshot,
18446 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18447 ranges.iter().flat_map(move |range| {
18448 let end_excerpt_id = range.end.excerpt_id;
18449 let range = range.to_point(buffer);
18450 let mut peek_end = range.end;
18451 if range.end.row < buffer.max_row().0 {
18452 peek_end = Point::new(range.end.row + 1, 0);
18453 }
18454 buffer
18455 .diff_hunks_in_range(range.start..peek_end)
18456 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18457 })
18458 }
18459
18460 pub fn has_stageable_diff_hunks_in_ranges(
18461 &self,
18462 ranges: &[Range<Anchor>],
18463 snapshot: &MultiBufferSnapshot,
18464 ) -> bool {
18465 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18466 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18467 }
18468
18469 pub fn toggle_staged_selected_diff_hunks(
18470 &mut self,
18471 _: &::git::ToggleStaged,
18472 _: &mut Window,
18473 cx: &mut Context<Self>,
18474 ) {
18475 let snapshot = self.buffer.read(cx).snapshot(cx);
18476 let ranges: Vec<_> = self
18477 .selections
18478 .disjoint_anchors()
18479 .iter()
18480 .map(|s| s.range())
18481 .collect();
18482 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18483 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18484 }
18485
18486 pub fn set_render_diff_hunk_controls(
18487 &mut self,
18488 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18489 cx: &mut Context<Self>,
18490 ) {
18491 self.render_diff_hunk_controls = render_diff_hunk_controls;
18492 cx.notify();
18493 }
18494
18495 pub fn stage_and_next(
18496 &mut self,
18497 _: &::git::StageAndNext,
18498 window: &mut Window,
18499 cx: &mut Context<Self>,
18500 ) {
18501 self.do_stage_or_unstage_and_next(true, window, cx);
18502 }
18503
18504 pub fn unstage_and_next(
18505 &mut self,
18506 _: &::git::UnstageAndNext,
18507 window: &mut Window,
18508 cx: &mut Context<Self>,
18509 ) {
18510 self.do_stage_or_unstage_and_next(false, window, cx);
18511 }
18512
18513 pub fn stage_or_unstage_diff_hunks(
18514 &mut self,
18515 stage: bool,
18516 ranges: Vec<Range<Anchor>>,
18517 cx: &mut Context<Self>,
18518 ) {
18519 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18520 cx.spawn(async move |this, cx| {
18521 task.await?;
18522 this.update(cx, |this, cx| {
18523 let snapshot = this.buffer.read(cx).snapshot(cx);
18524 let chunk_by = this
18525 .diff_hunks_in_ranges(&ranges, &snapshot)
18526 .chunk_by(|hunk| hunk.buffer_id);
18527 for (buffer_id, hunks) in &chunk_by {
18528 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18529 }
18530 })
18531 })
18532 .detach_and_log_err(cx);
18533 }
18534
18535 fn save_buffers_for_ranges_if_needed(
18536 &mut self,
18537 ranges: &[Range<Anchor>],
18538 cx: &mut Context<Editor>,
18539 ) -> Task<Result<()>> {
18540 let multibuffer = self.buffer.read(cx);
18541 let snapshot = multibuffer.read(cx);
18542 let buffer_ids: HashSet<_> = ranges
18543 .iter()
18544 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18545 .collect();
18546 drop(snapshot);
18547
18548 let mut buffers = HashSet::default();
18549 for buffer_id in buffer_ids {
18550 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18551 let buffer = buffer_entity.read(cx);
18552 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18553 {
18554 buffers.insert(buffer_entity);
18555 }
18556 }
18557 }
18558
18559 if let Some(project) = &self.project {
18560 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18561 } else {
18562 Task::ready(Ok(()))
18563 }
18564 }
18565
18566 fn do_stage_or_unstage_and_next(
18567 &mut self,
18568 stage: bool,
18569 window: &mut Window,
18570 cx: &mut Context<Self>,
18571 ) {
18572 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18573
18574 if ranges.iter().any(|range| range.start != range.end) {
18575 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18576 return;
18577 }
18578
18579 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18580 let snapshot = self.snapshot(window, cx);
18581 let position = self.selections.newest::<Point>(cx).head();
18582 let mut row = snapshot
18583 .buffer_snapshot
18584 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18585 .find(|hunk| hunk.row_range.start.0 > position.row)
18586 .map(|hunk| hunk.row_range.start);
18587
18588 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18589 // Outside of the project diff editor, wrap around to the beginning.
18590 if !all_diff_hunks_expanded {
18591 row = row.or_else(|| {
18592 snapshot
18593 .buffer_snapshot
18594 .diff_hunks_in_range(Point::zero()..position)
18595 .find(|hunk| hunk.row_range.end.0 < position.row)
18596 .map(|hunk| hunk.row_range.start)
18597 });
18598 }
18599
18600 if let Some(row) = row {
18601 let destination = Point::new(row.0, 0);
18602 let autoscroll = Autoscroll::center();
18603
18604 self.unfold_ranges(&[destination..destination], false, false, cx);
18605 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18606 s.select_ranges([destination..destination]);
18607 });
18608 }
18609 }
18610
18611 fn do_stage_or_unstage(
18612 &self,
18613 stage: bool,
18614 buffer_id: BufferId,
18615 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18616 cx: &mut App,
18617 ) -> Option<()> {
18618 let project = self.project()?;
18619 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18620 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18621 let buffer_snapshot = buffer.read(cx).snapshot();
18622 let file_exists = buffer_snapshot
18623 .file()
18624 .is_some_and(|file| file.disk_state().exists());
18625 diff.update(cx, |diff, cx| {
18626 diff.stage_or_unstage_hunks(
18627 stage,
18628 &hunks
18629 .map(|hunk| buffer_diff::DiffHunk {
18630 buffer_range: hunk.buffer_range,
18631 diff_base_byte_range: hunk.diff_base_byte_range,
18632 secondary_status: hunk.secondary_status,
18633 range: Point::zero()..Point::zero(), // unused
18634 })
18635 .collect::<Vec<_>>(),
18636 &buffer_snapshot,
18637 file_exists,
18638 cx,
18639 )
18640 });
18641 None
18642 }
18643
18644 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18645 let ranges: Vec<_> = self
18646 .selections
18647 .disjoint_anchors()
18648 .iter()
18649 .map(|s| s.range())
18650 .collect();
18651 self.buffer
18652 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18653 }
18654
18655 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18656 self.buffer.update(cx, |buffer, cx| {
18657 let ranges = vec![Anchor::min()..Anchor::max()];
18658 if !buffer.all_diff_hunks_expanded()
18659 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18660 {
18661 buffer.collapse_diff_hunks(ranges, cx);
18662 true
18663 } else {
18664 false
18665 }
18666 })
18667 }
18668
18669 fn toggle_diff_hunks_in_ranges(
18670 &mut self,
18671 ranges: Vec<Range<Anchor>>,
18672 cx: &mut Context<Editor>,
18673 ) {
18674 self.buffer.update(cx, |buffer, cx| {
18675 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18676 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18677 })
18678 }
18679
18680 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18681 self.buffer.update(cx, |buffer, cx| {
18682 let snapshot = buffer.snapshot(cx);
18683 let excerpt_id = range.end.excerpt_id;
18684 let point_range = range.to_point(&snapshot);
18685 let expand = !buffer.single_hunk_is_expanded(range, cx);
18686 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18687 })
18688 }
18689
18690 pub(crate) fn apply_all_diff_hunks(
18691 &mut self,
18692 _: &ApplyAllDiffHunks,
18693 window: &mut Window,
18694 cx: &mut Context<Self>,
18695 ) {
18696 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18697
18698 let buffers = self.buffer.read(cx).all_buffers();
18699 for branch_buffer in buffers {
18700 branch_buffer.update(cx, |branch_buffer, cx| {
18701 branch_buffer.merge_into_base(Vec::new(), cx);
18702 });
18703 }
18704
18705 if let Some(project) = self.project.clone() {
18706 self.save(
18707 SaveOptions {
18708 format: true,
18709 autosave: false,
18710 },
18711 project,
18712 window,
18713 cx,
18714 )
18715 .detach_and_log_err(cx);
18716 }
18717 }
18718
18719 pub(crate) fn apply_selected_diff_hunks(
18720 &mut self,
18721 _: &ApplyDiffHunk,
18722 window: &mut Window,
18723 cx: &mut Context<Self>,
18724 ) {
18725 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18726 let snapshot = self.snapshot(window, cx);
18727 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18728 let mut ranges_by_buffer = HashMap::default();
18729 self.transact(window, cx, |editor, _window, cx| {
18730 for hunk in hunks {
18731 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18732 ranges_by_buffer
18733 .entry(buffer.clone())
18734 .or_insert_with(Vec::new)
18735 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18736 }
18737 }
18738
18739 for (buffer, ranges) in ranges_by_buffer {
18740 buffer.update(cx, |buffer, cx| {
18741 buffer.merge_into_base(ranges, cx);
18742 });
18743 }
18744 });
18745
18746 if let Some(project) = self.project.clone() {
18747 self.save(
18748 SaveOptions {
18749 format: true,
18750 autosave: false,
18751 },
18752 project,
18753 window,
18754 cx,
18755 )
18756 .detach_and_log_err(cx);
18757 }
18758 }
18759
18760 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18761 if hovered != self.gutter_hovered {
18762 self.gutter_hovered = hovered;
18763 cx.notify();
18764 }
18765 }
18766
18767 pub fn insert_blocks(
18768 &mut self,
18769 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18770 autoscroll: Option<Autoscroll>,
18771 cx: &mut Context<Self>,
18772 ) -> Vec<CustomBlockId> {
18773 let blocks = self
18774 .display_map
18775 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18776 if let Some(autoscroll) = autoscroll {
18777 self.request_autoscroll(autoscroll, cx);
18778 }
18779 cx.notify();
18780 blocks
18781 }
18782
18783 pub fn resize_blocks(
18784 &mut self,
18785 heights: HashMap<CustomBlockId, u32>,
18786 autoscroll: Option<Autoscroll>,
18787 cx: &mut Context<Self>,
18788 ) {
18789 self.display_map
18790 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18791 if let Some(autoscroll) = autoscroll {
18792 self.request_autoscroll(autoscroll, cx);
18793 }
18794 cx.notify();
18795 }
18796
18797 pub fn replace_blocks(
18798 &mut self,
18799 renderers: HashMap<CustomBlockId, RenderBlock>,
18800 autoscroll: Option<Autoscroll>,
18801 cx: &mut Context<Self>,
18802 ) {
18803 self.display_map
18804 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18805 if let Some(autoscroll) = autoscroll {
18806 self.request_autoscroll(autoscroll, cx);
18807 }
18808 cx.notify();
18809 }
18810
18811 pub fn remove_blocks(
18812 &mut self,
18813 block_ids: HashSet<CustomBlockId>,
18814 autoscroll: Option<Autoscroll>,
18815 cx: &mut Context<Self>,
18816 ) {
18817 self.display_map.update(cx, |display_map, cx| {
18818 display_map.remove_blocks(block_ids, cx)
18819 });
18820 if let Some(autoscroll) = autoscroll {
18821 self.request_autoscroll(autoscroll, cx);
18822 }
18823 cx.notify();
18824 }
18825
18826 pub fn row_for_block(
18827 &self,
18828 block_id: CustomBlockId,
18829 cx: &mut Context<Self>,
18830 ) -> Option<DisplayRow> {
18831 self.display_map
18832 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18833 }
18834
18835 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18836 self.focused_block = Some(focused_block);
18837 }
18838
18839 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18840 self.focused_block.take()
18841 }
18842
18843 pub fn insert_creases(
18844 &mut self,
18845 creases: impl IntoIterator<Item = Crease<Anchor>>,
18846 cx: &mut Context<Self>,
18847 ) -> Vec<CreaseId> {
18848 self.display_map
18849 .update(cx, |map, cx| map.insert_creases(creases, cx))
18850 }
18851
18852 pub fn remove_creases(
18853 &mut self,
18854 ids: impl IntoIterator<Item = CreaseId>,
18855 cx: &mut Context<Self>,
18856 ) -> Vec<(CreaseId, Range<Anchor>)> {
18857 self.display_map
18858 .update(cx, |map, cx| map.remove_creases(ids, cx))
18859 }
18860
18861 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18862 self.display_map
18863 .update(cx, |map, cx| map.snapshot(cx))
18864 .longest_row()
18865 }
18866
18867 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18868 self.display_map
18869 .update(cx, |map, cx| map.snapshot(cx))
18870 .max_point()
18871 }
18872
18873 pub fn text(&self, cx: &App) -> String {
18874 self.buffer.read(cx).read(cx).text()
18875 }
18876
18877 pub fn is_empty(&self, cx: &App) -> bool {
18878 self.buffer.read(cx).read(cx).is_empty()
18879 }
18880
18881 pub fn text_option(&self, cx: &App) -> Option<String> {
18882 let text = self.text(cx);
18883 let text = text.trim();
18884
18885 if text.is_empty() {
18886 return None;
18887 }
18888
18889 Some(text.to_string())
18890 }
18891
18892 pub fn set_text(
18893 &mut self,
18894 text: impl Into<Arc<str>>,
18895 window: &mut Window,
18896 cx: &mut Context<Self>,
18897 ) {
18898 self.transact(window, cx, |this, _, cx| {
18899 this.buffer
18900 .read(cx)
18901 .as_singleton()
18902 .expect("you can only call set_text on editors for singleton buffers")
18903 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18904 });
18905 }
18906
18907 pub fn display_text(&self, cx: &mut App) -> String {
18908 self.display_map
18909 .update(cx, |map, cx| map.snapshot(cx))
18910 .text()
18911 }
18912
18913 fn create_minimap(
18914 &self,
18915 minimap_settings: MinimapSettings,
18916 window: &mut Window,
18917 cx: &mut Context<Self>,
18918 ) -> Option<Entity<Self>> {
18919 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18920 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18921 }
18922
18923 fn initialize_new_minimap(
18924 &self,
18925 minimap_settings: MinimapSettings,
18926 window: &mut Window,
18927 cx: &mut Context<Self>,
18928 ) -> Entity<Self> {
18929 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18930
18931 let mut minimap = Editor::new_internal(
18932 EditorMode::Minimap {
18933 parent: cx.weak_entity(),
18934 },
18935 self.buffer.clone(),
18936 None,
18937 Some(self.display_map.clone()),
18938 window,
18939 cx,
18940 );
18941 minimap.scroll_manager.clone_state(&self.scroll_manager);
18942 minimap.set_text_style_refinement(TextStyleRefinement {
18943 font_size: Some(MINIMAP_FONT_SIZE),
18944 font_weight: Some(MINIMAP_FONT_WEIGHT),
18945 ..Default::default()
18946 });
18947 minimap.update_minimap_configuration(minimap_settings, cx);
18948 cx.new(|_| minimap)
18949 }
18950
18951 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18952 let current_line_highlight = minimap_settings
18953 .current_line_highlight
18954 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18955 self.set_current_line_highlight(Some(current_line_highlight));
18956 }
18957
18958 pub fn minimap(&self) -> Option<&Entity<Self>> {
18959 self.minimap
18960 .as_ref()
18961 .filter(|_| self.minimap_visibility.visible())
18962 }
18963
18964 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18965 let mut wrap_guides = smallvec![];
18966
18967 if self.show_wrap_guides == Some(false) {
18968 return wrap_guides;
18969 }
18970
18971 let settings = self.buffer.read(cx).language_settings(cx);
18972 if settings.show_wrap_guides {
18973 match self.soft_wrap_mode(cx) {
18974 SoftWrap::Column(soft_wrap) => {
18975 wrap_guides.push((soft_wrap as usize, true));
18976 }
18977 SoftWrap::Bounded(soft_wrap) => {
18978 wrap_guides.push((soft_wrap as usize, true));
18979 }
18980 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18981 }
18982 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18983 }
18984
18985 wrap_guides
18986 }
18987
18988 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18989 let settings = self.buffer.read(cx).language_settings(cx);
18990 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18991 match mode {
18992 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18993 SoftWrap::None
18994 }
18995 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18996 language_settings::SoftWrap::PreferredLineLength => {
18997 SoftWrap::Column(settings.preferred_line_length)
18998 }
18999 language_settings::SoftWrap::Bounded => {
19000 SoftWrap::Bounded(settings.preferred_line_length)
19001 }
19002 }
19003 }
19004
19005 pub fn set_soft_wrap_mode(
19006 &mut self,
19007 mode: language_settings::SoftWrap,
19008
19009 cx: &mut Context<Self>,
19010 ) {
19011 self.soft_wrap_mode_override = Some(mode);
19012 cx.notify();
19013 }
19014
19015 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19016 self.hard_wrap = hard_wrap;
19017 cx.notify();
19018 }
19019
19020 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19021 self.text_style_refinement = Some(style);
19022 }
19023
19024 /// called by the Element so we know what style we were most recently rendered with.
19025 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19026 // We intentionally do not inform the display map about the minimap style
19027 // so that wrapping is not recalculated and stays consistent for the editor
19028 // and its linked minimap.
19029 if !self.mode.is_minimap() {
19030 let font = style.text.font();
19031 let font_size = style.text.font_size.to_pixels(window.rem_size());
19032 let display_map = self
19033 .placeholder_display_map
19034 .as_ref()
19035 .filter(|_| self.is_empty(cx))
19036 .unwrap_or(&self.display_map);
19037
19038 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19039 }
19040 self.style = Some(style);
19041 }
19042
19043 pub fn style(&self) -> Option<&EditorStyle> {
19044 self.style.as_ref()
19045 }
19046
19047 // Called by the element. This method is not designed to be called outside of the editor
19048 // element's layout code because it does not notify when rewrapping is computed synchronously.
19049 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19050 if self.is_empty(cx) {
19051 self.placeholder_display_map
19052 .as_ref()
19053 .map_or(false, |display_map| {
19054 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19055 })
19056 } else {
19057 self.display_map
19058 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19059 }
19060 }
19061
19062 pub fn set_soft_wrap(&mut self) {
19063 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19064 }
19065
19066 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19067 if self.soft_wrap_mode_override.is_some() {
19068 self.soft_wrap_mode_override.take();
19069 } else {
19070 let soft_wrap = match self.soft_wrap_mode(cx) {
19071 SoftWrap::GitDiff => return,
19072 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19073 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19074 language_settings::SoftWrap::None
19075 }
19076 };
19077 self.soft_wrap_mode_override = Some(soft_wrap);
19078 }
19079 cx.notify();
19080 }
19081
19082 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19083 let Some(workspace) = self.workspace() else {
19084 return;
19085 };
19086 let fs = workspace.read(cx).app_state().fs.clone();
19087 let current_show = TabBarSettings::get_global(cx).show;
19088 update_settings_file(fs, cx, move |setting, _| {
19089 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19090 });
19091 }
19092
19093 pub fn toggle_indent_guides(
19094 &mut self,
19095 _: &ToggleIndentGuides,
19096 _: &mut Window,
19097 cx: &mut Context<Self>,
19098 ) {
19099 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19100 self.buffer
19101 .read(cx)
19102 .language_settings(cx)
19103 .indent_guides
19104 .enabled
19105 });
19106 self.show_indent_guides = Some(!currently_enabled);
19107 cx.notify();
19108 }
19109
19110 fn should_show_indent_guides(&self) -> Option<bool> {
19111 self.show_indent_guides
19112 }
19113
19114 pub fn toggle_line_numbers(
19115 &mut self,
19116 _: &ToggleLineNumbers,
19117 _: &mut Window,
19118 cx: &mut Context<Self>,
19119 ) {
19120 let mut editor_settings = EditorSettings::get_global(cx).clone();
19121 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19122 EditorSettings::override_global(editor_settings, cx);
19123 }
19124
19125 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19126 if let Some(show_line_numbers) = self.show_line_numbers {
19127 return show_line_numbers;
19128 }
19129 EditorSettings::get_global(cx).gutter.line_numbers
19130 }
19131
19132 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19133 self.use_relative_line_numbers
19134 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19135 }
19136
19137 pub fn toggle_relative_line_numbers(
19138 &mut self,
19139 _: &ToggleRelativeLineNumbers,
19140 _: &mut Window,
19141 cx: &mut Context<Self>,
19142 ) {
19143 let is_relative = self.should_use_relative_line_numbers(cx);
19144 self.set_relative_line_number(Some(!is_relative), cx)
19145 }
19146
19147 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19148 self.use_relative_line_numbers = is_relative;
19149 cx.notify();
19150 }
19151
19152 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19153 self.show_gutter = show_gutter;
19154 cx.notify();
19155 }
19156
19157 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19158 self.show_scrollbars = ScrollbarAxes {
19159 horizontal: show,
19160 vertical: show,
19161 };
19162 cx.notify();
19163 }
19164
19165 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19166 self.show_scrollbars.vertical = show;
19167 cx.notify();
19168 }
19169
19170 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19171 self.show_scrollbars.horizontal = show;
19172 cx.notify();
19173 }
19174
19175 pub fn set_minimap_visibility(
19176 &mut self,
19177 minimap_visibility: MinimapVisibility,
19178 window: &mut Window,
19179 cx: &mut Context<Self>,
19180 ) {
19181 if self.minimap_visibility != minimap_visibility {
19182 if minimap_visibility.visible() && self.minimap.is_none() {
19183 let minimap_settings = EditorSettings::get_global(cx).minimap;
19184 self.minimap =
19185 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19186 }
19187 self.minimap_visibility = minimap_visibility;
19188 cx.notify();
19189 }
19190 }
19191
19192 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19193 self.set_show_scrollbars(false, cx);
19194 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19195 }
19196
19197 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19198 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19199 }
19200
19201 /// Normally the text in full mode and auto height editors is padded on the
19202 /// left side by roughly half a character width for improved hit testing.
19203 ///
19204 /// Use this method to disable this for cases where this is not wanted (e.g.
19205 /// if you want to align the editor text with some other text above or below)
19206 /// or if you want to add this padding to single-line editors.
19207 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19208 self.offset_content = offset_content;
19209 cx.notify();
19210 }
19211
19212 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19213 self.show_line_numbers = Some(show_line_numbers);
19214 cx.notify();
19215 }
19216
19217 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19218 self.disable_expand_excerpt_buttons = true;
19219 cx.notify();
19220 }
19221
19222 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19223 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19224 cx.notify();
19225 }
19226
19227 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19228 self.show_code_actions = Some(show_code_actions);
19229 cx.notify();
19230 }
19231
19232 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19233 self.show_runnables = Some(show_runnables);
19234 cx.notify();
19235 }
19236
19237 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19238 self.show_breakpoints = Some(show_breakpoints);
19239 cx.notify();
19240 }
19241
19242 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19243 if self.display_map.read(cx).masked != masked {
19244 self.display_map.update(cx, |map, _| map.masked = masked);
19245 }
19246 cx.notify()
19247 }
19248
19249 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19250 self.show_wrap_guides = Some(show_wrap_guides);
19251 cx.notify();
19252 }
19253
19254 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19255 self.show_indent_guides = Some(show_indent_guides);
19256 cx.notify();
19257 }
19258
19259 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19260 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19261 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19262 && let Some(dir) = file.abs_path(cx).parent()
19263 {
19264 return Some(dir.to_owned());
19265 }
19266
19267 if let Some(project_path) = buffer.read(cx).project_path(cx) {
19268 return Some(project_path.path.to_path_buf());
19269 }
19270 }
19271
19272 None
19273 }
19274
19275 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19276 self.active_excerpt(cx)?
19277 .1
19278 .read(cx)
19279 .file()
19280 .and_then(|f| f.as_local())
19281 }
19282
19283 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19284 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19285 let buffer = buffer.read(cx);
19286 if let Some(project_path) = buffer.project_path(cx) {
19287 let project = self.project()?.read(cx);
19288 project.absolute_path(&project_path, cx)
19289 } else {
19290 buffer
19291 .file()
19292 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19293 }
19294 })
19295 }
19296
19297 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19298 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19299 let project_path = buffer.read(cx).project_path(cx)?;
19300 let project = self.project()?.read(cx);
19301 let entry = project.entry_for_path(&project_path, cx)?;
19302 let path = entry.path.to_path_buf();
19303 Some(path)
19304 })
19305 }
19306
19307 pub fn reveal_in_finder(
19308 &mut self,
19309 _: &RevealInFileManager,
19310 _window: &mut Window,
19311 cx: &mut Context<Self>,
19312 ) {
19313 if let Some(target) = self.target_file(cx) {
19314 cx.reveal_path(&target.abs_path(cx));
19315 }
19316 }
19317
19318 pub fn copy_path(
19319 &mut self,
19320 _: &zed_actions::workspace::CopyPath,
19321 _window: &mut Window,
19322 cx: &mut Context<Self>,
19323 ) {
19324 if let Some(path) = self.target_file_abs_path(cx)
19325 && let Some(path) = path.to_str()
19326 {
19327 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19328 } else {
19329 cx.propagate();
19330 }
19331 }
19332
19333 pub fn copy_relative_path(
19334 &mut self,
19335 _: &zed_actions::workspace::CopyRelativePath,
19336 _window: &mut Window,
19337 cx: &mut Context<Self>,
19338 ) {
19339 if let Some(path) = self.target_file_path(cx)
19340 && let Some(path) = path.to_str()
19341 {
19342 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19343 } else {
19344 cx.propagate();
19345 }
19346 }
19347
19348 /// Returns the project path for the editor's buffer, if any buffer is
19349 /// opened in the editor.
19350 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19351 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19352 buffer.read(cx).project_path(cx)
19353 } else {
19354 None
19355 }
19356 }
19357
19358 // Returns true if the editor handled a go-to-line request
19359 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19360 maybe!({
19361 let breakpoint_store = self.breakpoint_store.as_ref()?;
19362
19363 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19364 else {
19365 self.clear_row_highlights::<ActiveDebugLine>();
19366 return None;
19367 };
19368
19369 let position = active_stack_frame.position;
19370 let buffer_id = position.buffer_id?;
19371 let snapshot = self
19372 .project
19373 .as_ref()?
19374 .read(cx)
19375 .buffer_for_id(buffer_id, cx)?
19376 .read(cx)
19377 .snapshot();
19378
19379 let mut handled = false;
19380 for (id, ExcerptRange { context, .. }) in
19381 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19382 {
19383 if context.start.cmp(&position, &snapshot).is_ge()
19384 || context.end.cmp(&position, &snapshot).is_lt()
19385 {
19386 continue;
19387 }
19388 let snapshot = self.buffer.read(cx).snapshot(cx);
19389 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19390
19391 handled = true;
19392 self.clear_row_highlights::<ActiveDebugLine>();
19393
19394 self.go_to_line::<ActiveDebugLine>(
19395 multibuffer_anchor,
19396 Some(cx.theme().colors().editor_debugger_active_line_background),
19397 window,
19398 cx,
19399 );
19400
19401 cx.notify();
19402 }
19403
19404 handled.then_some(())
19405 })
19406 .is_some()
19407 }
19408
19409 pub fn copy_file_name_without_extension(
19410 &mut self,
19411 _: &CopyFileNameWithoutExtension,
19412 _: &mut Window,
19413 cx: &mut Context<Self>,
19414 ) {
19415 if let Some(file) = self.target_file(cx)
19416 && let Some(file_stem) = file.path().file_stem()
19417 && let Some(name) = file_stem.to_str()
19418 {
19419 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19420 }
19421 }
19422
19423 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19424 if let Some(file) = self.target_file(cx)
19425 && let Some(file_name) = file.path().file_name()
19426 && let Some(name) = file_name.to_str()
19427 {
19428 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19429 }
19430 }
19431
19432 pub fn toggle_git_blame(
19433 &mut self,
19434 _: &::git::Blame,
19435 window: &mut Window,
19436 cx: &mut Context<Self>,
19437 ) {
19438 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19439
19440 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19441 self.start_git_blame(true, window, cx);
19442 }
19443
19444 cx.notify();
19445 }
19446
19447 pub fn toggle_git_blame_inline(
19448 &mut self,
19449 _: &ToggleGitBlameInline,
19450 window: &mut Window,
19451 cx: &mut Context<Self>,
19452 ) {
19453 self.toggle_git_blame_inline_internal(true, window, cx);
19454 cx.notify();
19455 }
19456
19457 pub fn open_git_blame_commit(
19458 &mut self,
19459 _: &OpenGitBlameCommit,
19460 window: &mut Window,
19461 cx: &mut Context<Self>,
19462 ) {
19463 self.open_git_blame_commit_internal(window, cx);
19464 }
19465
19466 fn open_git_blame_commit_internal(
19467 &mut self,
19468 window: &mut Window,
19469 cx: &mut Context<Self>,
19470 ) -> Option<()> {
19471 let blame = self.blame.as_ref()?;
19472 let snapshot = self.snapshot(window, cx);
19473 let cursor = self.selections.newest::<Point>(cx).head();
19474 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19475 let (_, blame_entry) = blame
19476 .update(cx, |blame, cx| {
19477 blame
19478 .blame_for_rows(
19479 &[RowInfo {
19480 buffer_id: Some(buffer.remote_id()),
19481 buffer_row: Some(point.row),
19482 ..Default::default()
19483 }],
19484 cx,
19485 )
19486 .next()
19487 })
19488 .flatten()?;
19489 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19490 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19491 let workspace = self.workspace()?.downgrade();
19492 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19493 None
19494 }
19495
19496 pub fn git_blame_inline_enabled(&self) -> bool {
19497 self.git_blame_inline_enabled
19498 }
19499
19500 pub fn toggle_selection_menu(
19501 &mut self,
19502 _: &ToggleSelectionMenu,
19503 _: &mut Window,
19504 cx: &mut Context<Self>,
19505 ) {
19506 self.show_selection_menu = self
19507 .show_selection_menu
19508 .map(|show_selections_menu| !show_selections_menu)
19509 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19510
19511 cx.notify();
19512 }
19513
19514 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19515 self.show_selection_menu
19516 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19517 }
19518
19519 fn start_git_blame(
19520 &mut self,
19521 user_triggered: bool,
19522 window: &mut Window,
19523 cx: &mut Context<Self>,
19524 ) {
19525 if let Some(project) = self.project() {
19526 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19527 && buffer.read(cx).file().is_none()
19528 {
19529 return;
19530 }
19531
19532 let focused = self.focus_handle(cx).contains_focused(window, cx);
19533
19534 let project = project.clone();
19535 let blame = cx
19536 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19537 self.blame_subscription =
19538 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19539 self.blame = Some(blame);
19540 }
19541 }
19542
19543 fn toggle_git_blame_inline_internal(
19544 &mut self,
19545 user_triggered: bool,
19546 window: &mut Window,
19547 cx: &mut Context<Self>,
19548 ) {
19549 if self.git_blame_inline_enabled {
19550 self.git_blame_inline_enabled = false;
19551 self.show_git_blame_inline = false;
19552 self.show_git_blame_inline_delay_task.take();
19553 } else {
19554 self.git_blame_inline_enabled = true;
19555 self.start_git_blame_inline(user_triggered, window, cx);
19556 }
19557
19558 cx.notify();
19559 }
19560
19561 fn start_git_blame_inline(
19562 &mut self,
19563 user_triggered: bool,
19564 window: &mut Window,
19565 cx: &mut Context<Self>,
19566 ) {
19567 self.start_git_blame(user_triggered, window, cx);
19568
19569 if ProjectSettings::get_global(cx)
19570 .git
19571 .inline_blame_delay()
19572 .is_some()
19573 {
19574 self.start_inline_blame_timer(window, cx);
19575 } else {
19576 self.show_git_blame_inline = true
19577 }
19578 }
19579
19580 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19581 self.blame.as_ref()
19582 }
19583
19584 pub fn show_git_blame_gutter(&self) -> bool {
19585 self.show_git_blame_gutter
19586 }
19587
19588 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19589 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19590 }
19591
19592 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19593 self.show_git_blame_inline
19594 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19595 && !self.newest_selection_head_on_empty_line(cx)
19596 && self.has_blame_entries(cx)
19597 }
19598
19599 fn has_blame_entries(&self, cx: &App) -> bool {
19600 self.blame()
19601 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19602 }
19603
19604 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19605 let cursor_anchor = self.selections.newest_anchor().head();
19606
19607 let snapshot = self.buffer.read(cx).snapshot(cx);
19608 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19609
19610 snapshot.line_len(buffer_row) == 0
19611 }
19612
19613 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19614 let buffer_and_selection = maybe!({
19615 let selection = self.selections.newest::<Point>(cx);
19616 let selection_range = selection.range();
19617
19618 let multi_buffer = self.buffer().read(cx);
19619 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19620 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19621
19622 let (buffer, range, _) = if selection.reversed {
19623 buffer_ranges.first()
19624 } else {
19625 buffer_ranges.last()
19626 }?;
19627
19628 let selection = text::ToPoint::to_point(&range.start, buffer).row
19629 ..text::ToPoint::to_point(&range.end, buffer).row;
19630 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19631 });
19632
19633 let Some((buffer, selection)) = buffer_and_selection else {
19634 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19635 };
19636
19637 let Some(project) = self.project() else {
19638 return Task::ready(Err(anyhow!("editor does not have project")));
19639 };
19640
19641 project.update(cx, |project, cx| {
19642 project.get_permalink_to_line(&buffer, selection, cx)
19643 })
19644 }
19645
19646 pub fn copy_permalink_to_line(
19647 &mut self,
19648 _: &CopyPermalinkToLine,
19649 window: &mut Window,
19650 cx: &mut Context<Self>,
19651 ) {
19652 let permalink_task = self.get_permalink_to_line(cx);
19653 let workspace = self.workspace();
19654
19655 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19656 Ok(permalink) => {
19657 cx.update(|_, cx| {
19658 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19659 })
19660 .ok();
19661 }
19662 Err(err) => {
19663 let message = format!("Failed to copy permalink: {err}");
19664
19665 anyhow::Result::<()>::Err(err).log_err();
19666
19667 if let Some(workspace) = workspace {
19668 workspace
19669 .update_in(cx, |workspace, _, cx| {
19670 struct CopyPermalinkToLine;
19671
19672 workspace.show_toast(
19673 Toast::new(
19674 NotificationId::unique::<CopyPermalinkToLine>(),
19675 message,
19676 ),
19677 cx,
19678 )
19679 })
19680 .ok();
19681 }
19682 }
19683 })
19684 .detach();
19685 }
19686
19687 pub fn copy_file_location(
19688 &mut self,
19689 _: &CopyFileLocation,
19690 _: &mut Window,
19691 cx: &mut Context<Self>,
19692 ) {
19693 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19694 if let Some(file) = self.target_file(cx)
19695 && let Some(path) = file.path().to_str()
19696 {
19697 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19698 }
19699 }
19700
19701 pub fn open_permalink_to_line(
19702 &mut self,
19703 _: &OpenPermalinkToLine,
19704 window: &mut Window,
19705 cx: &mut Context<Self>,
19706 ) {
19707 let permalink_task = self.get_permalink_to_line(cx);
19708 let workspace = self.workspace();
19709
19710 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19711 Ok(permalink) => {
19712 cx.update(|_, cx| {
19713 cx.open_url(permalink.as_ref());
19714 })
19715 .ok();
19716 }
19717 Err(err) => {
19718 let message = format!("Failed to open permalink: {err}");
19719
19720 anyhow::Result::<()>::Err(err).log_err();
19721
19722 if let Some(workspace) = workspace {
19723 workspace
19724 .update(cx, |workspace, cx| {
19725 struct OpenPermalinkToLine;
19726
19727 workspace.show_toast(
19728 Toast::new(
19729 NotificationId::unique::<OpenPermalinkToLine>(),
19730 message,
19731 ),
19732 cx,
19733 )
19734 })
19735 .ok();
19736 }
19737 }
19738 })
19739 .detach();
19740 }
19741
19742 pub fn insert_uuid_v4(
19743 &mut self,
19744 _: &InsertUuidV4,
19745 window: &mut Window,
19746 cx: &mut Context<Self>,
19747 ) {
19748 self.insert_uuid(UuidVersion::V4, window, cx);
19749 }
19750
19751 pub fn insert_uuid_v7(
19752 &mut self,
19753 _: &InsertUuidV7,
19754 window: &mut Window,
19755 cx: &mut Context<Self>,
19756 ) {
19757 self.insert_uuid(UuidVersion::V7, window, cx);
19758 }
19759
19760 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19761 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19762 self.transact(window, cx, |this, window, cx| {
19763 let edits = this
19764 .selections
19765 .all::<Point>(cx)
19766 .into_iter()
19767 .map(|selection| {
19768 let uuid = match version {
19769 UuidVersion::V4 => uuid::Uuid::new_v4(),
19770 UuidVersion::V7 => uuid::Uuid::now_v7(),
19771 };
19772
19773 (selection.range(), uuid.to_string())
19774 });
19775 this.edit(edits, cx);
19776 this.refresh_edit_prediction(true, false, window, cx);
19777 });
19778 }
19779
19780 pub fn open_selections_in_multibuffer(
19781 &mut self,
19782 _: &OpenSelectionsInMultibuffer,
19783 window: &mut Window,
19784 cx: &mut Context<Self>,
19785 ) {
19786 let multibuffer = self.buffer.read(cx);
19787
19788 let Some(buffer) = multibuffer.as_singleton() else {
19789 return;
19790 };
19791
19792 let Some(workspace) = self.workspace() else {
19793 return;
19794 };
19795
19796 let title = multibuffer.title(cx).to_string();
19797
19798 let locations = self
19799 .selections
19800 .all_anchors(cx)
19801 .iter()
19802 .map(|selection| {
19803 (
19804 buffer.clone(),
19805 (selection.start.text_anchor..selection.end.text_anchor)
19806 .to_point(buffer.read(cx)),
19807 )
19808 })
19809 .into_group_map();
19810
19811 cx.spawn_in(window, async move |_, cx| {
19812 workspace.update_in(cx, |workspace, window, cx| {
19813 Self::open_locations_in_multibuffer(
19814 workspace,
19815 locations,
19816 format!("Selections for '{title}'"),
19817 false,
19818 MultibufferSelectionMode::All,
19819 window,
19820 cx,
19821 );
19822 })
19823 })
19824 .detach();
19825 }
19826
19827 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19828 /// last highlight added will be used.
19829 ///
19830 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19831 pub fn highlight_rows<T: 'static>(
19832 &mut self,
19833 range: Range<Anchor>,
19834 color: Hsla,
19835 options: RowHighlightOptions,
19836 cx: &mut Context<Self>,
19837 ) {
19838 let snapshot = self.buffer().read(cx).snapshot(cx);
19839 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19840 let ix = row_highlights.binary_search_by(|highlight| {
19841 Ordering::Equal
19842 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19843 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19844 });
19845
19846 if let Err(mut ix) = ix {
19847 let index = post_inc(&mut self.highlight_order);
19848
19849 // If this range intersects with the preceding highlight, then merge it with
19850 // the preceding highlight. Otherwise insert a new highlight.
19851 let mut merged = false;
19852 if ix > 0 {
19853 let prev_highlight = &mut row_highlights[ix - 1];
19854 if prev_highlight
19855 .range
19856 .end
19857 .cmp(&range.start, &snapshot)
19858 .is_ge()
19859 {
19860 ix -= 1;
19861 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19862 prev_highlight.range.end = range.end;
19863 }
19864 merged = true;
19865 prev_highlight.index = index;
19866 prev_highlight.color = color;
19867 prev_highlight.options = options;
19868 }
19869 }
19870
19871 if !merged {
19872 row_highlights.insert(
19873 ix,
19874 RowHighlight {
19875 range,
19876 index,
19877 color,
19878 options,
19879 type_id: TypeId::of::<T>(),
19880 },
19881 );
19882 }
19883
19884 // If any of the following highlights intersect with this one, merge them.
19885 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19886 let highlight = &row_highlights[ix];
19887 if next_highlight
19888 .range
19889 .start
19890 .cmp(&highlight.range.end, &snapshot)
19891 .is_le()
19892 {
19893 if next_highlight
19894 .range
19895 .end
19896 .cmp(&highlight.range.end, &snapshot)
19897 .is_gt()
19898 {
19899 row_highlights[ix].range.end = next_highlight.range.end;
19900 }
19901 row_highlights.remove(ix + 1);
19902 } else {
19903 break;
19904 }
19905 }
19906 }
19907 }
19908
19909 /// Remove any highlighted row ranges of the given type that intersect the
19910 /// given ranges.
19911 pub fn remove_highlighted_rows<T: 'static>(
19912 &mut self,
19913 ranges_to_remove: Vec<Range<Anchor>>,
19914 cx: &mut Context<Self>,
19915 ) {
19916 let snapshot = self.buffer().read(cx).snapshot(cx);
19917 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19918 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19919 row_highlights.retain(|highlight| {
19920 while let Some(range_to_remove) = ranges_to_remove.peek() {
19921 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19922 Ordering::Less | Ordering::Equal => {
19923 ranges_to_remove.next();
19924 }
19925 Ordering::Greater => {
19926 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19927 Ordering::Less | Ordering::Equal => {
19928 return false;
19929 }
19930 Ordering::Greater => break,
19931 }
19932 }
19933 }
19934 }
19935
19936 true
19937 })
19938 }
19939
19940 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19941 pub fn clear_row_highlights<T: 'static>(&mut self) {
19942 self.highlighted_rows.remove(&TypeId::of::<T>());
19943 }
19944
19945 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19946 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19947 self.highlighted_rows
19948 .get(&TypeId::of::<T>())
19949 .map_or(&[] as &[_], |vec| vec.as_slice())
19950 .iter()
19951 .map(|highlight| (highlight.range.clone(), highlight.color))
19952 }
19953
19954 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19955 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19956 /// Allows to ignore certain kinds of highlights.
19957 pub fn highlighted_display_rows(
19958 &self,
19959 window: &mut Window,
19960 cx: &mut App,
19961 ) -> BTreeMap<DisplayRow, LineHighlight> {
19962 let snapshot = self.snapshot(window, cx);
19963 let mut used_highlight_orders = HashMap::default();
19964 self.highlighted_rows
19965 .iter()
19966 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19967 .fold(
19968 BTreeMap::<DisplayRow, LineHighlight>::new(),
19969 |mut unique_rows, highlight| {
19970 let start = highlight.range.start.to_display_point(&snapshot);
19971 let end = highlight.range.end.to_display_point(&snapshot);
19972 let start_row = start.row().0;
19973 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19974 && end.column() == 0
19975 {
19976 end.row().0.saturating_sub(1)
19977 } else {
19978 end.row().0
19979 };
19980 for row in start_row..=end_row {
19981 let used_index =
19982 used_highlight_orders.entry(row).or_insert(highlight.index);
19983 if highlight.index >= *used_index {
19984 *used_index = highlight.index;
19985 unique_rows.insert(
19986 DisplayRow(row),
19987 LineHighlight {
19988 include_gutter: highlight.options.include_gutter,
19989 border: None,
19990 background: highlight.color.into(),
19991 type_id: Some(highlight.type_id),
19992 },
19993 );
19994 }
19995 }
19996 unique_rows
19997 },
19998 )
19999 }
20000
20001 pub fn highlighted_display_row_for_autoscroll(
20002 &self,
20003 snapshot: &DisplaySnapshot,
20004 ) -> Option<DisplayRow> {
20005 self.highlighted_rows
20006 .values()
20007 .flat_map(|highlighted_rows| highlighted_rows.iter())
20008 .filter_map(|highlight| {
20009 if highlight.options.autoscroll {
20010 Some(highlight.range.start.to_display_point(snapshot).row())
20011 } else {
20012 None
20013 }
20014 })
20015 .min()
20016 }
20017
20018 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20019 self.highlight_background::<SearchWithinRange>(
20020 ranges,
20021 |colors| colors.colors().editor_document_highlight_read_background,
20022 cx,
20023 )
20024 }
20025
20026 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20027 self.breadcrumb_header = Some(new_header);
20028 }
20029
20030 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20031 self.clear_background_highlights::<SearchWithinRange>(cx);
20032 }
20033
20034 pub fn highlight_background<T: 'static>(
20035 &mut self,
20036 ranges: &[Range<Anchor>],
20037 color_fetcher: fn(&Theme) -> Hsla,
20038 cx: &mut Context<Self>,
20039 ) {
20040 self.background_highlights.insert(
20041 HighlightKey::Type(TypeId::of::<T>()),
20042 (color_fetcher, Arc::from(ranges)),
20043 );
20044 self.scrollbar_marker_state.dirty = true;
20045 cx.notify();
20046 }
20047
20048 pub fn highlight_background_key<T: 'static>(
20049 &mut self,
20050 key: usize,
20051 ranges: &[Range<Anchor>],
20052 color_fetcher: fn(&Theme) -> Hsla,
20053 cx: &mut Context<Self>,
20054 ) {
20055 self.background_highlights.insert(
20056 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20057 (color_fetcher, Arc::from(ranges)),
20058 );
20059 self.scrollbar_marker_state.dirty = true;
20060 cx.notify();
20061 }
20062
20063 pub fn clear_background_highlights<T: 'static>(
20064 &mut self,
20065 cx: &mut Context<Self>,
20066 ) -> Option<BackgroundHighlight> {
20067 let text_highlights = self
20068 .background_highlights
20069 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20070 if !text_highlights.1.is_empty() {
20071 self.scrollbar_marker_state.dirty = true;
20072 cx.notify();
20073 }
20074 Some(text_highlights)
20075 }
20076
20077 pub fn highlight_gutter<T: 'static>(
20078 &mut self,
20079 ranges: impl Into<Vec<Range<Anchor>>>,
20080 color_fetcher: fn(&App) -> Hsla,
20081 cx: &mut Context<Self>,
20082 ) {
20083 self.gutter_highlights
20084 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20085 cx.notify();
20086 }
20087
20088 pub fn clear_gutter_highlights<T: 'static>(
20089 &mut self,
20090 cx: &mut Context<Self>,
20091 ) -> Option<GutterHighlight> {
20092 cx.notify();
20093 self.gutter_highlights.remove(&TypeId::of::<T>())
20094 }
20095
20096 pub fn insert_gutter_highlight<T: 'static>(
20097 &mut self,
20098 range: Range<Anchor>,
20099 color_fetcher: fn(&App) -> Hsla,
20100 cx: &mut Context<Self>,
20101 ) {
20102 let snapshot = self.buffer().read(cx).snapshot(cx);
20103 let mut highlights = self
20104 .gutter_highlights
20105 .remove(&TypeId::of::<T>())
20106 .map(|(_, highlights)| highlights)
20107 .unwrap_or_default();
20108 let ix = highlights.binary_search_by(|highlight| {
20109 Ordering::Equal
20110 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20111 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20112 });
20113 if let Err(ix) = ix {
20114 highlights.insert(ix, range);
20115 }
20116 self.gutter_highlights
20117 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20118 }
20119
20120 pub fn remove_gutter_highlights<T: 'static>(
20121 &mut self,
20122 ranges_to_remove: Vec<Range<Anchor>>,
20123 cx: &mut Context<Self>,
20124 ) {
20125 let snapshot = self.buffer().read(cx).snapshot(cx);
20126 let Some((color_fetcher, mut gutter_highlights)) =
20127 self.gutter_highlights.remove(&TypeId::of::<T>())
20128 else {
20129 return;
20130 };
20131 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20132 gutter_highlights.retain(|highlight| {
20133 while let Some(range_to_remove) = ranges_to_remove.peek() {
20134 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20135 Ordering::Less | Ordering::Equal => {
20136 ranges_to_remove.next();
20137 }
20138 Ordering::Greater => {
20139 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20140 Ordering::Less | Ordering::Equal => {
20141 return false;
20142 }
20143 Ordering::Greater => break,
20144 }
20145 }
20146 }
20147 }
20148
20149 true
20150 });
20151 self.gutter_highlights
20152 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20153 }
20154
20155 #[cfg(feature = "test-support")]
20156 pub fn all_text_highlights(
20157 &self,
20158 window: &mut Window,
20159 cx: &mut Context<Self>,
20160 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20161 let snapshot = self.snapshot(window, cx);
20162 self.display_map.update(cx, |display_map, _| {
20163 display_map
20164 .all_text_highlights()
20165 .map(|highlight| {
20166 let (style, ranges) = highlight.as_ref();
20167 (
20168 *style,
20169 ranges
20170 .iter()
20171 .map(|range| range.clone().to_display_points(&snapshot))
20172 .collect(),
20173 )
20174 })
20175 .collect()
20176 })
20177 }
20178
20179 #[cfg(feature = "test-support")]
20180 pub fn all_text_background_highlights(
20181 &self,
20182 window: &mut Window,
20183 cx: &mut Context<Self>,
20184 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20185 let snapshot = self.snapshot(window, cx);
20186 let buffer = &snapshot.buffer_snapshot;
20187 let start = buffer.anchor_before(0);
20188 let end = buffer.anchor_after(buffer.len());
20189 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20190 }
20191
20192 #[cfg(any(test, feature = "test-support"))]
20193 pub fn sorted_background_highlights_in_range(
20194 &self,
20195 search_range: Range<Anchor>,
20196 display_snapshot: &DisplaySnapshot,
20197 theme: &Theme,
20198 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20199 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20200 res.sort_by(|a, b| {
20201 a.0.start
20202 .cmp(&b.0.start)
20203 .then_with(|| a.0.end.cmp(&b.0.end))
20204 .then_with(|| a.1.cmp(&b.1))
20205 });
20206 res
20207 }
20208
20209 #[cfg(feature = "test-support")]
20210 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20211 let snapshot = self.buffer().read(cx).snapshot(cx);
20212
20213 let highlights = self
20214 .background_highlights
20215 .get(&HighlightKey::Type(TypeId::of::<
20216 items::BufferSearchHighlights,
20217 >()));
20218
20219 if let Some((_color, ranges)) = highlights {
20220 ranges
20221 .iter()
20222 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20223 .collect_vec()
20224 } else {
20225 vec![]
20226 }
20227 }
20228
20229 fn document_highlights_for_position<'a>(
20230 &'a self,
20231 position: Anchor,
20232 buffer: &'a MultiBufferSnapshot,
20233 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20234 let read_highlights = self
20235 .background_highlights
20236 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20237 .map(|h| &h.1);
20238 let write_highlights = self
20239 .background_highlights
20240 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20241 .map(|h| &h.1);
20242 let left_position = position.bias_left(buffer);
20243 let right_position = position.bias_right(buffer);
20244 read_highlights
20245 .into_iter()
20246 .chain(write_highlights)
20247 .flat_map(move |ranges| {
20248 let start_ix = match ranges.binary_search_by(|probe| {
20249 let cmp = probe.end.cmp(&left_position, buffer);
20250 if cmp.is_ge() {
20251 Ordering::Greater
20252 } else {
20253 Ordering::Less
20254 }
20255 }) {
20256 Ok(i) | Err(i) => i,
20257 };
20258
20259 ranges[start_ix..]
20260 .iter()
20261 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20262 })
20263 }
20264
20265 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20266 self.background_highlights
20267 .get(&HighlightKey::Type(TypeId::of::<T>()))
20268 .is_some_and(|(_, highlights)| !highlights.is_empty())
20269 }
20270
20271 /// Returns all background highlights for a given range.
20272 ///
20273 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20274 pub fn background_highlights_in_range(
20275 &self,
20276 search_range: Range<Anchor>,
20277 display_snapshot: &DisplaySnapshot,
20278 theme: &Theme,
20279 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20280 let mut results = Vec::new();
20281 for (color_fetcher, ranges) in self.background_highlights.values() {
20282 let color = color_fetcher(theme);
20283 let start_ix = match ranges.binary_search_by(|probe| {
20284 let cmp = probe
20285 .end
20286 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20287 if cmp.is_gt() {
20288 Ordering::Greater
20289 } else {
20290 Ordering::Less
20291 }
20292 }) {
20293 Ok(i) | Err(i) => i,
20294 };
20295 for range in &ranges[start_ix..] {
20296 if range
20297 .start
20298 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20299 .is_ge()
20300 {
20301 break;
20302 }
20303
20304 let start = range.start.to_display_point(display_snapshot);
20305 let end = range.end.to_display_point(display_snapshot);
20306 results.push((start..end, color))
20307 }
20308 }
20309 results
20310 }
20311
20312 pub fn gutter_highlights_in_range(
20313 &self,
20314 search_range: Range<Anchor>,
20315 display_snapshot: &DisplaySnapshot,
20316 cx: &App,
20317 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20318 let mut results = Vec::new();
20319 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20320 let color = color_fetcher(cx);
20321 let start_ix = match ranges.binary_search_by(|probe| {
20322 let cmp = probe
20323 .end
20324 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20325 if cmp.is_gt() {
20326 Ordering::Greater
20327 } else {
20328 Ordering::Less
20329 }
20330 }) {
20331 Ok(i) | Err(i) => i,
20332 };
20333 for range in &ranges[start_ix..] {
20334 if range
20335 .start
20336 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20337 .is_ge()
20338 {
20339 break;
20340 }
20341
20342 let start = range.start.to_display_point(display_snapshot);
20343 let end = range.end.to_display_point(display_snapshot);
20344 results.push((start..end, color))
20345 }
20346 }
20347 results
20348 }
20349
20350 /// Get the text ranges corresponding to the redaction query
20351 pub fn redacted_ranges(
20352 &self,
20353 search_range: Range<Anchor>,
20354 display_snapshot: &DisplaySnapshot,
20355 cx: &App,
20356 ) -> Vec<Range<DisplayPoint>> {
20357 display_snapshot
20358 .buffer_snapshot
20359 .redacted_ranges(search_range, |file| {
20360 if let Some(file) = file {
20361 file.is_private()
20362 && EditorSettings::get(
20363 Some(SettingsLocation {
20364 worktree_id: file.worktree_id(cx),
20365 path: file.path().as_ref(),
20366 }),
20367 cx,
20368 )
20369 .redact_private_values
20370 } else {
20371 false
20372 }
20373 })
20374 .map(|range| {
20375 range.start.to_display_point(display_snapshot)
20376 ..range.end.to_display_point(display_snapshot)
20377 })
20378 .collect()
20379 }
20380
20381 pub fn highlight_text_key<T: 'static>(
20382 &mut self,
20383 key: usize,
20384 ranges: Vec<Range<Anchor>>,
20385 style: HighlightStyle,
20386 cx: &mut Context<Self>,
20387 ) {
20388 self.display_map.update(cx, |map, _| {
20389 map.highlight_text(
20390 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20391 ranges,
20392 style,
20393 );
20394 });
20395 cx.notify();
20396 }
20397
20398 pub fn highlight_text<T: 'static>(
20399 &mut self,
20400 ranges: Vec<Range<Anchor>>,
20401 style: HighlightStyle,
20402 cx: &mut Context<Self>,
20403 ) {
20404 self.display_map.update(cx, |map, _| {
20405 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20406 });
20407 cx.notify();
20408 }
20409
20410 pub(crate) fn highlight_inlays<T: 'static>(
20411 &mut self,
20412 highlights: Vec<InlayHighlight>,
20413 style: HighlightStyle,
20414 cx: &mut Context<Self>,
20415 ) {
20416 self.display_map.update(cx, |map, _| {
20417 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20418 });
20419 cx.notify();
20420 }
20421
20422 pub fn text_highlights<'a, T: 'static>(
20423 &'a self,
20424 cx: &'a App,
20425 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20426 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20427 }
20428
20429 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20430 let cleared = self
20431 .display_map
20432 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20433 if cleared {
20434 cx.notify();
20435 }
20436 }
20437
20438 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20439 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20440 && self.focus_handle.is_focused(window)
20441 }
20442
20443 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20444 self.show_cursor_when_unfocused = is_enabled;
20445 cx.notify();
20446 }
20447
20448 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20449 cx.notify();
20450 }
20451
20452 fn on_debug_session_event(
20453 &mut self,
20454 _session: Entity<Session>,
20455 event: &SessionEvent,
20456 cx: &mut Context<Self>,
20457 ) {
20458 if let SessionEvent::InvalidateInlineValue = event {
20459 self.refresh_inline_values(cx);
20460 }
20461 }
20462
20463 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20464 let Some(project) = self.project.clone() else {
20465 return;
20466 };
20467
20468 if !self.inline_value_cache.enabled {
20469 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20470 self.splice_inlays(&inlays, Vec::new(), cx);
20471 return;
20472 }
20473
20474 let current_execution_position = self
20475 .highlighted_rows
20476 .get(&TypeId::of::<ActiveDebugLine>())
20477 .and_then(|lines| lines.last().map(|line| line.range.end));
20478
20479 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20480 let inline_values = editor
20481 .update(cx, |editor, cx| {
20482 let Some(current_execution_position) = current_execution_position else {
20483 return Some(Task::ready(Ok(Vec::new())));
20484 };
20485
20486 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20487 let snapshot = buffer.snapshot(cx);
20488
20489 let excerpt = snapshot.excerpt_containing(
20490 current_execution_position..current_execution_position,
20491 )?;
20492
20493 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20494 })?;
20495
20496 let range =
20497 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20498
20499 project.inline_values(buffer, range, cx)
20500 })
20501 .ok()
20502 .flatten()?
20503 .await
20504 .context("refreshing debugger inlays")
20505 .log_err()?;
20506
20507 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20508
20509 for (buffer_id, inline_value) in inline_values
20510 .into_iter()
20511 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20512 {
20513 buffer_inline_values
20514 .entry(buffer_id)
20515 .or_default()
20516 .push(inline_value);
20517 }
20518
20519 editor
20520 .update(cx, |editor, cx| {
20521 let snapshot = editor.buffer.read(cx).snapshot(cx);
20522 let mut new_inlays = Vec::default();
20523
20524 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20525 let buffer_id = buffer_snapshot.remote_id();
20526 buffer_inline_values
20527 .get(&buffer_id)
20528 .into_iter()
20529 .flatten()
20530 .for_each(|hint| {
20531 let inlay = Inlay::debugger(
20532 post_inc(&mut editor.next_inlay_id),
20533 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20534 hint.text(),
20535 );
20536 if !inlay.text.chars().contains(&'\n') {
20537 new_inlays.push(inlay);
20538 }
20539 });
20540 }
20541
20542 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20543 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20544
20545 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20546 })
20547 .ok()?;
20548 Some(())
20549 });
20550 }
20551
20552 fn on_buffer_event(
20553 &mut self,
20554 multibuffer: &Entity<MultiBuffer>,
20555 event: &multi_buffer::Event,
20556 window: &mut Window,
20557 cx: &mut Context<Self>,
20558 ) {
20559 match event {
20560 multi_buffer::Event::Edited {
20561 singleton_buffer_edited,
20562 edited_buffer,
20563 } => {
20564 self.scrollbar_marker_state.dirty = true;
20565 self.active_indent_guides_state.dirty = true;
20566 self.refresh_active_diagnostics(cx);
20567 self.refresh_code_actions(window, cx);
20568 self.refresh_selected_text_highlights(true, window, cx);
20569 self.refresh_single_line_folds(window, cx);
20570 refresh_matching_bracket_highlights(self, window, cx);
20571 if self.has_active_edit_prediction() {
20572 self.update_visible_edit_prediction(window, cx);
20573 }
20574 if let Some(project) = self.project.as_ref()
20575 && let Some(edited_buffer) = edited_buffer
20576 {
20577 project.update(cx, |project, cx| {
20578 self.registered_buffers
20579 .entry(edited_buffer.read(cx).remote_id())
20580 .or_insert_with(|| {
20581 project.register_buffer_with_language_servers(edited_buffer, cx)
20582 });
20583 });
20584 }
20585 cx.emit(EditorEvent::BufferEdited);
20586 cx.emit(SearchEvent::MatchesInvalidated);
20587
20588 if let Some(buffer) = edited_buffer {
20589 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20590 }
20591
20592 if *singleton_buffer_edited {
20593 if let Some(buffer) = edited_buffer
20594 && buffer.read(cx).file().is_none()
20595 {
20596 cx.emit(EditorEvent::TitleChanged);
20597 }
20598 if let Some(project) = &self.project {
20599 #[allow(clippy::mutable_key_type)]
20600 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20601 multibuffer
20602 .all_buffers()
20603 .into_iter()
20604 .filter_map(|buffer| {
20605 buffer.update(cx, |buffer, cx| {
20606 let language = buffer.language()?;
20607 let should_discard = project.update(cx, |project, cx| {
20608 project.is_local()
20609 && !project.has_language_servers_for(buffer, cx)
20610 });
20611 should_discard.not().then_some(language.clone())
20612 })
20613 })
20614 .collect::<HashSet<_>>()
20615 });
20616 if !languages_affected.is_empty() {
20617 self.refresh_inlay_hints(
20618 InlayHintRefreshReason::BufferEdited(languages_affected),
20619 cx,
20620 );
20621 }
20622 }
20623 }
20624
20625 let Some(project) = &self.project else { return };
20626 let (telemetry, is_via_ssh) = {
20627 let project = project.read(cx);
20628 let telemetry = project.client().telemetry().clone();
20629 let is_via_ssh = project.is_via_remote_server();
20630 (telemetry, is_via_ssh)
20631 };
20632 refresh_linked_ranges(self, window, cx);
20633 telemetry.log_edit_event("editor", is_via_ssh);
20634 }
20635 multi_buffer::Event::ExcerptsAdded {
20636 buffer,
20637 predecessor,
20638 excerpts,
20639 } => {
20640 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20641 let buffer_id = buffer.read(cx).remote_id();
20642 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20643 && let Some(project) = &self.project
20644 {
20645 update_uncommitted_diff_for_buffer(
20646 cx.entity(),
20647 project,
20648 [buffer.clone()],
20649 self.buffer.clone(),
20650 cx,
20651 )
20652 .detach();
20653 }
20654 if self.active_diagnostics != ActiveDiagnostic::All {
20655 self.update_lsp_data(false, Some(buffer_id), window, cx);
20656 }
20657 cx.emit(EditorEvent::ExcerptsAdded {
20658 buffer: buffer.clone(),
20659 predecessor: *predecessor,
20660 excerpts: excerpts.clone(),
20661 });
20662 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20663 }
20664 multi_buffer::Event::ExcerptsRemoved {
20665 ids,
20666 removed_buffer_ids,
20667 } => {
20668 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20669 let buffer = self.buffer.read(cx);
20670 self.registered_buffers
20671 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20672 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20673 cx.emit(EditorEvent::ExcerptsRemoved {
20674 ids: ids.clone(),
20675 removed_buffer_ids: removed_buffer_ids.clone(),
20676 });
20677 }
20678 multi_buffer::Event::ExcerptsEdited {
20679 excerpt_ids,
20680 buffer_ids,
20681 } => {
20682 self.display_map.update(cx, |map, cx| {
20683 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20684 });
20685 cx.emit(EditorEvent::ExcerptsEdited {
20686 ids: excerpt_ids.clone(),
20687 });
20688 }
20689 multi_buffer::Event::ExcerptsExpanded { ids } => {
20690 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20691 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20692 }
20693 multi_buffer::Event::Reparsed(buffer_id) => {
20694 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20695 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20696
20697 cx.emit(EditorEvent::Reparsed(*buffer_id));
20698 }
20699 multi_buffer::Event::DiffHunksToggled => {
20700 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20701 }
20702 multi_buffer::Event::LanguageChanged(buffer_id) => {
20703 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20704 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20705 cx.emit(EditorEvent::Reparsed(*buffer_id));
20706 cx.notify();
20707 }
20708 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20709 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20710 multi_buffer::Event::FileHandleChanged
20711 | multi_buffer::Event::Reloaded
20712 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20713 multi_buffer::Event::DiagnosticsUpdated => {
20714 self.update_diagnostics_state(window, cx);
20715 }
20716 _ => {}
20717 };
20718 }
20719
20720 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20721 if !self.diagnostics_enabled() {
20722 return;
20723 }
20724 self.refresh_active_diagnostics(cx);
20725 self.refresh_inline_diagnostics(true, window, cx);
20726 self.scrollbar_marker_state.dirty = true;
20727 cx.notify();
20728 }
20729
20730 pub fn start_temporary_diff_override(&mut self) {
20731 self.load_diff_task.take();
20732 self.temporary_diff_override = true;
20733 }
20734
20735 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20736 self.temporary_diff_override = false;
20737 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20738 self.buffer.update(cx, |buffer, cx| {
20739 buffer.set_all_diff_hunks_collapsed(cx);
20740 });
20741
20742 if let Some(project) = self.project.clone() {
20743 self.load_diff_task = Some(
20744 update_uncommitted_diff_for_buffer(
20745 cx.entity(),
20746 &project,
20747 self.buffer.read(cx).all_buffers(),
20748 self.buffer.clone(),
20749 cx,
20750 )
20751 .shared(),
20752 );
20753 }
20754 }
20755
20756 fn on_display_map_changed(
20757 &mut self,
20758 _: Entity<DisplayMap>,
20759 _: &mut Window,
20760 cx: &mut Context<Self>,
20761 ) {
20762 cx.notify();
20763 }
20764
20765 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20766 if self.diagnostics_enabled() {
20767 let new_severity = EditorSettings::get_global(cx)
20768 .diagnostics_max_severity
20769 .unwrap_or(DiagnosticSeverity::Hint);
20770 self.set_max_diagnostics_severity(new_severity, cx);
20771 }
20772 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20773 self.update_edit_prediction_settings(cx);
20774 self.refresh_edit_prediction(true, false, window, cx);
20775 self.refresh_inline_values(cx);
20776 self.refresh_inlay_hints(
20777 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20778 self.selections.newest_anchor().head(),
20779 &self.buffer.read(cx).snapshot(cx),
20780 cx,
20781 )),
20782 cx,
20783 );
20784
20785 let old_cursor_shape = self.cursor_shape;
20786 let old_show_breadcrumbs = self.show_breadcrumbs;
20787
20788 {
20789 let editor_settings = EditorSettings::get_global(cx);
20790 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20791 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20792 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20793 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20794 }
20795
20796 if old_cursor_shape != self.cursor_shape {
20797 cx.emit(EditorEvent::CursorShapeChanged);
20798 }
20799
20800 if old_show_breadcrumbs != self.show_breadcrumbs {
20801 cx.emit(EditorEvent::BreadcrumbsChanged);
20802 }
20803
20804 let project_settings = ProjectSettings::get_global(cx);
20805 self.serialize_dirty_buffers =
20806 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20807
20808 if self.mode.is_full() {
20809 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20810 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20811 if self.show_inline_diagnostics != show_inline_diagnostics {
20812 self.show_inline_diagnostics = show_inline_diagnostics;
20813 self.refresh_inline_diagnostics(false, window, cx);
20814 }
20815
20816 if self.git_blame_inline_enabled != inline_blame_enabled {
20817 self.toggle_git_blame_inline_internal(false, window, cx);
20818 }
20819
20820 let minimap_settings = EditorSettings::get_global(cx).minimap;
20821 if self.minimap_visibility != MinimapVisibility::Disabled {
20822 if self.minimap_visibility.settings_visibility()
20823 != minimap_settings.minimap_enabled()
20824 {
20825 self.set_minimap_visibility(
20826 MinimapVisibility::for_mode(self.mode(), cx),
20827 window,
20828 cx,
20829 );
20830 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20831 minimap_entity.update(cx, |minimap_editor, cx| {
20832 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20833 })
20834 }
20835 }
20836 }
20837
20838 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20839 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20840 }) {
20841 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20842 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20843 }
20844 self.refresh_colors(false, None, window, cx);
20845 }
20846
20847 cx.notify();
20848 }
20849
20850 pub fn set_searchable(&mut self, searchable: bool) {
20851 self.searchable = searchable;
20852 }
20853
20854 pub fn searchable(&self) -> bool {
20855 self.searchable
20856 }
20857
20858 fn open_proposed_changes_editor(
20859 &mut self,
20860 _: &OpenProposedChangesEditor,
20861 window: &mut Window,
20862 cx: &mut Context<Self>,
20863 ) {
20864 let Some(workspace) = self.workspace() else {
20865 cx.propagate();
20866 return;
20867 };
20868
20869 let selections = self.selections.all::<usize>(cx);
20870 let multi_buffer = self.buffer.read(cx);
20871 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20872 let mut new_selections_by_buffer = HashMap::default();
20873 for selection in selections {
20874 for (buffer, range, _) in
20875 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20876 {
20877 let mut range = range.to_point(buffer);
20878 range.start.column = 0;
20879 range.end.column = buffer.line_len(range.end.row);
20880 new_selections_by_buffer
20881 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20882 .or_insert(Vec::new())
20883 .push(range)
20884 }
20885 }
20886
20887 let proposed_changes_buffers = new_selections_by_buffer
20888 .into_iter()
20889 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20890 .collect::<Vec<_>>();
20891 let proposed_changes_editor = cx.new(|cx| {
20892 ProposedChangesEditor::new(
20893 "Proposed changes",
20894 proposed_changes_buffers,
20895 self.project.clone(),
20896 window,
20897 cx,
20898 )
20899 });
20900
20901 window.defer(cx, move |window, cx| {
20902 workspace.update(cx, |workspace, cx| {
20903 workspace.active_pane().update(cx, |pane, cx| {
20904 pane.add_item(
20905 Box::new(proposed_changes_editor),
20906 true,
20907 true,
20908 None,
20909 window,
20910 cx,
20911 );
20912 });
20913 });
20914 });
20915 }
20916
20917 pub fn open_excerpts_in_split(
20918 &mut self,
20919 _: &OpenExcerptsSplit,
20920 window: &mut Window,
20921 cx: &mut Context<Self>,
20922 ) {
20923 self.open_excerpts_common(None, true, window, cx)
20924 }
20925
20926 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20927 self.open_excerpts_common(None, false, window, cx)
20928 }
20929
20930 fn open_excerpts_common(
20931 &mut self,
20932 jump_data: Option<JumpData>,
20933 split: bool,
20934 window: &mut Window,
20935 cx: &mut Context<Self>,
20936 ) {
20937 let Some(workspace) = self.workspace() else {
20938 cx.propagate();
20939 return;
20940 };
20941
20942 if self.buffer.read(cx).is_singleton() {
20943 cx.propagate();
20944 return;
20945 }
20946
20947 let mut new_selections_by_buffer = HashMap::default();
20948 match &jump_data {
20949 Some(JumpData::MultiBufferPoint {
20950 excerpt_id,
20951 position,
20952 anchor,
20953 line_offset_from_top,
20954 }) => {
20955 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20956 if let Some(buffer) = multi_buffer_snapshot
20957 .buffer_id_for_excerpt(*excerpt_id)
20958 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20959 {
20960 let buffer_snapshot = buffer.read(cx).snapshot();
20961 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20962 language::ToPoint::to_point(anchor, &buffer_snapshot)
20963 } else {
20964 buffer_snapshot.clip_point(*position, Bias::Left)
20965 };
20966 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20967 new_selections_by_buffer.insert(
20968 buffer,
20969 (
20970 vec![jump_to_offset..jump_to_offset],
20971 Some(*line_offset_from_top),
20972 ),
20973 );
20974 }
20975 }
20976 Some(JumpData::MultiBufferRow {
20977 row,
20978 line_offset_from_top,
20979 }) => {
20980 let point = MultiBufferPoint::new(row.0, 0);
20981 if let Some((buffer, buffer_point, _)) =
20982 self.buffer.read(cx).point_to_buffer_point(point, cx)
20983 {
20984 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20985 new_selections_by_buffer
20986 .entry(buffer)
20987 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20988 .0
20989 .push(buffer_offset..buffer_offset)
20990 }
20991 }
20992 None => {
20993 let selections = self.selections.all::<usize>(cx);
20994 let multi_buffer = self.buffer.read(cx);
20995 for selection in selections {
20996 for (snapshot, range, _, anchor) in multi_buffer
20997 .snapshot(cx)
20998 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20999 {
21000 if let Some(anchor) = anchor {
21001 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21002 else {
21003 continue;
21004 };
21005 let offset = text::ToOffset::to_offset(
21006 &anchor.text_anchor,
21007 &buffer_handle.read(cx).snapshot(),
21008 );
21009 let range = offset..offset;
21010 new_selections_by_buffer
21011 .entry(buffer_handle)
21012 .or_insert((Vec::new(), None))
21013 .0
21014 .push(range)
21015 } else {
21016 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21017 else {
21018 continue;
21019 };
21020 new_selections_by_buffer
21021 .entry(buffer_handle)
21022 .or_insert((Vec::new(), None))
21023 .0
21024 .push(range)
21025 }
21026 }
21027 }
21028 }
21029 }
21030
21031 new_selections_by_buffer
21032 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21033
21034 if new_selections_by_buffer.is_empty() {
21035 return;
21036 }
21037
21038 // We defer the pane interaction because we ourselves are a workspace item
21039 // and activating a new item causes the pane to call a method on us reentrantly,
21040 // which panics if we're on the stack.
21041 window.defer(cx, move |window, cx| {
21042 workspace.update(cx, |workspace, cx| {
21043 let pane = if split {
21044 workspace.adjacent_pane(window, cx)
21045 } else {
21046 workspace.active_pane().clone()
21047 };
21048
21049 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21050 let editor = buffer
21051 .read(cx)
21052 .file()
21053 .is_none()
21054 .then(|| {
21055 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21056 // so `workspace.open_project_item` will never find them, always opening a new editor.
21057 // Instead, we try to activate the existing editor in the pane first.
21058 let (editor, pane_item_index) =
21059 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21060 let editor = item.downcast::<Editor>()?;
21061 let singleton_buffer =
21062 editor.read(cx).buffer().read(cx).as_singleton()?;
21063 if singleton_buffer == buffer {
21064 Some((editor, i))
21065 } else {
21066 None
21067 }
21068 })?;
21069 pane.update(cx, |pane, cx| {
21070 pane.activate_item(pane_item_index, true, true, window, cx)
21071 });
21072 Some(editor)
21073 })
21074 .flatten()
21075 .unwrap_or_else(|| {
21076 workspace.open_project_item::<Self>(
21077 pane.clone(),
21078 buffer,
21079 true,
21080 true,
21081 window,
21082 cx,
21083 )
21084 });
21085
21086 editor.update(cx, |editor, cx| {
21087 let autoscroll = match scroll_offset {
21088 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21089 None => Autoscroll::newest(),
21090 };
21091 let nav_history = editor.nav_history.take();
21092 editor.change_selections(
21093 SelectionEffects::scroll(autoscroll),
21094 window,
21095 cx,
21096 |s| {
21097 s.select_ranges(ranges);
21098 },
21099 );
21100 editor.nav_history = nav_history;
21101 });
21102 }
21103 })
21104 });
21105 }
21106
21107 // For now, don't allow opening excerpts in buffers that aren't backed by
21108 // regular project files.
21109 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21110 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21111 }
21112
21113 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21114 let snapshot = self.buffer.read(cx).read(cx);
21115 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21116 Some(
21117 ranges
21118 .iter()
21119 .map(move |range| {
21120 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21121 })
21122 .collect(),
21123 )
21124 }
21125
21126 fn selection_replacement_ranges(
21127 &self,
21128 range: Range<OffsetUtf16>,
21129 cx: &mut App,
21130 ) -> Vec<Range<OffsetUtf16>> {
21131 let selections = self.selections.all::<OffsetUtf16>(cx);
21132 let newest_selection = selections
21133 .iter()
21134 .max_by_key(|selection| selection.id)
21135 .unwrap();
21136 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21137 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21138 let snapshot = self.buffer.read(cx).read(cx);
21139 selections
21140 .into_iter()
21141 .map(|mut selection| {
21142 selection.start.0 =
21143 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21144 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21145 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21146 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21147 })
21148 .collect()
21149 }
21150
21151 fn report_editor_event(
21152 &self,
21153 reported_event: ReportEditorEvent,
21154 file_extension: Option<String>,
21155 cx: &App,
21156 ) {
21157 if cfg!(any(test, feature = "test-support")) {
21158 return;
21159 }
21160
21161 let Some(project) = &self.project else { return };
21162
21163 // If None, we are in a file without an extension
21164 let file = self
21165 .buffer
21166 .read(cx)
21167 .as_singleton()
21168 .and_then(|b| b.read(cx).file());
21169 let file_extension = file_extension.or(file
21170 .as_ref()
21171 .and_then(|file| Path::new(file.file_name(cx)).extension())
21172 .and_then(|e| e.to_str())
21173 .map(|a| a.to_string()));
21174
21175 let vim_mode = vim_enabled(cx);
21176
21177 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21178 let copilot_enabled = edit_predictions_provider
21179 == language::language_settings::EditPredictionProvider::Copilot;
21180 let copilot_enabled_for_language = self
21181 .buffer
21182 .read(cx)
21183 .language_settings(cx)
21184 .show_edit_predictions;
21185
21186 let project = project.read(cx);
21187 let event_type = reported_event.event_type();
21188
21189 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21190 telemetry::event!(
21191 event_type,
21192 type = if auto_saved {"autosave"} else {"manual"},
21193 file_extension,
21194 vim_mode,
21195 copilot_enabled,
21196 copilot_enabled_for_language,
21197 edit_predictions_provider,
21198 is_via_ssh = project.is_via_remote_server(),
21199 );
21200 } else {
21201 telemetry::event!(
21202 event_type,
21203 file_extension,
21204 vim_mode,
21205 copilot_enabled,
21206 copilot_enabled_for_language,
21207 edit_predictions_provider,
21208 is_via_ssh = project.is_via_remote_server(),
21209 );
21210 };
21211 }
21212
21213 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21214 /// with each line being an array of {text, highlight} objects.
21215 fn copy_highlight_json(
21216 &mut self,
21217 _: &CopyHighlightJson,
21218 window: &mut Window,
21219 cx: &mut Context<Self>,
21220 ) {
21221 #[derive(Serialize)]
21222 struct Chunk<'a> {
21223 text: String,
21224 highlight: Option<&'a str>,
21225 }
21226
21227 let snapshot = self.buffer.read(cx).snapshot(cx);
21228 let range = self
21229 .selected_text_range(false, window, cx)
21230 .and_then(|selection| {
21231 if selection.range.is_empty() {
21232 None
21233 } else {
21234 Some(selection.range)
21235 }
21236 })
21237 .unwrap_or_else(|| 0..snapshot.len());
21238
21239 let chunks = snapshot.chunks(range, true);
21240 let mut lines = Vec::new();
21241 let mut line: VecDeque<Chunk> = VecDeque::new();
21242
21243 let Some(style) = self.style.as_ref() else {
21244 return;
21245 };
21246
21247 for chunk in chunks {
21248 let highlight = chunk
21249 .syntax_highlight_id
21250 .and_then(|id| id.name(&style.syntax));
21251 let mut chunk_lines = chunk.text.split('\n').peekable();
21252 while let Some(text) = chunk_lines.next() {
21253 let mut merged_with_last_token = false;
21254 if let Some(last_token) = line.back_mut()
21255 && last_token.highlight == highlight
21256 {
21257 last_token.text.push_str(text);
21258 merged_with_last_token = true;
21259 }
21260
21261 if !merged_with_last_token {
21262 line.push_back(Chunk {
21263 text: text.into(),
21264 highlight,
21265 });
21266 }
21267
21268 if chunk_lines.peek().is_some() {
21269 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21270 line.pop_front();
21271 }
21272 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21273 line.pop_back();
21274 }
21275
21276 lines.push(mem::take(&mut line));
21277 }
21278 }
21279 }
21280
21281 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21282 return;
21283 };
21284 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21285 }
21286
21287 pub fn open_context_menu(
21288 &mut self,
21289 _: &OpenContextMenu,
21290 window: &mut Window,
21291 cx: &mut Context<Self>,
21292 ) {
21293 self.request_autoscroll(Autoscroll::newest(), cx);
21294 let position = self.selections.newest_display(cx).start;
21295 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21296 }
21297
21298 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21299 &self.inlay_hint_cache
21300 }
21301
21302 pub fn replay_insert_event(
21303 &mut self,
21304 text: &str,
21305 relative_utf16_range: Option<Range<isize>>,
21306 window: &mut Window,
21307 cx: &mut Context<Self>,
21308 ) {
21309 if !self.input_enabled {
21310 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21311 return;
21312 }
21313 if let Some(relative_utf16_range) = relative_utf16_range {
21314 let selections = self.selections.all::<OffsetUtf16>(cx);
21315 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21316 let new_ranges = selections.into_iter().map(|range| {
21317 let start = OffsetUtf16(
21318 range
21319 .head()
21320 .0
21321 .saturating_add_signed(relative_utf16_range.start),
21322 );
21323 let end = OffsetUtf16(
21324 range
21325 .head()
21326 .0
21327 .saturating_add_signed(relative_utf16_range.end),
21328 );
21329 start..end
21330 });
21331 s.select_ranges(new_ranges);
21332 });
21333 }
21334
21335 self.handle_input(text, window, cx);
21336 }
21337
21338 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21339 let Some(provider) = self.semantics_provider.as_ref() else {
21340 return false;
21341 };
21342
21343 let mut supports = false;
21344 self.buffer().update(cx, |this, cx| {
21345 this.for_each_buffer(|buffer| {
21346 supports |= provider.supports_inlay_hints(buffer, cx);
21347 });
21348 });
21349
21350 supports
21351 }
21352
21353 pub fn is_focused(&self, window: &Window) -> bool {
21354 self.focus_handle.is_focused(window)
21355 }
21356
21357 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21358 cx.emit(EditorEvent::Focused);
21359
21360 if let Some(descendant) = self
21361 .last_focused_descendant
21362 .take()
21363 .and_then(|descendant| descendant.upgrade())
21364 {
21365 window.focus(&descendant);
21366 } else {
21367 if let Some(blame) = self.blame.as_ref() {
21368 blame.update(cx, GitBlame::focus)
21369 }
21370
21371 self.blink_manager.update(cx, BlinkManager::enable);
21372 self.show_cursor_names(window, cx);
21373 self.buffer.update(cx, |buffer, cx| {
21374 buffer.finalize_last_transaction(cx);
21375 if self.leader_id.is_none() {
21376 buffer.set_active_selections(
21377 &self.selections.disjoint_anchors_arc(),
21378 self.selections.line_mode,
21379 self.cursor_shape,
21380 cx,
21381 );
21382 }
21383 });
21384 }
21385 }
21386
21387 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21388 cx.emit(EditorEvent::FocusedIn)
21389 }
21390
21391 fn handle_focus_out(
21392 &mut self,
21393 event: FocusOutEvent,
21394 _window: &mut Window,
21395 cx: &mut Context<Self>,
21396 ) {
21397 if event.blurred != self.focus_handle {
21398 self.last_focused_descendant = Some(event.blurred);
21399 }
21400 self.selection_drag_state = SelectionDragState::None;
21401 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21402 }
21403
21404 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21405 self.blink_manager.update(cx, BlinkManager::disable);
21406 self.buffer
21407 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21408
21409 if let Some(blame) = self.blame.as_ref() {
21410 blame.update(cx, GitBlame::blur)
21411 }
21412 if !self.hover_state.focused(window, cx) {
21413 hide_hover(self, cx);
21414 }
21415 if !self
21416 .context_menu
21417 .borrow()
21418 .as_ref()
21419 .is_some_and(|context_menu| context_menu.focused(window, cx))
21420 {
21421 self.hide_context_menu(window, cx);
21422 }
21423 self.discard_edit_prediction(false, cx);
21424 cx.emit(EditorEvent::Blurred);
21425 cx.notify();
21426 }
21427
21428 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21429 let mut pending: String = window
21430 .pending_input_keystrokes()
21431 .into_iter()
21432 .flatten()
21433 .filter_map(|keystroke| {
21434 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21435 keystroke.key_char.clone()
21436 } else {
21437 None
21438 }
21439 })
21440 .collect();
21441
21442 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21443 pending = "".to_string();
21444 }
21445
21446 let existing_pending = self
21447 .text_highlights::<PendingInput>(cx)
21448 .map(|(_, ranges)| ranges.to_vec());
21449 if existing_pending.is_none() && pending.is_empty() {
21450 return;
21451 }
21452 let transaction =
21453 self.transact(window, cx, |this, window, cx| {
21454 let selections = this.selections.all::<usize>(cx);
21455 let edits = selections
21456 .iter()
21457 .map(|selection| (selection.end..selection.end, pending.clone()));
21458 this.edit(edits, cx);
21459 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21460 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21461 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21462 }));
21463 });
21464 if let Some(existing_ranges) = existing_pending {
21465 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21466 this.edit(edits, cx);
21467 }
21468 });
21469
21470 let snapshot = self.snapshot(window, cx);
21471 let ranges = self
21472 .selections
21473 .all::<usize>(cx)
21474 .into_iter()
21475 .map(|selection| {
21476 snapshot.buffer_snapshot.anchor_after(selection.end)
21477 ..snapshot
21478 .buffer_snapshot
21479 .anchor_before(selection.end + pending.len())
21480 })
21481 .collect();
21482
21483 if pending.is_empty() {
21484 self.clear_highlights::<PendingInput>(cx);
21485 } else {
21486 self.highlight_text::<PendingInput>(
21487 ranges,
21488 HighlightStyle {
21489 underline: Some(UnderlineStyle {
21490 thickness: px(1.),
21491 color: None,
21492 wavy: false,
21493 }),
21494 ..Default::default()
21495 },
21496 cx,
21497 );
21498 }
21499
21500 self.ime_transaction = self.ime_transaction.or(transaction);
21501 if let Some(transaction) = self.ime_transaction {
21502 self.buffer.update(cx, |buffer, cx| {
21503 buffer.group_until_transaction(transaction, cx);
21504 });
21505 }
21506
21507 if self.text_highlights::<PendingInput>(cx).is_none() {
21508 self.ime_transaction.take();
21509 }
21510 }
21511
21512 pub fn register_action_renderer(
21513 &mut self,
21514 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21515 ) -> Subscription {
21516 let id = self.next_editor_action_id.post_inc();
21517 self.editor_actions
21518 .borrow_mut()
21519 .insert(id, Box::new(listener));
21520
21521 let editor_actions = self.editor_actions.clone();
21522 Subscription::new(move || {
21523 editor_actions.borrow_mut().remove(&id);
21524 })
21525 }
21526
21527 pub fn register_action<A: Action>(
21528 &mut self,
21529 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21530 ) -> Subscription {
21531 let id = self.next_editor_action_id.post_inc();
21532 let listener = Arc::new(listener);
21533 self.editor_actions.borrow_mut().insert(
21534 id,
21535 Box::new(move |_, window, _| {
21536 let listener = listener.clone();
21537 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21538 let action = action.downcast_ref().unwrap();
21539 if phase == DispatchPhase::Bubble {
21540 listener(action, window, cx)
21541 }
21542 })
21543 }),
21544 );
21545
21546 let editor_actions = self.editor_actions.clone();
21547 Subscription::new(move || {
21548 editor_actions.borrow_mut().remove(&id);
21549 })
21550 }
21551
21552 pub fn file_header_size(&self) -> u32 {
21553 FILE_HEADER_HEIGHT
21554 }
21555
21556 pub fn restore(
21557 &mut self,
21558 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21559 window: &mut Window,
21560 cx: &mut Context<Self>,
21561 ) {
21562 let workspace = self.workspace();
21563 let project = self.project();
21564 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21565 let mut tasks = Vec::new();
21566 for (buffer_id, changes) in revert_changes {
21567 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21568 buffer.update(cx, |buffer, cx| {
21569 buffer.edit(
21570 changes
21571 .into_iter()
21572 .map(|(range, text)| (range, text.to_string())),
21573 None,
21574 cx,
21575 );
21576 });
21577
21578 if let Some(project) =
21579 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21580 {
21581 project.update(cx, |project, cx| {
21582 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21583 })
21584 }
21585 }
21586 }
21587 tasks
21588 });
21589 cx.spawn_in(window, async move |_, cx| {
21590 for (buffer, task) in save_tasks {
21591 let result = task.await;
21592 if result.is_err() {
21593 let Some(path) = buffer
21594 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21595 .ok()
21596 else {
21597 continue;
21598 };
21599 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21600 let Some(task) = cx
21601 .update_window_entity(workspace, |workspace, window, cx| {
21602 workspace
21603 .open_path_preview(path, None, false, false, false, window, cx)
21604 })
21605 .ok()
21606 else {
21607 continue;
21608 };
21609 task.await.log_err();
21610 }
21611 }
21612 }
21613 })
21614 .detach();
21615 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21616 selections.refresh()
21617 });
21618 }
21619
21620 pub fn to_pixel_point(
21621 &self,
21622 source: multi_buffer::Anchor,
21623 editor_snapshot: &EditorSnapshot,
21624 window: &mut Window,
21625 ) -> Option<gpui::Point<Pixels>> {
21626 let source_point = source.to_display_point(editor_snapshot);
21627 self.display_to_pixel_point(source_point, editor_snapshot, window)
21628 }
21629
21630 pub fn display_to_pixel_point(
21631 &self,
21632 source: DisplayPoint,
21633 editor_snapshot: &EditorSnapshot,
21634 window: &mut Window,
21635 ) -> Option<gpui::Point<Pixels>> {
21636 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21637 let text_layout_details = self.text_layout_details(window);
21638 let scroll_top = text_layout_details
21639 .scroll_anchor
21640 .scroll_position(editor_snapshot)
21641 .y;
21642
21643 if source.row().as_f32() < scroll_top.floor() {
21644 return None;
21645 }
21646 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21647 let source_y = line_height * (source.row().as_f32() - scroll_top);
21648 Some(gpui::Point::new(source_x, source_y))
21649 }
21650
21651 pub fn has_visible_completions_menu(&self) -> bool {
21652 !self.edit_prediction_preview_is_active()
21653 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21654 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21655 })
21656 }
21657
21658 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21659 if self.mode.is_minimap() {
21660 return;
21661 }
21662 self.addons
21663 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21664 }
21665
21666 pub fn unregister_addon<T: Addon>(&mut self) {
21667 self.addons.remove(&std::any::TypeId::of::<T>());
21668 }
21669
21670 pub fn addon<T: Addon>(&self) -> Option<&T> {
21671 let type_id = std::any::TypeId::of::<T>();
21672 self.addons
21673 .get(&type_id)
21674 .and_then(|item| item.to_any().downcast_ref::<T>())
21675 }
21676
21677 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21678 let type_id = std::any::TypeId::of::<T>();
21679 self.addons
21680 .get_mut(&type_id)
21681 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21682 }
21683
21684 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21685 let text_layout_details = self.text_layout_details(window);
21686 let style = &text_layout_details.editor_style;
21687 let font_id = window.text_system().resolve_font(&style.text.font());
21688 let font_size = style.text.font_size.to_pixels(window.rem_size());
21689 let line_height = style.text.line_height_in_pixels(window.rem_size());
21690 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21691 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21692
21693 CharacterDimensions {
21694 em_width,
21695 em_advance,
21696 line_height,
21697 }
21698 }
21699
21700 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21701 self.load_diff_task.clone()
21702 }
21703
21704 fn read_metadata_from_db(
21705 &mut self,
21706 item_id: u64,
21707 workspace_id: WorkspaceId,
21708 window: &mut Window,
21709 cx: &mut Context<Editor>,
21710 ) {
21711 if self.is_singleton(cx)
21712 && !self.mode.is_minimap()
21713 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21714 {
21715 let buffer_snapshot = OnceCell::new();
21716
21717 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21718 && !folds.is_empty()
21719 {
21720 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21721 self.fold_ranges(
21722 folds
21723 .into_iter()
21724 .map(|(start, end)| {
21725 snapshot.clip_offset(start, Bias::Left)
21726 ..snapshot.clip_offset(end, Bias::Right)
21727 })
21728 .collect(),
21729 false,
21730 window,
21731 cx,
21732 );
21733 }
21734
21735 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21736 && !selections.is_empty()
21737 {
21738 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21739 // skip adding the initial selection to selection history
21740 self.selection_history.mode = SelectionHistoryMode::Skipping;
21741 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21742 s.select_ranges(selections.into_iter().map(|(start, end)| {
21743 snapshot.clip_offset(start, Bias::Left)
21744 ..snapshot.clip_offset(end, Bias::Right)
21745 }));
21746 });
21747 self.selection_history.mode = SelectionHistoryMode::Normal;
21748 };
21749 }
21750
21751 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21752 }
21753
21754 fn update_lsp_data(
21755 &mut self,
21756 ignore_cache: bool,
21757 for_buffer: Option<BufferId>,
21758 window: &mut Window,
21759 cx: &mut Context<'_, Self>,
21760 ) {
21761 self.pull_diagnostics(for_buffer, window, cx);
21762 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21763 }
21764}
21765
21766fn edit_for_markdown_paste<'a>(
21767 buffer: &MultiBufferSnapshot,
21768 range: Range<usize>,
21769 to_insert: &'a str,
21770 url: Option<url::Url>,
21771) -> (Range<usize>, Cow<'a, str>) {
21772 if url.is_none() {
21773 return (range, Cow::Borrowed(to_insert));
21774 };
21775
21776 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21777
21778 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21779 Cow::Borrowed(to_insert)
21780 } else {
21781 Cow::Owned(format!("[{old_text}]({to_insert})"))
21782 };
21783 (range, new_text)
21784}
21785
21786fn vim_enabled(cx: &App) -> bool {
21787 vim_mode_setting::VimModeSetting::try_get(cx)
21788 .map(|vim_mode| vim_mode.0)
21789 .unwrap_or(false)
21790}
21791
21792fn process_completion_for_edit(
21793 completion: &Completion,
21794 intent: CompletionIntent,
21795 buffer: &Entity<Buffer>,
21796 cursor_position: &text::Anchor,
21797 cx: &mut Context<Editor>,
21798) -> CompletionEdit {
21799 let buffer = buffer.read(cx);
21800 let buffer_snapshot = buffer.snapshot();
21801 let (snippet, new_text) = if completion.is_snippet() {
21802 // Workaround for typescript language server issues so that methods don't expand within
21803 // strings and functions with type expressions. The previous point is used because the query
21804 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21805 let mut snippet_source = completion.new_text.clone();
21806 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21807 previous_point.column = previous_point.column.saturating_sub(1);
21808 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21809 && scope.prefers_label_for_snippet_in_completion()
21810 && let Some(label) = completion.label()
21811 && matches!(
21812 completion.kind(),
21813 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21814 )
21815 {
21816 snippet_source = label;
21817 }
21818 match Snippet::parse(&snippet_source).log_err() {
21819 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21820 None => (None, completion.new_text.clone()),
21821 }
21822 } else {
21823 (None, completion.new_text.clone())
21824 };
21825
21826 let mut range_to_replace = {
21827 let replace_range = &completion.replace_range;
21828 if let CompletionSource::Lsp {
21829 insert_range: Some(insert_range),
21830 ..
21831 } = &completion.source
21832 {
21833 debug_assert_eq!(
21834 insert_range.start, replace_range.start,
21835 "insert_range and replace_range should start at the same position"
21836 );
21837 debug_assert!(
21838 insert_range
21839 .start
21840 .cmp(cursor_position, &buffer_snapshot)
21841 .is_le(),
21842 "insert_range should start before or at cursor position"
21843 );
21844 debug_assert!(
21845 replace_range
21846 .start
21847 .cmp(cursor_position, &buffer_snapshot)
21848 .is_le(),
21849 "replace_range should start before or at cursor position"
21850 );
21851
21852 let should_replace = match intent {
21853 CompletionIntent::CompleteWithInsert => false,
21854 CompletionIntent::CompleteWithReplace => true,
21855 CompletionIntent::Complete | CompletionIntent::Compose => {
21856 let insert_mode =
21857 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21858 .completions
21859 .lsp_insert_mode;
21860 match insert_mode {
21861 LspInsertMode::Insert => false,
21862 LspInsertMode::Replace => true,
21863 LspInsertMode::ReplaceSubsequence => {
21864 let mut text_to_replace = buffer.chars_for_range(
21865 buffer.anchor_before(replace_range.start)
21866 ..buffer.anchor_after(replace_range.end),
21867 );
21868 let mut current_needle = text_to_replace.next();
21869 for haystack_ch in completion.label.text.chars() {
21870 if let Some(needle_ch) = current_needle
21871 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21872 {
21873 current_needle = text_to_replace.next();
21874 }
21875 }
21876 current_needle.is_none()
21877 }
21878 LspInsertMode::ReplaceSuffix => {
21879 if replace_range
21880 .end
21881 .cmp(cursor_position, &buffer_snapshot)
21882 .is_gt()
21883 {
21884 let range_after_cursor = *cursor_position..replace_range.end;
21885 let text_after_cursor = buffer
21886 .text_for_range(
21887 buffer.anchor_before(range_after_cursor.start)
21888 ..buffer.anchor_after(range_after_cursor.end),
21889 )
21890 .collect::<String>()
21891 .to_ascii_lowercase();
21892 completion
21893 .label
21894 .text
21895 .to_ascii_lowercase()
21896 .ends_with(&text_after_cursor)
21897 } else {
21898 true
21899 }
21900 }
21901 }
21902 }
21903 };
21904
21905 if should_replace {
21906 replace_range.clone()
21907 } else {
21908 insert_range.clone()
21909 }
21910 } else {
21911 replace_range.clone()
21912 }
21913 };
21914
21915 if range_to_replace
21916 .end
21917 .cmp(cursor_position, &buffer_snapshot)
21918 .is_lt()
21919 {
21920 range_to_replace.end = *cursor_position;
21921 }
21922
21923 CompletionEdit {
21924 new_text,
21925 replace_range: range_to_replace.to_offset(buffer),
21926 snippet,
21927 }
21928}
21929
21930struct CompletionEdit {
21931 new_text: String,
21932 replace_range: Range<usize>,
21933 snippet: Option<Snippet>,
21934}
21935
21936fn insert_extra_newline_brackets(
21937 buffer: &MultiBufferSnapshot,
21938 range: Range<usize>,
21939 language: &language::LanguageScope,
21940) -> bool {
21941 let leading_whitespace_len = buffer
21942 .reversed_chars_at(range.start)
21943 .take_while(|c| c.is_whitespace() && *c != '\n')
21944 .map(|c| c.len_utf8())
21945 .sum::<usize>();
21946 let trailing_whitespace_len = buffer
21947 .chars_at(range.end)
21948 .take_while(|c| c.is_whitespace() && *c != '\n')
21949 .map(|c| c.len_utf8())
21950 .sum::<usize>();
21951 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21952
21953 language.brackets().any(|(pair, enabled)| {
21954 let pair_start = pair.start.trim_end();
21955 let pair_end = pair.end.trim_start();
21956
21957 enabled
21958 && pair.newline
21959 && buffer.contains_str_at(range.end, pair_end)
21960 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21961 })
21962}
21963
21964fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21965 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21966 [(buffer, range, _)] => (*buffer, range.clone()),
21967 _ => return false,
21968 };
21969 let pair = {
21970 let mut result: Option<BracketMatch> = None;
21971
21972 for pair in buffer
21973 .all_bracket_ranges(range.clone())
21974 .filter(move |pair| {
21975 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21976 })
21977 {
21978 let len = pair.close_range.end - pair.open_range.start;
21979
21980 if let Some(existing) = &result {
21981 let existing_len = existing.close_range.end - existing.open_range.start;
21982 if len > existing_len {
21983 continue;
21984 }
21985 }
21986
21987 result = Some(pair);
21988 }
21989
21990 result
21991 };
21992 let Some(pair) = pair else {
21993 return false;
21994 };
21995 pair.newline_only
21996 && buffer
21997 .chars_for_range(pair.open_range.end..range.start)
21998 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21999 .all(|c| c.is_whitespace() && c != '\n')
22000}
22001
22002fn update_uncommitted_diff_for_buffer(
22003 editor: Entity<Editor>,
22004 project: &Entity<Project>,
22005 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22006 buffer: Entity<MultiBuffer>,
22007 cx: &mut App,
22008) -> Task<()> {
22009 let mut tasks = Vec::new();
22010 project.update(cx, |project, cx| {
22011 for buffer in buffers {
22012 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22013 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22014 }
22015 }
22016 });
22017 cx.spawn(async move |cx| {
22018 let diffs = future::join_all(tasks).await;
22019 if editor
22020 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22021 .unwrap_or(false)
22022 {
22023 return;
22024 }
22025
22026 buffer
22027 .update(cx, |buffer, cx| {
22028 for diff in diffs.into_iter().flatten() {
22029 buffer.add_diff(diff, cx);
22030 }
22031 })
22032 .ok();
22033 })
22034}
22035
22036fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22037 let tab_size = tab_size.get() as usize;
22038 let mut width = offset;
22039
22040 for ch in text.chars() {
22041 width += if ch == '\t' {
22042 tab_size - (width % tab_size)
22043 } else {
22044 1
22045 };
22046 }
22047
22048 width - offset
22049}
22050
22051#[cfg(test)]
22052mod tests {
22053 use super::*;
22054
22055 #[test]
22056 fn test_string_size_with_expanded_tabs() {
22057 let nz = |val| NonZeroU32::new(val).unwrap();
22058 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22059 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22060 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22061 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22062 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22063 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22064 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22065 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22066 }
22067}
22068
22069/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22070struct WordBreakingTokenizer<'a> {
22071 input: &'a str,
22072}
22073
22074impl<'a> WordBreakingTokenizer<'a> {
22075 fn new(input: &'a str) -> Self {
22076 Self { input }
22077 }
22078}
22079
22080fn is_char_ideographic(ch: char) -> bool {
22081 use unicode_script::Script::*;
22082 use unicode_script::UnicodeScript;
22083 matches!(ch.script(), Han | Tangut | Yi)
22084}
22085
22086fn is_grapheme_ideographic(text: &str) -> bool {
22087 text.chars().any(is_char_ideographic)
22088}
22089
22090fn is_grapheme_whitespace(text: &str) -> bool {
22091 text.chars().any(|x| x.is_whitespace())
22092}
22093
22094fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22095 text.chars()
22096 .next()
22097 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22098}
22099
22100#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22101enum WordBreakToken<'a> {
22102 Word { token: &'a str, grapheme_len: usize },
22103 InlineWhitespace { token: &'a str, grapheme_len: usize },
22104 Newline,
22105}
22106
22107impl<'a> Iterator for WordBreakingTokenizer<'a> {
22108 /// Yields a span, the count of graphemes in the token, and whether it was
22109 /// whitespace. Note that it also breaks at word boundaries.
22110 type Item = WordBreakToken<'a>;
22111
22112 fn next(&mut self) -> Option<Self::Item> {
22113 use unicode_segmentation::UnicodeSegmentation;
22114 if self.input.is_empty() {
22115 return None;
22116 }
22117
22118 let mut iter = self.input.graphemes(true).peekable();
22119 let mut offset = 0;
22120 let mut grapheme_len = 0;
22121 if let Some(first_grapheme) = iter.next() {
22122 let is_newline = first_grapheme == "\n";
22123 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22124 offset += first_grapheme.len();
22125 grapheme_len += 1;
22126 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22127 if let Some(grapheme) = iter.peek().copied()
22128 && should_stay_with_preceding_ideograph(grapheme)
22129 {
22130 offset += grapheme.len();
22131 grapheme_len += 1;
22132 }
22133 } else {
22134 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22135 let mut next_word_bound = words.peek().copied();
22136 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22137 next_word_bound = words.next();
22138 }
22139 while let Some(grapheme) = iter.peek().copied() {
22140 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22141 break;
22142 };
22143 if is_grapheme_whitespace(grapheme) != is_whitespace
22144 || (grapheme == "\n") != is_newline
22145 {
22146 break;
22147 };
22148 offset += grapheme.len();
22149 grapheme_len += 1;
22150 iter.next();
22151 }
22152 }
22153 let token = &self.input[..offset];
22154 self.input = &self.input[offset..];
22155 if token == "\n" {
22156 Some(WordBreakToken::Newline)
22157 } else if is_whitespace {
22158 Some(WordBreakToken::InlineWhitespace {
22159 token,
22160 grapheme_len,
22161 })
22162 } else {
22163 Some(WordBreakToken::Word {
22164 token,
22165 grapheme_len,
22166 })
22167 }
22168 } else {
22169 None
22170 }
22171 }
22172}
22173
22174#[test]
22175fn test_word_breaking_tokenizer() {
22176 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22177 ("", &[]),
22178 (" ", &[whitespace(" ", 2)]),
22179 ("Ʒ", &[word("Ʒ", 1)]),
22180 ("Ǽ", &[word("Ǽ", 1)]),
22181 ("⋑", &[word("⋑", 1)]),
22182 ("⋑⋑", &[word("⋑⋑", 2)]),
22183 (
22184 "原理,进而",
22185 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22186 ),
22187 (
22188 "hello world",
22189 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22190 ),
22191 (
22192 "hello, world",
22193 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22194 ),
22195 (
22196 " hello world",
22197 &[
22198 whitespace(" ", 2),
22199 word("hello", 5),
22200 whitespace(" ", 1),
22201 word("world", 5),
22202 ],
22203 ),
22204 (
22205 "这是什么 \n 钢笔",
22206 &[
22207 word("这", 1),
22208 word("是", 1),
22209 word("什", 1),
22210 word("么", 1),
22211 whitespace(" ", 1),
22212 newline(),
22213 whitespace(" ", 1),
22214 word("钢", 1),
22215 word("笔", 1),
22216 ],
22217 ),
22218 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22219 ];
22220
22221 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22222 WordBreakToken::Word {
22223 token,
22224 grapheme_len,
22225 }
22226 }
22227
22228 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22229 WordBreakToken::InlineWhitespace {
22230 token,
22231 grapheme_len,
22232 }
22233 }
22234
22235 fn newline() -> WordBreakToken<'static> {
22236 WordBreakToken::Newline
22237 }
22238
22239 for (input, result) in tests {
22240 assert_eq!(
22241 WordBreakingTokenizer::new(input)
22242 .collect::<Vec<_>>()
22243 .as_slice(),
22244 *result,
22245 );
22246 }
22247}
22248
22249fn wrap_with_prefix(
22250 first_line_prefix: String,
22251 subsequent_lines_prefix: String,
22252 unwrapped_text: String,
22253 wrap_column: usize,
22254 tab_size: NonZeroU32,
22255 preserve_existing_whitespace: bool,
22256) -> String {
22257 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22258 let subsequent_lines_prefix_len =
22259 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22260 let mut wrapped_text = String::new();
22261 let mut current_line = first_line_prefix;
22262 let mut is_first_line = true;
22263
22264 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22265 let mut current_line_len = first_line_prefix_len;
22266 let mut in_whitespace = false;
22267 for token in tokenizer {
22268 let have_preceding_whitespace = in_whitespace;
22269 match token {
22270 WordBreakToken::Word {
22271 token,
22272 grapheme_len,
22273 } => {
22274 in_whitespace = false;
22275 let current_prefix_len = if is_first_line {
22276 first_line_prefix_len
22277 } else {
22278 subsequent_lines_prefix_len
22279 };
22280 if current_line_len + grapheme_len > wrap_column
22281 && current_line_len != current_prefix_len
22282 {
22283 wrapped_text.push_str(current_line.trim_end());
22284 wrapped_text.push('\n');
22285 is_first_line = false;
22286 current_line = subsequent_lines_prefix.clone();
22287 current_line_len = subsequent_lines_prefix_len;
22288 }
22289 current_line.push_str(token);
22290 current_line_len += grapheme_len;
22291 }
22292 WordBreakToken::InlineWhitespace {
22293 mut token,
22294 mut grapheme_len,
22295 } => {
22296 in_whitespace = true;
22297 if have_preceding_whitespace && !preserve_existing_whitespace {
22298 continue;
22299 }
22300 if !preserve_existing_whitespace {
22301 token = " ";
22302 grapheme_len = 1;
22303 }
22304 let current_prefix_len = if is_first_line {
22305 first_line_prefix_len
22306 } else {
22307 subsequent_lines_prefix_len
22308 };
22309 if current_line_len + grapheme_len > wrap_column {
22310 wrapped_text.push_str(current_line.trim_end());
22311 wrapped_text.push('\n');
22312 is_first_line = false;
22313 current_line = subsequent_lines_prefix.clone();
22314 current_line_len = subsequent_lines_prefix_len;
22315 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22316 current_line.push_str(token);
22317 current_line_len += grapheme_len;
22318 }
22319 }
22320 WordBreakToken::Newline => {
22321 in_whitespace = true;
22322 let current_prefix_len = if is_first_line {
22323 first_line_prefix_len
22324 } else {
22325 subsequent_lines_prefix_len
22326 };
22327 if preserve_existing_whitespace {
22328 wrapped_text.push_str(current_line.trim_end());
22329 wrapped_text.push('\n');
22330 is_first_line = false;
22331 current_line = subsequent_lines_prefix.clone();
22332 current_line_len = subsequent_lines_prefix_len;
22333 } else if have_preceding_whitespace {
22334 continue;
22335 } else if current_line_len + 1 > wrap_column
22336 && current_line_len != current_prefix_len
22337 {
22338 wrapped_text.push_str(current_line.trim_end());
22339 wrapped_text.push('\n');
22340 is_first_line = false;
22341 current_line = subsequent_lines_prefix.clone();
22342 current_line_len = subsequent_lines_prefix_len;
22343 } else if current_line_len != current_prefix_len {
22344 current_line.push(' ');
22345 current_line_len += 1;
22346 }
22347 }
22348 }
22349 }
22350
22351 if !current_line.is_empty() {
22352 wrapped_text.push_str(¤t_line);
22353 }
22354 wrapped_text
22355}
22356
22357#[test]
22358fn test_wrap_with_prefix() {
22359 assert_eq!(
22360 wrap_with_prefix(
22361 "# ".to_string(),
22362 "# ".to_string(),
22363 "abcdefg".to_string(),
22364 4,
22365 NonZeroU32::new(4).unwrap(),
22366 false,
22367 ),
22368 "# abcdefg"
22369 );
22370 assert_eq!(
22371 wrap_with_prefix(
22372 "".to_string(),
22373 "".to_string(),
22374 "\thello world".to_string(),
22375 8,
22376 NonZeroU32::new(4).unwrap(),
22377 false,
22378 ),
22379 "hello\nworld"
22380 );
22381 assert_eq!(
22382 wrap_with_prefix(
22383 "// ".to_string(),
22384 "// ".to_string(),
22385 "xx \nyy zz aa bb cc".to_string(),
22386 12,
22387 NonZeroU32::new(4).unwrap(),
22388 false,
22389 ),
22390 "// xx yy zz\n// aa bb cc"
22391 );
22392 assert_eq!(
22393 wrap_with_prefix(
22394 String::new(),
22395 String::new(),
22396 "这是什么 \n 钢笔".to_string(),
22397 3,
22398 NonZeroU32::new(4).unwrap(),
22399 false,
22400 ),
22401 "这是什\n么 钢\n笔"
22402 );
22403}
22404
22405pub trait CollaborationHub {
22406 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22407 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22408 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22409}
22410
22411impl CollaborationHub for Entity<Project> {
22412 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22413 self.read(cx).collaborators()
22414 }
22415
22416 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22417 self.read(cx).user_store().read(cx).participant_indices()
22418 }
22419
22420 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22421 let this = self.read(cx);
22422 let user_ids = this.collaborators().values().map(|c| c.user_id);
22423 this.user_store().read(cx).participant_names(user_ids, cx)
22424 }
22425}
22426
22427pub trait SemanticsProvider {
22428 fn hover(
22429 &self,
22430 buffer: &Entity<Buffer>,
22431 position: text::Anchor,
22432 cx: &mut App,
22433 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22434
22435 fn inline_values(
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 inlay_hints(
22443 &self,
22444 buffer_handle: Entity<Buffer>,
22445 range: Range<text::Anchor>,
22446 cx: &mut App,
22447 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22448
22449 fn resolve_inlay_hint(
22450 &self,
22451 hint: InlayHint,
22452 buffer_handle: Entity<Buffer>,
22453 server_id: LanguageServerId,
22454 cx: &mut App,
22455 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22456
22457 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22458
22459 fn document_highlights(
22460 &self,
22461 buffer: &Entity<Buffer>,
22462 position: text::Anchor,
22463 cx: &mut App,
22464 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22465
22466 fn definitions(
22467 &self,
22468 buffer: &Entity<Buffer>,
22469 position: text::Anchor,
22470 kind: GotoDefinitionKind,
22471 cx: &mut App,
22472 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22473
22474 fn range_for_rename(
22475 &self,
22476 buffer: &Entity<Buffer>,
22477 position: text::Anchor,
22478 cx: &mut App,
22479 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22480
22481 fn perform_rename(
22482 &self,
22483 buffer: &Entity<Buffer>,
22484 position: text::Anchor,
22485 new_name: String,
22486 cx: &mut App,
22487 ) -> Option<Task<Result<ProjectTransaction>>>;
22488}
22489
22490pub trait CompletionProvider {
22491 fn completions(
22492 &self,
22493 excerpt_id: ExcerptId,
22494 buffer: &Entity<Buffer>,
22495 buffer_position: text::Anchor,
22496 trigger: CompletionContext,
22497 window: &mut Window,
22498 cx: &mut Context<Editor>,
22499 ) -> Task<Result<Vec<CompletionResponse>>>;
22500
22501 fn resolve_completions(
22502 &self,
22503 _buffer: Entity<Buffer>,
22504 _completion_indices: Vec<usize>,
22505 _completions: Rc<RefCell<Box<[Completion]>>>,
22506 _cx: &mut Context<Editor>,
22507 ) -> Task<Result<bool>> {
22508 Task::ready(Ok(false))
22509 }
22510
22511 fn apply_additional_edits_for_completion(
22512 &self,
22513 _buffer: Entity<Buffer>,
22514 _completions: Rc<RefCell<Box<[Completion]>>>,
22515 _completion_index: usize,
22516 _push_to_history: bool,
22517 _cx: &mut Context<Editor>,
22518 ) -> Task<Result<Option<language::Transaction>>> {
22519 Task::ready(Ok(None))
22520 }
22521
22522 fn is_completion_trigger(
22523 &self,
22524 buffer: &Entity<Buffer>,
22525 position: language::Anchor,
22526 text: &str,
22527 trigger_in_words: bool,
22528 menu_is_open: bool,
22529 cx: &mut Context<Editor>,
22530 ) -> bool;
22531
22532 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22533
22534 fn sort_completions(&self) -> bool {
22535 true
22536 }
22537
22538 fn filter_completions(&self) -> bool {
22539 true
22540 }
22541}
22542
22543pub trait CodeActionProvider {
22544 fn id(&self) -> Arc<str>;
22545
22546 fn code_actions(
22547 &self,
22548 buffer: &Entity<Buffer>,
22549 range: Range<text::Anchor>,
22550 window: &mut Window,
22551 cx: &mut App,
22552 ) -> Task<Result<Vec<CodeAction>>>;
22553
22554 fn apply_code_action(
22555 &self,
22556 buffer_handle: Entity<Buffer>,
22557 action: CodeAction,
22558 excerpt_id: ExcerptId,
22559 push_to_history: bool,
22560 window: &mut Window,
22561 cx: &mut App,
22562 ) -> Task<Result<ProjectTransaction>>;
22563}
22564
22565impl CodeActionProvider for Entity<Project> {
22566 fn id(&self) -> Arc<str> {
22567 "project".into()
22568 }
22569
22570 fn code_actions(
22571 &self,
22572 buffer: &Entity<Buffer>,
22573 range: Range<text::Anchor>,
22574 _window: &mut Window,
22575 cx: &mut App,
22576 ) -> Task<Result<Vec<CodeAction>>> {
22577 self.update(cx, |project, cx| {
22578 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22579 let code_actions = project.code_actions(buffer, range, None, cx);
22580 cx.background_spawn(async move {
22581 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22582 Ok(code_lens_actions
22583 .context("code lens fetch")?
22584 .into_iter()
22585 .flatten()
22586 .chain(
22587 code_actions
22588 .context("code action fetch")?
22589 .into_iter()
22590 .flatten(),
22591 )
22592 .collect())
22593 })
22594 })
22595 }
22596
22597 fn apply_code_action(
22598 &self,
22599 buffer_handle: Entity<Buffer>,
22600 action: CodeAction,
22601 _excerpt_id: ExcerptId,
22602 push_to_history: bool,
22603 _window: &mut Window,
22604 cx: &mut App,
22605 ) -> Task<Result<ProjectTransaction>> {
22606 self.update(cx, |project, cx| {
22607 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22608 })
22609 }
22610}
22611
22612fn snippet_completions(
22613 project: &Project,
22614 buffer: &Entity<Buffer>,
22615 buffer_position: text::Anchor,
22616 cx: &mut App,
22617) -> Task<Result<CompletionResponse>> {
22618 let languages = buffer.read(cx).languages_at(buffer_position);
22619 let snippet_store = project.snippets().read(cx);
22620
22621 let scopes: Vec<_> = languages
22622 .iter()
22623 .filter_map(|language| {
22624 let language_name = language.lsp_id();
22625 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22626
22627 if snippets.is_empty() {
22628 None
22629 } else {
22630 Some((language.default_scope(), snippets))
22631 }
22632 })
22633 .collect();
22634
22635 if scopes.is_empty() {
22636 return Task::ready(Ok(CompletionResponse {
22637 completions: vec![],
22638 display_options: CompletionDisplayOptions::default(),
22639 is_incomplete: false,
22640 }));
22641 }
22642
22643 let snapshot = buffer.read(cx).text_snapshot();
22644 let chars: String = snapshot
22645 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22646 .collect();
22647 let executor = cx.background_executor().clone();
22648
22649 cx.background_spawn(async move {
22650 let mut is_incomplete = false;
22651 let mut completions: Vec<Completion> = Vec::new();
22652 for (scope, snippets) in scopes.into_iter() {
22653 let classifier =
22654 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22655 let mut last_word = chars
22656 .chars()
22657 .take_while(|c| classifier.is_word(*c))
22658 .collect::<String>();
22659 last_word = last_word.chars().rev().collect();
22660
22661 if last_word.is_empty() {
22662 return Ok(CompletionResponse {
22663 completions: vec![],
22664 display_options: CompletionDisplayOptions::default(),
22665 is_incomplete: true,
22666 });
22667 }
22668
22669 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22670 let to_lsp = |point: &text::Anchor| {
22671 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22672 point_to_lsp(end)
22673 };
22674 let lsp_end = to_lsp(&buffer_position);
22675
22676 let candidates = snippets
22677 .iter()
22678 .enumerate()
22679 .flat_map(|(ix, snippet)| {
22680 snippet
22681 .prefix
22682 .iter()
22683 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22684 })
22685 .collect::<Vec<StringMatchCandidate>>();
22686
22687 const MAX_RESULTS: usize = 100;
22688 let mut matches = fuzzy::match_strings(
22689 &candidates,
22690 &last_word,
22691 last_word.chars().any(|c| c.is_uppercase()),
22692 true,
22693 MAX_RESULTS,
22694 &Default::default(),
22695 executor.clone(),
22696 )
22697 .await;
22698
22699 if matches.len() >= MAX_RESULTS {
22700 is_incomplete = true;
22701 }
22702
22703 // Remove all candidates where the query's start does not match the start of any word in the candidate
22704 if let Some(query_start) = last_word.chars().next() {
22705 matches.retain(|string_match| {
22706 split_words(&string_match.string).any(|word| {
22707 // Check that the first codepoint of the word as lowercase matches the first
22708 // codepoint of the query as lowercase
22709 word.chars()
22710 .flat_map(|codepoint| codepoint.to_lowercase())
22711 .zip(query_start.to_lowercase())
22712 .all(|(word_cp, query_cp)| word_cp == query_cp)
22713 })
22714 });
22715 }
22716
22717 let matched_strings = matches
22718 .into_iter()
22719 .map(|m| m.string)
22720 .collect::<HashSet<_>>();
22721
22722 completions.extend(snippets.iter().filter_map(|snippet| {
22723 let matching_prefix = snippet
22724 .prefix
22725 .iter()
22726 .find(|prefix| matched_strings.contains(*prefix))?;
22727 let start = as_offset - last_word.len();
22728 let start = snapshot.anchor_before(start);
22729 let range = start..buffer_position;
22730 let lsp_start = to_lsp(&start);
22731 let lsp_range = lsp::Range {
22732 start: lsp_start,
22733 end: lsp_end,
22734 };
22735 Some(Completion {
22736 replace_range: range,
22737 new_text: snippet.body.clone(),
22738 source: CompletionSource::Lsp {
22739 insert_range: None,
22740 server_id: LanguageServerId(usize::MAX),
22741 resolved: true,
22742 lsp_completion: Box::new(lsp::CompletionItem {
22743 label: snippet.prefix.first().unwrap().clone(),
22744 kind: Some(CompletionItemKind::SNIPPET),
22745 label_details: snippet.description.as_ref().map(|description| {
22746 lsp::CompletionItemLabelDetails {
22747 detail: Some(description.clone()),
22748 description: None,
22749 }
22750 }),
22751 insert_text_format: Some(InsertTextFormat::SNIPPET),
22752 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22753 lsp::InsertReplaceEdit {
22754 new_text: snippet.body.clone(),
22755 insert: lsp_range,
22756 replace: lsp_range,
22757 },
22758 )),
22759 filter_text: Some(snippet.body.clone()),
22760 sort_text: Some(char::MAX.to_string()),
22761 ..lsp::CompletionItem::default()
22762 }),
22763 lsp_defaults: None,
22764 },
22765 label: CodeLabel {
22766 text: matching_prefix.clone(),
22767 runs: Vec::new(),
22768 filter_range: 0..matching_prefix.len(),
22769 },
22770 icon_path: None,
22771 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22772 single_line: snippet.name.clone().into(),
22773 plain_text: snippet
22774 .description
22775 .clone()
22776 .map(|description| description.into()),
22777 }),
22778 insert_text_mode: None,
22779 confirm: None,
22780 })
22781 }))
22782 }
22783
22784 Ok(CompletionResponse {
22785 completions,
22786 display_options: CompletionDisplayOptions::default(),
22787 is_incomplete,
22788 })
22789 })
22790}
22791
22792impl CompletionProvider for Entity<Project> {
22793 fn completions(
22794 &self,
22795 _excerpt_id: ExcerptId,
22796 buffer: &Entity<Buffer>,
22797 buffer_position: text::Anchor,
22798 options: CompletionContext,
22799 _window: &mut Window,
22800 cx: &mut Context<Editor>,
22801 ) -> Task<Result<Vec<CompletionResponse>>> {
22802 self.update(cx, |project, cx| {
22803 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22804 let project_completions = project.completions(buffer, buffer_position, options, cx);
22805 cx.background_spawn(async move {
22806 let mut responses = project_completions.await?;
22807 let snippets = snippets.await?;
22808 if !snippets.completions.is_empty() {
22809 responses.push(snippets);
22810 }
22811 Ok(responses)
22812 })
22813 })
22814 }
22815
22816 fn resolve_completions(
22817 &self,
22818 buffer: Entity<Buffer>,
22819 completion_indices: Vec<usize>,
22820 completions: Rc<RefCell<Box<[Completion]>>>,
22821 cx: &mut Context<Editor>,
22822 ) -> Task<Result<bool>> {
22823 self.update(cx, |project, cx| {
22824 project.lsp_store().update(cx, |lsp_store, cx| {
22825 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22826 })
22827 })
22828 }
22829
22830 fn apply_additional_edits_for_completion(
22831 &self,
22832 buffer: Entity<Buffer>,
22833 completions: Rc<RefCell<Box<[Completion]>>>,
22834 completion_index: usize,
22835 push_to_history: bool,
22836 cx: &mut Context<Editor>,
22837 ) -> Task<Result<Option<language::Transaction>>> {
22838 self.update(cx, |project, cx| {
22839 project.lsp_store().update(cx, |lsp_store, cx| {
22840 lsp_store.apply_additional_edits_for_completion(
22841 buffer,
22842 completions,
22843 completion_index,
22844 push_to_history,
22845 cx,
22846 )
22847 })
22848 })
22849 }
22850
22851 fn is_completion_trigger(
22852 &self,
22853 buffer: &Entity<Buffer>,
22854 position: language::Anchor,
22855 text: &str,
22856 trigger_in_words: bool,
22857 menu_is_open: bool,
22858 cx: &mut Context<Editor>,
22859 ) -> bool {
22860 let mut chars = text.chars();
22861 let char = if let Some(char) = chars.next() {
22862 char
22863 } else {
22864 return false;
22865 };
22866 if chars.next().is_some() {
22867 return false;
22868 }
22869
22870 let buffer = buffer.read(cx);
22871 let snapshot = buffer.snapshot();
22872 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22873 return false;
22874 }
22875 let classifier = snapshot
22876 .char_classifier_at(position)
22877 .scope_context(Some(CharScopeContext::Completion));
22878 if trigger_in_words && classifier.is_word(char) {
22879 return true;
22880 }
22881
22882 buffer.completion_triggers().contains(text)
22883 }
22884}
22885
22886impl SemanticsProvider for Entity<Project> {
22887 fn hover(
22888 &self,
22889 buffer: &Entity<Buffer>,
22890 position: text::Anchor,
22891 cx: &mut App,
22892 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22893 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22894 }
22895
22896 fn document_highlights(
22897 &self,
22898 buffer: &Entity<Buffer>,
22899 position: text::Anchor,
22900 cx: &mut App,
22901 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22902 Some(self.update(cx, |project, cx| {
22903 project.document_highlights(buffer, position, cx)
22904 }))
22905 }
22906
22907 fn definitions(
22908 &self,
22909 buffer: &Entity<Buffer>,
22910 position: text::Anchor,
22911 kind: GotoDefinitionKind,
22912 cx: &mut App,
22913 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22914 Some(self.update(cx, |project, cx| match kind {
22915 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22916 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22917 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22918 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22919 }))
22920 }
22921
22922 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22923 self.update(cx, |project, cx| {
22924 if project
22925 .active_debug_session(cx)
22926 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22927 {
22928 return true;
22929 }
22930
22931 buffer.update(cx, |buffer, cx| {
22932 project.any_language_server_supports_inlay_hints(buffer, cx)
22933 })
22934 })
22935 }
22936
22937 fn inline_values(
22938 &self,
22939 buffer_handle: Entity<Buffer>,
22940 range: Range<text::Anchor>,
22941 cx: &mut App,
22942 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22943 self.update(cx, |project, cx| {
22944 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22945
22946 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22947 })
22948 }
22949
22950 fn inlay_hints(
22951 &self,
22952 buffer_handle: Entity<Buffer>,
22953 range: Range<text::Anchor>,
22954 cx: &mut App,
22955 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22956 Some(self.update(cx, |project, cx| {
22957 project.inlay_hints(buffer_handle, range, cx)
22958 }))
22959 }
22960
22961 fn resolve_inlay_hint(
22962 &self,
22963 hint: InlayHint,
22964 buffer_handle: Entity<Buffer>,
22965 server_id: LanguageServerId,
22966 cx: &mut App,
22967 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22968 Some(self.update(cx, |project, cx| {
22969 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22970 }))
22971 }
22972
22973 fn range_for_rename(
22974 &self,
22975 buffer: &Entity<Buffer>,
22976 position: text::Anchor,
22977 cx: &mut App,
22978 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22979 Some(self.update(cx, |project, cx| {
22980 let buffer = buffer.clone();
22981 let task = project.prepare_rename(buffer.clone(), position, cx);
22982 cx.spawn(async move |_, cx| {
22983 Ok(match task.await? {
22984 PrepareRenameResponse::Success(range) => Some(range),
22985 PrepareRenameResponse::InvalidPosition => None,
22986 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22987 // Fallback on using TreeSitter info to determine identifier range
22988 buffer.read_with(cx, |buffer, _| {
22989 let snapshot = buffer.snapshot();
22990 let (range, kind) = snapshot.surrounding_word(position, None);
22991 if kind != Some(CharKind::Word) {
22992 return None;
22993 }
22994 Some(
22995 snapshot.anchor_before(range.start)
22996 ..snapshot.anchor_after(range.end),
22997 )
22998 })?
22999 }
23000 })
23001 })
23002 }))
23003 }
23004
23005 fn perform_rename(
23006 &self,
23007 buffer: &Entity<Buffer>,
23008 position: text::Anchor,
23009 new_name: String,
23010 cx: &mut App,
23011 ) -> Option<Task<Result<ProjectTransaction>>> {
23012 Some(self.update(cx, |project, cx| {
23013 project.perform_rename(buffer.clone(), position, new_name, cx)
23014 }))
23015 }
23016}
23017
23018fn inlay_hint_settings(
23019 location: Anchor,
23020 snapshot: &MultiBufferSnapshot,
23021 cx: &mut Context<Editor>,
23022) -> InlayHintSettings {
23023 let file = snapshot.file_at(location);
23024 let language = snapshot.language_at(location).map(|l| l.name());
23025 language_settings(language, file, cx).inlay_hints
23026}
23027
23028fn consume_contiguous_rows(
23029 contiguous_row_selections: &mut Vec<Selection<Point>>,
23030 selection: &Selection<Point>,
23031 display_map: &DisplaySnapshot,
23032 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23033) -> (MultiBufferRow, MultiBufferRow) {
23034 contiguous_row_selections.push(selection.clone());
23035 let start_row = starting_row(selection, display_map);
23036 let mut end_row = ending_row(selection, display_map);
23037
23038 while let Some(next_selection) = selections.peek() {
23039 if next_selection.start.row <= end_row.0 {
23040 end_row = ending_row(next_selection, display_map);
23041 contiguous_row_selections.push(selections.next().unwrap().clone());
23042 } else {
23043 break;
23044 }
23045 }
23046 (start_row, end_row)
23047}
23048
23049fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23050 if selection.start.column > 0 {
23051 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23052 } else {
23053 MultiBufferRow(selection.start.row)
23054 }
23055}
23056
23057fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23058 if next_selection.end.column > 0 || next_selection.is_empty() {
23059 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23060 } else {
23061 MultiBufferRow(next_selection.end.row)
23062 }
23063}
23064
23065impl EditorSnapshot {
23066 pub fn remote_selections_in_range<'a>(
23067 &'a self,
23068 range: &'a Range<Anchor>,
23069 collaboration_hub: &dyn CollaborationHub,
23070 cx: &'a App,
23071 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23072 let participant_names = collaboration_hub.user_names(cx);
23073 let participant_indices = collaboration_hub.user_participant_indices(cx);
23074 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23075 let collaborators_by_replica_id = collaborators_by_peer_id
23076 .values()
23077 .map(|collaborator| (collaborator.replica_id, collaborator))
23078 .collect::<HashMap<_, _>>();
23079 self.buffer_snapshot
23080 .selections_in_range(range, false)
23081 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23082 if replica_id == AGENT_REPLICA_ID {
23083 Some(RemoteSelection {
23084 replica_id,
23085 selection,
23086 cursor_shape,
23087 line_mode,
23088 collaborator_id: CollaboratorId::Agent,
23089 user_name: Some("Agent".into()),
23090 color: cx.theme().players().agent(),
23091 })
23092 } else {
23093 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23094 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23095 let user_name = participant_names.get(&collaborator.user_id).cloned();
23096 Some(RemoteSelection {
23097 replica_id,
23098 selection,
23099 cursor_shape,
23100 line_mode,
23101 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23102 user_name,
23103 color: if let Some(index) = participant_index {
23104 cx.theme().players().color_for_participant(index.0)
23105 } else {
23106 cx.theme().players().absent()
23107 },
23108 })
23109 }
23110 })
23111 }
23112
23113 pub fn hunks_for_ranges(
23114 &self,
23115 ranges: impl IntoIterator<Item = Range<Point>>,
23116 ) -> Vec<MultiBufferDiffHunk> {
23117 let mut hunks = Vec::new();
23118 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23119 HashMap::default();
23120 for query_range in ranges {
23121 let query_rows =
23122 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23123 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23124 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23125 ) {
23126 // Include deleted hunks that are adjacent to the query range, because
23127 // otherwise they would be missed.
23128 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23129 if hunk.status().is_deleted() {
23130 intersects_range |= hunk.row_range.start == query_rows.end;
23131 intersects_range |= hunk.row_range.end == query_rows.start;
23132 }
23133 if intersects_range {
23134 if !processed_buffer_rows
23135 .entry(hunk.buffer_id)
23136 .or_default()
23137 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23138 {
23139 continue;
23140 }
23141 hunks.push(hunk);
23142 }
23143 }
23144 }
23145
23146 hunks
23147 }
23148
23149 fn display_diff_hunks_for_rows<'a>(
23150 &'a self,
23151 display_rows: Range<DisplayRow>,
23152 folded_buffers: &'a HashSet<BufferId>,
23153 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23154 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23155 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23156
23157 self.buffer_snapshot
23158 .diff_hunks_in_range(buffer_start..buffer_end)
23159 .filter_map(|hunk| {
23160 if folded_buffers.contains(&hunk.buffer_id) {
23161 return None;
23162 }
23163
23164 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23165 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23166
23167 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23168 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23169
23170 let display_hunk = if hunk_display_start.column() != 0 {
23171 DisplayDiffHunk::Folded {
23172 display_row: hunk_display_start.row(),
23173 }
23174 } else {
23175 let mut end_row = hunk_display_end.row();
23176 if hunk_display_end.column() > 0 {
23177 end_row.0 += 1;
23178 }
23179 let is_created_file = hunk.is_created_file();
23180 DisplayDiffHunk::Unfolded {
23181 status: hunk.status(),
23182 diff_base_byte_range: hunk.diff_base_byte_range,
23183 display_row_range: hunk_display_start.row()..end_row,
23184 multi_buffer_range: Anchor::range_in_buffer(
23185 hunk.excerpt_id,
23186 hunk.buffer_id,
23187 hunk.buffer_range,
23188 ),
23189 is_created_file,
23190 }
23191 };
23192
23193 Some(display_hunk)
23194 })
23195 }
23196
23197 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23198 self.display_snapshot.buffer_snapshot.language_at(position)
23199 }
23200
23201 pub fn is_focused(&self) -> bool {
23202 self.is_focused
23203 }
23204
23205 pub fn placeholder_text(&self) -> Option<String> {
23206 self.placeholder_display_snapshot
23207 .as_ref()
23208 .map(|display_map| display_map.text())
23209 }
23210
23211 pub fn scroll_position(&self) -> gpui::Point<f32> {
23212 self.scroll_anchor.scroll_position(&self.display_snapshot)
23213 }
23214
23215 fn gutter_dimensions(
23216 &self,
23217 font_id: FontId,
23218 font_size: Pixels,
23219 max_line_number_width: Pixels,
23220 cx: &App,
23221 ) -> Option<GutterDimensions> {
23222 if !self.show_gutter {
23223 return None;
23224 }
23225
23226 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23227 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23228
23229 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23230 matches!(
23231 ProjectSettings::get_global(cx).git.git_gutter,
23232 GitGutterSetting::TrackedFiles
23233 )
23234 });
23235 let gutter_settings = EditorSettings::get_global(cx).gutter;
23236 let show_line_numbers = self
23237 .show_line_numbers
23238 .unwrap_or(gutter_settings.line_numbers);
23239 let line_gutter_width = if show_line_numbers {
23240 // Avoid flicker-like gutter resizes when the line number gains another digit by
23241 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23242 let min_width_for_number_on_gutter =
23243 ch_advance * gutter_settings.min_line_number_digits as f32;
23244 max_line_number_width.max(min_width_for_number_on_gutter)
23245 } else {
23246 0.0.into()
23247 };
23248
23249 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23250 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23251
23252 let git_blame_entries_width =
23253 self.git_blame_gutter_max_author_length
23254 .map(|max_author_length| {
23255 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23256 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23257
23258 /// The number of characters to dedicate to gaps and margins.
23259 const SPACING_WIDTH: usize = 4;
23260
23261 let max_char_count = max_author_length.min(renderer.max_author_length())
23262 + ::git::SHORT_SHA_LENGTH
23263 + MAX_RELATIVE_TIMESTAMP.len()
23264 + SPACING_WIDTH;
23265
23266 ch_advance * max_char_count
23267 });
23268
23269 let is_singleton = self.buffer_snapshot.is_singleton();
23270
23271 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23272 left_padding += if !is_singleton {
23273 ch_width * 4.0
23274 } else if show_runnables || show_breakpoints {
23275 ch_width * 3.0
23276 } else if show_git_gutter && show_line_numbers {
23277 ch_width * 2.0
23278 } else if show_git_gutter || show_line_numbers {
23279 ch_width
23280 } else {
23281 px(0.)
23282 };
23283
23284 let shows_folds = is_singleton && gutter_settings.folds;
23285
23286 let right_padding = if shows_folds && show_line_numbers {
23287 ch_width * 4.0
23288 } else if shows_folds || (!is_singleton && show_line_numbers) {
23289 ch_width * 3.0
23290 } else if show_line_numbers {
23291 ch_width
23292 } else {
23293 px(0.)
23294 };
23295
23296 Some(GutterDimensions {
23297 left_padding,
23298 right_padding,
23299 width: line_gutter_width + left_padding + right_padding,
23300 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23301 git_blame_entries_width,
23302 })
23303 }
23304
23305 pub fn render_crease_toggle(
23306 &self,
23307 buffer_row: MultiBufferRow,
23308 row_contains_cursor: bool,
23309 editor: Entity<Editor>,
23310 window: &mut Window,
23311 cx: &mut App,
23312 ) -> Option<AnyElement> {
23313 let folded = self.is_line_folded(buffer_row);
23314 let mut is_foldable = false;
23315
23316 if let Some(crease) = self
23317 .crease_snapshot
23318 .query_row(buffer_row, &self.buffer_snapshot)
23319 {
23320 is_foldable = true;
23321 match crease {
23322 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23323 if let Some(render_toggle) = render_toggle {
23324 let toggle_callback =
23325 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23326 if folded {
23327 editor.update(cx, |editor, cx| {
23328 editor.fold_at(buffer_row, window, cx)
23329 });
23330 } else {
23331 editor.update(cx, |editor, cx| {
23332 editor.unfold_at(buffer_row, window, cx)
23333 });
23334 }
23335 });
23336 return Some((render_toggle)(
23337 buffer_row,
23338 folded,
23339 toggle_callback,
23340 window,
23341 cx,
23342 ));
23343 }
23344 }
23345 }
23346 }
23347
23348 is_foldable |= self.starts_indent(buffer_row);
23349
23350 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23351 Some(
23352 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23353 .toggle_state(folded)
23354 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23355 if folded {
23356 this.unfold_at(buffer_row, window, cx);
23357 } else {
23358 this.fold_at(buffer_row, window, cx);
23359 }
23360 }))
23361 .into_any_element(),
23362 )
23363 } else {
23364 None
23365 }
23366 }
23367
23368 pub fn render_crease_trailer(
23369 &self,
23370 buffer_row: MultiBufferRow,
23371 window: &mut Window,
23372 cx: &mut App,
23373 ) -> Option<AnyElement> {
23374 let folded = self.is_line_folded(buffer_row);
23375 if let Crease::Inline { render_trailer, .. } = self
23376 .crease_snapshot
23377 .query_row(buffer_row, &self.buffer_snapshot)?
23378 {
23379 let render_trailer = render_trailer.as_ref()?;
23380 Some(render_trailer(buffer_row, folded, window, cx))
23381 } else {
23382 None
23383 }
23384 }
23385}
23386
23387impl Deref for EditorSnapshot {
23388 type Target = DisplaySnapshot;
23389
23390 fn deref(&self) -> &Self::Target {
23391 &self.display_snapshot
23392 }
23393}
23394
23395#[derive(Clone, Debug, PartialEq, Eq)]
23396pub enum EditorEvent {
23397 InputIgnored {
23398 text: Arc<str>,
23399 },
23400 InputHandled {
23401 utf16_range_to_replace: Option<Range<isize>>,
23402 text: Arc<str>,
23403 },
23404 ExcerptsAdded {
23405 buffer: Entity<Buffer>,
23406 predecessor: ExcerptId,
23407 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23408 },
23409 ExcerptsRemoved {
23410 ids: Vec<ExcerptId>,
23411 removed_buffer_ids: Vec<BufferId>,
23412 },
23413 BufferFoldToggled {
23414 ids: Vec<ExcerptId>,
23415 folded: bool,
23416 },
23417 ExcerptsEdited {
23418 ids: Vec<ExcerptId>,
23419 },
23420 ExcerptsExpanded {
23421 ids: Vec<ExcerptId>,
23422 },
23423 BufferEdited,
23424 Edited {
23425 transaction_id: clock::Lamport,
23426 },
23427 Reparsed(BufferId),
23428 Focused,
23429 FocusedIn,
23430 Blurred,
23431 DirtyChanged,
23432 Saved,
23433 TitleChanged,
23434 SelectionsChanged {
23435 local: bool,
23436 },
23437 ScrollPositionChanged {
23438 local: bool,
23439 autoscroll: bool,
23440 },
23441 TransactionUndone {
23442 transaction_id: clock::Lamport,
23443 },
23444 TransactionBegun {
23445 transaction_id: clock::Lamport,
23446 },
23447 CursorShapeChanged,
23448 BreadcrumbsChanged,
23449 PushedToNavHistory {
23450 anchor: Anchor,
23451 is_deactivate: bool,
23452 },
23453}
23454
23455impl EventEmitter<EditorEvent> for Editor {}
23456
23457impl Focusable for Editor {
23458 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23459 self.focus_handle.clone()
23460 }
23461}
23462
23463impl Render for Editor {
23464 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23465 let settings = ThemeSettings::get_global(cx);
23466
23467 let mut text_style = match self.mode {
23468 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23469 color: cx.theme().colors().editor_foreground,
23470 font_family: settings.ui_font.family.clone(),
23471 font_features: settings.ui_font.features.clone(),
23472 font_fallbacks: settings.ui_font.fallbacks.clone(),
23473 font_size: rems(0.875).into(),
23474 font_weight: settings.ui_font.weight,
23475 line_height: relative(settings.buffer_line_height.value()),
23476 ..Default::default()
23477 },
23478 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23479 color: cx.theme().colors().editor_foreground,
23480 font_family: settings.buffer_font.family.clone(),
23481 font_features: settings.buffer_font.features.clone(),
23482 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23483 font_size: settings.buffer_font_size(cx).into(),
23484 font_weight: settings.buffer_font.weight,
23485 line_height: relative(settings.buffer_line_height.value()),
23486 ..Default::default()
23487 },
23488 };
23489 if let Some(text_style_refinement) = &self.text_style_refinement {
23490 text_style.refine(text_style_refinement)
23491 }
23492
23493 let background = match self.mode {
23494 EditorMode::SingleLine => cx.theme().system().transparent,
23495 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23496 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23497 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23498 };
23499
23500 EditorElement::new(
23501 &cx.entity(),
23502 EditorStyle {
23503 background,
23504 border: cx.theme().colors().border,
23505 local_player: cx.theme().players().local(),
23506 text: text_style,
23507 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23508 syntax: cx.theme().syntax().clone(),
23509 status: cx.theme().status().clone(),
23510 inlay_hints_style: make_inlay_hints_style(cx),
23511 edit_prediction_styles: make_suggestion_styles(cx),
23512 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23513 show_underlines: self.diagnostics_enabled(),
23514 },
23515 )
23516 }
23517}
23518
23519impl EntityInputHandler for Editor {
23520 fn text_for_range(
23521 &mut self,
23522 range_utf16: Range<usize>,
23523 adjusted_range: &mut Option<Range<usize>>,
23524 _: &mut Window,
23525 cx: &mut Context<Self>,
23526 ) -> Option<String> {
23527 let snapshot = self.buffer.read(cx).read(cx);
23528 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23529 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23530 if (start.0..end.0) != range_utf16 {
23531 adjusted_range.replace(start.0..end.0);
23532 }
23533 Some(snapshot.text_for_range(start..end).collect())
23534 }
23535
23536 fn selected_text_range(
23537 &mut self,
23538 ignore_disabled_input: bool,
23539 _: &mut Window,
23540 cx: &mut Context<Self>,
23541 ) -> Option<UTF16Selection> {
23542 // Prevent the IME menu from appearing when holding down an alphabetic key
23543 // while input is disabled.
23544 if !ignore_disabled_input && !self.input_enabled {
23545 return None;
23546 }
23547
23548 let selection = self.selections.newest::<OffsetUtf16>(cx);
23549 let range = selection.range();
23550
23551 Some(UTF16Selection {
23552 range: range.start.0..range.end.0,
23553 reversed: selection.reversed,
23554 })
23555 }
23556
23557 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23558 let snapshot = self.buffer.read(cx).read(cx);
23559 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23560 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23561 }
23562
23563 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23564 self.clear_highlights::<InputComposition>(cx);
23565 self.ime_transaction.take();
23566 }
23567
23568 fn replace_text_in_range(
23569 &mut self,
23570 range_utf16: Option<Range<usize>>,
23571 text: &str,
23572 window: &mut Window,
23573 cx: &mut Context<Self>,
23574 ) {
23575 if !self.input_enabled {
23576 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23577 return;
23578 }
23579
23580 self.transact(window, cx, |this, window, cx| {
23581 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23582 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23583 Some(this.selection_replacement_ranges(range_utf16, cx))
23584 } else {
23585 this.marked_text_ranges(cx)
23586 };
23587
23588 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23589 let newest_selection_id = this.selections.newest_anchor().id;
23590 this.selections
23591 .all::<OffsetUtf16>(cx)
23592 .iter()
23593 .zip(ranges_to_replace.iter())
23594 .find_map(|(selection, range)| {
23595 if selection.id == newest_selection_id {
23596 Some(
23597 (range.start.0 as isize - selection.head().0 as isize)
23598 ..(range.end.0 as isize - selection.head().0 as isize),
23599 )
23600 } else {
23601 None
23602 }
23603 })
23604 });
23605
23606 cx.emit(EditorEvent::InputHandled {
23607 utf16_range_to_replace: range_to_replace,
23608 text: text.into(),
23609 });
23610
23611 if let Some(new_selected_ranges) = new_selected_ranges {
23612 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23613 selections.select_ranges(new_selected_ranges)
23614 });
23615 this.backspace(&Default::default(), window, cx);
23616 }
23617
23618 this.handle_input(text, window, cx);
23619 });
23620
23621 if let Some(transaction) = self.ime_transaction {
23622 self.buffer.update(cx, |buffer, cx| {
23623 buffer.group_until_transaction(transaction, cx);
23624 });
23625 }
23626
23627 self.unmark_text(window, cx);
23628 }
23629
23630 fn replace_and_mark_text_in_range(
23631 &mut self,
23632 range_utf16: Option<Range<usize>>,
23633 text: &str,
23634 new_selected_range_utf16: Option<Range<usize>>,
23635 window: &mut Window,
23636 cx: &mut Context<Self>,
23637 ) {
23638 if !self.input_enabled {
23639 return;
23640 }
23641
23642 let transaction = self.transact(window, cx, |this, window, cx| {
23643 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23644 let snapshot = this.buffer.read(cx).read(cx);
23645 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23646 for marked_range in &mut marked_ranges {
23647 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23648 marked_range.start.0 += relative_range_utf16.start;
23649 marked_range.start =
23650 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23651 marked_range.end =
23652 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23653 }
23654 }
23655 Some(marked_ranges)
23656 } else if let Some(range_utf16) = range_utf16 {
23657 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23658 Some(this.selection_replacement_ranges(range_utf16, cx))
23659 } else {
23660 None
23661 };
23662
23663 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23664 let newest_selection_id = this.selections.newest_anchor().id;
23665 this.selections
23666 .all::<OffsetUtf16>(cx)
23667 .iter()
23668 .zip(ranges_to_replace.iter())
23669 .find_map(|(selection, range)| {
23670 if selection.id == newest_selection_id {
23671 Some(
23672 (range.start.0 as isize - selection.head().0 as isize)
23673 ..(range.end.0 as isize - selection.head().0 as isize),
23674 )
23675 } else {
23676 None
23677 }
23678 })
23679 });
23680
23681 cx.emit(EditorEvent::InputHandled {
23682 utf16_range_to_replace: range_to_replace,
23683 text: text.into(),
23684 });
23685
23686 if let Some(ranges) = ranges_to_replace {
23687 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23688 s.select_ranges(ranges)
23689 });
23690 }
23691
23692 let marked_ranges = {
23693 let snapshot = this.buffer.read(cx).read(cx);
23694 this.selections
23695 .disjoint_anchors_arc()
23696 .iter()
23697 .map(|selection| {
23698 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23699 })
23700 .collect::<Vec<_>>()
23701 };
23702
23703 if text.is_empty() {
23704 this.unmark_text(window, cx);
23705 } else {
23706 this.highlight_text::<InputComposition>(
23707 marked_ranges.clone(),
23708 HighlightStyle {
23709 underline: Some(UnderlineStyle {
23710 thickness: px(1.),
23711 color: None,
23712 wavy: false,
23713 }),
23714 ..Default::default()
23715 },
23716 cx,
23717 );
23718 }
23719
23720 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23721 let use_autoclose = this.use_autoclose;
23722 let use_auto_surround = this.use_auto_surround;
23723 this.set_use_autoclose(false);
23724 this.set_use_auto_surround(false);
23725 this.handle_input(text, window, cx);
23726 this.set_use_autoclose(use_autoclose);
23727 this.set_use_auto_surround(use_auto_surround);
23728
23729 if let Some(new_selected_range) = new_selected_range_utf16 {
23730 let snapshot = this.buffer.read(cx).read(cx);
23731 let new_selected_ranges = marked_ranges
23732 .into_iter()
23733 .map(|marked_range| {
23734 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23735 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23736 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23737 snapshot.clip_offset_utf16(new_start, Bias::Left)
23738 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23739 })
23740 .collect::<Vec<_>>();
23741
23742 drop(snapshot);
23743 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23744 selections.select_ranges(new_selected_ranges)
23745 });
23746 }
23747 });
23748
23749 self.ime_transaction = self.ime_transaction.or(transaction);
23750 if let Some(transaction) = self.ime_transaction {
23751 self.buffer.update(cx, |buffer, cx| {
23752 buffer.group_until_transaction(transaction, cx);
23753 });
23754 }
23755
23756 if self.text_highlights::<InputComposition>(cx).is_none() {
23757 self.ime_transaction.take();
23758 }
23759 }
23760
23761 fn bounds_for_range(
23762 &mut self,
23763 range_utf16: Range<usize>,
23764 element_bounds: gpui::Bounds<Pixels>,
23765 window: &mut Window,
23766 cx: &mut Context<Self>,
23767 ) -> Option<gpui::Bounds<Pixels>> {
23768 let text_layout_details = self.text_layout_details(window);
23769 let CharacterDimensions {
23770 em_width,
23771 em_advance,
23772 line_height,
23773 } = self.character_dimensions(window);
23774
23775 let snapshot = self.snapshot(window, cx);
23776 let scroll_position = snapshot.scroll_position();
23777 let scroll_left = scroll_position.x * em_advance;
23778
23779 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23780 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23781 + self.gutter_dimensions.full_width();
23782 let y = line_height * (start.row().as_f32() - scroll_position.y);
23783
23784 Some(Bounds {
23785 origin: element_bounds.origin + point(x, y),
23786 size: size(em_width, line_height),
23787 })
23788 }
23789
23790 fn character_index_for_point(
23791 &mut self,
23792 point: gpui::Point<Pixels>,
23793 _window: &mut Window,
23794 _cx: &mut Context<Self>,
23795 ) -> Option<usize> {
23796 let position_map = self.last_position_map.as_ref()?;
23797 if !position_map.text_hitbox.contains(&point) {
23798 return None;
23799 }
23800 let display_point = position_map.point_for_position(point).previous_valid;
23801 let anchor = position_map
23802 .snapshot
23803 .display_point_to_anchor(display_point, Bias::Left);
23804 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23805 Some(utf16_offset.0)
23806 }
23807}
23808
23809trait SelectionExt {
23810 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23811 fn spanned_rows(
23812 &self,
23813 include_end_if_at_line_start: bool,
23814 map: &DisplaySnapshot,
23815 ) -> Range<MultiBufferRow>;
23816}
23817
23818impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23819 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23820 let start = self
23821 .start
23822 .to_point(&map.buffer_snapshot)
23823 .to_display_point(map);
23824 let end = self
23825 .end
23826 .to_point(&map.buffer_snapshot)
23827 .to_display_point(map);
23828 if self.reversed {
23829 end..start
23830 } else {
23831 start..end
23832 }
23833 }
23834
23835 fn spanned_rows(
23836 &self,
23837 include_end_if_at_line_start: bool,
23838 map: &DisplaySnapshot,
23839 ) -> Range<MultiBufferRow> {
23840 let start = self.start.to_point(&map.buffer_snapshot);
23841 let mut end = self.end.to_point(&map.buffer_snapshot);
23842 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23843 end.row -= 1;
23844 }
23845
23846 let buffer_start = map.prev_line_boundary(start).0;
23847 let buffer_end = map.next_line_boundary(end).0;
23848 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23849 }
23850}
23851
23852impl<T: InvalidationRegion> InvalidationStack<T> {
23853 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23854 where
23855 S: Clone + ToOffset,
23856 {
23857 while let Some(region) = self.last() {
23858 let all_selections_inside_invalidation_ranges =
23859 if selections.len() == region.ranges().len() {
23860 selections
23861 .iter()
23862 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23863 .all(|(selection, invalidation_range)| {
23864 let head = selection.head().to_offset(buffer);
23865 invalidation_range.start <= head && invalidation_range.end >= head
23866 })
23867 } else {
23868 false
23869 };
23870
23871 if all_selections_inside_invalidation_ranges {
23872 break;
23873 } else {
23874 self.pop();
23875 }
23876 }
23877 }
23878}
23879
23880impl<T> Default for InvalidationStack<T> {
23881 fn default() -> Self {
23882 Self(Default::default())
23883 }
23884}
23885
23886impl<T> Deref for InvalidationStack<T> {
23887 type Target = Vec<T>;
23888
23889 fn deref(&self) -> &Self::Target {
23890 &self.0
23891 }
23892}
23893
23894impl<T> DerefMut for InvalidationStack<T> {
23895 fn deref_mut(&mut self) -> &mut Self::Target {
23896 &mut self.0
23897 }
23898}
23899
23900impl InvalidationRegion for SnippetState {
23901 fn ranges(&self) -> &[Range<Anchor>] {
23902 &self.ranges[self.active_index]
23903 }
23904}
23905
23906fn edit_prediction_edit_text(
23907 current_snapshot: &BufferSnapshot,
23908 edits: &[(Range<Anchor>, String)],
23909 edit_preview: &EditPreview,
23910 include_deletions: bool,
23911 cx: &App,
23912) -> HighlightedText {
23913 let edits = edits
23914 .iter()
23915 .map(|(anchor, text)| {
23916 (
23917 anchor.start.text_anchor..anchor.end.text_anchor,
23918 text.clone(),
23919 )
23920 })
23921 .collect::<Vec<_>>();
23922
23923 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23924}
23925
23926fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23927 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23928 // Just show the raw edit text with basic styling
23929 let mut text = String::new();
23930 let mut highlights = Vec::new();
23931
23932 let insertion_highlight_style = HighlightStyle {
23933 color: Some(cx.theme().colors().text),
23934 ..Default::default()
23935 };
23936
23937 for (_, edit_text) in edits {
23938 let start_offset = text.len();
23939 text.push_str(edit_text);
23940 let end_offset = text.len();
23941
23942 if start_offset < end_offset {
23943 highlights.push((start_offset..end_offset, insertion_highlight_style));
23944 }
23945 }
23946
23947 HighlightedText {
23948 text: text.into(),
23949 highlights,
23950 }
23951}
23952
23953pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23954 match severity {
23955 lsp::DiagnosticSeverity::ERROR => colors.error,
23956 lsp::DiagnosticSeverity::WARNING => colors.warning,
23957 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23958 lsp::DiagnosticSeverity::HINT => colors.info,
23959 _ => colors.ignored,
23960 }
23961}
23962
23963pub fn styled_runs_for_code_label<'a>(
23964 label: &'a CodeLabel,
23965 syntax_theme: &'a theme::SyntaxTheme,
23966) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23967 let fade_out = HighlightStyle {
23968 fade_out: Some(0.35),
23969 ..Default::default()
23970 };
23971
23972 let mut prev_end = label.filter_range.end;
23973 label
23974 .runs
23975 .iter()
23976 .enumerate()
23977 .flat_map(move |(ix, (range, highlight_id))| {
23978 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23979 style
23980 } else {
23981 return Default::default();
23982 };
23983 let muted_style = style.highlight(fade_out);
23984
23985 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23986 if range.start >= label.filter_range.end {
23987 if range.start > prev_end {
23988 runs.push((prev_end..range.start, fade_out));
23989 }
23990 runs.push((range.clone(), muted_style));
23991 } else if range.end <= label.filter_range.end {
23992 runs.push((range.clone(), style));
23993 } else {
23994 runs.push((range.start..label.filter_range.end, style));
23995 runs.push((label.filter_range.end..range.end, muted_style));
23996 }
23997 prev_end = cmp::max(prev_end, range.end);
23998
23999 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24000 runs.push((prev_end..label.text.len(), fade_out));
24001 }
24002
24003 runs
24004 })
24005}
24006
24007pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24008 let mut prev_index = 0;
24009 let mut prev_codepoint: Option<char> = None;
24010 text.char_indices()
24011 .chain([(text.len(), '\0')])
24012 .filter_map(move |(index, codepoint)| {
24013 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24014 let is_boundary = index == text.len()
24015 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24016 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24017 if is_boundary {
24018 let chunk = &text[prev_index..index];
24019 prev_index = index;
24020 Some(chunk)
24021 } else {
24022 None
24023 }
24024 })
24025}
24026
24027pub trait RangeToAnchorExt: Sized {
24028 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24029
24030 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24031 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
24032 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24033 }
24034}
24035
24036impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24037 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24038 let start_offset = self.start.to_offset(snapshot);
24039 let end_offset = self.end.to_offset(snapshot);
24040 if start_offset == end_offset {
24041 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24042 } else {
24043 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24044 }
24045 }
24046}
24047
24048pub trait RowExt {
24049 fn as_f32(&self) -> f32;
24050
24051 fn next_row(&self) -> Self;
24052
24053 fn previous_row(&self) -> Self;
24054
24055 fn minus(&self, other: Self) -> u32;
24056}
24057
24058impl RowExt for DisplayRow {
24059 fn as_f32(&self) -> f32 {
24060 self.0 as f32
24061 }
24062
24063 fn next_row(&self) -> Self {
24064 Self(self.0 + 1)
24065 }
24066
24067 fn previous_row(&self) -> Self {
24068 Self(self.0.saturating_sub(1))
24069 }
24070
24071 fn minus(&self, other: Self) -> u32 {
24072 self.0 - other.0
24073 }
24074}
24075
24076impl RowExt for MultiBufferRow {
24077 fn as_f32(&self) -> f32 {
24078 self.0 as f32
24079 }
24080
24081 fn next_row(&self) -> Self {
24082 Self(self.0 + 1)
24083 }
24084
24085 fn previous_row(&self) -> Self {
24086 Self(self.0.saturating_sub(1))
24087 }
24088
24089 fn minus(&self, other: Self) -> u32 {
24090 self.0 - other.0
24091 }
24092}
24093
24094trait RowRangeExt {
24095 type Row;
24096
24097 fn len(&self) -> usize;
24098
24099 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24100}
24101
24102impl RowRangeExt for Range<MultiBufferRow> {
24103 type Row = MultiBufferRow;
24104
24105 fn len(&self) -> usize {
24106 (self.end.0 - self.start.0) as usize
24107 }
24108
24109 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24110 (self.start.0..self.end.0).map(MultiBufferRow)
24111 }
24112}
24113
24114impl RowRangeExt for Range<DisplayRow> {
24115 type Row = DisplayRow;
24116
24117 fn len(&self) -> usize {
24118 (self.end.0 - self.start.0) as usize
24119 }
24120
24121 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24122 (self.start.0..self.end.0).map(DisplayRow)
24123 }
24124}
24125
24126/// If select range has more than one line, we
24127/// just point the cursor to range.start.
24128fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24129 if range.start.row == range.end.row {
24130 range
24131 } else {
24132 range.start..range.start
24133 }
24134}
24135pub struct KillRing(ClipboardItem);
24136impl Global for KillRing {}
24137
24138const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24139
24140enum BreakpointPromptEditAction {
24141 Log,
24142 Condition,
24143 HitCondition,
24144}
24145
24146struct BreakpointPromptEditor {
24147 pub(crate) prompt: Entity<Editor>,
24148 editor: WeakEntity<Editor>,
24149 breakpoint_anchor: Anchor,
24150 breakpoint: Breakpoint,
24151 edit_action: BreakpointPromptEditAction,
24152 block_ids: HashSet<CustomBlockId>,
24153 editor_margins: Arc<Mutex<EditorMargins>>,
24154 _subscriptions: Vec<Subscription>,
24155}
24156
24157impl BreakpointPromptEditor {
24158 const MAX_LINES: u8 = 4;
24159
24160 fn new(
24161 editor: WeakEntity<Editor>,
24162 breakpoint_anchor: Anchor,
24163 breakpoint: Breakpoint,
24164 edit_action: BreakpointPromptEditAction,
24165 window: &mut Window,
24166 cx: &mut Context<Self>,
24167 ) -> Self {
24168 let base_text = match edit_action {
24169 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24170 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24171 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24172 }
24173 .map(|msg| msg.to_string())
24174 .unwrap_or_default();
24175
24176 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24177 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24178
24179 let prompt = cx.new(|cx| {
24180 let mut prompt = Editor::new(
24181 EditorMode::AutoHeight {
24182 min_lines: 1,
24183 max_lines: Some(Self::MAX_LINES as usize),
24184 },
24185 buffer,
24186 None,
24187 window,
24188 cx,
24189 );
24190 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24191 prompt.set_show_cursor_when_unfocused(false, cx);
24192 prompt.set_placeholder_text(
24193 match edit_action {
24194 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24195 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24196 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24197 },
24198 window,
24199 cx,
24200 );
24201
24202 prompt
24203 });
24204
24205 Self {
24206 prompt,
24207 editor,
24208 breakpoint_anchor,
24209 breakpoint,
24210 edit_action,
24211 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24212 block_ids: Default::default(),
24213 _subscriptions: vec![],
24214 }
24215 }
24216
24217 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24218 self.block_ids.extend(block_ids)
24219 }
24220
24221 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24222 if let Some(editor) = self.editor.upgrade() {
24223 let message = self
24224 .prompt
24225 .read(cx)
24226 .buffer
24227 .read(cx)
24228 .as_singleton()
24229 .expect("A multi buffer in breakpoint prompt isn't possible")
24230 .read(cx)
24231 .as_rope()
24232 .to_string();
24233
24234 editor.update(cx, |editor, cx| {
24235 editor.edit_breakpoint_at_anchor(
24236 self.breakpoint_anchor,
24237 self.breakpoint.clone(),
24238 match self.edit_action {
24239 BreakpointPromptEditAction::Log => {
24240 BreakpointEditAction::EditLogMessage(message.into())
24241 }
24242 BreakpointPromptEditAction::Condition => {
24243 BreakpointEditAction::EditCondition(message.into())
24244 }
24245 BreakpointPromptEditAction::HitCondition => {
24246 BreakpointEditAction::EditHitCondition(message.into())
24247 }
24248 },
24249 cx,
24250 );
24251
24252 editor.remove_blocks(self.block_ids.clone(), None, cx);
24253 cx.focus_self(window);
24254 });
24255 }
24256 }
24257
24258 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24259 self.editor
24260 .update(cx, |editor, cx| {
24261 editor.remove_blocks(self.block_ids.clone(), None, cx);
24262 window.focus(&editor.focus_handle);
24263 })
24264 .log_err();
24265 }
24266
24267 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24268 let settings = ThemeSettings::get_global(cx);
24269 let text_style = TextStyle {
24270 color: if self.prompt.read(cx).read_only(cx) {
24271 cx.theme().colors().text_disabled
24272 } else {
24273 cx.theme().colors().text
24274 },
24275 font_family: settings.buffer_font.family.clone(),
24276 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24277 font_size: settings.buffer_font_size(cx).into(),
24278 font_weight: settings.buffer_font.weight,
24279 line_height: relative(settings.buffer_line_height.value()),
24280 ..Default::default()
24281 };
24282 EditorElement::new(
24283 &self.prompt,
24284 EditorStyle {
24285 background: cx.theme().colors().editor_background,
24286 local_player: cx.theme().players().local(),
24287 text: text_style,
24288 ..Default::default()
24289 },
24290 )
24291 }
24292}
24293
24294impl Render for BreakpointPromptEditor {
24295 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24296 let editor_margins = *self.editor_margins.lock();
24297 let gutter_dimensions = editor_margins.gutter;
24298 h_flex()
24299 .key_context("Editor")
24300 .bg(cx.theme().colors().editor_background)
24301 .border_y_1()
24302 .border_color(cx.theme().status().info_border)
24303 .size_full()
24304 .py(window.line_height() / 2.5)
24305 .on_action(cx.listener(Self::confirm))
24306 .on_action(cx.listener(Self::cancel))
24307 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24308 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24309 }
24310}
24311
24312impl Focusable for BreakpointPromptEditor {
24313 fn focus_handle(&self, cx: &App) -> FocusHandle {
24314 self.prompt.focus_handle(cx)
24315 }
24316}
24317
24318fn all_edits_insertions_or_deletions(
24319 edits: &Vec<(Range<Anchor>, String)>,
24320 snapshot: &MultiBufferSnapshot,
24321) -> bool {
24322 let mut all_insertions = true;
24323 let mut all_deletions = true;
24324
24325 for (range, new_text) in edits.iter() {
24326 let range_is_empty = range.to_offset(snapshot).is_empty();
24327 let text_is_empty = new_text.is_empty();
24328
24329 if range_is_empty != text_is_empty {
24330 if range_is_empty {
24331 all_deletions = false;
24332 } else {
24333 all_insertions = false;
24334 }
24335 } else {
24336 return false;
24337 }
24338
24339 if !all_insertions && !all_deletions {
24340 return false;
24341 }
24342 }
24343 all_insertions || all_deletions
24344}
24345
24346struct MissingEditPredictionKeybindingTooltip;
24347
24348impl Render for MissingEditPredictionKeybindingTooltip {
24349 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24350 ui::tooltip_container(window, cx, |container, _, cx| {
24351 container
24352 .flex_shrink_0()
24353 .max_w_80()
24354 .min_h(rems_from_px(124.))
24355 .justify_between()
24356 .child(
24357 v_flex()
24358 .flex_1()
24359 .text_ui_sm(cx)
24360 .child(Label::new("Conflict with Accept Keybinding"))
24361 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24362 )
24363 .child(
24364 h_flex()
24365 .pb_1()
24366 .gap_1()
24367 .items_end()
24368 .w_full()
24369 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24370 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24371 }))
24372 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24373 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24374 })),
24375 )
24376 })
24377 }
24378}
24379
24380#[derive(Debug, Clone, Copy, PartialEq)]
24381pub struct LineHighlight {
24382 pub background: Background,
24383 pub border: Option<gpui::Hsla>,
24384 pub include_gutter: bool,
24385 pub type_id: Option<TypeId>,
24386}
24387
24388struct LineManipulationResult {
24389 pub new_text: String,
24390 pub line_count_before: usize,
24391 pub line_count_after: usize,
24392}
24393
24394fn render_diff_hunk_controls(
24395 row: u32,
24396 status: &DiffHunkStatus,
24397 hunk_range: Range<Anchor>,
24398 is_created_file: bool,
24399 line_height: Pixels,
24400 editor: &Entity<Editor>,
24401 _window: &mut Window,
24402 cx: &mut App,
24403) -> AnyElement {
24404 h_flex()
24405 .h(line_height)
24406 .mr_1()
24407 .gap_1()
24408 .px_0p5()
24409 .pb_1()
24410 .border_x_1()
24411 .border_b_1()
24412 .border_color(cx.theme().colors().border_variant)
24413 .rounded_b_lg()
24414 .bg(cx.theme().colors().editor_background)
24415 .gap_1()
24416 .block_mouse_except_scroll()
24417 .shadow_md()
24418 .child(if status.has_secondary_hunk() {
24419 Button::new(("stage", row as u64), "Stage")
24420 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24421 .tooltip({
24422 let focus_handle = editor.focus_handle(cx);
24423 move |window, cx| {
24424 Tooltip::for_action_in(
24425 "Stage Hunk",
24426 &::git::ToggleStaged,
24427 &focus_handle,
24428 window,
24429 cx,
24430 )
24431 }
24432 })
24433 .on_click({
24434 let editor = editor.clone();
24435 move |_event, _window, cx| {
24436 editor.update(cx, |editor, cx| {
24437 editor.stage_or_unstage_diff_hunks(
24438 true,
24439 vec![hunk_range.start..hunk_range.start],
24440 cx,
24441 );
24442 });
24443 }
24444 })
24445 } else {
24446 Button::new(("unstage", row as u64), "Unstage")
24447 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24448 .tooltip({
24449 let focus_handle = editor.focus_handle(cx);
24450 move |window, cx| {
24451 Tooltip::for_action_in(
24452 "Unstage Hunk",
24453 &::git::ToggleStaged,
24454 &focus_handle,
24455 window,
24456 cx,
24457 )
24458 }
24459 })
24460 .on_click({
24461 let editor = editor.clone();
24462 move |_event, _window, cx| {
24463 editor.update(cx, |editor, cx| {
24464 editor.stage_or_unstage_diff_hunks(
24465 false,
24466 vec![hunk_range.start..hunk_range.start],
24467 cx,
24468 );
24469 });
24470 }
24471 })
24472 })
24473 .child(
24474 Button::new(("restore", row as u64), "Restore")
24475 .tooltip({
24476 let focus_handle = editor.focus_handle(cx);
24477 move |window, cx| {
24478 Tooltip::for_action_in(
24479 "Restore Hunk",
24480 &::git::Restore,
24481 &focus_handle,
24482 window,
24483 cx,
24484 )
24485 }
24486 })
24487 .on_click({
24488 let editor = editor.clone();
24489 move |_event, window, cx| {
24490 editor.update(cx, |editor, cx| {
24491 let snapshot = editor.snapshot(window, cx);
24492 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24493 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24494 });
24495 }
24496 })
24497 .disabled(is_created_file),
24498 )
24499 .when(
24500 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24501 |el| {
24502 el.child(
24503 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24504 .shape(IconButtonShape::Square)
24505 .icon_size(IconSize::Small)
24506 // .disabled(!has_multiple_hunks)
24507 .tooltip({
24508 let focus_handle = editor.focus_handle(cx);
24509 move |window, cx| {
24510 Tooltip::for_action_in(
24511 "Next Hunk",
24512 &GoToHunk,
24513 &focus_handle,
24514 window,
24515 cx,
24516 )
24517 }
24518 })
24519 .on_click({
24520 let editor = editor.clone();
24521 move |_event, window, cx| {
24522 editor.update(cx, |editor, cx| {
24523 let snapshot = editor.snapshot(window, cx);
24524 let position =
24525 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24526 editor.go_to_hunk_before_or_after_position(
24527 &snapshot,
24528 position,
24529 Direction::Next,
24530 window,
24531 cx,
24532 );
24533 editor.expand_selected_diff_hunks(cx);
24534 });
24535 }
24536 }),
24537 )
24538 .child(
24539 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24540 .shape(IconButtonShape::Square)
24541 .icon_size(IconSize::Small)
24542 // .disabled(!has_multiple_hunks)
24543 .tooltip({
24544 let focus_handle = editor.focus_handle(cx);
24545 move |window, cx| {
24546 Tooltip::for_action_in(
24547 "Previous Hunk",
24548 &GoToPreviousHunk,
24549 &focus_handle,
24550 window,
24551 cx,
24552 )
24553 }
24554 })
24555 .on_click({
24556 let editor = editor.clone();
24557 move |_event, window, cx| {
24558 editor.update(cx, |editor, cx| {
24559 let snapshot = editor.snapshot(window, cx);
24560 let point =
24561 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24562 editor.go_to_hunk_before_or_after_position(
24563 &snapshot,
24564 point,
24565 Direction::Prev,
24566 window,
24567 cx,
24568 );
24569 editor.expand_selected_diff_hunks(cx);
24570 });
24571 }
24572 }),
24573 )
24574 },
24575 )
24576 .into_any_element()
24577}
24578
24579pub fn multibuffer_context_lines(cx: &App) -> u32 {
24580 EditorSettings::try_get(cx)
24581 .map(|settings| settings.excerpt_context_lines)
24582 .unwrap_or(2)
24583 .min(32)
24584}