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 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 self.update_visible_edit_prediction(window, cx);
7156
7157 if !user_requested
7158 && (!self.should_show_edit_predictions()
7159 || !self.is_focused(window)
7160 || buffer.read(cx).is_empty())
7161 {
7162 self.discard_edit_prediction(false, cx);
7163 return None;
7164 }
7165
7166 provider.refresh(
7167 self.project.clone(),
7168 buffer,
7169 cursor_buffer_position,
7170 debounce,
7171 cx,
7172 );
7173 Some(())
7174 }
7175
7176 fn show_edit_predictions_in_menu(&self) -> bool {
7177 match self.edit_prediction_settings {
7178 EditPredictionSettings::Disabled => false,
7179 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7180 }
7181 }
7182
7183 pub fn edit_predictions_enabled(&self) -> bool {
7184 match self.edit_prediction_settings {
7185 EditPredictionSettings::Disabled => false,
7186 EditPredictionSettings::Enabled { .. } => true,
7187 }
7188 }
7189
7190 fn edit_prediction_requires_modifier(&self) -> bool {
7191 match self.edit_prediction_settings {
7192 EditPredictionSettings::Disabled => false,
7193 EditPredictionSettings::Enabled {
7194 preview_requires_modifier,
7195 ..
7196 } => preview_requires_modifier,
7197 }
7198 }
7199
7200 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7201 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7202 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7203 self.discard_edit_prediction(false, cx);
7204 } else {
7205 let selection = self.selections.newest_anchor();
7206 let cursor = selection.head();
7207
7208 if let Some((buffer, cursor_buffer_position)) =
7209 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7210 {
7211 self.edit_prediction_settings =
7212 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7213 }
7214 }
7215 }
7216
7217 fn edit_prediction_settings_at_position(
7218 &self,
7219 buffer: &Entity<Buffer>,
7220 buffer_position: language::Anchor,
7221 cx: &App,
7222 ) -> EditPredictionSettings {
7223 if !self.mode.is_full()
7224 || !self.show_edit_predictions_override.unwrap_or(true)
7225 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7226 {
7227 return EditPredictionSettings::Disabled;
7228 }
7229
7230 let buffer = buffer.read(cx);
7231
7232 let file = buffer.file();
7233
7234 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7235 return EditPredictionSettings::Disabled;
7236 };
7237
7238 let by_provider = matches!(
7239 self.menu_edit_predictions_policy,
7240 MenuEditPredictionsPolicy::ByProvider
7241 );
7242
7243 let show_in_menu = by_provider
7244 && self
7245 .edit_prediction_provider
7246 .as_ref()
7247 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7248
7249 let preview_requires_modifier =
7250 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7251
7252 EditPredictionSettings::Enabled {
7253 show_in_menu,
7254 preview_requires_modifier,
7255 }
7256 }
7257
7258 fn should_show_edit_predictions(&self) -> bool {
7259 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7260 }
7261
7262 pub fn edit_prediction_preview_is_active(&self) -> bool {
7263 matches!(
7264 self.edit_prediction_preview,
7265 EditPredictionPreview::Active { .. }
7266 )
7267 }
7268
7269 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7270 let cursor = self.selections.newest_anchor().head();
7271 if let Some((buffer, cursor_position)) =
7272 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7273 {
7274 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7275 } else {
7276 false
7277 }
7278 }
7279
7280 pub fn supports_minimap(&self, cx: &App) -> bool {
7281 !self.minimap_visibility.disabled() && self.is_singleton(cx)
7282 }
7283
7284 fn edit_predictions_enabled_in_buffer(
7285 &self,
7286 buffer: &Entity<Buffer>,
7287 buffer_position: language::Anchor,
7288 cx: &App,
7289 ) -> bool {
7290 maybe!({
7291 if self.read_only(cx) {
7292 return Some(false);
7293 }
7294 let provider = self.edit_prediction_provider()?;
7295 if !provider.is_enabled(buffer, buffer_position, cx) {
7296 return Some(false);
7297 }
7298 let buffer = buffer.read(cx);
7299 let Some(file) = buffer.file() else {
7300 return Some(true);
7301 };
7302 let settings = all_language_settings(Some(file), cx);
7303 Some(settings.edit_predictions_enabled_for_file(file, cx))
7304 })
7305 .unwrap_or(false)
7306 }
7307
7308 fn cycle_edit_prediction(
7309 &mut self,
7310 direction: Direction,
7311 window: &mut Window,
7312 cx: &mut Context<Self>,
7313 ) -> Option<()> {
7314 let provider = self.edit_prediction_provider()?;
7315 let cursor = self.selections.newest_anchor().head();
7316 let (buffer, cursor_buffer_position) =
7317 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7318 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7319 return None;
7320 }
7321
7322 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7323 self.update_visible_edit_prediction(window, cx);
7324
7325 Some(())
7326 }
7327
7328 pub fn show_edit_prediction(
7329 &mut self,
7330 _: &ShowEditPrediction,
7331 window: &mut Window,
7332 cx: &mut Context<Self>,
7333 ) {
7334 if !self.has_active_edit_prediction() {
7335 self.refresh_edit_prediction(false, true, window, cx);
7336 return;
7337 }
7338
7339 self.update_visible_edit_prediction(window, cx);
7340 }
7341
7342 pub fn display_cursor_names(
7343 &mut self,
7344 _: &DisplayCursorNames,
7345 window: &mut Window,
7346 cx: &mut Context<Self>,
7347 ) {
7348 self.show_cursor_names(window, cx);
7349 }
7350
7351 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7352 self.show_cursor_names = true;
7353 cx.notify();
7354 cx.spawn_in(window, async move |this, cx| {
7355 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7356 this.update(cx, |this, cx| {
7357 this.show_cursor_names = false;
7358 cx.notify()
7359 })
7360 .ok()
7361 })
7362 .detach();
7363 }
7364
7365 pub fn next_edit_prediction(
7366 &mut self,
7367 _: &NextEditPrediction,
7368 window: &mut Window,
7369 cx: &mut Context<Self>,
7370 ) {
7371 if self.has_active_edit_prediction() {
7372 self.cycle_edit_prediction(Direction::Next, window, cx);
7373 } else {
7374 let is_copilot_disabled = self
7375 .refresh_edit_prediction(false, true, window, cx)
7376 .is_none();
7377 if is_copilot_disabled {
7378 cx.propagate();
7379 }
7380 }
7381 }
7382
7383 pub fn previous_edit_prediction(
7384 &mut self,
7385 _: &PreviousEditPrediction,
7386 window: &mut Window,
7387 cx: &mut Context<Self>,
7388 ) {
7389 if self.has_active_edit_prediction() {
7390 self.cycle_edit_prediction(Direction::Prev, window, cx);
7391 } else {
7392 let is_copilot_disabled = self
7393 .refresh_edit_prediction(false, true, window, cx)
7394 .is_none();
7395 if is_copilot_disabled {
7396 cx.propagate();
7397 }
7398 }
7399 }
7400
7401 pub fn accept_edit_prediction(
7402 &mut self,
7403 _: &AcceptEditPrediction,
7404 window: &mut Window,
7405 cx: &mut Context<Self>,
7406 ) {
7407 if self.show_edit_predictions_in_menu() {
7408 self.hide_context_menu(window, cx);
7409 }
7410
7411 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7412 return;
7413 };
7414
7415 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7416
7417 match &active_edit_prediction.completion {
7418 EditPrediction::Move { target, .. } => {
7419 let target = *target;
7420
7421 if let Some(position_map) = &self.last_position_map {
7422 if position_map
7423 .visible_row_range
7424 .contains(&target.to_display_point(&position_map.snapshot).row())
7425 || !self.edit_prediction_requires_modifier()
7426 {
7427 self.unfold_ranges(&[target..target], true, false, cx);
7428 // Note that this is also done in vim's handler of the Tab action.
7429 self.change_selections(
7430 SelectionEffects::scroll(Autoscroll::newest()),
7431 window,
7432 cx,
7433 |selections| {
7434 selections.select_anchor_ranges([target..target]);
7435 },
7436 );
7437 self.clear_row_highlights::<EditPredictionPreview>();
7438
7439 self.edit_prediction_preview
7440 .set_previous_scroll_position(None);
7441 } else {
7442 self.edit_prediction_preview
7443 .set_previous_scroll_position(Some(
7444 position_map.snapshot.scroll_anchor,
7445 ));
7446
7447 self.highlight_rows::<EditPredictionPreview>(
7448 target..target,
7449 cx.theme().colors().editor_highlighted_line_background,
7450 RowHighlightOptions {
7451 autoscroll: true,
7452 ..Default::default()
7453 },
7454 cx,
7455 );
7456 self.request_autoscroll(Autoscroll::fit(), cx);
7457 }
7458 }
7459 }
7460 EditPrediction::Edit { edits, .. } => {
7461 if let Some(provider) = self.edit_prediction_provider() {
7462 provider.accept(cx);
7463 }
7464
7465 // Store the transaction ID and selections before applying the edit
7466 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7467
7468 let snapshot = self.buffer.read(cx).snapshot(cx);
7469 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7470
7471 self.buffer.update(cx, |buffer, cx| {
7472 buffer.edit(edits.iter().cloned(), None, cx)
7473 });
7474
7475 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7476 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7477 });
7478
7479 let selections = self.selections.disjoint_anchors_arc();
7480 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7481 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7482 if has_new_transaction {
7483 self.selection_history
7484 .insert_transaction(transaction_id_now, selections);
7485 }
7486 }
7487
7488 self.update_visible_edit_prediction(window, cx);
7489 if self.active_edit_prediction.is_none() {
7490 self.refresh_edit_prediction(true, true, window, cx);
7491 }
7492
7493 cx.notify();
7494 }
7495 }
7496
7497 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7498 }
7499
7500 pub fn accept_partial_edit_prediction(
7501 &mut self,
7502 _: &AcceptPartialEditPrediction,
7503 window: &mut Window,
7504 cx: &mut Context<Self>,
7505 ) {
7506 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7507 return;
7508 };
7509 if self.selections.count() != 1 {
7510 return;
7511 }
7512
7513 self.report_edit_prediction_event(active_edit_prediction.completion_id.clone(), true, cx);
7514
7515 match &active_edit_prediction.completion {
7516 EditPrediction::Move { target, .. } => {
7517 let target = *target;
7518 self.change_selections(
7519 SelectionEffects::scroll(Autoscroll::newest()),
7520 window,
7521 cx,
7522 |selections| {
7523 selections.select_anchor_ranges([target..target]);
7524 },
7525 );
7526 }
7527 EditPrediction::Edit { edits, .. } => {
7528 // Find an insertion that starts at the cursor position.
7529 let snapshot = self.buffer.read(cx).snapshot(cx);
7530 let cursor_offset = self.selections.newest::<usize>(cx).head();
7531 let insertion = edits.iter().find_map(|(range, text)| {
7532 let range = range.to_offset(&snapshot);
7533 if range.is_empty() && range.start == cursor_offset {
7534 Some(text)
7535 } else {
7536 None
7537 }
7538 });
7539
7540 if let Some(text) = insertion {
7541 let mut partial_completion = text
7542 .chars()
7543 .by_ref()
7544 .take_while(|c| c.is_alphabetic())
7545 .collect::<String>();
7546 if partial_completion.is_empty() {
7547 partial_completion = text
7548 .chars()
7549 .by_ref()
7550 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7551 .collect::<String>();
7552 }
7553
7554 cx.emit(EditorEvent::InputHandled {
7555 utf16_range_to_replace: None,
7556 text: partial_completion.clone().into(),
7557 });
7558
7559 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7560
7561 self.refresh_edit_prediction(true, true, window, cx);
7562 cx.notify();
7563 } else {
7564 self.accept_edit_prediction(&Default::default(), window, cx);
7565 }
7566 }
7567 }
7568 }
7569
7570 fn discard_edit_prediction(
7571 &mut self,
7572 should_report_edit_prediction_event: bool,
7573 cx: &mut Context<Self>,
7574 ) -> bool {
7575 if should_report_edit_prediction_event {
7576 let completion_id = self
7577 .active_edit_prediction
7578 .as_ref()
7579 .and_then(|active_completion| active_completion.completion_id.clone());
7580
7581 self.report_edit_prediction_event(completion_id, false, cx);
7582 }
7583
7584 if let Some(provider) = self.edit_prediction_provider() {
7585 provider.discard(cx);
7586 }
7587
7588 self.take_active_edit_prediction(cx)
7589 }
7590
7591 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7592 let Some(provider) = self.edit_prediction_provider() else {
7593 return;
7594 };
7595
7596 let Some((_, buffer, _)) = self
7597 .buffer
7598 .read(cx)
7599 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7600 else {
7601 return;
7602 };
7603
7604 let extension = buffer
7605 .read(cx)
7606 .file()
7607 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7608
7609 let event_type = match accepted {
7610 true => "Edit Prediction Accepted",
7611 false => "Edit Prediction Discarded",
7612 };
7613 telemetry::event!(
7614 event_type,
7615 provider = provider.name(),
7616 prediction_id = id,
7617 suggestion_accepted = accepted,
7618 file_extension = extension,
7619 );
7620 }
7621
7622 pub fn has_active_edit_prediction(&self) -> bool {
7623 self.active_edit_prediction.is_some()
7624 }
7625
7626 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7627 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7628 return false;
7629 };
7630
7631 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7632 self.clear_highlights::<EditPredictionHighlight>(cx);
7633 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7634 true
7635 }
7636
7637 /// Returns true when we're displaying the edit prediction popover below the cursor
7638 /// like we are not previewing and the LSP autocomplete menu is visible
7639 /// or we are in `when_holding_modifier` mode.
7640 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7641 if self.edit_prediction_preview_is_active()
7642 || !self.show_edit_predictions_in_menu()
7643 || !self.edit_predictions_enabled()
7644 {
7645 return false;
7646 }
7647
7648 if self.has_visible_completions_menu() {
7649 return true;
7650 }
7651
7652 has_completion && self.edit_prediction_requires_modifier()
7653 }
7654
7655 fn handle_modifiers_changed(
7656 &mut self,
7657 modifiers: Modifiers,
7658 position_map: &PositionMap,
7659 window: &mut Window,
7660 cx: &mut Context<Self>,
7661 ) {
7662 if self.show_edit_predictions_in_menu() {
7663 self.update_edit_prediction_preview(&modifiers, window, cx);
7664 }
7665
7666 self.update_selection_mode(&modifiers, position_map, window, cx);
7667
7668 let mouse_position = window.mouse_position();
7669 if !position_map.text_hitbox.is_hovered(window) {
7670 return;
7671 }
7672
7673 self.update_hovered_link(
7674 position_map.point_for_position(mouse_position),
7675 &position_map.snapshot,
7676 modifiers,
7677 window,
7678 cx,
7679 )
7680 }
7681
7682 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7683 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7684 if invert {
7685 match multi_cursor_setting {
7686 MultiCursorModifier::Alt => modifiers.alt,
7687 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7688 }
7689 } else {
7690 match multi_cursor_setting {
7691 MultiCursorModifier::Alt => modifiers.secondary(),
7692 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7693 }
7694 }
7695 }
7696
7697 fn columnar_selection_mode(
7698 modifiers: &Modifiers,
7699 cx: &mut Context<Self>,
7700 ) -> Option<ColumnarMode> {
7701 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7702 if Self::multi_cursor_modifier(false, modifiers, cx) {
7703 Some(ColumnarMode::FromMouse)
7704 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7705 Some(ColumnarMode::FromSelection)
7706 } else {
7707 None
7708 }
7709 } else {
7710 None
7711 }
7712 }
7713
7714 fn update_selection_mode(
7715 &mut self,
7716 modifiers: &Modifiers,
7717 position_map: &PositionMap,
7718 window: &mut Window,
7719 cx: &mut Context<Self>,
7720 ) {
7721 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7722 return;
7723 };
7724 if self.selections.pending_anchor().is_none() {
7725 return;
7726 }
7727
7728 let mouse_position = window.mouse_position();
7729 let point_for_position = position_map.point_for_position(mouse_position);
7730 let position = point_for_position.previous_valid;
7731
7732 self.select(
7733 SelectPhase::BeginColumnar {
7734 position,
7735 reset: false,
7736 mode,
7737 goal_column: point_for_position.exact_unclipped.column(),
7738 },
7739 window,
7740 cx,
7741 );
7742 }
7743
7744 fn update_edit_prediction_preview(
7745 &mut self,
7746 modifiers: &Modifiers,
7747 window: &mut Window,
7748 cx: &mut Context<Self>,
7749 ) {
7750 let mut modifiers_held = false;
7751 if let Some(accept_keystroke) = self
7752 .accept_edit_prediction_keybind(false, window, cx)
7753 .keystroke()
7754 {
7755 modifiers_held = modifiers_held
7756 || (accept_keystroke.modifiers() == modifiers
7757 && accept_keystroke.modifiers().modified());
7758 };
7759 if let Some(accept_partial_keystroke) = self
7760 .accept_edit_prediction_keybind(true, window, cx)
7761 .keystroke()
7762 {
7763 modifiers_held = modifiers_held
7764 || (accept_partial_keystroke.modifiers() == modifiers
7765 && accept_partial_keystroke.modifiers().modified());
7766 }
7767
7768 if modifiers_held {
7769 if matches!(
7770 self.edit_prediction_preview,
7771 EditPredictionPreview::Inactive { .. }
7772 ) {
7773 self.edit_prediction_preview = EditPredictionPreview::Active {
7774 previous_scroll_position: None,
7775 since: Instant::now(),
7776 };
7777
7778 self.update_visible_edit_prediction(window, cx);
7779 cx.notify();
7780 }
7781 } else if let EditPredictionPreview::Active {
7782 previous_scroll_position,
7783 since,
7784 } = self.edit_prediction_preview
7785 {
7786 if let (Some(previous_scroll_position), Some(position_map)) =
7787 (previous_scroll_position, self.last_position_map.as_ref())
7788 {
7789 self.set_scroll_position(
7790 previous_scroll_position
7791 .scroll_position(&position_map.snapshot.display_snapshot),
7792 window,
7793 cx,
7794 );
7795 }
7796
7797 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7798 released_too_fast: since.elapsed() < Duration::from_millis(200),
7799 };
7800 self.clear_row_highlights::<EditPredictionPreview>();
7801 self.update_visible_edit_prediction(window, cx);
7802 cx.notify();
7803 }
7804 }
7805
7806 fn update_visible_edit_prediction(
7807 &mut self,
7808 _window: &mut Window,
7809 cx: &mut Context<Self>,
7810 ) -> Option<()> {
7811 if DisableAiSettings::get_global(cx).disable_ai {
7812 return None;
7813 }
7814
7815 if self.ime_transaction.is_some() {
7816 self.discard_edit_prediction(false, cx);
7817 return None;
7818 }
7819
7820 let selection = self.selections.newest_anchor();
7821 let cursor = selection.head();
7822 let multibuffer = self.buffer.read(cx).snapshot(cx);
7823 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7824 let excerpt_id = cursor.excerpt_id;
7825
7826 let show_in_menu = self.show_edit_predictions_in_menu();
7827 let completions_menu_has_precedence = !show_in_menu
7828 && (self.context_menu.borrow().is_some()
7829 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7830
7831 if completions_menu_has_precedence
7832 || !offset_selection.is_empty()
7833 || self
7834 .active_edit_prediction
7835 .as_ref()
7836 .is_some_and(|completion| {
7837 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7838 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7839 !invalidation_range.contains(&offset_selection.head())
7840 })
7841 {
7842 self.discard_edit_prediction(false, cx);
7843 return None;
7844 }
7845
7846 self.take_active_edit_prediction(cx);
7847 let Some(provider) = self.edit_prediction_provider() else {
7848 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7849 return None;
7850 };
7851
7852 let (buffer, cursor_buffer_position) =
7853 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7854
7855 self.edit_prediction_settings =
7856 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7857
7858 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7859
7860 if self.edit_prediction_indent_conflict {
7861 let cursor_point = cursor.to_point(&multibuffer);
7862
7863 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7864
7865 if let Some((_, indent)) = indents.iter().next()
7866 && indent.len == cursor_point.column
7867 {
7868 self.edit_prediction_indent_conflict = false;
7869 }
7870 }
7871
7872 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7873 let edits = edit_prediction
7874 .edits
7875 .into_iter()
7876 .flat_map(|(range, new_text)| {
7877 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7878 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7879 Some((start..end, new_text))
7880 })
7881 .collect::<Vec<_>>();
7882 if edits.is_empty() {
7883 return None;
7884 }
7885
7886 let first_edit_start = edits.first().unwrap().0.start;
7887 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7888 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7889
7890 let last_edit_end = edits.last().unwrap().0.end;
7891 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7892 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7893
7894 let cursor_row = cursor.to_point(&multibuffer).row;
7895
7896 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7897
7898 let mut inlay_ids = Vec::new();
7899 let invalidation_row_range;
7900 let move_invalidation_row_range = if cursor_row < edit_start_row {
7901 Some(cursor_row..edit_end_row)
7902 } else if cursor_row > edit_end_row {
7903 Some(edit_start_row..cursor_row)
7904 } else {
7905 None
7906 };
7907 let supports_jump = self
7908 .edit_prediction_provider
7909 .as_ref()
7910 .map(|provider| provider.provider.supports_jump_to_edit())
7911 .unwrap_or(true);
7912
7913 let is_move = supports_jump
7914 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7915 let completion = if is_move {
7916 invalidation_row_range =
7917 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7918 let target = first_edit_start;
7919 EditPrediction::Move { target, snapshot }
7920 } else {
7921 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7922 && !self.edit_predictions_hidden_for_vim_mode;
7923
7924 if show_completions_in_buffer {
7925 if edits
7926 .iter()
7927 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7928 {
7929 let mut inlays = Vec::new();
7930 for (range, new_text) in &edits {
7931 let inlay = Inlay::edit_prediction(
7932 post_inc(&mut self.next_inlay_id),
7933 range.start,
7934 new_text.as_str(),
7935 );
7936 inlay_ids.push(inlay.id);
7937 inlays.push(inlay);
7938 }
7939
7940 self.splice_inlays(&[], inlays, cx);
7941 } else {
7942 let background_color = cx.theme().status().deleted_background;
7943 self.highlight_text::<EditPredictionHighlight>(
7944 edits.iter().map(|(range, _)| range.clone()).collect(),
7945 HighlightStyle {
7946 background_color: Some(background_color),
7947 ..Default::default()
7948 },
7949 cx,
7950 );
7951 }
7952 }
7953
7954 invalidation_row_range = edit_start_row..edit_end_row;
7955
7956 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7957 if provider.show_tab_accept_marker() {
7958 EditDisplayMode::TabAccept
7959 } else {
7960 EditDisplayMode::Inline
7961 }
7962 } else {
7963 EditDisplayMode::DiffPopover
7964 };
7965
7966 EditPrediction::Edit {
7967 edits,
7968 edit_preview: edit_prediction.edit_preview,
7969 display_mode,
7970 snapshot,
7971 }
7972 };
7973
7974 let invalidation_range = multibuffer
7975 .anchor_before(Point::new(invalidation_row_range.start, 0))
7976 ..multibuffer.anchor_after(Point::new(
7977 invalidation_row_range.end,
7978 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7979 ));
7980
7981 self.stale_edit_prediction_in_menu = None;
7982 self.active_edit_prediction = Some(EditPredictionState {
7983 inlay_ids,
7984 completion,
7985 completion_id: edit_prediction.id,
7986 invalidation_range,
7987 });
7988
7989 cx.notify();
7990
7991 Some(())
7992 }
7993
7994 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7995 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7996 }
7997
7998 fn clear_tasks(&mut self) {
7999 self.tasks.clear()
8000 }
8001
8002 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8003 if self.tasks.insert(key, value).is_some() {
8004 // This case should hopefully be rare, but just in case...
8005 log::error!(
8006 "multiple different run targets found on a single line, only the last target will be rendered"
8007 )
8008 }
8009 }
8010
8011 /// Get all display points of breakpoints that will be rendered within editor
8012 ///
8013 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8014 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8015 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8016 fn active_breakpoints(
8017 &self,
8018 range: Range<DisplayRow>,
8019 window: &mut Window,
8020 cx: &mut Context<Self>,
8021 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8022 let mut breakpoint_display_points = HashMap::default();
8023
8024 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8025 return breakpoint_display_points;
8026 };
8027
8028 let snapshot = self.snapshot(window, cx);
8029
8030 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
8031 let Some(project) = self.project() else {
8032 return breakpoint_display_points;
8033 };
8034
8035 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8036 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8037
8038 for (buffer_snapshot, range, excerpt_id) in
8039 multi_buffer_snapshot.range_to_buffer_ranges(range)
8040 {
8041 let Some(buffer) = project
8042 .read(cx)
8043 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8044 else {
8045 continue;
8046 };
8047 let breakpoints = breakpoint_store.read(cx).breakpoints(
8048 &buffer,
8049 Some(
8050 buffer_snapshot.anchor_before(range.start)
8051 ..buffer_snapshot.anchor_after(range.end),
8052 ),
8053 buffer_snapshot,
8054 cx,
8055 );
8056 for (breakpoint, state) in breakpoints {
8057 let multi_buffer_anchor =
8058 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8059 let position = multi_buffer_anchor
8060 .to_point(multi_buffer_snapshot)
8061 .to_display_point(&snapshot);
8062
8063 breakpoint_display_points.insert(
8064 position.row(),
8065 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8066 );
8067 }
8068 }
8069
8070 breakpoint_display_points
8071 }
8072
8073 fn breakpoint_context_menu(
8074 &self,
8075 anchor: Anchor,
8076 window: &mut Window,
8077 cx: &mut Context<Self>,
8078 ) -> Entity<ui::ContextMenu> {
8079 let weak_editor = cx.weak_entity();
8080 let focus_handle = self.focus_handle(cx);
8081
8082 let row = self
8083 .buffer
8084 .read(cx)
8085 .snapshot(cx)
8086 .summary_for_anchor::<Point>(&anchor)
8087 .row;
8088
8089 let breakpoint = self
8090 .breakpoint_at_row(row, window, cx)
8091 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8092
8093 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8094 "Edit Log Breakpoint"
8095 } else {
8096 "Set Log Breakpoint"
8097 };
8098
8099 let condition_breakpoint_msg = if breakpoint
8100 .as_ref()
8101 .is_some_and(|bp| bp.1.condition.is_some())
8102 {
8103 "Edit Condition Breakpoint"
8104 } else {
8105 "Set Condition Breakpoint"
8106 };
8107
8108 let hit_condition_breakpoint_msg = if breakpoint
8109 .as_ref()
8110 .is_some_and(|bp| bp.1.hit_condition.is_some())
8111 {
8112 "Edit Hit Condition Breakpoint"
8113 } else {
8114 "Set Hit Condition Breakpoint"
8115 };
8116
8117 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8118 "Unset Breakpoint"
8119 } else {
8120 "Set Breakpoint"
8121 };
8122
8123 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8124
8125 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8126 BreakpointState::Enabled => Some("Disable"),
8127 BreakpointState::Disabled => Some("Enable"),
8128 });
8129
8130 let (anchor, breakpoint) =
8131 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8132
8133 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8134 menu.on_blur_subscription(Subscription::new(|| {}))
8135 .context(focus_handle)
8136 .when(run_to_cursor, |this| {
8137 let weak_editor = weak_editor.clone();
8138 this.entry("Run to cursor", None, move |window, cx| {
8139 weak_editor
8140 .update(cx, |editor, cx| {
8141 editor.change_selections(
8142 SelectionEffects::no_scroll(),
8143 window,
8144 cx,
8145 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8146 );
8147 })
8148 .ok();
8149
8150 window.dispatch_action(Box::new(RunToCursor), cx);
8151 })
8152 .separator()
8153 })
8154 .when_some(toggle_state_msg, |this, msg| {
8155 this.entry(msg, None, {
8156 let weak_editor = weak_editor.clone();
8157 let breakpoint = breakpoint.clone();
8158 move |_window, cx| {
8159 weak_editor
8160 .update(cx, |this, cx| {
8161 this.edit_breakpoint_at_anchor(
8162 anchor,
8163 breakpoint.as_ref().clone(),
8164 BreakpointEditAction::InvertState,
8165 cx,
8166 );
8167 })
8168 .log_err();
8169 }
8170 })
8171 })
8172 .entry(set_breakpoint_msg, None, {
8173 let weak_editor = weak_editor.clone();
8174 let breakpoint = breakpoint.clone();
8175 move |_window, cx| {
8176 weak_editor
8177 .update(cx, |this, cx| {
8178 this.edit_breakpoint_at_anchor(
8179 anchor,
8180 breakpoint.as_ref().clone(),
8181 BreakpointEditAction::Toggle,
8182 cx,
8183 );
8184 })
8185 .log_err();
8186 }
8187 })
8188 .entry(log_breakpoint_msg, None, {
8189 let breakpoint = breakpoint.clone();
8190 let weak_editor = weak_editor.clone();
8191 move |window, cx| {
8192 weak_editor
8193 .update(cx, |this, cx| {
8194 this.add_edit_breakpoint_block(
8195 anchor,
8196 breakpoint.as_ref(),
8197 BreakpointPromptEditAction::Log,
8198 window,
8199 cx,
8200 );
8201 })
8202 .log_err();
8203 }
8204 })
8205 .entry(condition_breakpoint_msg, None, {
8206 let breakpoint = breakpoint.clone();
8207 let weak_editor = weak_editor.clone();
8208 move |window, cx| {
8209 weak_editor
8210 .update(cx, |this, cx| {
8211 this.add_edit_breakpoint_block(
8212 anchor,
8213 breakpoint.as_ref(),
8214 BreakpointPromptEditAction::Condition,
8215 window,
8216 cx,
8217 );
8218 })
8219 .log_err();
8220 }
8221 })
8222 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8223 weak_editor
8224 .update(cx, |this, cx| {
8225 this.add_edit_breakpoint_block(
8226 anchor,
8227 breakpoint.as_ref(),
8228 BreakpointPromptEditAction::HitCondition,
8229 window,
8230 cx,
8231 );
8232 })
8233 .log_err();
8234 })
8235 })
8236 }
8237
8238 fn render_breakpoint(
8239 &self,
8240 position: Anchor,
8241 row: DisplayRow,
8242 breakpoint: &Breakpoint,
8243 state: Option<BreakpointSessionState>,
8244 cx: &mut Context<Self>,
8245 ) -> IconButton {
8246 let is_rejected = state.is_some_and(|s| !s.verified);
8247 // Is it a breakpoint that shows up when hovering over gutter?
8248 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8249 (false, false),
8250 |PhantomBreakpointIndicator {
8251 is_active,
8252 display_row,
8253 collides_with_existing_breakpoint,
8254 }| {
8255 (
8256 is_active && display_row == row,
8257 collides_with_existing_breakpoint,
8258 )
8259 },
8260 );
8261
8262 let (color, icon) = {
8263 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8264 (false, false) => ui::IconName::DebugBreakpoint,
8265 (true, false) => ui::IconName::DebugLogBreakpoint,
8266 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8267 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8268 };
8269
8270 let color = if is_phantom {
8271 Color::Hint
8272 } else if is_rejected {
8273 Color::Disabled
8274 } else {
8275 Color::Debugger
8276 };
8277
8278 (color, icon)
8279 };
8280
8281 let breakpoint = Arc::from(breakpoint.clone());
8282
8283 let alt_as_text = gpui::Keystroke {
8284 modifiers: Modifiers::secondary_key(),
8285 ..Default::default()
8286 };
8287 let primary_action_text = if breakpoint.is_disabled() {
8288 "Enable breakpoint"
8289 } else if is_phantom && !collides_with_existing {
8290 "Set breakpoint"
8291 } else {
8292 "Unset breakpoint"
8293 };
8294 let focus_handle = self.focus_handle.clone();
8295
8296 let meta = if is_rejected {
8297 SharedString::from("No executable code is associated with this line.")
8298 } else if collides_with_existing && !breakpoint.is_disabled() {
8299 SharedString::from(format!(
8300 "{alt_as_text}-click to disable,\nright-click for more options."
8301 ))
8302 } else {
8303 SharedString::from("Right-click for more options.")
8304 };
8305 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8306 .icon_size(IconSize::XSmall)
8307 .size(ui::ButtonSize::None)
8308 .when(is_rejected, |this| {
8309 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8310 })
8311 .icon_color(color)
8312 .style(ButtonStyle::Transparent)
8313 .on_click(cx.listener({
8314 move |editor, event: &ClickEvent, window, cx| {
8315 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8316 BreakpointEditAction::InvertState
8317 } else {
8318 BreakpointEditAction::Toggle
8319 };
8320
8321 window.focus(&editor.focus_handle(cx));
8322 editor.edit_breakpoint_at_anchor(
8323 position,
8324 breakpoint.as_ref().clone(),
8325 edit_action,
8326 cx,
8327 );
8328 }
8329 }))
8330 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8331 editor.set_breakpoint_context_menu(
8332 row,
8333 Some(position),
8334 event.position(),
8335 window,
8336 cx,
8337 );
8338 }))
8339 .tooltip(move |window, cx| {
8340 Tooltip::with_meta_in(
8341 primary_action_text,
8342 Some(&ToggleBreakpoint),
8343 meta.clone(),
8344 &focus_handle,
8345 window,
8346 cx,
8347 )
8348 })
8349 }
8350
8351 fn build_tasks_context(
8352 project: &Entity<Project>,
8353 buffer: &Entity<Buffer>,
8354 buffer_row: u32,
8355 tasks: &Arc<RunnableTasks>,
8356 cx: &mut Context<Self>,
8357 ) -> Task<Option<task::TaskContext>> {
8358 let position = Point::new(buffer_row, tasks.column);
8359 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8360 let location = Location {
8361 buffer: buffer.clone(),
8362 range: range_start..range_start,
8363 };
8364 // Fill in the environmental variables from the tree-sitter captures
8365 let mut captured_task_variables = TaskVariables::default();
8366 for (capture_name, value) in tasks.extra_variables.clone() {
8367 captured_task_variables.insert(
8368 task::VariableName::Custom(capture_name.into()),
8369 value.clone(),
8370 );
8371 }
8372 project.update(cx, |project, cx| {
8373 project.task_store().update(cx, |task_store, cx| {
8374 task_store.task_context_for_location(captured_task_variables, location, cx)
8375 })
8376 })
8377 }
8378
8379 pub fn spawn_nearest_task(
8380 &mut self,
8381 action: &SpawnNearestTask,
8382 window: &mut Window,
8383 cx: &mut Context<Self>,
8384 ) {
8385 let Some((workspace, _)) = self.workspace.clone() else {
8386 return;
8387 };
8388 let Some(project) = self.project.clone() else {
8389 return;
8390 };
8391
8392 // Try to find a closest, enclosing node using tree-sitter that has a task
8393 let Some((buffer, buffer_row, tasks)) = self
8394 .find_enclosing_node_task(cx)
8395 // Or find the task that's closest in row-distance.
8396 .or_else(|| self.find_closest_task(cx))
8397 else {
8398 return;
8399 };
8400
8401 let reveal_strategy = action.reveal;
8402 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8403 cx.spawn_in(window, async move |_, cx| {
8404 let context = task_context.await?;
8405 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8406
8407 let resolved = &mut resolved_task.resolved;
8408 resolved.reveal = reveal_strategy;
8409
8410 workspace
8411 .update_in(cx, |workspace, window, cx| {
8412 workspace.schedule_resolved_task(
8413 task_source_kind,
8414 resolved_task,
8415 false,
8416 window,
8417 cx,
8418 );
8419 })
8420 .ok()
8421 })
8422 .detach();
8423 }
8424
8425 fn find_closest_task(
8426 &mut self,
8427 cx: &mut Context<Self>,
8428 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8429 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8430
8431 let ((buffer_id, row), tasks) = self
8432 .tasks
8433 .iter()
8434 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8435
8436 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8437 let tasks = Arc::new(tasks.to_owned());
8438 Some((buffer, *row, tasks))
8439 }
8440
8441 fn find_enclosing_node_task(
8442 &mut self,
8443 cx: &mut Context<Self>,
8444 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8445 let snapshot = self.buffer.read(cx).snapshot(cx);
8446 let offset = self.selections.newest::<usize>(cx).head();
8447 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8448 let buffer_id = excerpt.buffer().remote_id();
8449
8450 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8451 let mut cursor = layer.node().walk();
8452
8453 while cursor.goto_first_child_for_byte(offset).is_some() {
8454 if cursor.node().end_byte() == offset {
8455 cursor.goto_next_sibling();
8456 }
8457 }
8458
8459 // Ascend to the smallest ancestor that contains the range and has a task.
8460 loop {
8461 let node = cursor.node();
8462 let node_range = node.byte_range();
8463 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8464
8465 // Check if this node contains our offset
8466 if node_range.start <= offset && node_range.end >= offset {
8467 // If it contains offset, check for task
8468 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8469 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8470 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8471 }
8472 }
8473
8474 if !cursor.goto_parent() {
8475 break;
8476 }
8477 }
8478 None
8479 }
8480
8481 fn render_run_indicator(
8482 &self,
8483 _style: &EditorStyle,
8484 is_active: bool,
8485 row: DisplayRow,
8486 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8487 cx: &mut Context<Self>,
8488 ) -> IconButton {
8489 let color = Color::Muted;
8490 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8491
8492 IconButton::new(
8493 ("run_indicator", row.0 as usize),
8494 ui::IconName::PlayOutlined,
8495 )
8496 .shape(ui::IconButtonShape::Square)
8497 .icon_size(IconSize::XSmall)
8498 .icon_color(color)
8499 .toggle_state(is_active)
8500 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8501 let quick_launch = match e {
8502 ClickEvent::Keyboard(_) => true,
8503 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8504 };
8505
8506 window.focus(&editor.focus_handle(cx));
8507 editor.toggle_code_actions(
8508 &ToggleCodeActions {
8509 deployed_from: Some(CodeActionSource::RunMenu(row)),
8510 quick_launch,
8511 },
8512 window,
8513 cx,
8514 );
8515 }))
8516 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8517 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8518 }))
8519 }
8520
8521 pub fn context_menu_visible(&self) -> bool {
8522 !self.edit_prediction_preview_is_active()
8523 && self
8524 .context_menu
8525 .borrow()
8526 .as_ref()
8527 .is_some_and(|menu| menu.visible())
8528 }
8529
8530 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8531 self.context_menu
8532 .borrow()
8533 .as_ref()
8534 .map(|menu| menu.origin())
8535 }
8536
8537 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8538 self.context_menu_options = Some(options);
8539 }
8540
8541 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8542 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8543
8544 fn render_edit_prediction_popover(
8545 &mut self,
8546 text_bounds: &Bounds<Pixels>,
8547 content_origin: gpui::Point<Pixels>,
8548 right_margin: Pixels,
8549 editor_snapshot: &EditorSnapshot,
8550 visible_row_range: Range<DisplayRow>,
8551 scroll_top: f32,
8552 scroll_bottom: f32,
8553 line_layouts: &[LineWithInvisibles],
8554 line_height: Pixels,
8555 scroll_pixel_position: gpui::Point<Pixels>,
8556 newest_selection_head: Option<DisplayPoint>,
8557 editor_width: Pixels,
8558 style: &EditorStyle,
8559 window: &mut Window,
8560 cx: &mut App,
8561 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8562 if self.mode().is_minimap() {
8563 return None;
8564 }
8565 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8566
8567 if self.edit_prediction_visible_in_cursor_popover(true) {
8568 return None;
8569 }
8570
8571 match &active_edit_prediction.completion {
8572 EditPrediction::Move { target, .. } => {
8573 let target_display_point = target.to_display_point(editor_snapshot);
8574
8575 if self.edit_prediction_requires_modifier() {
8576 if !self.edit_prediction_preview_is_active() {
8577 return None;
8578 }
8579
8580 self.render_edit_prediction_modifier_jump_popover(
8581 text_bounds,
8582 content_origin,
8583 visible_row_range,
8584 line_layouts,
8585 line_height,
8586 scroll_pixel_position,
8587 newest_selection_head,
8588 target_display_point,
8589 window,
8590 cx,
8591 )
8592 } else {
8593 self.render_edit_prediction_eager_jump_popover(
8594 text_bounds,
8595 content_origin,
8596 editor_snapshot,
8597 visible_row_range,
8598 scroll_top,
8599 scroll_bottom,
8600 line_height,
8601 scroll_pixel_position,
8602 target_display_point,
8603 editor_width,
8604 window,
8605 cx,
8606 )
8607 }
8608 }
8609 EditPrediction::Edit {
8610 display_mode: EditDisplayMode::Inline,
8611 ..
8612 } => None,
8613 EditPrediction::Edit {
8614 display_mode: EditDisplayMode::TabAccept,
8615 edits,
8616 ..
8617 } => {
8618 let range = &edits.first()?.0;
8619 let target_display_point = range.end.to_display_point(editor_snapshot);
8620
8621 self.render_edit_prediction_end_of_line_popover(
8622 "Accept",
8623 editor_snapshot,
8624 visible_row_range,
8625 target_display_point,
8626 line_height,
8627 scroll_pixel_position,
8628 content_origin,
8629 editor_width,
8630 window,
8631 cx,
8632 )
8633 }
8634 EditPrediction::Edit {
8635 edits,
8636 edit_preview,
8637 display_mode: EditDisplayMode::DiffPopover,
8638 snapshot,
8639 } => self.render_edit_prediction_diff_popover(
8640 text_bounds,
8641 content_origin,
8642 right_margin,
8643 editor_snapshot,
8644 visible_row_range,
8645 line_layouts,
8646 line_height,
8647 scroll_pixel_position,
8648 newest_selection_head,
8649 editor_width,
8650 style,
8651 edits,
8652 edit_preview,
8653 snapshot,
8654 window,
8655 cx,
8656 ),
8657 }
8658 }
8659
8660 fn render_edit_prediction_modifier_jump_popover(
8661 &mut self,
8662 text_bounds: &Bounds<Pixels>,
8663 content_origin: gpui::Point<Pixels>,
8664 visible_row_range: Range<DisplayRow>,
8665 line_layouts: &[LineWithInvisibles],
8666 line_height: Pixels,
8667 scroll_pixel_position: gpui::Point<Pixels>,
8668 newest_selection_head: Option<DisplayPoint>,
8669 target_display_point: DisplayPoint,
8670 window: &mut Window,
8671 cx: &mut App,
8672 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8673 let scrolled_content_origin =
8674 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8675
8676 const SCROLL_PADDING_Y: Pixels = px(12.);
8677
8678 if target_display_point.row() < visible_row_range.start {
8679 return self.render_edit_prediction_scroll_popover(
8680 |_| SCROLL_PADDING_Y,
8681 IconName::ArrowUp,
8682 visible_row_range,
8683 line_layouts,
8684 newest_selection_head,
8685 scrolled_content_origin,
8686 window,
8687 cx,
8688 );
8689 } else if target_display_point.row() >= visible_row_range.end {
8690 return self.render_edit_prediction_scroll_popover(
8691 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8692 IconName::ArrowDown,
8693 visible_row_range,
8694 line_layouts,
8695 newest_selection_head,
8696 scrolled_content_origin,
8697 window,
8698 cx,
8699 );
8700 }
8701
8702 const POLE_WIDTH: Pixels = px(2.);
8703
8704 let line_layout =
8705 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8706 let target_column = target_display_point.column() as usize;
8707
8708 let target_x = line_layout.x_for_index(target_column);
8709 let target_y =
8710 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8711
8712 let flag_on_right = target_x < text_bounds.size.width / 2.;
8713
8714 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8715 border_color.l += 0.001;
8716
8717 let mut element = v_flex()
8718 .items_end()
8719 .when(flag_on_right, |el| el.items_start())
8720 .child(if flag_on_right {
8721 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8722 .rounded_bl(px(0.))
8723 .rounded_tl(px(0.))
8724 .border_l_2()
8725 .border_color(border_color)
8726 } else {
8727 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8728 .rounded_br(px(0.))
8729 .rounded_tr(px(0.))
8730 .border_r_2()
8731 .border_color(border_color)
8732 })
8733 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8734 .into_any();
8735
8736 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8737
8738 let mut origin = scrolled_content_origin + point(target_x, target_y)
8739 - point(
8740 if flag_on_right {
8741 POLE_WIDTH
8742 } else {
8743 size.width - POLE_WIDTH
8744 },
8745 size.height - line_height,
8746 );
8747
8748 origin.x = origin.x.max(content_origin.x);
8749
8750 element.prepaint_at(origin, window, cx);
8751
8752 Some((element, origin))
8753 }
8754
8755 fn render_edit_prediction_scroll_popover(
8756 &mut self,
8757 to_y: impl Fn(Size<Pixels>) -> Pixels,
8758 scroll_icon: IconName,
8759 visible_row_range: Range<DisplayRow>,
8760 line_layouts: &[LineWithInvisibles],
8761 newest_selection_head: Option<DisplayPoint>,
8762 scrolled_content_origin: gpui::Point<Pixels>,
8763 window: &mut Window,
8764 cx: &mut App,
8765 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8766 let mut element = self
8767 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8768 .into_any();
8769
8770 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8771
8772 let cursor = newest_selection_head?;
8773 let cursor_row_layout =
8774 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8775 let cursor_column = cursor.column() as usize;
8776
8777 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8778
8779 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8780
8781 element.prepaint_at(origin, window, cx);
8782 Some((element, origin))
8783 }
8784
8785 fn render_edit_prediction_eager_jump_popover(
8786 &mut self,
8787 text_bounds: &Bounds<Pixels>,
8788 content_origin: gpui::Point<Pixels>,
8789 editor_snapshot: &EditorSnapshot,
8790 visible_row_range: Range<DisplayRow>,
8791 scroll_top: f32,
8792 scroll_bottom: f32,
8793 line_height: Pixels,
8794 scroll_pixel_position: gpui::Point<Pixels>,
8795 target_display_point: DisplayPoint,
8796 editor_width: Pixels,
8797 window: &mut Window,
8798 cx: &mut App,
8799 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8800 if target_display_point.row().as_f32() < scroll_top {
8801 let mut element = self
8802 .render_edit_prediction_line_popover(
8803 "Jump to Edit",
8804 Some(IconName::ArrowUp),
8805 window,
8806 cx,
8807 )?
8808 .into_any();
8809
8810 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8811 let offset = point(
8812 (text_bounds.size.width - size.width) / 2.,
8813 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8814 );
8815
8816 let origin = text_bounds.origin + offset;
8817 element.prepaint_at(origin, window, cx);
8818 Some((element, origin))
8819 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8820 let mut element = self
8821 .render_edit_prediction_line_popover(
8822 "Jump to Edit",
8823 Some(IconName::ArrowDown),
8824 window,
8825 cx,
8826 )?
8827 .into_any();
8828
8829 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8830 let offset = point(
8831 (text_bounds.size.width - size.width) / 2.,
8832 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8833 );
8834
8835 let origin = text_bounds.origin + offset;
8836 element.prepaint_at(origin, window, cx);
8837 Some((element, origin))
8838 } else {
8839 self.render_edit_prediction_end_of_line_popover(
8840 "Jump to Edit",
8841 editor_snapshot,
8842 visible_row_range,
8843 target_display_point,
8844 line_height,
8845 scroll_pixel_position,
8846 content_origin,
8847 editor_width,
8848 window,
8849 cx,
8850 )
8851 }
8852 }
8853
8854 fn render_edit_prediction_end_of_line_popover(
8855 self: &mut Editor,
8856 label: &'static str,
8857 editor_snapshot: &EditorSnapshot,
8858 visible_row_range: Range<DisplayRow>,
8859 target_display_point: DisplayPoint,
8860 line_height: Pixels,
8861 scroll_pixel_position: gpui::Point<Pixels>,
8862 content_origin: gpui::Point<Pixels>,
8863 editor_width: Pixels,
8864 window: &mut Window,
8865 cx: &mut App,
8866 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8867 let target_line_end = DisplayPoint::new(
8868 target_display_point.row(),
8869 editor_snapshot.line_len(target_display_point.row()),
8870 );
8871
8872 let mut element = self
8873 .render_edit_prediction_line_popover(label, None, window, cx)?
8874 .into_any();
8875
8876 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8877
8878 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8879
8880 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8881 let mut origin = start_point
8882 + line_origin
8883 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8884 origin.x = origin.x.max(content_origin.x);
8885
8886 let max_x = content_origin.x + editor_width - size.width;
8887
8888 if origin.x > max_x {
8889 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8890
8891 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8892 origin.y += offset;
8893 IconName::ArrowUp
8894 } else {
8895 origin.y -= offset;
8896 IconName::ArrowDown
8897 };
8898
8899 element = self
8900 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8901 .into_any();
8902
8903 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8904
8905 origin.x = content_origin.x + editor_width - size.width - px(2.);
8906 }
8907
8908 element.prepaint_at(origin, window, cx);
8909 Some((element, origin))
8910 }
8911
8912 fn render_edit_prediction_diff_popover(
8913 self: &Editor,
8914 text_bounds: &Bounds<Pixels>,
8915 content_origin: gpui::Point<Pixels>,
8916 right_margin: Pixels,
8917 editor_snapshot: &EditorSnapshot,
8918 visible_row_range: Range<DisplayRow>,
8919 line_layouts: &[LineWithInvisibles],
8920 line_height: Pixels,
8921 scroll_pixel_position: gpui::Point<Pixels>,
8922 newest_selection_head: Option<DisplayPoint>,
8923 editor_width: Pixels,
8924 style: &EditorStyle,
8925 edits: &Vec<(Range<Anchor>, String)>,
8926 edit_preview: &Option<language::EditPreview>,
8927 snapshot: &language::BufferSnapshot,
8928 window: &mut Window,
8929 cx: &mut App,
8930 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8931 let edit_start = edits
8932 .first()
8933 .unwrap()
8934 .0
8935 .start
8936 .to_display_point(editor_snapshot);
8937 let edit_end = edits
8938 .last()
8939 .unwrap()
8940 .0
8941 .end
8942 .to_display_point(editor_snapshot);
8943
8944 let is_visible = visible_row_range.contains(&edit_start.row())
8945 || visible_row_range.contains(&edit_end.row());
8946 if !is_visible {
8947 return None;
8948 }
8949
8950 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8951 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8952 } else {
8953 // Fallback for providers without edit_preview
8954 crate::edit_prediction_fallback_text(edits, cx)
8955 };
8956
8957 let styled_text = highlighted_edits.to_styled_text(&style.text);
8958 let line_count = highlighted_edits.text.lines().count();
8959
8960 const BORDER_WIDTH: Pixels = px(1.);
8961
8962 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8963 let has_keybind = keybind.is_some();
8964
8965 let mut element = h_flex()
8966 .items_start()
8967 .child(
8968 h_flex()
8969 .bg(cx.theme().colors().editor_background)
8970 .border(BORDER_WIDTH)
8971 .shadow_xs()
8972 .border_color(cx.theme().colors().border)
8973 .rounded_l_lg()
8974 .when(line_count > 1, |el| el.rounded_br_lg())
8975 .pr_1()
8976 .child(styled_text),
8977 )
8978 .child(
8979 h_flex()
8980 .h(line_height + BORDER_WIDTH * 2.)
8981 .px_1p5()
8982 .gap_1()
8983 // Workaround: For some reason, there's a gap if we don't do this
8984 .ml(-BORDER_WIDTH)
8985 .shadow(vec![gpui::BoxShadow {
8986 color: gpui::black().opacity(0.05),
8987 offset: point(px(1.), px(1.)),
8988 blur_radius: px(2.),
8989 spread_radius: px(0.),
8990 }])
8991 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8992 .border(BORDER_WIDTH)
8993 .border_color(cx.theme().colors().border)
8994 .rounded_r_lg()
8995 .id("edit_prediction_diff_popover_keybind")
8996 .when(!has_keybind, |el| {
8997 let status_colors = cx.theme().status();
8998
8999 el.bg(status_colors.error_background)
9000 .border_color(status_colors.error.opacity(0.6))
9001 .child(Icon::new(IconName::Info).color(Color::Error))
9002 .cursor_default()
9003 .hoverable_tooltip(move |_window, cx| {
9004 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9005 })
9006 })
9007 .children(keybind),
9008 )
9009 .into_any();
9010
9011 let longest_row =
9012 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9013 let longest_line_width = if visible_row_range.contains(&longest_row) {
9014 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9015 } else {
9016 layout_line(
9017 longest_row,
9018 editor_snapshot,
9019 style,
9020 editor_width,
9021 |_| false,
9022 window,
9023 cx,
9024 )
9025 .width
9026 };
9027
9028 let viewport_bounds =
9029 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9030 right: -right_margin,
9031 ..Default::default()
9032 });
9033
9034 let x_after_longest =
9035 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
9036 - scroll_pixel_position.x;
9037
9038 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9039
9040 // Fully visible if it can be displayed within the window (allow overlapping other
9041 // panes). However, this is only allowed if the popover starts within text_bounds.
9042 let can_position_to_the_right = x_after_longest < text_bounds.right()
9043 && x_after_longest + element_bounds.width < viewport_bounds.right();
9044
9045 let mut origin = if can_position_to_the_right {
9046 point(
9047 x_after_longest,
9048 text_bounds.origin.y + edit_start.row().as_f32() * line_height
9049 - scroll_pixel_position.y,
9050 )
9051 } else {
9052 let cursor_row = newest_selection_head.map(|head| head.row());
9053 let above_edit = edit_start
9054 .row()
9055 .0
9056 .checked_sub(line_count as u32)
9057 .map(DisplayRow);
9058 let below_edit = Some(edit_end.row() + 1);
9059 let above_cursor =
9060 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9061 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9062
9063 // Place the edit popover adjacent to the edit if there is a location
9064 // available that is onscreen and does not obscure the cursor. Otherwise,
9065 // place it adjacent to the cursor.
9066 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9067 .into_iter()
9068 .flatten()
9069 .find(|&start_row| {
9070 let end_row = start_row + line_count as u32;
9071 visible_row_range.contains(&start_row)
9072 && visible_row_range.contains(&end_row)
9073 && cursor_row
9074 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9075 })?;
9076
9077 content_origin
9078 + point(
9079 -scroll_pixel_position.x,
9080 row_target.as_f32() * line_height - scroll_pixel_position.y,
9081 )
9082 };
9083
9084 origin.x -= BORDER_WIDTH;
9085
9086 window.defer_draw(element, origin, 1);
9087
9088 // Do not return an element, since it will already be drawn due to defer_draw.
9089 None
9090 }
9091
9092 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9093 px(30.)
9094 }
9095
9096 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9097 if self.read_only(cx) {
9098 cx.theme().players().read_only()
9099 } else {
9100 self.style.as_ref().unwrap().local_player
9101 }
9102 }
9103
9104 fn render_edit_prediction_accept_keybind(
9105 &self,
9106 window: &mut Window,
9107 cx: &App,
9108 ) -> Option<AnyElement> {
9109 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9110 let accept_keystroke = accept_binding.keystroke()?;
9111
9112 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9113
9114 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9115 Color::Accent
9116 } else {
9117 Color::Muted
9118 };
9119
9120 h_flex()
9121 .px_0p5()
9122 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9123 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9124 .text_size(TextSize::XSmall.rems(cx))
9125 .child(h_flex().children(ui::render_modifiers(
9126 accept_keystroke.modifiers(),
9127 PlatformStyle::platform(),
9128 Some(modifiers_color),
9129 Some(IconSize::XSmall.rems().into()),
9130 true,
9131 )))
9132 .when(is_platform_style_mac, |parent| {
9133 parent.child(accept_keystroke.key().to_string())
9134 })
9135 .when(!is_platform_style_mac, |parent| {
9136 parent.child(
9137 Key::new(
9138 util::capitalize(accept_keystroke.key()),
9139 Some(Color::Default),
9140 )
9141 .size(Some(IconSize::XSmall.rems().into())),
9142 )
9143 })
9144 .into_any()
9145 .into()
9146 }
9147
9148 fn render_edit_prediction_line_popover(
9149 &self,
9150 label: impl Into<SharedString>,
9151 icon: Option<IconName>,
9152 window: &mut Window,
9153 cx: &App,
9154 ) -> Option<Stateful<Div>> {
9155 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9156
9157 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9158 let has_keybind = keybind.is_some();
9159
9160 let result = h_flex()
9161 .id("ep-line-popover")
9162 .py_0p5()
9163 .pl_1()
9164 .pr(padding_right)
9165 .gap_1()
9166 .rounded_md()
9167 .border_1()
9168 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9169 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9170 .shadow_xs()
9171 .when(!has_keybind, |el| {
9172 let status_colors = cx.theme().status();
9173
9174 el.bg(status_colors.error_background)
9175 .border_color(status_colors.error.opacity(0.6))
9176 .pl_2()
9177 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9178 .cursor_default()
9179 .hoverable_tooltip(move |_window, cx| {
9180 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9181 })
9182 })
9183 .children(keybind)
9184 .child(
9185 Label::new(label)
9186 .size(LabelSize::Small)
9187 .when(!has_keybind, |el| {
9188 el.color(cx.theme().status().error.into()).strikethrough()
9189 }),
9190 )
9191 .when(!has_keybind, |el| {
9192 el.child(
9193 h_flex().ml_1().child(
9194 Icon::new(IconName::Info)
9195 .size(IconSize::Small)
9196 .color(cx.theme().status().error.into()),
9197 ),
9198 )
9199 })
9200 .when_some(icon, |element, icon| {
9201 element.child(
9202 div()
9203 .mt(px(1.5))
9204 .child(Icon::new(icon).size(IconSize::Small)),
9205 )
9206 });
9207
9208 Some(result)
9209 }
9210
9211 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9212 let accent_color = cx.theme().colors().text_accent;
9213 let editor_bg_color = cx.theme().colors().editor_background;
9214 editor_bg_color.blend(accent_color.opacity(0.1))
9215 }
9216
9217 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9218 let accent_color = cx.theme().colors().text_accent;
9219 let editor_bg_color = cx.theme().colors().editor_background;
9220 editor_bg_color.blend(accent_color.opacity(0.6))
9221 }
9222 fn get_prediction_provider_icon_name(
9223 provider: &Option<RegisteredEditPredictionProvider>,
9224 ) -> IconName {
9225 match provider {
9226 Some(provider) => match provider.provider.name() {
9227 "copilot" => IconName::Copilot,
9228 "supermaven" => IconName::Supermaven,
9229 _ => IconName::ZedPredict,
9230 },
9231 None => IconName::ZedPredict,
9232 }
9233 }
9234
9235 fn render_edit_prediction_cursor_popover(
9236 &self,
9237 min_width: Pixels,
9238 max_width: Pixels,
9239 cursor_point: Point,
9240 style: &EditorStyle,
9241 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9242 _window: &Window,
9243 cx: &mut Context<Editor>,
9244 ) -> Option<AnyElement> {
9245 let provider = self.edit_prediction_provider.as_ref()?;
9246 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9247
9248 let is_refreshing = provider.provider.is_refreshing(cx);
9249
9250 fn pending_completion_container(icon: IconName) -> Div {
9251 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9252 }
9253
9254 let completion = match &self.active_edit_prediction {
9255 Some(prediction) => {
9256 if !self.has_visible_completions_menu() {
9257 const RADIUS: Pixels = px(6.);
9258 const BORDER_WIDTH: Pixels = px(1.);
9259
9260 return Some(
9261 h_flex()
9262 .elevation_2(cx)
9263 .border(BORDER_WIDTH)
9264 .border_color(cx.theme().colors().border)
9265 .when(accept_keystroke.is_none(), |el| {
9266 el.border_color(cx.theme().status().error)
9267 })
9268 .rounded(RADIUS)
9269 .rounded_tl(px(0.))
9270 .overflow_hidden()
9271 .child(div().px_1p5().child(match &prediction.completion {
9272 EditPrediction::Move { target, snapshot } => {
9273 use text::ToPoint as _;
9274 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9275 {
9276 Icon::new(IconName::ZedPredictDown)
9277 } else {
9278 Icon::new(IconName::ZedPredictUp)
9279 }
9280 }
9281 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9282 }))
9283 .child(
9284 h_flex()
9285 .gap_1()
9286 .py_1()
9287 .px_2()
9288 .rounded_r(RADIUS - BORDER_WIDTH)
9289 .border_l_1()
9290 .border_color(cx.theme().colors().border)
9291 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9292 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9293 el.child(
9294 Label::new("Hold")
9295 .size(LabelSize::Small)
9296 .when(accept_keystroke.is_none(), |el| {
9297 el.strikethrough()
9298 })
9299 .line_height_style(LineHeightStyle::UiLabel),
9300 )
9301 })
9302 .id("edit_prediction_cursor_popover_keybind")
9303 .when(accept_keystroke.is_none(), |el| {
9304 let status_colors = cx.theme().status();
9305
9306 el.bg(status_colors.error_background)
9307 .border_color(status_colors.error.opacity(0.6))
9308 .child(Icon::new(IconName::Info).color(Color::Error))
9309 .cursor_default()
9310 .hoverable_tooltip(move |_window, cx| {
9311 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9312 .into()
9313 })
9314 })
9315 .when_some(
9316 accept_keystroke.as_ref(),
9317 |el, accept_keystroke| {
9318 el.child(h_flex().children(ui::render_modifiers(
9319 accept_keystroke.modifiers(),
9320 PlatformStyle::platform(),
9321 Some(Color::Default),
9322 Some(IconSize::XSmall.rems().into()),
9323 false,
9324 )))
9325 },
9326 ),
9327 )
9328 .into_any(),
9329 );
9330 }
9331
9332 self.render_edit_prediction_cursor_popover_preview(
9333 prediction,
9334 cursor_point,
9335 style,
9336 cx,
9337 )?
9338 }
9339
9340 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9341 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9342 stale_completion,
9343 cursor_point,
9344 style,
9345 cx,
9346 )?,
9347
9348 None => pending_completion_container(provider_icon)
9349 .child(Label::new("...").size(LabelSize::Small)),
9350 },
9351
9352 None => pending_completion_container(provider_icon)
9353 .child(Label::new("...").size(LabelSize::Small)),
9354 };
9355
9356 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9357 completion
9358 .with_animation(
9359 "loading-completion",
9360 Animation::new(Duration::from_secs(2))
9361 .repeat()
9362 .with_easing(pulsating_between(0.4, 0.8)),
9363 |label, delta| label.opacity(delta),
9364 )
9365 .into_any_element()
9366 } else {
9367 completion.into_any_element()
9368 };
9369
9370 let has_completion = self.active_edit_prediction.is_some();
9371
9372 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9373 Some(
9374 h_flex()
9375 .min_w(min_width)
9376 .max_w(max_width)
9377 .flex_1()
9378 .elevation_2(cx)
9379 .border_color(cx.theme().colors().border)
9380 .child(
9381 div()
9382 .flex_1()
9383 .py_1()
9384 .px_2()
9385 .overflow_hidden()
9386 .child(completion),
9387 )
9388 .when_some(accept_keystroke, |el, accept_keystroke| {
9389 if !accept_keystroke.modifiers().modified() {
9390 return el;
9391 }
9392
9393 el.child(
9394 h_flex()
9395 .h_full()
9396 .border_l_1()
9397 .rounded_r_lg()
9398 .border_color(cx.theme().colors().border)
9399 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9400 .gap_1()
9401 .py_1()
9402 .px_2()
9403 .child(
9404 h_flex()
9405 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9406 .when(is_platform_style_mac, |parent| parent.gap_1())
9407 .child(h_flex().children(ui::render_modifiers(
9408 accept_keystroke.modifiers(),
9409 PlatformStyle::platform(),
9410 Some(if !has_completion {
9411 Color::Muted
9412 } else {
9413 Color::Default
9414 }),
9415 None,
9416 false,
9417 ))),
9418 )
9419 .child(Label::new("Preview").into_any_element())
9420 .opacity(if has_completion { 1.0 } else { 0.4 }),
9421 )
9422 })
9423 .into_any(),
9424 )
9425 }
9426
9427 fn render_edit_prediction_cursor_popover_preview(
9428 &self,
9429 completion: &EditPredictionState,
9430 cursor_point: Point,
9431 style: &EditorStyle,
9432 cx: &mut Context<Editor>,
9433 ) -> Option<Div> {
9434 use text::ToPoint as _;
9435
9436 fn render_relative_row_jump(
9437 prefix: impl Into<String>,
9438 current_row: u32,
9439 target_row: u32,
9440 ) -> Div {
9441 let (row_diff, arrow) = if target_row < current_row {
9442 (current_row - target_row, IconName::ArrowUp)
9443 } else {
9444 (target_row - current_row, IconName::ArrowDown)
9445 };
9446
9447 h_flex()
9448 .child(
9449 Label::new(format!("{}{}", prefix.into(), row_diff))
9450 .color(Color::Muted)
9451 .size(LabelSize::Small),
9452 )
9453 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9454 }
9455
9456 let supports_jump = self
9457 .edit_prediction_provider
9458 .as_ref()
9459 .map(|provider| provider.provider.supports_jump_to_edit())
9460 .unwrap_or(true);
9461
9462 match &completion.completion {
9463 EditPrediction::Move {
9464 target, snapshot, ..
9465 } => {
9466 if !supports_jump {
9467 return None;
9468 }
9469
9470 Some(
9471 h_flex()
9472 .px_2()
9473 .gap_2()
9474 .flex_1()
9475 .child(
9476 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9477 Icon::new(IconName::ZedPredictDown)
9478 } else {
9479 Icon::new(IconName::ZedPredictUp)
9480 },
9481 )
9482 .child(Label::new("Jump to Edit")),
9483 )
9484 }
9485
9486 EditPrediction::Edit {
9487 edits,
9488 edit_preview,
9489 snapshot,
9490 display_mode: _,
9491 } => {
9492 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9493
9494 let (highlighted_edits, has_more_lines) =
9495 if let Some(edit_preview) = edit_preview.as_ref() {
9496 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9497 .first_line_preview()
9498 } else {
9499 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9500 };
9501
9502 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9503 .with_default_highlights(&style.text, highlighted_edits.highlights);
9504
9505 let preview = h_flex()
9506 .gap_1()
9507 .min_w_16()
9508 .child(styled_text)
9509 .when(has_more_lines, |parent| parent.child("…"));
9510
9511 let left = if supports_jump && first_edit_row != cursor_point.row {
9512 render_relative_row_jump("", cursor_point.row, first_edit_row)
9513 .into_any_element()
9514 } else {
9515 let icon_name =
9516 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9517 Icon::new(icon_name).into_any_element()
9518 };
9519
9520 Some(
9521 h_flex()
9522 .h_full()
9523 .flex_1()
9524 .gap_2()
9525 .pr_1()
9526 .overflow_x_hidden()
9527 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9528 .child(left)
9529 .child(preview),
9530 )
9531 }
9532 }
9533 }
9534
9535 pub fn render_context_menu(
9536 &self,
9537 style: &EditorStyle,
9538 max_height_in_lines: u32,
9539 window: &mut Window,
9540 cx: &mut Context<Editor>,
9541 ) -> Option<AnyElement> {
9542 let menu = self.context_menu.borrow();
9543 let menu = menu.as_ref()?;
9544 if !menu.visible() {
9545 return None;
9546 };
9547 Some(menu.render(style, max_height_in_lines, window, cx))
9548 }
9549
9550 fn render_context_menu_aside(
9551 &mut self,
9552 max_size: Size<Pixels>,
9553 window: &mut Window,
9554 cx: &mut Context<Editor>,
9555 ) -> Option<AnyElement> {
9556 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9557 if menu.visible() {
9558 menu.render_aside(max_size, window, cx)
9559 } else {
9560 None
9561 }
9562 })
9563 }
9564
9565 fn hide_context_menu(
9566 &mut self,
9567 window: &mut Window,
9568 cx: &mut Context<Self>,
9569 ) -> Option<CodeContextMenu> {
9570 cx.notify();
9571 self.completion_tasks.clear();
9572 let context_menu = self.context_menu.borrow_mut().take();
9573 self.stale_edit_prediction_in_menu.take();
9574 self.update_visible_edit_prediction(window, cx);
9575 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9576 && let Some(completion_provider) = &self.completion_provider
9577 {
9578 completion_provider.selection_changed(None, window, cx);
9579 }
9580 context_menu
9581 }
9582
9583 fn show_snippet_choices(
9584 &mut self,
9585 choices: &Vec<String>,
9586 selection: Range<Anchor>,
9587 cx: &mut Context<Self>,
9588 ) {
9589 let Some((_, buffer, _)) = self
9590 .buffer()
9591 .read(cx)
9592 .excerpt_containing(selection.start, cx)
9593 else {
9594 return;
9595 };
9596 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9597 else {
9598 return;
9599 };
9600 if buffer != end_buffer {
9601 log::error!("expected anchor range to have matching buffer IDs");
9602 return;
9603 }
9604
9605 let id = post_inc(&mut self.next_completion_id);
9606 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9607 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9608 CompletionsMenu::new_snippet_choices(
9609 id,
9610 true,
9611 choices,
9612 selection,
9613 buffer,
9614 snippet_sort_order,
9615 ),
9616 ));
9617 }
9618
9619 pub fn insert_snippet(
9620 &mut self,
9621 insertion_ranges: &[Range<usize>],
9622 snippet: Snippet,
9623 window: &mut Window,
9624 cx: &mut Context<Self>,
9625 ) -> Result<()> {
9626 struct Tabstop<T> {
9627 is_end_tabstop: bool,
9628 ranges: Vec<Range<T>>,
9629 choices: Option<Vec<String>>,
9630 }
9631
9632 let tabstops = self.buffer.update(cx, |buffer, cx| {
9633 let snippet_text: Arc<str> = snippet.text.clone().into();
9634 let edits = insertion_ranges
9635 .iter()
9636 .cloned()
9637 .map(|range| (range, snippet_text.clone()));
9638 let autoindent_mode = AutoindentMode::Block {
9639 original_indent_columns: Vec::new(),
9640 };
9641 buffer.edit(edits, Some(autoindent_mode), cx);
9642
9643 let snapshot = &*buffer.read(cx);
9644 let snippet = &snippet;
9645 snippet
9646 .tabstops
9647 .iter()
9648 .map(|tabstop| {
9649 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9650 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9651 });
9652 let mut tabstop_ranges = tabstop
9653 .ranges
9654 .iter()
9655 .flat_map(|tabstop_range| {
9656 let mut delta = 0_isize;
9657 insertion_ranges.iter().map(move |insertion_range| {
9658 let insertion_start = insertion_range.start as isize + delta;
9659 delta +=
9660 snippet.text.len() as isize - insertion_range.len() as isize;
9661
9662 let start = ((insertion_start + tabstop_range.start) as usize)
9663 .min(snapshot.len());
9664 let end = ((insertion_start + tabstop_range.end) as usize)
9665 .min(snapshot.len());
9666 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9667 })
9668 })
9669 .collect::<Vec<_>>();
9670 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9671
9672 Tabstop {
9673 is_end_tabstop,
9674 ranges: tabstop_ranges,
9675 choices: tabstop.choices.clone(),
9676 }
9677 })
9678 .collect::<Vec<_>>()
9679 });
9680 if let Some(tabstop) = tabstops.first() {
9681 self.change_selections(Default::default(), window, cx, |s| {
9682 // Reverse order so that the first range is the newest created selection.
9683 // Completions will use it and autoscroll will prioritize it.
9684 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9685 });
9686
9687 if let Some(choices) = &tabstop.choices
9688 && let Some(selection) = tabstop.ranges.first()
9689 {
9690 self.show_snippet_choices(choices, selection.clone(), cx)
9691 }
9692
9693 // If we're already at the last tabstop and it's at the end of the snippet,
9694 // we're done, we don't need to keep the state around.
9695 if !tabstop.is_end_tabstop {
9696 let choices = tabstops
9697 .iter()
9698 .map(|tabstop| tabstop.choices.clone())
9699 .collect();
9700
9701 let ranges = tabstops
9702 .into_iter()
9703 .map(|tabstop| tabstop.ranges)
9704 .collect::<Vec<_>>();
9705
9706 self.snippet_stack.push(SnippetState {
9707 active_index: 0,
9708 ranges,
9709 choices,
9710 });
9711 }
9712
9713 // Check whether the just-entered snippet ends with an auto-closable bracket.
9714 if self.autoclose_regions.is_empty() {
9715 let snapshot = self.buffer.read(cx).snapshot(cx);
9716 let mut all_selections = self.selections.all::<Point>(cx);
9717 for selection in &mut all_selections {
9718 let selection_head = selection.head();
9719 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9720 continue;
9721 };
9722
9723 let mut bracket_pair = None;
9724 let max_lookup_length = scope
9725 .brackets()
9726 .map(|(pair, _)| {
9727 pair.start
9728 .as_str()
9729 .chars()
9730 .count()
9731 .max(pair.end.as_str().chars().count())
9732 })
9733 .max();
9734 if let Some(max_lookup_length) = max_lookup_length {
9735 let next_text = snapshot
9736 .chars_at(selection_head)
9737 .take(max_lookup_length)
9738 .collect::<String>();
9739 let prev_text = snapshot
9740 .reversed_chars_at(selection_head)
9741 .take(max_lookup_length)
9742 .collect::<String>();
9743
9744 for (pair, enabled) in scope.brackets() {
9745 if enabled
9746 && pair.close
9747 && prev_text.starts_with(pair.start.as_str())
9748 && next_text.starts_with(pair.end.as_str())
9749 {
9750 bracket_pair = Some(pair.clone());
9751 break;
9752 }
9753 }
9754 }
9755
9756 if let Some(pair) = bracket_pair {
9757 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9758 let autoclose_enabled =
9759 self.use_autoclose && snapshot_settings.use_autoclose;
9760 if autoclose_enabled {
9761 let start = snapshot.anchor_after(selection_head);
9762 let end = snapshot.anchor_after(selection_head);
9763 self.autoclose_regions.push(AutocloseRegion {
9764 selection_id: selection.id,
9765 range: start..end,
9766 pair,
9767 });
9768 }
9769 }
9770 }
9771 }
9772 }
9773 Ok(())
9774 }
9775
9776 pub fn move_to_next_snippet_tabstop(
9777 &mut self,
9778 window: &mut Window,
9779 cx: &mut Context<Self>,
9780 ) -> bool {
9781 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9782 }
9783
9784 pub fn move_to_prev_snippet_tabstop(
9785 &mut self,
9786 window: &mut Window,
9787 cx: &mut Context<Self>,
9788 ) -> bool {
9789 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9790 }
9791
9792 pub fn move_to_snippet_tabstop(
9793 &mut self,
9794 bias: Bias,
9795 window: &mut Window,
9796 cx: &mut Context<Self>,
9797 ) -> bool {
9798 if let Some(mut snippet) = self.snippet_stack.pop() {
9799 match bias {
9800 Bias::Left => {
9801 if snippet.active_index > 0 {
9802 snippet.active_index -= 1;
9803 } else {
9804 self.snippet_stack.push(snippet);
9805 return false;
9806 }
9807 }
9808 Bias::Right => {
9809 if snippet.active_index + 1 < snippet.ranges.len() {
9810 snippet.active_index += 1;
9811 } else {
9812 self.snippet_stack.push(snippet);
9813 return false;
9814 }
9815 }
9816 }
9817 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9818 self.change_selections(Default::default(), window, cx, |s| {
9819 // Reverse order so that the first range is the newest created selection.
9820 // Completions will use it and autoscroll will prioritize it.
9821 s.select_ranges(current_ranges.iter().rev().cloned())
9822 });
9823
9824 if let Some(choices) = &snippet.choices[snippet.active_index]
9825 && let Some(selection) = current_ranges.first()
9826 {
9827 self.show_snippet_choices(choices, selection.clone(), cx);
9828 }
9829
9830 // If snippet state is not at the last tabstop, push it back on the stack
9831 if snippet.active_index + 1 < snippet.ranges.len() {
9832 self.snippet_stack.push(snippet);
9833 }
9834 return true;
9835 }
9836 }
9837
9838 false
9839 }
9840
9841 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9842 self.transact(window, cx, |this, window, cx| {
9843 this.select_all(&SelectAll, window, cx);
9844 this.insert("", window, cx);
9845 });
9846 }
9847
9848 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9849 if self.read_only(cx) {
9850 return;
9851 }
9852 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9853 self.transact(window, cx, |this, window, cx| {
9854 this.select_autoclose_pair(window, cx);
9855 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9856 if !this.linked_edit_ranges.is_empty() {
9857 let selections = this.selections.all::<MultiBufferPoint>(cx);
9858 let snapshot = this.buffer.read(cx).snapshot(cx);
9859
9860 for selection in selections.iter() {
9861 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9862 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9863 if selection_start.buffer_id != selection_end.buffer_id {
9864 continue;
9865 }
9866 if let Some(ranges) =
9867 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9868 {
9869 for (buffer, entries) in ranges {
9870 linked_ranges.entry(buffer).or_default().extend(entries);
9871 }
9872 }
9873 }
9874 }
9875
9876 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9877 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9878 for selection in &mut selections {
9879 if selection.is_empty() {
9880 let old_head = selection.head();
9881 let mut new_head =
9882 movement::left(&display_map, old_head.to_display_point(&display_map))
9883 .to_point(&display_map);
9884 if let Some((buffer, line_buffer_range)) = display_map
9885 .buffer_snapshot
9886 .buffer_line_for_row(MultiBufferRow(old_head.row))
9887 {
9888 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9889 let indent_len = match indent_size.kind {
9890 IndentKind::Space => {
9891 buffer.settings_at(line_buffer_range.start, cx).tab_size
9892 }
9893 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9894 };
9895 if old_head.column <= indent_size.len && old_head.column > 0 {
9896 let indent_len = indent_len.get();
9897 new_head = cmp::min(
9898 new_head,
9899 MultiBufferPoint::new(
9900 old_head.row,
9901 ((old_head.column - 1) / indent_len) * indent_len,
9902 ),
9903 );
9904 }
9905 }
9906
9907 selection.set_head(new_head, SelectionGoal::None);
9908 }
9909 }
9910
9911 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9912 this.insert("", window, cx);
9913 let empty_str: Arc<str> = Arc::from("");
9914 for (buffer, edits) in linked_ranges {
9915 let snapshot = buffer.read(cx).snapshot();
9916 use text::ToPoint as TP;
9917
9918 let edits = edits
9919 .into_iter()
9920 .map(|range| {
9921 let end_point = TP::to_point(&range.end, &snapshot);
9922 let mut start_point = TP::to_point(&range.start, &snapshot);
9923
9924 if end_point == start_point {
9925 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9926 .saturating_sub(1);
9927 start_point =
9928 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9929 };
9930
9931 (start_point..end_point, empty_str.clone())
9932 })
9933 .sorted_by_key(|(range, _)| range.start)
9934 .collect::<Vec<_>>();
9935 buffer.update(cx, |this, cx| {
9936 this.edit(edits, None, cx);
9937 })
9938 }
9939 this.refresh_edit_prediction(true, false, window, cx);
9940 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9941 });
9942 }
9943
9944 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9945 if self.read_only(cx) {
9946 return;
9947 }
9948 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9949 self.transact(window, cx, |this, window, cx| {
9950 this.change_selections(Default::default(), window, cx, |s| {
9951 s.move_with(|map, selection| {
9952 if selection.is_empty() {
9953 let cursor = movement::right(map, selection.head());
9954 selection.end = cursor;
9955 selection.reversed = true;
9956 selection.goal = SelectionGoal::None;
9957 }
9958 })
9959 });
9960 this.insert("", window, cx);
9961 this.refresh_edit_prediction(true, false, window, cx);
9962 });
9963 }
9964
9965 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9966 if self.mode.is_single_line() {
9967 cx.propagate();
9968 return;
9969 }
9970
9971 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9972 if self.move_to_prev_snippet_tabstop(window, cx) {
9973 return;
9974 }
9975 self.outdent(&Outdent, window, cx);
9976 }
9977
9978 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9979 if self.mode.is_single_line() {
9980 cx.propagate();
9981 return;
9982 }
9983
9984 if self.move_to_next_snippet_tabstop(window, cx) {
9985 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9986 return;
9987 }
9988 if self.read_only(cx) {
9989 return;
9990 }
9991 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9992 let mut selections = self.selections.all_adjusted(cx);
9993 let buffer = self.buffer.read(cx);
9994 let snapshot = buffer.snapshot(cx);
9995 let rows_iter = selections.iter().map(|s| s.head().row);
9996 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9997
9998 let has_some_cursor_in_whitespace = selections
9999 .iter()
10000 .filter(|selection| selection.is_empty())
10001 .any(|selection| {
10002 let cursor = selection.head();
10003 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10004 cursor.column < current_indent.len
10005 });
10006
10007 let mut edits = Vec::new();
10008 let mut prev_edited_row = 0;
10009 let mut row_delta = 0;
10010 for selection in &mut selections {
10011 if selection.start.row != prev_edited_row {
10012 row_delta = 0;
10013 }
10014 prev_edited_row = selection.end.row;
10015
10016 // If the selection is non-empty, then increase the indentation of the selected lines.
10017 if !selection.is_empty() {
10018 row_delta =
10019 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10020 continue;
10021 }
10022
10023 let cursor = selection.head();
10024 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10025 if let Some(suggested_indent) =
10026 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10027 {
10028 // Don't do anything if already at suggested indent
10029 // and there is any other cursor which is not
10030 if has_some_cursor_in_whitespace
10031 && cursor.column == current_indent.len
10032 && current_indent.len == suggested_indent.len
10033 {
10034 continue;
10035 }
10036
10037 // Adjust line and move cursor to suggested indent
10038 // if cursor is not at suggested indent
10039 if cursor.column < suggested_indent.len
10040 && cursor.column <= current_indent.len
10041 && current_indent.len <= suggested_indent.len
10042 {
10043 selection.start = Point::new(cursor.row, suggested_indent.len);
10044 selection.end = selection.start;
10045 if row_delta == 0 {
10046 edits.extend(Buffer::edit_for_indent_size_adjustment(
10047 cursor.row,
10048 current_indent,
10049 suggested_indent,
10050 ));
10051 row_delta = suggested_indent.len - current_indent.len;
10052 }
10053 continue;
10054 }
10055
10056 // If current indent is more than suggested indent
10057 // only move cursor to current indent and skip indent
10058 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10059 selection.start = Point::new(cursor.row, current_indent.len);
10060 selection.end = selection.start;
10061 continue;
10062 }
10063 }
10064
10065 // Otherwise, insert a hard or soft tab.
10066 let settings = buffer.language_settings_at(cursor, cx);
10067 let tab_size = if settings.hard_tabs {
10068 IndentSize::tab()
10069 } else {
10070 let tab_size = settings.tab_size.get();
10071 let indent_remainder = snapshot
10072 .text_for_range(Point::new(cursor.row, 0)..cursor)
10073 .flat_map(str::chars)
10074 .fold(row_delta % tab_size, |counter: u32, c| {
10075 if c == '\t' {
10076 0
10077 } else {
10078 (counter + 1) % tab_size
10079 }
10080 });
10081
10082 let chars_to_next_tab_stop = tab_size - indent_remainder;
10083 IndentSize::spaces(chars_to_next_tab_stop)
10084 };
10085 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10086 selection.end = selection.start;
10087 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10088 row_delta += tab_size.len;
10089 }
10090
10091 self.transact(window, cx, |this, window, cx| {
10092 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10093 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10094 this.refresh_edit_prediction(true, false, window, cx);
10095 });
10096 }
10097
10098 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10099 if self.read_only(cx) {
10100 return;
10101 }
10102 if self.mode.is_single_line() {
10103 cx.propagate();
10104 return;
10105 }
10106
10107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10108 let mut selections = self.selections.all::<Point>(cx);
10109 let mut prev_edited_row = 0;
10110 let mut row_delta = 0;
10111 let mut edits = Vec::new();
10112 let buffer = self.buffer.read(cx);
10113 let snapshot = buffer.snapshot(cx);
10114 for selection in &mut selections {
10115 if selection.start.row != prev_edited_row {
10116 row_delta = 0;
10117 }
10118 prev_edited_row = selection.end.row;
10119
10120 row_delta =
10121 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10122 }
10123
10124 self.transact(window, cx, |this, window, cx| {
10125 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10126 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10127 });
10128 }
10129
10130 fn indent_selection(
10131 buffer: &MultiBuffer,
10132 snapshot: &MultiBufferSnapshot,
10133 selection: &mut Selection<Point>,
10134 edits: &mut Vec<(Range<Point>, String)>,
10135 delta_for_start_row: u32,
10136 cx: &App,
10137 ) -> u32 {
10138 let settings = buffer.language_settings_at(selection.start, cx);
10139 let tab_size = settings.tab_size.get();
10140 let indent_kind = if settings.hard_tabs {
10141 IndentKind::Tab
10142 } else {
10143 IndentKind::Space
10144 };
10145 let mut start_row = selection.start.row;
10146 let mut end_row = selection.end.row + 1;
10147
10148 // If a selection ends at the beginning of a line, don't indent
10149 // that last line.
10150 if selection.end.column == 0 && selection.end.row > selection.start.row {
10151 end_row -= 1;
10152 }
10153
10154 // Avoid re-indenting a row that has already been indented by a
10155 // previous selection, but still update this selection's column
10156 // to reflect that indentation.
10157 if delta_for_start_row > 0 {
10158 start_row += 1;
10159 selection.start.column += delta_for_start_row;
10160 if selection.end.row == selection.start.row {
10161 selection.end.column += delta_for_start_row;
10162 }
10163 }
10164
10165 let mut delta_for_end_row = 0;
10166 let has_multiple_rows = start_row + 1 != end_row;
10167 for row in start_row..end_row {
10168 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10169 let indent_delta = match (current_indent.kind, indent_kind) {
10170 (IndentKind::Space, IndentKind::Space) => {
10171 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10172 IndentSize::spaces(columns_to_next_tab_stop)
10173 }
10174 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10175 (_, IndentKind::Tab) => IndentSize::tab(),
10176 };
10177
10178 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10179 0
10180 } else {
10181 selection.start.column
10182 };
10183 let row_start = Point::new(row, start);
10184 edits.push((
10185 row_start..row_start,
10186 indent_delta.chars().collect::<String>(),
10187 ));
10188
10189 // Update this selection's endpoints to reflect the indentation.
10190 if row == selection.start.row {
10191 selection.start.column += indent_delta.len;
10192 }
10193 if row == selection.end.row {
10194 selection.end.column += indent_delta.len;
10195 delta_for_end_row = indent_delta.len;
10196 }
10197 }
10198
10199 if selection.start.row == selection.end.row {
10200 delta_for_start_row + delta_for_end_row
10201 } else {
10202 delta_for_end_row
10203 }
10204 }
10205
10206 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10207 if self.read_only(cx) {
10208 return;
10209 }
10210 if self.mode.is_single_line() {
10211 cx.propagate();
10212 return;
10213 }
10214
10215 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10216 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10217 let selections = self.selections.all::<Point>(cx);
10218 let mut deletion_ranges = Vec::new();
10219 let mut last_outdent = None;
10220 {
10221 let buffer = self.buffer.read(cx);
10222 let snapshot = buffer.snapshot(cx);
10223 for selection in &selections {
10224 let settings = buffer.language_settings_at(selection.start, cx);
10225 let tab_size = settings.tab_size.get();
10226 let mut rows = selection.spanned_rows(false, &display_map);
10227
10228 // Avoid re-outdenting a row that has already been outdented by a
10229 // previous selection.
10230 if let Some(last_row) = last_outdent
10231 && last_row == rows.start
10232 {
10233 rows.start = rows.start.next_row();
10234 }
10235 let has_multiple_rows = rows.len() > 1;
10236 for row in rows.iter_rows() {
10237 let indent_size = snapshot.indent_size_for_line(row);
10238 if indent_size.len > 0 {
10239 let deletion_len = match indent_size.kind {
10240 IndentKind::Space => {
10241 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10242 if columns_to_prev_tab_stop == 0 {
10243 tab_size
10244 } else {
10245 columns_to_prev_tab_stop
10246 }
10247 }
10248 IndentKind::Tab => 1,
10249 };
10250 let start = if has_multiple_rows
10251 || deletion_len > selection.start.column
10252 || indent_size.len < selection.start.column
10253 {
10254 0
10255 } else {
10256 selection.start.column - deletion_len
10257 };
10258 deletion_ranges.push(
10259 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10260 );
10261 last_outdent = Some(row);
10262 }
10263 }
10264 }
10265 }
10266
10267 self.transact(window, cx, |this, window, cx| {
10268 this.buffer.update(cx, |buffer, cx| {
10269 let empty_str: Arc<str> = Arc::default();
10270 buffer.edit(
10271 deletion_ranges
10272 .into_iter()
10273 .map(|range| (range, empty_str.clone())),
10274 None,
10275 cx,
10276 );
10277 });
10278 let selections = this.selections.all::<usize>(cx);
10279 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10280 });
10281 }
10282
10283 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10284 if self.read_only(cx) {
10285 return;
10286 }
10287 if self.mode.is_single_line() {
10288 cx.propagate();
10289 return;
10290 }
10291
10292 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10293 let selections = self
10294 .selections
10295 .all::<usize>(cx)
10296 .into_iter()
10297 .map(|s| s.range());
10298
10299 self.transact(window, cx, |this, window, cx| {
10300 this.buffer.update(cx, |buffer, cx| {
10301 buffer.autoindent_ranges(selections, cx);
10302 });
10303 let selections = this.selections.all::<usize>(cx);
10304 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10305 });
10306 }
10307
10308 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10310 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10311 let selections = self.selections.all::<Point>(cx);
10312
10313 let mut new_cursors = Vec::new();
10314 let mut edit_ranges = Vec::new();
10315 let mut selections = selections.iter().peekable();
10316 while let Some(selection) = selections.next() {
10317 let mut rows = selection.spanned_rows(false, &display_map);
10318 let goal_display_column = selection.head().to_display_point(&display_map).column();
10319
10320 // Accumulate contiguous regions of rows that we want to delete.
10321 while let Some(next_selection) = selections.peek() {
10322 let next_rows = next_selection.spanned_rows(false, &display_map);
10323 if next_rows.start <= rows.end {
10324 rows.end = next_rows.end;
10325 selections.next().unwrap();
10326 } else {
10327 break;
10328 }
10329 }
10330
10331 let buffer = &display_map.buffer_snapshot;
10332 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
10333 let edit_end;
10334 let cursor_buffer_row;
10335 if buffer.max_point().row >= rows.end.0 {
10336 // If there's a line after the range, delete the \n from the end of the row range
10337 // and position the cursor on the next line.
10338 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
10339 cursor_buffer_row = rows.end;
10340 } else {
10341 // If there isn't a line after the range, delete the \n from the line before the
10342 // start of the row range and position the cursor there.
10343 edit_start = edit_start.saturating_sub(1);
10344 edit_end = buffer.len();
10345 cursor_buffer_row = rows.start.previous_row();
10346 }
10347
10348 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
10349 *cursor.column_mut() =
10350 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
10351
10352 new_cursors.push((
10353 selection.id,
10354 buffer.anchor_after(cursor.to_point(&display_map)),
10355 ));
10356 edit_ranges.push(edit_start..edit_end);
10357 }
10358
10359 self.transact(window, cx, |this, window, cx| {
10360 let buffer = this.buffer.update(cx, |buffer, cx| {
10361 let empty_str: Arc<str> = Arc::default();
10362 buffer.edit(
10363 edit_ranges
10364 .into_iter()
10365 .map(|range| (range, empty_str.clone())),
10366 None,
10367 cx,
10368 );
10369 buffer.snapshot(cx)
10370 });
10371 let new_selections = new_cursors
10372 .into_iter()
10373 .map(|(id, cursor)| {
10374 let cursor = cursor.to_point(&buffer);
10375 Selection {
10376 id,
10377 start: cursor,
10378 end: cursor,
10379 reversed: false,
10380 goal: SelectionGoal::None,
10381 }
10382 })
10383 .collect();
10384
10385 this.change_selections(Default::default(), window, cx, |s| {
10386 s.select(new_selections);
10387 });
10388 });
10389 }
10390
10391 pub fn join_lines_impl(
10392 &mut self,
10393 insert_whitespace: bool,
10394 window: &mut Window,
10395 cx: &mut Context<Self>,
10396 ) {
10397 if self.read_only(cx) {
10398 return;
10399 }
10400 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10401 for selection in self.selections.all::<Point>(cx) {
10402 let start = MultiBufferRow(selection.start.row);
10403 // Treat single line selections as if they include the next line. Otherwise this action
10404 // would do nothing for single line selections individual cursors.
10405 let end = if selection.start.row == selection.end.row {
10406 MultiBufferRow(selection.start.row + 1)
10407 } else {
10408 MultiBufferRow(selection.end.row)
10409 };
10410
10411 if let Some(last_row_range) = row_ranges.last_mut()
10412 && start <= last_row_range.end
10413 {
10414 last_row_range.end = end;
10415 continue;
10416 }
10417 row_ranges.push(start..end);
10418 }
10419
10420 let snapshot = self.buffer.read(cx).snapshot(cx);
10421 let mut cursor_positions = Vec::new();
10422 for row_range in &row_ranges {
10423 let anchor = snapshot.anchor_before(Point::new(
10424 row_range.end.previous_row().0,
10425 snapshot.line_len(row_range.end.previous_row()),
10426 ));
10427 cursor_positions.push(anchor..anchor);
10428 }
10429
10430 self.transact(window, cx, |this, window, cx| {
10431 for row_range in row_ranges.into_iter().rev() {
10432 for row in row_range.iter_rows().rev() {
10433 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10434 let next_line_row = row.next_row();
10435 let indent = snapshot.indent_size_for_line(next_line_row);
10436 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10437
10438 let replace =
10439 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10440 " "
10441 } else {
10442 ""
10443 };
10444
10445 this.buffer.update(cx, |buffer, cx| {
10446 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10447 });
10448 }
10449 }
10450
10451 this.change_selections(Default::default(), window, cx, |s| {
10452 s.select_anchor_ranges(cursor_positions)
10453 });
10454 });
10455 }
10456
10457 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10459 self.join_lines_impl(true, window, cx);
10460 }
10461
10462 pub fn sort_lines_case_sensitive(
10463 &mut self,
10464 _: &SortLinesCaseSensitive,
10465 window: &mut Window,
10466 cx: &mut Context<Self>,
10467 ) {
10468 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10469 }
10470
10471 pub fn sort_lines_by_length(
10472 &mut self,
10473 _: &SortLinesByLength,
10474 window: &mut Window,
10475 cx: &mut Context<Self>,
10476 ) {
10477 self.manipulate_immutable_lines(window, cx, |lines| {
10478 lines.sort_by_key(|&line| line.chars().count())
10479 })
10480 }
10481
10482 pub fn sort_lines_case_insensitive(
10483 &mut self,
10484 _: &SortLinesCaseInsensitive,
10485 window: &mut Window,
10486 cx: &mut Context<Self>,
10487 ) {
10488 self.manipulate_immutable_lines(window, cx, |lines| {
10489 lines.sort_by_key(|line| line.to_lowercase())
10490 })
10491 }
10492
10493 pub fn unique_lines_case_insensitive(
10494 &mut self,
10495 _: &UniqueLinesCaseInsensitive,
10496 window: &mut Window,
10497 cx: &mut Context<Self>,
10498 ) {
10499 self.manipulate_immutable_lines(window, cx, |lines| {
10500 let mut seen = HashSet::default();
10501 lines.retain(|line| seen.insert(line.to_lowercase()));
10502 })
10503 }
10504
10505 pub fn unique_lines_case_sensitive(
10506 &mut self,
10507 _: &UniqueLinesCaseSensitive,
10508 window: &mut Window,
10509 cx: &mut Context<Self>,
10510 ) {
10511 self.manipulate_immutable_lines(window, cx, |lines| {
10512 let mut seen = HashSet::default();
10513 lines.retain(|line| seen.insert(*line));
10514 })
10515 }
10516
10517 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10518 let snapshot = self.buffer.read(cx).snapshot(cx);
10519 for selection in self.selections.disjoint_anchors_arc().iter() {
10520 if snapshot
10521 .language_at(selection.start)
10522 .and_then(|lang| lang.config().wrap_characters.as_ref())
10523 .is_some()
10524 {
10525 return true;
10526 }
10527 }
10528 false
10529 }
10530
10531 fn wrap_selections_in_tag(
10532 &mut self,
10533 _: &WrapSelectionsInTag,
10534 window: &mut Window,
10535 cx: &mut Context<Self>,
10536 ) {
10537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10538
10539 let snapshot = self.buffer.read(cx).snapshot(cx);
10540
10541 let mut edits = Vec::new();
10542 let mut boundaries = Vec::new();
10543
10544 for selection in self.selections.all::<Point>(cx).iter() {
10545 let Some(wrap_config) = snapshot
10546 .language_at(selection.start)
10547 .and_then(|lang| lang.config().wrap_characters.clone())
10548 else {
10549 continue;
10550 };
10551
10552 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10553 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10554
10555 let start_before = snapshot.anchor_before(selection.start);
10556 let end_after = snapshot.anchor_after(selection.end);
10557
10558 edits.push((start_before..start_before, open_tag));
10559 edits.push((end_after..end_after, close_tag));
10560
10561 boundaries.push((
10562 start_before,
10563 end_after,
10564 wrap_config.start_prefix.len(),
10565 wrap_config.end_suffix.len(),
10566 ));
10567 }
10568
10569 if edits.is_empty() {
10570 return;
10571 }
10572
10573 self.transact(window, cx, |this, window, cx| {
10574 let buffer = this.buffer.update(cx, |buffer, cx| {
10575 buffer.edit(edits, None, cx);
10576 buffer.snapshot(cx)
10577 });
10578
10579 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10580 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10581 boundaries.into_iter()
10582 {
10583 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10584 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10585 new_selections.push(open_offset..open_offset);
10586 new_selections.push(close_offset..close_offset);
10587 }
10588
10589 this.change_selections(Default::default(), window, cx, |s| {
10590 s.select_ranges(new_selections);
10591 });
10592
10593 this.request_autoscroll(Autoscroll::fit(), cx);
10594 });
10595 }
10596
10597 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10598 let Some(project) = self.project.clone() else {
10599 return;
10600 };
10601 self.reload(project, window, cx)
10602 .detach_and_notify_err(window, cx);
10603 }
10604
10605 pub fn restore_file(
10606 &mut self,
10607 _: &::git::RestoreFile,
10608 window: &mut Window,
10609 cx: &mut Context<Self>,
10610 ) {
10611 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10612 let mut buffer_ids = HashSet::default();
10613 let snapshot = self.buffer().read(cx).snapshot(cx);
10614 for selection in self.selections.all::<usize>(cx) {
10615 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10616 }
10617
10618 let buffer = self.buffer().read(cx);
10619 let ranges = buffer_ids
10620 .into_iter()
10621 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10622 .collect::<Vec<_>>();
10623
10624 self.restore_hunks_in_ranges(ranges, window, cx);
10625 }
10626
10627 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10628 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10629 let selections = self
10630 .selections
10631 .all(cx)
10632 .into_iter()
10633 .map(|s| s.range())
10634 .collect();
10635 self.restore_hunks_in_ranges(selections, window, cx);
10636 }
10637
10638 pub fn restore_hunks_in_ranges(
10639 &mut self,
10640 ranges: Vec<Range<Point>>,
10641 window: &mut Window,
10642 cx: &mut Context<Editor>,
10643 ) {
10644 let mut revert_changes = HashMap::default();
10645 let chunk_by = self
10646 .snapshot(window, cx)
10647 .hunks_for_ranges(ranges)
10648 .into_iter()
10649 .chunk_by(|hunk| hunk.buffer_id);
10650 for (buffer_id, hunks) in &chunk_by {
10651 let hunks = hunks.collect::<Vec<_>>();
10652 for hunk in &hunks {
10653 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10654 }
10655 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10656 }
10657 drop(chunk_by);
10658 if !revert_changes.is_empty() {
10659 self.transact(window, cx, |editor, window, cx| {
10660 editor.restore(revert_changes, window, cx);
10661 });
10662 }
10663 }
10664
10665 pub fn open_active_item_in_terminal(
10666 &mut self,
10667 _: &OpenInTerminal,
10668 window: &mut Window,
10669 cx: &mut Context<Self>,
10670 ) {
10671 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10672 let project_path = buffer.read(cx).project_path(cx)?;
10673 let project = self.project()?.read(cx);
10674 let entry = project.entry_for_path(&project_path, cx)?;
10675 let parent = match &entry.canonical_path {
10676 Some(canonical_path) => canonical_path.to_path_buf(),
10677 None => project.absolute_path(&project_path, cx)?,
10678 }
10679 .parent()?
10680 .to_path_buf();
10681 Some(parent)
10682 }) {
10683 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10684 }
10685 }
10686
10687 fn set_breakpoint_context_menu(
10688 &mut self,
10689 display_row: DisplayRow,
10690 position: Option<Anchor>,
10691 clicked_point: gpui::Point<Pixels>,
10692 window: &mut Window,
10693 cx: &mut Context<Self>,
10694 ) {
10695 let source = self
10696 .buffer
10697 .read(cx)
10698 .snapshot(cx)
10699 .anchor_before(Point::new(display_row.0, 0u32));
10700
10701 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10702
10703 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10704 self,
10705 source,
10706 clicked_point,
10707 context_menu,
10708 window,
10709 cx,
10710 );
10711 }
10712
10713 fn add_edit_breakpoint_block(
10714 &mut self,
10715 anchor: Anchor,
10716 breakpoint: &Breakpoint,
10717 edit_action: BreakpointPromptEditAction,
10718 window: &mut Window,
10719 cx: &mut Context<Self>,
10720 ) {
10721 let weak_editor = cx.weak_entity();
10722 let bp_prompt = cx.new(|cx| {
10723 BreakpointPromptEditor::new(
10724 weak_editor,
10725 anchor,
10726 breakpoint.clone(),
10727 edit_action,
10728 window,
10729 cx,
10730 )
10731 });
10732
10733 let height = bp_prompt.update(cx, |this, cx| {
10734 this.prompt
10735 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10736 });
10737 let cloned_prompt = bp_prompt.clone();
10738 let blocks = vec![BlockProperties {
10739 style: BlockStyle::Sticky,
10740 placement: BlockPlacement::Above(anchor),
10741 height: Some(height),
10742 render: Arc::new(move |cx| {
10743 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10744 cloned_prompt.clone().into_any_element()
10745 }),
10746 priority: 0,
10747 }];
10748
10749 let focus_handle = bp_prompt.focus_handle(cx);
10750 window.focus(&focus_handle);
10751
10752 let block_ids = self.insert_blocks(blocks, None, cx);
10753 bp_prompt.update(cx, |prompt, _| {
10754 prompt.add_block_ids(block_ids);
10755 });
10756 }
10757
10758 pub(crate) fn breakpoint_at_row(
10759 &self,
10760 row: u32,
10761 window: &mut Window,
10762 cx: &mut Context<Self>,
10763 ) -> Option<(Anchor, Breakpoint)> {
10764 let snapshot = self.snapshot(window, cx);
10765 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10766
10767 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10768 }
10769
10770 pub(crate) fn breakpoint_at_anchor(
10771 &self,
10772 breakpoint_position: Anchor,
10773 snapshot: &EditorSnapshot,
10774 cx: &mut Context<Self>,
10775 ) -> Option<(Anchor, Breakpoint)> {
10776 let buffer = self
10777 .buffer
10778 .read(cx)
10779 .buffer_for_anchor(breakpoint_position, cx)?;
10780
10781 let enclosing_excerpt = breakpoint_position.excerpt_id;
10782 let buffer_snapshot = buffer.read(cx).snapshot();
10783
10784 let row = buffer_snapshot
10785 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10786 .row;
10787
10788 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10789 let anchor_end = snapshot
10790 .buffer_snapshot
10791 .anchor_after(Point::new(row, line_len));
10792
10793 self.breakpoint_store
10794 .as_ref()?
10795 .read_with(cx, |breakpoint_store, cx| {
10796 breakpoint_store
10797 .breakpoints(
10798 &buffer,
10799 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10800 &buffer_snapshot,
10801 cx,
10802 )
10803 .next()
10804 .and_then(|(bp, _)| {
10805 let breakpoint_row = buffer_snapshot
10806 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10807 .row;
10808
10809 if breakpoint_row == row {
10810 snapshot
10811 .buffer_snapshot
10812 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10813 .map(|position| (position, bp.bp.clone()))
10814 } else {
10815 None
10816 }
10817 })
10818 })
10819 }
10820
10821 pub fn edit_log_breakpoint(
10822 &mut self,
10823 _: &EditLogBreakpoint,
10824 window: &mut Window,
10825 cx: &mut Context<Self>,
10826 ) {
10827 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10828 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10829 message: None,
10830 state: BreakpointState::Enabled,
10831 condition: None,
10832 hit_condition: None,
10833 });
10834
10835 self.add_edit_breakpoint_block(
10836 anchor,
10837 &breakpoint,
10838 BreakpointPromptEditAction::Log,
10839 window,
10840 cx,
10841 );
10842 }
10843 }
10844
10845 fn breakpoints_at_cursors(
10846 &self,
10847 window: &mut Window,
10848 cx: &mut Context<Self>,
10849 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10850 let snapshot = self.snapshot(window, cx);
10851 let cursors = self
10852 .selections
10853 .disjoint_anchors_arc()
10854 .iter()
10855 .map(|selection| {
10856 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10857
10858 let breakpoint_position = self
10859 .breakpoint_at_row(cursor_position.row, window, cx)
10860 .map(|bp| bp.0)
10861 .unwrap_or_else(|| {
10862 snapshot
10863 .display_snapshot
10864 .buffer_snapshot
10865 .anchor_after(Point::new(cursor_position.row, 0))
10866 });
10867
10868 let breakpoint = self
10869 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10870 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10871
10872 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10873 })
10874 // 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.
10875 .collect::<HashMap<Anchor, _>>();
10876
10877 cursors.into_iter().collect()
10878 }
10879
10880 pub fn enable_breakpoint(
10881 &mut self,
10882 _: &crate::actions::EnableBreakpoint,
10883 window: &mut Window,
10884 cx: &mut Context<Self>,
10885 ) {
10886 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10887 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10888 continue;
10889 };
10890 self.edit_breakpoint_at_anchor(
10891 anchor,
10892 breakpoint,
10893 BreakpointEditAction::InvertState,
10894 cx,
10895 );
10896 }
10897 }
10898
10899 pub fn disable_breakpoint(
10900 &mut self,
10901 _: &crate::actions::DisableBreakpoint,
10902 window: &mut Window,
10903 cx: &mut Context<Self>,
10904 ) {
10905 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10906 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10907 continue;
10908 };
10909 self.edit_breakpoint_at_anchor(
10910 anchor,
10911 breakpoint,
10912 BreakpointEditAction::InvertState,
10913 cx,
10914 );
10915 }
10916 }
10917
10918 pub fn toggle_breakpoint(
10919 &mut self,
10920 _: &crate::actions::ToggleBreakpoint,
10921 window: &mut Window,
10922 cx: &mut Context<Self>,
10923 ) {
10924 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10925 if let Some(breakpoint) = breakpoint {
10926 self.edit_breakpoint_at_anchor(
10927 anchor,
10928 breakpoint,
10929 BreakpointEditAction::Toggle,
10930 cx,
10931 );
10932 } else {
10933 self.edit_breakpoint_at_anchor(
10934 anchor,
10935 Breakpoint::new_standard(),
10936 BreakpointEditAction::Toggle,
10937 cx,
10938 );
10939 }
10940 }
10941 }
10942
10943 pub fn edit_breakpoint_at_anchor(
10944 &mut self,
10945 breakpoint_position: Anchor,
10946 breakpoint: Breakpoint,
10947 edit_action: BreakpointEditAction,
10948 cx: &mut Context<Self>,
10949 ) {
10950 let Some(breakpoint_store) = &self.breakpoint_store else {
10951 return;
10952 };
10953
10954 let Some(buffer) = self
10955 .buffer
10956 .read(cx)
10957 .buffer_for_anchor(breakpoint_position, cx)
10958 else {
10959 return;
10960 };
10961
10962 breakpoint_store.update(cx, |breakpoint_store, cx| {
10963 breakpoint_store.toggle_breakpoint(
10964 buffer,
10965 BreakpointWithPosition {
10966 position: breakpoint_position.text_anchor,
10967 bp: breakpoint,
10968 },
10969 edit_action,
10970 cx,
10971 );
10972 });
10973
10974 cx.notify();
10975 }
10976
10977 #[cfg(any(test, feature = "test-support"))]
10978 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10979 self.breakpoint_store.clone()
10980 }
10981
10982 pub fn prepare_restore_change(
10983 &self,
10984 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10985 hunk: &MultiBufferDiffHunk,
10986 cx: &mut App,
10987 ) -> Option<()> {
10988 if hunk.is_created_file() {
10989 return None;
10990 }
10991 let buffer = self.buffer.read(cx);
10992 let diff = buffer.diff_for(hunk.buffer_id)?;
10993 let buffer = buffer.buffer(hunk.buffer_id)?;
10994 let buffer = buffer.read(cx);
10995 let original_text = diff
10996 .read(cx)
10997 .base_text()
10998 .as_rope()
10999 .slice(hunk.diff_base_byte_range.clone());
11000 let buffer_snapshot = buffer.snapshot();
11001 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11002 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11003 probe
11004 .0
11005 .start
11006 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11007 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11008 }) {
11009 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11010 Some(())
11011 } else {
11012 None
11013 }
11014 }
11015
11016 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11017 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11018 }
11019
11020 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11021 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11022 }
11023
11024 fn manipulate_lines<M>(
11025 &mut self,
11026 window: &mut Window,
11027 cx: &mut Context<Self>,
11028 mut manipulate: M,
11029 ) where
11030 M: FnMut(&str) -> LineManipulationResult,
11031 {
11032 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11033
11034 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11035 let buffer = self.buffer.read(cx).snapshot(cx);
11036
11037 let mut edits = Vec::new();
11038
11039 let selections = self.selections.all::<Point>(cx);
11040 let mut selections = selections.iter().peekable();
11041 let mut contiguous_row_selections = Vec::new();
11042 let mut new_selections = Vec::new();
11043 let mut added_lines = 0;
11044 let mut removed_lines = 0;
11045
11046 while let Some(selection) = selections.next() {
11047 let (start_row, end_row) = consume_contiguous_rows(
11048 &mut contiguous_row_selections,
11049 selection,
11050 &display_map,
11051 &mut selections,
11052 );
11053
11054 let start_point = Point::new(start_row.0, 0);
11055 let end_point = Point::new(
11056 end_row.previous_row().0,
11057 buffer.line_len(end_row.previous_row()),
11058 );
11059 let text = buffer
11060 .text_for_range(start_point..end_point)
11061 .collect::<String>();
11062
11063 let LineManipulationResult {
11064 new_text,
11065 line_count_before,
11066 line_count_after,
11067 } = manipulate(&text);
11068
11069 edits.push((start_point..end_point, new_text));
11070
11071 // Selections must change based on added and removed line count
11072 let start_row =
11073 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11074 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11075 new_selections.push(Selection {
11076 id: selection.id,
11077 start: start_row,
11078 end: end_row,
11079 goal: SelectionGoal::None,
11080 reversed: selection.reversed,
11081 });
11082
11083 if line_count_after > line_count_before {
11084 added_lines += line_count_after - line_count_before;
11085 } else if line_count_before > line_count_after {
11086 removed_lines += line_count_before - line_count_after;
11087 }
11088 }
11089
11090 self.transact(window, cx, |this, window, cx| {
11091 let buffer = this.buffer.update(cx, |buffer, cx| {
11092 buffer.edit(edits, None, cx);
11093 buffer.snapshot(cx)
11094 });
11095
11096 // Recalculate offsets on newly edited buffer
11097 let new_selections = new_selections
11098 .iter()
11099 .map(|s| {
11100 let start_point = Point::new(s.start.0, 0);
11101 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11102 Selection {
11103 id: s.id,
11104 start: buffer.point_to_offset(start_point),
11105 end: buffer.point_to_offset(end_point),
11106 goal: s.goal,
11107 reversed: s.reversed,
11108 }
11109 })
11110 .collect();
11111
11112 this.change_selections(Default::default(), window, cx, |s| {
11113 s.select(new_selections);
11114 });
11115
11116 this.request_autoscroll(Autoscroll::fit(), cx);
11117 });
11118 }
11119
11120 fn manipulate_immutable_lines<Fn>(
11121 &mut self,
11122 window: &mut Window,
11123 cx: &mut Context<Self>,
11124 mut callback: Fn,
11125 ) where
11126 Fn: FnMut(&mut Vec<&str>),
11127 {
11128 self.manipulate_lines(window, cx, |text| {
11129 let mut lines: Vec<&str> = text.split('\n').collect();
11130 let line_count_before = lines.len();
11131
11132 callback(&mut lines);
11133
11134 LineManipulationResult {
11135 new_text: lines.join("\n"),
11136 line_count_before,
11137 line_count_after: lines.len(),
11138 }
11139 });
11140 }
11141
11142 fn manipulate_mutable_lines<Fn>(
11143 &mut self,
11144 window: &mut Window,
11145 cx: &mut Context<Self>,
11146 mut callback: Fn,
11147 ) where
11148 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11149 {
11150 self.manipulate_lines(window, cx, |text| {
11151 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11152 let line_count_before = lines.len();
11153
11154 callback(&mut lines);
11155
11156 LineManipulationResult {
11157 new_text: lines.join("\n"),
11158 line_count_before,
11159 line_count_after: lines.len(),
11160 }
11161 });
11162 }
11163
11164 pub fn convert_indentation_to_spaces(
11165 &mut self,
11166 _: &ConvertIndentationToSpaces,
11167 window: &mut Window,
11168 cx: &mut Context<Self>,
11169 ) {
11170 let settings = self.buffer.read(cx).language_settings(cx);
11171 let tab_size = settings.tab_size.get() as usize;
11172
11173 self.manipulate_mutable_lines(window, cx, |lines| {
11174 // Allocates a reasonably sized scratch buffer once for the whole loop
11175 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11176 // Avoids recomputing spaces that could be inserted many times
11177 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11178 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11179 .collect();
11180
11181 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11182 let mut chars = line.as_ref().chars();
11183 let mut col = 0;
11184 let mut changed = false;
11185
11186 for ch in chars.by_ref() {
11187 match ch {
11188 ' ' => {
11189 reindented_line.push(' ');
11190 col += 1;
11191 }
11192 '\t' => {
11193 // \t are converted to spaces depending on the current column
11194 let spaces_len = tab_size - (col % tab_size);
11195 reindented_line.extend(&space_cache[spaces_len - 1]);
11196 col += spaces_len;
11197 changed = true;
11198 }
11199 _ => {
11200 // If we dont append before break, the character is consumed
11201 reindented_line.push(ch);
11202 break;
11203 }
11204 }
11205 }
11206
11207 if !changed {
11208 reindented_line.clear();
11209 continue;
11210 }
11211 // Append the rest of the line and replace old reference with new one
11212 reindented_line.extend(chars);
11213 *line = Cow::Owned(reindented_line.clone());
11214 reindented_line.clear();
11215 }
11216 });
11217 }
11218
11219 pub fn convert_indentation_to_tabs(
11220 &mut self,
11221 _: &ConvertIndentationToTabs,
11222 window: &mut Window,
11223 cx: &mut Context<Self>,
11224 ) {
11225 let settings = self.buffer.read(cx).language_settings(cx);
11226 let tab_size = settings.tab_size.get() as usize;
11227
11228 self.manipulate_mutable_lines(window, cx, |lines| {
11229 // Allocates a reasonably sized buffer once for the whole loop
11230 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11231 // Avoids recomputing spaces that could be inserted many times
11232 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11233 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11234 .collect();
11235
11236 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11237 let mut chars = line.chars();
11238 let mut spaces_count = 0;
11239 let mut first_non_indent_char = None;
11240 let mut changed = false;
11241
11242 for ch in chars.by_ref() {
11243 match ch {
11244 ' ' => {
11245 // Keep track of spaces. Append \t when we reach tab_size
11246 spaces_count += 1;
11247 changed = true;
11248 if spaces_count == tab_size {
11249 reindented_line.push('\t');
11250 spaces_count = 0;
11251 }
11252 }
11253 '\t' => {
11254 reindented_line.push('\t');
11255 spaces_count = 0;
11256 }
11257 _ => {
11258 // Dont append it yet, we might have remaining spaces
11259 first_non_indent_char = Some(ch);
11260 break;
11261 }
11262 }
11263 }
11264
11265 if !changed {
11266 reindented_line.clear();
11267 continue;
11268 }
11269 // Remaining spaces that didn't make a full tab stop
11270 if spaces_count > 0 {
11271 reindented_line.extend(&space_cache[spaces_count - 1]);
11272 }
11273 // If we consume an extra character that was not indentation, add it back
11274 if let Some(extra_char) = first_non_indent_char {
11275 reindented_line.push(extra_char);
11276 }
11277 // Append the rest of the line and replace old reference with new one
11278 reindented_line.extend(chars);
11279 *line = Cow::Owned(reindented_line.clone());
11280 reindented_line.clear();
11281 }
11282 });
11283 }
11284
11285 pub fn convert_to_upper_case(
11286 &mut self,
11287 _: &ConvertToUpperCase,
11288 window: &mut Window,
11289 cx: &mut Context<Self>,
11290 ) {
11291 self.manipulate_text(window, cx, |text| text.to_uppercase())
11292 }
11293
11294 pub fn convert_to_lower_case(
11295 &mut self,
11296 _: &ConvertToLowerCase,
11297 window: &mut Window,
11298 cx: &mut Context<Self>,
11299 ) {
11300 self.manipulate_text(window, cx, |text| text.to_lowercase())
11301 }
11302
11303 pub fn convert_to_title_case(
11304 &mut self,
11305 _: &ConvertToTitleCase,
11306 window: &mut Window,
11307 cx: &mut Context<Self>,
11308 ) {
11309 self.manipulate_text(window, cx, |text| {
11310 text.split('\n')
11311 .map(|line| line.to_case(Case::Title))
11312 .join("\n")
11313 })
11314 }
11315
11316 pub fn convert_to_snake_case(
11317 &mut self,
11318 _: &ConvertToSnakeCase,
11319 window: &mut Window,
11320 cx: &mut Context<Self>,
11321 ) {
11322 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11323 }
11324
11325 pub fn convert_to_kebab_case(
11326 &mut self,
11327 _: &ConvertToKebabCase,
11328 window: &mut Window,
11329 cx: &mut Context<Self>,
11330 ) {
11331 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11332 }
11333
11334 pub fn convert_to_upper_camel_case(
11335 &mut self,
11336 _: &ConvertToUpperCamelCase,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) {
11340 self.manipulate_text(window, cx, |text| {
11341 text.split('\n')
11342 .map(|line| line.to_case(Case::UpperCamel))
11343 .join("\n")
11344 })
11345 }
11346
11347 pub fn convert_to_lower_camel_case(
11348 &mut self,
11349 _: &ConvertToLowerCamelCase,
11350 window: &mut Window,
11351 cx: &mut Context<Self>,
11352 ) {
11353 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11354 }
11355
11356 pub fn convert_to_opposite_case(
11357 &mut self,
11358 _: &ConvertToOppositeCase,
11359 window: &mut Window,
11360 cx: &mut Context<Self>,
11361 ) {
11362 self.manipulate_text(window, cx, |text| {
11363 text.chars()
11364 .fold(String::with_capacity(text.len()), |mut t, c| {
11365 if c.is_uppercase() {
11366 t.extend(c.to_lowercase());
11367 } else {
11368 t.extend(c.to_uppercase());
11369 }
11370 t
11371 })
11372 })
11373 }
11374
11375 pub fn convert_to_sentence_case(
11376 &mut self,
11377 _: &ConvertToSentenceCase,
11378 window: &mut Window,
11379 cx: &mut Context<Self>,
11380 ) {
11381 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11382 }
11383
11384 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11385 self.manipulate_text(window, cx, |text| {
11386 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11387 if has_upper_case_characters {
11388 text.to_lowercase()
11389 } else {
11390 text.to_uppercase()
11391 }
11392 })
11393 }
11394
11395 pub fn convert_to_rot13(
11396 &mut self,
11397 _: &ConvertToRot13,
11398 window: &mut Window,
11399 cx: &mut Context<Self>,
11400 ) {
11401 self.manipulate_text(window, cx, |text| {
11402 text.chars()
11403 .map(|c| match c {
11404 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11405 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11406 _ => c,
11407 })
11408 .collect()
11409 })
11410 }
11411
11412 pub fn convert_to_rot47(
11413 &mut self,
11414 _: &ConvertToRot47,
11415 window: &mut Window,
11416 cx: &mut Context<Self>,
11417 ) {
11418 self.manipulate_text(window, cx, |text| {
11419 text.chars()
11420 .map(|c| {
11421 let code_point = c as u32;
11422 if code_point >= 33 && code_point <= 126 {
11423 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11424 }
11425 c
11426 })
11427 .collect()
11428 })
11429 }
11430
11431 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11432 where
11433 Fn: FnMut(&str) -> String,
11434 {
11435 let buffer = self.buffer.read(cx).snapshot(cx);
11436
11437 let mut new_selections = Vec::new();
11438 let mut edits = Vec::new();
11439 let mut selection_adjustment = 0i32;
11440
11441 for selection in self.selections.all_adjusted(cx) {
11442 let selection_is_empty = selection.is_empty();
11443
11444 let (start, end) = if selection_is_empty {
11445 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11446 (word_range.start, word_range.end)
11447 } else {
11448 (
11449 buffer.point_to_offset(selection.start),
11450 buffer.point_to_offset(selection.end),
11451 )
11452 };
11453
11454 let text = buffer.text_for_range(start..end).collect::<String>();
11455 let old_length = text.len() as i32;
11456 let text = callback(&text);
11457
11458 new_selections.push(Selection {
11459 start: (start as i32 - selection_adjustment) as usize,
11460 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11461 goal: SelectionGoal::None,
11462 id: selection.id,
11463 reversed: selection.reversed,
11464 });
11465
11466 selection_adjustment += old_length - text.len() as i32;
11467
11468 edits.push((start..end, text));
11469 }
11470
11471 self.transact(window, cx, |this, window, cx| {
11472 this.buffer.update(cx, |buffer, cx| {
11473 buffer.edit(edits, None, cx);
11474 });
11475
11476 this.change_selections(Default::default(), window, cx, |s| {
11477 s.select(new_selections);
11478 });
11479
11480 this.request_autoscroll(Autoscroll::fit(), cx);
11481 });
11482 }
11483
11484 pub fn move_selection_on_drop(
11485 &mut self,
11486 selection: &Selection<Anchor>,
11487 target: DisplayPoint,
11488 is_cut: bool,
11489 window: &mut Window,
11490 cx: &mut Context<Self>,
11491 ) {
11492 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11493 let buffer = &display_map.buffer_snapshot;
11494 let mut edits = Vec::new();
11495 let insert_point = display_map
11496 .clip_point(target, Bias::Left)
11497 .to_point(&display_map);
11498 let text = buffer
11499 .text_for_range(selection.start..selection.end)
11500 .collect::<String>();
11501 if is_cut {
11502 edits.push(((selection.start..selection.end), String::new()));
11503 }
11504 let insert_anchor = buffer.anchor_before(insert_point);
11505 edits.push(((insert_anchor..insert_anchor), text));
11506 let last_edit_start = insert_anchor.bias_left(buffer);
11507 let last_edit_end = insert_anchor.bias_right(buffer);
11508 self.transact(window, cx, |this, window, cx| {
11509 this.buffer.update(cx, |buffer, cx| {
11510 buffer.edit(edits, None, cx);
11511 });
11512 this.change_selections(Default::default(), window, cx, |s| {
11513 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11514 });
11515 });
11516 }
11517
11518 pub fn clear_selection_drag_state(&mut self) {
11519 self.selection_drag_state = SelectionDragState::None;
11520 }
11521
11522 pub fn duplicate(
11523 &mut self,
11524 upwards: bool,
11525 whole_lines: bool,
11526 window: &mut Window,
11527 cx: &mut Context<Self>,
11528 ) {
11529 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11530
11531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11532 let buffer = &display_map.buffer_snapshot;
11533 let selections = self.selections.all::<Point>(cx);
11534
11535 let mut edits = Vec::new();
11536 let mut selections_iter = selections.iter().peekable();
11537 while let Some(selection) = selections_iter.next() {
11538 let mut rows = selection.spanned_rows(false, &display_map);
11539 // duplicate line-wise
11540 if whole_lines || selection.start == selection.end {
11541 // Avoid duplicating the same lines twice.
11542 while let Some(next_selection) = selections_iter.peek() {
11543 let next_rows = next_selection.spanned_rows(false, &display_map);
11544 if next_rows.start < rows.end {
11545 rows.end = next_rows.end;
11546 selections_iter.next().unwrap();
11547 } else {
11548 break;
11549 }
11550 }
11551
11552 // Copy the text from the selected row region and splice it either at the start
11553 // or end of the region.
11554 let start = Point::new(rows.start.0, 0);
11555 let end = Point::new(
11556 rows.end.previous_row().0,
11557 buffer.line_len(rows.end.previous_row()),
11558 );
11559 let text = buffer
11560 .text_for_range(start..end)
11561 .chain(Some("\n"))
11562 .collect::<String>();
11563 let insert_location = if upwards {
11564 Point::new(rows.end.0, 0)
11565 } else {
11566 start
11567 };
11568 edits.push((insert_location..insert_location, text));
11569 } else {
11570 // duplicate character-wise
11571 let start = selection.start;
11572 let end = selection.end;
11573 let text = buffer.text_for_range(start..end).collect::<String>();
11574 edits.push((selection.end..selection.end, text));
11575 }
11576 }
11577
11578 self.transact(window, cx, |this, _, cx| {
11579 this.buffer.update(cx, |buffer, cx| {
11580 buffer.edit(edits, None, cx);
11581 });
11582
11583 this.request_autoscroll(Autoscroll::fit(), cx);
11584 });
11585 }
11586
11587 pub fn duplicate_line_up(
11588 &mut self,
11589 _: &DuplicateLineUp,
11590 window: &mut Window,
11591 cx: &mut Context<Self>,
11592 ) {
11593 self.duplicate(true, true, window, cx);
11594 }
11595
11596 pub fn duplicate_line_down(
11597 &mut self,
11598 _: &DuplicateLineDown,
11599 window: &mut Window,
11600 cx: &mut Context<Self>,
11601 ) {
11602 self.duplicate(false, true, window, cx);
11603 }
11604
11605 pub fn duplicate_selection(
11606 &mut self,
11607 _: &DuplicateSelection,
11608 window: &mut Window,
11609 cx: &mut Context<Self>,
11610 ) {
11611 self.duplicate(false, false, window, cx);
11612 }
11613
11614 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11616 if self.mode.is_single_line() {
11617 cx.propagate();
11618 return;
11619 }
11620
11621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11622 let buffer = self.buffer.read(cx).snapshot(cx);
11623
11624 let mut edits = Vec::new();
11625 let mut unfold_ranges = Vec::new();
11626 let mut refold_creases = Vec::new();
11627
11628 let selections = self.selections.all::<Point>(cx);
11629 let mut selections = selections.iter().peekable();
11630 let mut contiguous_row_selections = Vec::new();
11631 let mut new_selections = Vec::new();
11632
11633 while let Some(selection) = selections.next() {
11634 // Find all the selections that span a contiguous row range
11635 let (start_row, end_row) = consume_contiguous_rows(
11636 &mut contiguous_row_selections,
11637 selection,
11638 &display_map,
11639 &mut selections,
11640 );
11641
11642 // Move the text spanned by the row range to be before the line preceding the row range
11643 if start_row.0 > 0 {
11644 let range_to_move = Point::new(
11645 start_row.previous_row().0,
11646 buffer.line_len(start_row.previous_row()),
11647 )
11648 ..Point::new(
11649 end_row.previous_row().0,
11650 buffer.line_len(end_row.previous_row()),
11651 );
11652 let insertion_point = display_map
11653 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11654 .0;
11655
11656 // Don't move lines across excerpts
11657 if buffer
11658 .excerpt_containing(insertion_point..range_to_move.end)
11659 .is_some()
11660 {
11661 let text = buffer
11662 .text_for_range(range_to_move.clone())
11663 .flat_map(|s| s.chars())
11664 .skip(1)
11665 .chain(['\n'])
11666 .collect::<String>();
11667
11668 edits.push((
11669 buffer.anchor_after(range_to_move.start)
11670 ..buffer.anchor_before(range_to_move.end),
11671 String::new(),
11672 ));
11673 let insertion_anchor = buffer.anchor_after(insertion_point);
11674 edits.push((insertion_anchor..insertion_anchor, text));
11675
11676 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11677
11678 // Move selections up
11679 new_selections.extend(contiguous_row_selections.drain(..).map(
11680 |mut selection| {
11681 selection.start.row -= row_delta;
11682 selection.end.row -= row_delta;
11683 selection
11684 },
11685 ));
11686
11687 // Move folds up
11688 unfold_ranges.push(range_to_move.clone());
11689 for fold in display_map.folds_in_range(
11690 buffer.anchor_before(range_to_move.start)
11691 ..buffer.anchor_after(range_to_move.end),
11692 ) {
11693 let mut start = fold.range.start.to_point(&buffer);
11694 let mut end = fold.range.end.to_point(&buffer);
11695 start.row -= row_delta;
11696 end.row -= row_delta;
11697 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11698 }
11699 }
11700 }
11701
11702 // If we didn't move line(s), preserve the existing selections
11703 new_selections.append(&mut contiguous_row_selections);
11704 }
11705
11706 self.transact(window, cx, |this, window, cx| {
11707 this.unfold_ranges(&unfold_ranges, true, true, cx);
11708 this.buffer.update(cx, |buffer, cx| {
11709 for (range, text) in edits {
11710 buffer.edit([(range, text)], None, cx);
11711 }
11712 });
11713 this.fold_creases(refold_creases, true, window, cx);
11714 this.change_selections(Default::default(), window, cx, |s| {
11715 s.select(new_selections);
11716 })
11717 });
11718 }
11719
11720 pub fn move_line_down(
11721 &mut self,
11722 _: &MoveLineDown,
11723 window: &mut Window,
11724 cx: &mut Context<Self>,
11725 ) {
11726 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11727 if self.mode.is_single_line() {
11728 cx.propagate();
11729 return;
11730 }
11731
11732 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11733 let buffer = self.buffer.read(cx).snapshot(cx);
11734
11735 let mut edits = Vec::new();
11736 let mut unfold_ranges = Vec::new();
11737 let mut refold_creases = Vec::new();
11738
11739 let selections = self.selections.all::<Point>(cx);
11740 let mut selections = selections.iter().peekable();
11741 let mut contiguous_row_selections = Vec::new();
11742 let mut new_selections = Vec::new();
11743
11744 while let Some(selection) = selections.next() {
11745 // Find all the selections that span a contiguous row range
11746 let (start_row, end_row) = consume_contiguous_rows(
11747 &mut contiguous_row_selections,
11748 selection,
11749 &display_map,
11750 &mut selections,
11751 );
11752
11753 // Move the text spanned by the row range to be after the last line of the row range
11754 if end_row.0 <= buffer.max_point().row {
11755 let range_to_move =
11756 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11757 let insertion_point = display_map
11758 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11759 .0;
11760
11761 // Don't move lines across excerpt boundaries
11762 if buffer
11763 .excerpt_containing(range_to_move.start..insertion_point)
11764 .is_some()
11765 {
11766 let mut text = String::from("\n");
11767 text.extend(buffer.text_for_range(range_to_move.clone()));
11768 text.pop(); // Drop trailing newline
11769 edits.push((
11770 buffer.anchor_after(range_to_move.start)
11771 ..buffer.anchor_before(range_to_move.end),
11772 String::new(),
11773 ));
11774 let insertion_anchor = buffer.anchor_after(insertion_point);
11775 edits.push((insertion_anchor..insertion_anchor, text));
11776
11777 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11778
11779 // Move selections down
11780 new_selections.extend(contiguous_row_selections.drain(..).map(
11781 |mut selection| {
11782 selection.start.row += row_delta;
11783 selection.end.row += row_delta;
11784 selection
11785 },
11786 ));
11787
11788 // Move folds down
11789 unfold_ranges.push(range_to_move.clone());
11790 for fold in display_map.folds_in_range(
11791 buffer.anchor_before(range_to_move.start)
11792 ..buffer.anchor_after(range_to_move.end),
11793 ) {
11794 let mut start = fold.range.start.to_point(&buffer);
11795 let mut end = fold.range.end.to_point(&buffer);
11796 start.row += row_delta;
11797 end.row += row_delta;
11798 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11799 }
11800 }
11801 }
11802
11803 // If we didn't move line(s), preserve the existing selections
11804 new_selections.append(&mut contiguous_row_selections);
11805 }
11806
11807 self.transact(window, cx, |this, window, cx| {
11808 this.unfold_ranges(&unfold_ranges, true, true, cx);
11809 this.buffer.update(cx, |buffer, cx| {
11810 for (range, text) in edits {
11811 buffer.edit([(range, text)], None, cx);
11812 }
11813 });
11814 this.fold_creases(refold_creases, true, window, cx);
11815 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11816 });
11817 }
11818
11819 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11821 let text_layout_details = &self.text_layout_details(window);
11822 self.transact(window, cx, |this, window, cx| {
11823 let edits = this.change_selections(Default::default(), window, cx, |s| {
11824 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11825 s.move_with(|display_map, selection| {
11826 if !selection.is_empty() {
11827 return;
11828 }
11829
11830 let mut head = selection.head();
11831 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11832 if head.column() == display_map.line_len(head.row()) {
11833 transpose_offset = display_map
11834 .buffer_snapshot
11835 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11836 }
11837
11838 if transpose_offset == 0 {
11839 return;
11840 }
11841
11842 *head.column_mut() += 1;
11843 head = display_map.clip_point(head, Bias::Right);
11844 let goal = SelectionGoal::HorizontalPosition(
11845 display_map
11846 .x_for_display_point(head, text_layout_details)
11847 .into(),
11848 );
11849 selection.collapse_to(head, goal);
11850
11851 let transpose_start = display_map
11852 .buffer_snapshot
11853 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11854 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11855 let transpose_end = display_map
11856 .buffer_snapshot
11857 .clip_offset(transpose_offset + 1, Bias::Right);
11858 if let Some(ch) =
11859 display_map.buffer_snapshot.chars_at(transpose_start).next()
11860 {
11861 edits.push((transpose_start..transpose_offset, String::new()));
11862 edits.push((transpose_end..transpose_end, ch.to_string()));
11863 }
11864 }
11865 });
11866 edits
11867 });
11868 this.buffer
11869 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11870 let selections = this.selections.all::<usize>(cx);
11871 this.change_selections(Default::default(), window, cx, |s| {
11872 s.select(selections);
11873 });
11874 });
11875 }
11876
11877 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11878 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11879 if self.mode.is_single_line() {
11880 cx.propagate();
11881 return;
11882 }
11883
11884 self.rewrap_impl(RewrapOptions::default(), cx)
11885 }
11886
11887 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11888 let buffer = self.buffer.read(cx).snapshot(cx);
11889 let selections = self.selections.all::<Point>(cx);
11890
11891 #[derive(Clone, Debug, PartialEq)]
11892 enum CommentFormat {
11893 /// single line comment, with prefix for line
11894 Line(String),
11895 /// single line within a block comment, with prefix for line
11896 BlockLine(String),
11897 /// a single line of a block comment that includes the initial delimiter
11898 BlockCommentWithStart(BlockCommentConfig),
11899 /// a single line of a block comment that includes the ending delimiter
11900 BlockCommentWithEnd(BlockCommentConfig),
11901 }
11902
11903 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11904 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11905 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11906 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11907 .peekable();
11908
11909 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11910 row
11911 } else {
11912 return Vec::new();
11913 };
11914
11915 let language_settings = buffer.language_settings_at(selection.head(), cx);
11916 let language_scope = buffer.language_scope_at(selection.head());
11917
11918 let indent_and_prefix_for_row =
11919 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11920 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11921 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11922 &language_scope
11923 {
11924 let indent_end = Point::new(row, indent.len);
11925 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11926 let line_text_after_indent = buffer
11927 .text_for_range(indent_end..line_end)
11928 .collect::<String>();
11929
11930 let is_within_comment_override = buffer
11931 .language_scope_at(indent_end)
11932 .is_some_and(|scope| scope.override_name() == Some("comment"));
11933 let comment_delimiters = if is_within_comment_override {
11934 // we are within a comment syntax node, but we don't
11935 // yet know what kind of comment: block, doc or line
11936 match (
11937 language_scope.documentation_comment(),
11938 language_scope.block_comment(),
11939 ) {
11940 (Some(config), _) | (_, Some(config))
11941 if buffer.contains_str_at(indent_end, &config.start) =>
11942 {
11943 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11944 }
11945 (Some(config), _) | (_, Some(config))
11946 if line_text_after_indent.ends_with(config.end.as_ref()) =>
11947 {
11948 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
11949 }
11950 (Some(config), _) | (_, Some(config))
11951 if buffer.contains_str_at(indent_end, &config.prefix) =>
11952 {
11953 Some(CommentFormat::BlockLine(config.prefix.to_string()))
11954 }
11955 (_, _) => language_scope
11956 .line_comment_prefixes()
11957 .iter()
11958 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11959 .map(|prefix| CommentFormat::Line(prefix.to_string())),
11960 }
11961 } else {
11962 // we not in an overridden comment node, but we may
11963 // be within a non-overridden line comment node
11964 language_scope
11965 .line_comment_prefixes()
11966 .iter()
11967 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11968 .map(|prefix| CommentFormat::Line(prefix.to_string()))
11969 };
11970
11971 let rewrap_prefix = language_scope
11972 .rewrap_prefixes()
11973 .iter()
11974 .find_map(|prefix_regex| {
11975 prefix_regex.find(&line_text_after_indent).map(|mat| {
11976 if mat.start() == 0 {
11977 Some(mat.as_str().to_string())
11978 } else {
11979 None
11980 }
11981 })
11982 })
11983 .flatten();
11984 (comment_delimiters, rewrap_prefix)
11985 } else {
11986 (None, None)
11987 };
11988 (indent, comment_prefix, rewrap_prefix)
11989 };
11990
11991 let mut ranges = Vec::new();
11992 let from_empty_selection = selection.is_empty();
11993
11994 let mut current_range_start = first_row;
11995 let mut prev_row = first_row;
11996 let (
11997 mut current_range_indent,
11998 mut current_range_comment_delimiters,
11999 mut current_range_rewrap_prefix,
12000 ) = indent_and_prefix_for_row(first_row);
12001
12002 for row in non_blank_rows_iter.skip(1) {
12003 let has_paragraph_break = row > prev_row + 1;
12004
12005 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12006 indent_and_prefix_for_row(row);
12007
12008 let has_indent_change = row_indent != current_range_indent;
12009 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12010
12011 let has_boundary_change = has_comment_change
12012 || row_rewrap_prefix.is_some()
12013 || (has_indent_change && current_range_comment_delimiters.is_some());
12014
12015 if has_paragraph_break || has_boundary_change {
12016 ranges.push((
12017 language_settings.clone(),
12018 Point::new(current_range_start, 0)
12019 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12020 current_range_indent,
12021 current_range_comment_delimiters.clone(),
12022 current_range_rewrap_prefix.clone(),
12023 from_empty_selection,
12024 ));
12025 current_range_start = row;
12026 current_range_indent = row_indent;
12027 current_range_comment_delimiters = row_comment_delimiters;
12028 current_range_rewrap_prefix = row_rewrap_prefix;
12029 }
12030 prev_row = row;
12031 }
12032
12033 ranges.push((
12034 language_settings.clone(),
12035 Point::new(current_range_start, 0)
12036 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12037 current_range_indent,
12038 current_range_comment_delimiters,
12039 current_range_rewrap_prefix,
12040 from_empty_selection,
12041 ));
12042
12043 ranges
12044 });
12045
12046 let mut edits = Vec::new();
12047 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12048
12049 for (
12050 language_settings,
12051 wrap_range,
12052 mut indent_size,
12053 comment_prefix,
12054 rewrap_prefix,
12055 from_empty_selection,
12056 ) in wrap_ranges
12057 {
12058 let mut start_row = wrap_range.start.row;
12059 let mut end_row = wrap_range.end.row;
12060
12061 // Skip selections that overlap with a range that has already been rewrapped.
12062 let selection_range = start_row..end_row;
12063 if rewrapped_row_ranges
12064 .iter()
12065 .any(|range| range.overlaps(&selection_range))
12066 {
12067 continue;
12068 }
12069
12070 let tab_size = language_settings.tab_size;
12071
12072 let (line_prefix, inside_comment) = match &comment_prefix {
12073 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12074 (Some(prefix.as_str()), true)
12075 }
12076 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12077 (Some(prefix.as_ref()), true)
12078 }
12079 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12080 start: _,
12081 end: _,
12082 prefix,
12083 tab_size,
12084 })) => {
12085 indent_size.len += tab_size;
12086 (Some(prefix.as_ref()), true)
12087 }
12088 None => (None, false),
12089 };
12090 let indent_prefix = indent_size.chars().collect::<String>();
12091 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12092
12093 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12094 RewrapBehavior::InComments => inside_comment,
12095 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12096 RewrapBehavior::Anywhere => true,
12097 };
12098
12099 let should_rewrap = options.override_language_settings
12100 || allow_rewrap_based_on_language
12101 || self.hard_wrap.is_some();
12102 if !should_rewrap {
12103 continue;
12104 }
12105
12106 if from_empty_selection {
12107 'expand_upwards: while start_row > 0 {
12108 let prev_row = start_row - 1;
12109 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12110 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12111 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12112 {
12113 start_row = prev_row;
12114 } else {
12115 break 'expand_upwards;
12116 }
12117 }
12118
12119 'expand_downwards: while end_row < buffer.max_point().row {
12120 let next_row = end_row + 1;
12121 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12122 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12123 && !buffer.is_line_blank(MultiBufferRow(next_row))
12124 {
12125 end_row = next_row;
12126 } else {
12127 break 'expand_downwards;
12128 }
12129 }
12130 }
12131
12132 let start = Point::new(start_row, 0);
12133 let start_offset = start.to_offset(&buffer);
12134 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12135 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12136 let mut first_line_delimiter = None;
12137 let mut last_line_delimiter = None;
12138 let Some(lines_without_prefixes) = selection_text
12139 .lines()
12140 .enumerate()
12141 .map(|(ix, line)| {
12142 let line_trimmed = line.trim_start();
12143 if rewrap_prefix.is_some() && ix > 0 {
12144 Ok(line_trimmed)
12145 } else if let Some(
12146 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12147 start,
12148 prefix,
12149 end,
12150 tab_size,
12151 })
12152 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12153 start,
12154 prefix,
12155 end,
12156 tab_size,
12157 }),
12158 ) = &comment_prefix
12159 {
12160 let line_trimmed = line_trimmed
12161 .strip_prefix(start.as_ref())
12162 .map(|s| {
12163 let mut indent_size = indent_size;
12164 indent_size.len -= tab_size;
12165 let indent_prefix: String = indent_size.chars().collect();
12166 first_line_delimiter = Some((indent_prefix, start));
12167 s.trim_start()
12168 })
12169 .unwrap_or(line_trimmed);
12170 let line_trimmed = line_trimmed
12171 .strip_suffix(end.as_ref())
12172 .map(|s| {
12173 last_line_delimiter = Some(end);
12174 s.trim_end()
12175 })
12176 .unwrap_or(line_trimmed);
12177 let line_trimmed = line_trimmed
12178 .strip_prefix(prefix.as_ref())
12179 .unwrap_or(line_trimmed);
12180 Ok(line_trimmed)
12181 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12182 line_trimmed.strip_prefix(prefix).with_context(|| {
12183 format!("line did not start with prefix {prefix:?}: {line:?}")
12184 })
12185 } else {
12186 line_trimmed
12187 .strip_prefix(&line_prefix.trim_start())
12188 .with_context(|| {
12189 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12190 })
12191 }
12192 })
12193 .collect::<Result<Vec<_>, _>>()
12194 .log_err()
12195 else {
12196 continue;
12197 };
12198
12199 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12200 buffer
12201 .language_settings_at(Point::new(start_row, 0), cx)
12202 .preferred_line_length as usize
12203 });
12204
12205 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12206 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12207 } else {
12208 line_prefix.clone()
12209 };
12210
12211 let wrapped_text = {
12212 let mut wrapped_text = wrap_with_prefix(
12213 line_prefix,
12214 subsequent_lines_prefix,
12215 lines_without_prefixes.join("\n"),
12216 wrap_column,
12217 tab_size,
12218 options.preserve_existing_whitespace,
12219 );
12220
12221 if let Some((indent, delimiter)) = first_line_delimiter {
12222 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12223 }
12224 if let Some(last_line) = last_line_delimiter {
12225 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12226 }
12227
12228 wrapped_text
12229 };
12230
12231 // TODO: should always use char-based diff while still supporting cursor behavior that
12232 // matches vim.
12233 let mut diff_options = DiffOptions::default();
12234 if options.override_language_settings {
12235 diff_options.max_word_diff_len = 0;
12236 diff_options.max_word_diff_line_count = 0;
12237 } else {
12238 diff_options.max_word_diff_len = usize::MAX;
12239 diff_options.max_word_diff_line_count = usize::MAX;
12240 }
12241
12242 for (old_range, new_text) in
12243 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12244 {
12245 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12246 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12247 edits.push((edit_start..edit_end, new_text));
12248 }
12249
12250 rewrapped_row_ranges.push(start_row..=end_row);
12251 }
12252
12253 self.buffer
12254 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12255 }
12256
12257 pub fn cut_common(
12258 &mut self,
12259 cut_no_selection_line: bool,
12260 window: &mut Window,
12261 cx: &mut Context<Self>,
12262 ) -> ClipboardItem {
12263 let mut text = String::new();
12264 let buffer = self.buffer.read(cx).snapshot(cx);
12265 let mut selections = self.selections.all::<Point>(cx);
12266 let mut clipboard_selections = Vec::with_capacity(selections.len());
12267 {
12268 let max_point = buffer.max_point();
12269 let mut is_first = true;
12270 for selection in &mut selections {
12271 let is_entire_line =
12272 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode;
12273 if is_entire_line {
12274 selection.start = Point::new(selection.start.row, 0);
12275 if !selection.is_empty() && selection.end.column == 0 {
12276 selection.end = cmp::min(max_point, selection.end);
12277 } else {
12278 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12279 }
12280 selection.goal = SelectionGoal::None;
12281 }
12282 if is_first {
12283 is_first = false;
12284 } else {
12285 text += "\n";
12286 }
12287 let mut len = 0;
12288 for chunk in buffer.text_for_range(selection.start..selection.end) {
12289 text.push_str(chunk);
12290 len += chunk.len();
12291 }
12292 clipboard_selections.push(ClipboardSelection {
12293 len,
12294 is_entire_line,
12295 first_line_indent: buffer
12296 .indent_size_for_line(MultiBufferRow(selection.start.row))
12297 .len,
12298 });
12299 }
12300 }
12301
12302 self.transact(window, cx, |this, window, cx| {
12303 this.change_selections(Default::default(), window, cx, |s| {
12304 s.select(selections);
12305 });
12306 this.insert("", window, cx);
12307 });
12308 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12309 }
12310
12311 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12312 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12313 let item = self.cut_common(true, window, cx);
12314 cx.write_to_clipboard(item);
12315 }
12316
12317 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12318 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12319 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12320 s.move_with(|snapshot, sel| {
12321 if sel.is_empty() {
12322 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12323 }
12324 if sel.is_empty() {
12325 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12326 }
12327 });
12328 });
12329 let item = self.cut_common(true, window, cx);
12330 cx.set_global(KillRing(item))
12331 }
12332
12333 pub fn kill_ring_yank(
12334 &mut self,
12335 _: &KillRingYank,
12336 window: &mut Window,
12337 cx: &mut Context<Self>,
12338 ) {
12339 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12340 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12341 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12342 (kill_ring.text().to_string(), kill_ring.metadata_json())
12343 } else {
12344 return;
12345 }
12346 } else {
12347 return;
12348 };
12349 self.do_paste(&text, metadata, false, window, cx);
12350 }
12351
12352 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12353 self.do_copy(true, cx);
12354 }
12355
12356 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12357 self.do_copy(false, cx);
12358 }
12359
12360 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12361 let selections = self.selections.all::<Point>(cx);
12362 let buffer = self.buffer.read(cx).read(cx);
12363 let mut text = String::new();
12364
12365 let mut clipboard_selections = Vec::with_capacity(selections.len());
12366 {
12367 let max_point = buffer.max_point();
12368 let mut is_first = true;
12369 for selection in &selections {
12370 let mut start = selection.start;
12371 let mut end = selection.end;
12372 let is_entire_line = selection.is_empty() || self.selections.line_mode;
12373 if is_entire_line {
12374 start = Point::new(start.row, 0);
12375 end = cmp::min(max_point, Point::new(end.row + 1, 0));
12376 }
12377
12378 let mut trimmed_selections = Vec::new();
12379 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12380 let row = MultiBufferRow(start.row);
12381 let first_indent = buffer.indent_size_for_line(row);
12382 if first_indent.len == 0 || start.column > first_indent.len {
12383 trimmed_selections.push(start..end);
12384 } else {
12385 trimmed_selections.push(
12386 Point::new(row.0, first_indent.len)
12387 ..Point::new(row.0, buffer.line_len(row)),
12388 );
12389 for row in start.row + 1..=end.row {
12390 let mut line_len = buffer.line_len(MultiBufferRow(row));
12391 if row == end.row {
12392 line_len = end.column;
12393 }
12394 if line_len == 0 {
12395 trimmed_selections
12396 .push(Point::new(row, 0)..Point::new(row, line_len));
12397 continue;
12398 }
12399 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12400 if row_indent_size.len >= first_indent.len {
12401 trimmed_selections.push(
12402 Point::new(row, first_indent.len)..Point::new(row, line_len),
12403 );
12404 } else {
12405 trimmed_selections.clear();
12406 trimmed_selections.push(start..end);
12407 break;
12408 }
12409 }
12410 }
12411 } else {
12412 trimmed_selections.push(start..end);
12413 }
12414
12415 for trimmed_range in trimmed_selections {
12416 if is_first {
12417 is_first = false;
12418 } else {
12419 text += "\n";
12420 }
12421 let mut len = 0;
12422 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12423 text.push_str(chunk);
12424 len += chunk.len();
12425 }
12426 clipboard_selections.push(ClipboardSelection {
12427 len,
12428 is_entire_line,
12429 first_line_indent: buffer
12430 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12431 .len,
12432 });
12433 }
12434 }
12435 }
12436
12437 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12438 text,
12439 clipboard_selections,
12440 ));
12441 }
12442
12443 pub fn do_paste(
12444 &mut self,
12445 text: &String,
12446 clipboard_selections: Option<Vec<ClipboardSelection>>,
12447 handle_entire_lines: bool,
12448 window: &mut Window,
12449 cx: &mut Context<Self>,
12450 ) {
12451 if self.read_only(cx) {
12452 return;
12453 }
12454
12455 let clipboard_text = Cow::Borrowed(text.as_str());
12456
12457 self.transact(window, cx, |this, window, cx| {
12458 let had_active_edit_prediction = this.has_active_edit_prediction();
12459 let old_selections = this.selections.all::<usize>(cx);
12460 let cursor_offset = this.selections.last::<usize>(cx).head();
12461
12462 if let Some(mut clipboard_selections) = clipboard_selections {
12463 let all_selections_were_entire_line =
12464 clipboard_selections.iter().all(|s| s.is_entire_line);
12465 let first_selection_indent_column =
12466 clipboard_selections.first().map(|s| s.first_line_indent);
12467 if clipboard_selections.len() != old_selections.len() {
12468 clipboard_selections.drain(..);
12469 }
12470 let mut auto_indent_on_paste = true;
12471
12472 this.buffer.update(cx, |buffer, cx| {
12473 let snapshot = buffer.read(cx);
12474 auto_indent_on_paste = snapshot
12475 .language_settings_at(cursor_offset, cx)
12476 .auto_indent_on_paste;
12477
12478 let mut start_offset = 0;
12479 let mut edits = Vec::new();
12480 let mut original_indent_columns = Vec::new();
12481 for (ix, selection) in old_selections.iter().enumerate() {
12482 let to_insert;
12483 let entire_line;
12484 let original_indent_column;
12485 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12486 let end_offset = start_offset + clipboard_selection.len;
12487 to_insert = &clipboard_text[start_offset..end_offset];
12488 entire_line = clipboard_selection.is_entire_line;
12489 start_offset = end_offset + 1;
12490 original_indent_column = Some(clipboard_selection.first_line_indent);
12491 } else {
12492 to_insert = &*clipboard_text;
12493 entire_line = all_selections_were_entire_line;
12494 original_indent_column = first_selection_indent_column
12495 }
12496
12497 let (range, to_insert) =
12498 if selection.is_empty() && handle_entire_lines && entire_line {
12499 // If the corresponding selection was empty when this slice of the
12500 // clipboard text was written, then the entire line containing the
12501 // selection was copied. If this selection is also currently empty,
12502 // then paste the line before the current line of the buffer.
12503 let column = selection.start.to_point(&snapshot).column as usize;
12504 let line_start = selection.start - column;
12505 (line_start..line_start, Cow::Borrowed(to_insert))
12506 } else {
12507 let language = snapshot.language_at(selection.head());
12508 let range = selection.range();
12509 if let Some(language) = language
12510 && language.name() == "Markdown".into()
12511 {
12512 edit_for_markdown_paste(
12513 &snapshot,
12514 range,
12515 to_insert,
12516 url::Url::parse(to_insert).ok(),
12517 )
12518 } else {
12519 (range, Cow::Borrowed(to_insert))
12520 }
12521 };
12522
12523 edits.push((range, to_insert));
12524 original_indent_columns.push(original_indent_column);
12525 }
12526 drop(snapshot);
12527
12528 buffer.edit(
12529 edits,
12530 if auto_indent_on_paste {
12531 Some(AutoindentMode::Block {
12532 original_indent_columns,
12533 })
12534 } else {
12535 None
12536 },
12537 cx,
12538 );
12539 });
12540
12541 let selections = this.selections.all::<usize>(cx);
12542 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12543 } else {
12544 let url = url::Url::parse(&clipboard_text).ok();
12545
12546 let auto_indent_mode = if !clipboard_text.is_empty() {
12547 Some(AutoindentMode::Block {
12548 original_indent_columns: Vec::new(),
12549 })
12550 } else {
12551 None
12552 };
12553
12554 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12555 let snapshot = buffer.snapshot(cx);
12556
12557 let anchors = old_selections
12558 .iter()
12559 .map(|s| {
12560 let anchor = snapshot.anchor_after(s.head());
12561 s.map(|_| anchor)
12562 })
12563 .collect::<Vec<_>>();
12564
12565 let mut edits = Vec::new();
12566
12567 for selection in old_selections.iter() {
12568 let language = snapshot.language_at(selection.head());
12569 let range = selection.range();
12570
12571 let (edit_range, edit_text) = if let Some(language) = language
12572 && language.name() == "Markdown".into()
12573 {
12574 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12575 } else {
12576 (range, clipboard_text.clone())
12577 };
12578
12579 edits.push((edit_range, edit_text));
12580 }
12581
12582 drop(snapshot);
12583 buffer.edit(edits, auto_indent_mode, cx);
12584
12585 anchors
12586 });
12587
12588 this.change_selections(Default::default(), window, cx, |s| {
12589 s.select_anchors(selection_anchors);
12590 });
12591 }
12592
12593 let trigger_in_words =
12594 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12595
12596 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12597 });
12598 }
12599
12600 pub fn diff_clipboard_with_selection(
12601 &mut self,
12602 _: &DiffClipboardWithSelection,
12603 window: &mut Window,
12604 cx: &mut Context<Self>,
12605 ) {
12606 let selections = self.selections.all::<usize>(cx);
12607
12608 if selections.is_empty() {
12609 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12610 return;
12611 };
12612
12613 let clipboard_text = match cx.read_from_clipboard() {
12614 Some(item) => match item.entries().first() {
12615 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12616 _ => None,
12617 },
12618 None => None,
12619 };
12620
12621 let Some(clipboard_text) = clipboard_text else {
12622 log::warn!("Clipboard doesn't contain text.");
12623 return;
12624 };
12625
12626 window.dispatch_action(
12627 Box::new(DiffClipboardWithSelectionData {
12628 clipboard_text,
12629 editor: cx.entity(),
12630 }),
12631 cx,
12632 );
12633 }
12634
12635 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12636 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12637 if let Some(item) = cx.read_from_clipboard() {
12638 let entries = item.entries();
12639
12640 match entries.first() {
12641 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12642 // of all the pasted entries.
12643 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12644 .do_paste(
12645 clipboard_string.text(),
12646 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12647 true,
12648 window,
12649 cx,
12650 ),
12651 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12652 }
12653 }
12654 }
12655
12656 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12657 if self.read_only(cx) {
12658 return;
12659 }
12660
12661 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12662
12663 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12664 if let Some((selections, _)) =
12665 self.selection_history.transaction(transaction_id).cloned()
12666 {
12667 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12668 s.select_anchors(selections.to_vec());
12669 });
12670 } else {
12671 log::error!(
12672 "No entry in selection_history found for undo. \
12673 This may correspond to a bug where undo does not update the selection. \
12674 If this is occurring, please add details to \
12675 https://github.com/zed-industries/zed/issues/22692"
12676 );
12677 }
12678 self.request_autoscroll(Autoscroll::fit(), cx);
12679 self.unmark_text(window, cx);
12680 self.refresh_edit_prediction(true, false, window, cx);
12681 cx.emit(EditorEvent::Edited { transaction_id });
12682 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12683 }
12684 }
12685
12686 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12687 if self.read_only(cx) {
12688 return;
12689 }
12690
12691 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12692
12693 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12694 if let Some((_, Some(selections))) =
12695 self.selection_history.transaction(transaction_id).cloned()
12696 {
12697 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12698 s.select_anchors(selections.to_vec());
12699 });
12700 } else {
12701 log::error!(
12702 "No entry in selection_history found for redo. \
12703 This may correspond to a bug where undo does not update the selection. \
12704 If this is occurring, please add details to \
12705 https://github.com/zed-industries/zed/issues/22692"
12706 );
12707 }
12708 self.request_autoscroll(Autoscroll::fit(), cx);
12709 self.unmark_text(window, cx);
12710 self.refresh_edit_prediction(true, false, window, cx);
12711 cx.emit(EditorEvent::Edited { transaction_id });
12712 }
12713 }
12714
12715 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12716 self.buffer
12717 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12718 }
12719
12720 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12721 self.buffer
12722 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12723 }
12724
12725 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12727 self.change_selections(Default::default(), window, cx, |s| {
12728 s.move_with(|map, selection| {
12729 let cursor = if selection.is_empty() {
12730 movement::left(map, selection.start)
12731 } else {
12732 selection.start
12733 };
12734 selection.collapse_to(cursor, SelectionGoal::None);
12735 });
12736 })
12737 }
12738
12739 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12740 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12741 self.change_selections(Default::default(), window, cx, |s| {
12742 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12743 })
12744 }
12745
12746 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12748 self.change_selections(Default::default(), window, cx, |s| {
12749 s.move_with(|map, selection| {
12750 let cursor = if selection.is_empty() {
12751 movement::right(map, selection.end)
12752 } else {
12753 selection.end
12754 };
12755 selection.collapse_to(cursor, SelectionGoal::None)
12756 });
12757 })
12758 }
12759
12760 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12761 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12762 self.change_selections(Default::default(), window, cx, |s| {
12763 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12764 })
12765 }
12766
12767 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12768 if self.take_rename(true, window, cx).is_some() {
12769 return;
12770 }
12771
12772 if self.mode.is_single_line() {
12773 cx.propagate();
12774 return;
12775 }
12776
12777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12778
12779 let text_layout_details = &self.text_layout_details(window);
12780 let selection_count = self.selections.count();
12781 let first_selection = self.selections.first_anchor();
12782
12783 self.change_selections(Default::default(), window, cx, |s| {
12784 s.move_with(|map, selection| {
12785 if !selection.is_empty() {
12786 selection.goal = SelectionGoal::None;
12787 }
12788 let (cursor, goal) = movement::up(
12789 map,
12790 selection.start,
12791 selection.goal,
12792 false,
12793 text_layout_details,
12794 );
12795 selection.collapse_to(cursor, goal);
12796 });
12797 });
12798
12799 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12800 {
12801 cx.propagate();
12802 }
12803 }
12804
12805 pub fn move_up_by_lines(
12806 &mut self,
12807 action: &MoveUpByLines,
12808 window: &mut Window,
12809 cx: &mut Context<Self>,
12810 ) {
12811 if self.take_rename(true, window, cx).is_some() {
12812 return;
12813 }
12814
12815 if self.mode.is_single_line() {
12816 cx.propagate();
12817 return;
12818 }
12819
12820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12821
12822 let text_layout_details = &self.text_layout_details(window);
12823
12824 self.change_selections(Default::default(), window, cx, |s| {
12825 s.move_with(|map, selection| {
12826 if !selection.is_empty() {
12827 selection.goal = SelectionGoal::None;
12828 }
12829 let (cursor, goal) = movement::up_by_rows(
12830 map,
12831 selection.start,
12832 action.lines,
12833 selection.goal,
12834 false,
12835 text_layout_details,
12836 );
12837 selection.collapse_to(cursor, goal);
12838 });
12839 })
12840 }
12841
12842 pub fn move_down_by_lines(
12843 &mut self,
12844 action: &MoveDownByLines,
12845 window: &mut Window,
12846 cx: &mut Context<Self>,
12847 ) {
12848 if self.take_rename(true, window, cx).is_some() {
12849 return;
12850 }
12851
12852 if self.mode.is_single_line() {
12853 cx.propagate();
12854 return;
12855 }
12856
12857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12858
12859 let text_layout_details = &self.text_layout_details(window);
12860
12861 self.change_selections(Default::default(), window, cx, |s| {
12862 s.move_with(|map, selection| {
12863 if !selection.is_empty() {
12864 selection.goal = SelectionGoal::None;
12865 }
12866 let (cursor, goal) = movement::down_by_rows(
12867 map,
12868 selection.start,
12869 action.lines,
12870 selection.goal,
12871 false,
12872 text_layout_details,
12873 );
12874 selection.collapse_to(cursor, goal);
12875 });
12876 })
12877 }
12878
12879 pub fn select_down_by_lines(
12880 &mut self,
12881 action: &SelectDownByLines,
12882 window: &mut Window,
12883 cx: &mut Context<Self>,
12884 ) {
12885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12886 let text_layout_details = &self.text_layout_details(window);
12887 self.change_selections(Default::default(), window, cx, |s| {
12888 s.move_heads_with(|map, head, goal| {
12889 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12890 })
12891 })
12892 }
12893
12894 pub fn select_up_by_lines(
12895 &mut self,
12896 action: &SelectUpByLines,
12897 window: &mut Window,
12898 cx: &mut Context<Self>,
12899 ) {
12900 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12901 let text_layout_details = &self.text_layout_details(window);
12902 self.change_selections(Default::default(), window, cx, |s| {
12903 s.move_heads_with(|map, head, goal| {
12904 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12905 })
12906 })
12907 }
12908
12909 pub fn select_page_up(
12910 &mut self,
12911 _: &SelectPageUp,
12912 window: &mut Window,
12913 cx: &mut Context<Self>,
12914 ) {
12915 let Some(row_count) = self.visible_row_count() else {
12916 return;
12917 };
12918
12919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12920
12921 let text_layout_details = &self.text_layout_details(window);
12922
12923 self.change_selections(Default::default(), window, cx, |s| {
12924 s.move_heads_with(|map, head, goal| {
12925 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12926 })
12927 })
12928 }
12929
12930 pub fn move_page_up(
12931 &mut self,
12932 action: &MovePageUp,
12933 window: &mut Window,
12934 cx: &mut Context<Self>,
12935 ) {
12936 if self.take_rename(true, window, cx).is_some() {
12937 return;
12938 }
12939
12940 if self
12941 .context_menu
12942 .borrow_mut()
12943 .as_mut()
12944 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12945 .unwrap_or(false)
12946 {
12947 return;
12948 }
12949
12950 if matches!(self.mode, EditorMode::SingleLine) {
12951 cx.propagate();
12952 return;
12953 }
12954
12955 let Some(row_count) = self.visible_row_count() else {
12956 return;
12957 };
12958
12959 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12960
12961 let effects = if action.center_cursor {
12962 SelectionEffects::scroll(Autoscroll::center())
12963 } else {
12964 SelectionEffects::default()
12965 };
12966
12967 let text_layout_details = &self.text_layout_details(window);
12968
12969 self.change_selections(effects, window, cx, |s| {
12970 s.move_with(|map, selection| {
12971 if !selection.is_empty() {
12972 selection.goal = SelectionGoal::None;
12973 }
12974 let (cursor, goal) = movement::up_by_rows(
12975 map,
12976 selection.end,
12977 row_count,
12978 selection.goal,
12979 false,
12980 text_layout_details,
12981 );
12982 selection.collapse_to(cursor, goal);
12983 });
12984 });
12985 }
12986
12987 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12989 let text_layout_details = &self.text_layout_details(window);
12990 self.change_selections(Default::default(), window, cx, |s| {
12991 s.move_heads_with(|map, head, goal| {
12992 movement::up(map, head, goal, false, text_layout_details)
12993 })
12994 })
12995 }
12996
12997 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12998 self.take_rename(true, window, cx);
12999
13000 if self.mode.is_single_line() {
13001 cx.propagate();
13002 return;
13003 }
13004
13005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13006
13007 let text_layout_details = &self.text_layout_details(window);
13008 let selection_count = self.selections.count();
13009 let first_selection = self.selections.first_anchor();
13010
13011 self.change_selections(Default::default(), window, cx, |s| {
13012 s.move_with(|map, selection| {
13013 if !selection.is_empty() {
13014 selection.goal = SelectionGoal::None;
13015 }
13016 let (cursor, goal) = movement::down(
13017 map,
13018 selection.end,
13019 selection.goal,
13020 false,
13021 text_layout_details,
13022 );
13023 selection.collapse_to(cursor, goal);
13024 });
13025 });
13026
13027 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13028 {
13029 cx.propagate();
13030 }
13031 }
13032
13033 pub fn select_page_down(
13034 &mut self,
13035 _: &SelectPageDown,
13036 window: &mut Window,
13037 cx: &mut Context<Self>,
13038 ) {
13039 let Some(row_count) = self.visible_row_count() else {
13040 return;
13041 };
13042
13043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13044
13045 let text_layout_details = &self.text_layout_details(window);
13046
13047 self.change_selections(Default::default(), window, cx, |s| {
13048 s.move_heads_with(|map, head, goal| {
13049 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13050 })
13051 })
13052 }
13053
13054 pub fn move_page_down(
13055 &mut self,
13056 action: &MovePageDown,
13057 window: &mut Window,
13058 cx: &mut Context<Self>,
13059 ) {
13060 if self.take_rename(true, window, cx).is_some() {
13061 return;
13062 }
13063
13064 if self
13065 .context_menu
13066 .borrow_mut()
13067 .as_mut()
13068 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13069 .unwrap_or(false)
13070 {
13071 return;
13072 }
13073
13074 if matches!(self.mode, EditorMode::SingleLine) {
13075 cx.propagate();
13076 return;
13077 }
13078
13079 let Some(row_count) = self.visible_row_count() else {
13080 return;
13081 };
13082
13083 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13084
13085 let effects = if action.center_cursor {
13086 SelectionEffects::scroll(Autoscroll::center())
13087 } else {
13088 SelectionEffects::default()
13089 };
13090
13091 let text_layout_details = &self.text_layout_details(window);
13092 self.change_selections(effects, window, cx, |s| {
13093 s.move_with(|map, selection| {
13094 if !selection.is_empty() {
13095 selection.goal = SelectionGoal::None;
13096 }
13097 let (cursor, goal) = movement::down_by_rows(
13098 map,
13099 selection.end,
13100 row_count,
13101 selection.goal,
13102 false,
13103 text_layout_details,
13104 );
13105 selection.collapse_to(cursor, goal);
13106 });
13107 });
13108 }
13109
13110 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13112 let text_layout_details = &self.text_layout_details(window);
13113 self.change_selections(Default::default(), window, cx, |s| {
13114 s.move_heads_with(|map, head, goal| {
13115 movement::down(map, head, goal, false, text_layout_details)
13116 })
13117 });
13118 }
13119
13120 pub fn context_menu_first(
13121 &mut self,
13122 _: &ContextMenuFirst,
13123 window: &mut Window,
13124 cx: &mut Context<Self>,
13125 ) {
13126 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13127 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13128 }
13129 }
13130
13131 pub fn context_menu_prev(
13132 &mut self,
13133 _: &ContextMenuPrevious,
13134 window: &mut Window,
13135 cx: &mut Context<Self>,
13136 ) {
13137 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13138 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13139 }
13140 }
13141
13142 pub fn context_menu_next(
13143 &mut self,
13144 _: &ContextMenuNext,
13145 window: &mut Window,
13146 cx: &mut Context<Self>,
13147 ) {
13148 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13149 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13150 }
13151 }
13152
13153 pub fn context_menu_last(
13154 &mut self,
13155 _: &ContextMenuLast,
13156 window: &mut Window,
13157 cx: &mut Context<Self>,
13158 ) {
13159 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13160 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13161 }
13162 }
13163
13164 pub fn signature_help_prev(
13165 &mut self,
13166 _: &SignatureHelpPrevious,
13167 _: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 if let Some(popover) = self.signature_help_state.popover_mut() {
13171 if popover.current_signature == 0 {
13172 popover.current_signature = popover.signatures.len() - 1;
13173 } else {
13174 popover.current_signature -= 1;
13175 }
13176 cx.notify();
13177 }
13178 }
13179
13180 pub fn signature_help_next(
13181 &mut self,
13182 _: &SignatureHelpNext,
13183 _: &mut Window,
13184 cx: &mut Context<Self>,
13185 ) {
13186 if let Some(popover) = self.signature_help_state.popover_mut() {
13187 if popover.current_signature + 1 == popover.signatures.len() {
13188 popover.current_signature = 0;
13189 } else {
13190 popover.current_signature += 1;
13191 }
13192 cx.notify();
13193 }
13194 }
13195
13196 pub fn move_to_previous_word_start(
13197 &mut self,
13198 _: &MoveToPreviousWordStart,
13199 window: &mut Window,
13200 cx: &mut Context<Self>,
13201 ) {
13202 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13203 self.change_selections(Default::default(), window, cx, |s| {
13204 s.move_cursors_with(|map, head, _| {
13205 (
13206 movement::previous_word_start(map, head),
13207 SelectionGoal::None,
13208 )
13209 });
13210 })
13211 }
13212
13213 pub fn move_to_previous_subword_start(
13214 &mut self,
13215 _: &MoveToPreviousSubwordStart,
13216 window: &mut Window,
13217 cx: &mut Context<Self>,
13218 ) {
13219 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13220 self.change_selections(Default::default(), window, cx, |s| {
13221 s.move_cursors_with(|map, head, _| {
13222 (
13223 movement::previous_subword_start(map, head),
13224 SelectionGoal::None,
13225 )
13226 });
13227 })
13228 }
13229
13230 pub fn select_to_previous_word_start(
13231 &mut self,
13232 _: &SelectToPreviousWordStart,
13233 window: &mut Window,
13234 cx: &mut Context<Self>,
13235 ) {
13236 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13237 self.change_selections(Default::default(), window, cx, |s| {
13238 s.move_heads_with(|map, head, _| {
13239 (
13240 movement::previous_word_start(map, head),
13241 SelectionGoal::None,
13242 )
13243 });
13244 })
13245 }
13246
13247 pub fn select_to_previous_subword_start(
13248 &mut self,
13249 _: &SelectToPreviousSubwordStart,
13250 window: &mut Window,
13251 cx: &mut Context<Self>,
13252 ) {
13253 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13254 self.change_selections(Default::default(), window, cx, |s| {
13255 s.move_heads_with(|map, head, _| {
13256 (
13257 movement::previous_subword_start(map, head),
13258 SelectionGoal::None,
13259 )
13260 });
13261 })
13262 }
13263
13264 pub fn delete_to_previous_word_start(
13265 &mut self,
13266 action: &DeleteToPreviousWordStart,
13267 window: &mut Window,
13268 cx: &mut Context<Self>,
13269 ) {
13270 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13271 self.transact(window, cx, |this, window, cx| {
13272 this.select_autoclose_pair(window, cx);
13273 this.change_selections(Default::default(), window, cx, |s| {
13274 s.move_with(|map, selection| {
13275 if selection.is_empty() {
13276 let mut cursor = if action.ignore_newlines {
13277 movement::previous_word_start(map, selection.head())
13278 } else {
13279 movement::previous_word_start_or_newline(map, selection.head())
13280 };
13281 cursor = movement::adjust_greedy_deletion(
13282 map,
13283 selection.head(),
13284 cursor,
13285 action.ignore_brackets,
13286 );
13287 selection.set_head(cursor, SelectionGoal::None);
13288 }
13289 });
13290 });
13291 this.insert("", window, cx);
13292 });
13293 }
13294
13295 pub fn delete_to_previous_subword_start(
13296 &mut self,
13297 _: &DeleteToPreviousSubwordStart,
13298 window: &mut Window,
13299 cx: &mut Context<Self>,
13300 ) {
13301 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13302 self.transact(window, cx, |this, window, cx| {
13303 this.select_autoclose_pair(window, cx);
13304 this.change_selections(Default::default(), window, cx, |s| {
13305 s.move_with(|map, selection| {
13306 if selection.is_empty() {
13307 let mut cursor = movement::previous_subword_start(map, selection.head());
13308 cursor =
13309 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13310 selection.set_head(cursor, SelectionGoal::None);
13311 }
13312 });
13313 });
13314 this.insert("", window, cx);
13315 });
13316 }
13317
13318 pub fn move_to_next_word_end(
13319 &mut self,
13320 _: &MoveToNextWordEnd,
13321 window: &mut Window,
13322 cx: &mut Context<Self>,
13323 ) {
13324 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13325 self.change_selections(Default::default(), window, cx, |s| {
13326 s.move_cursors_with(|map, head, _| {
13327 (movement::next_word_end(map, head), SelectionGoal::None)
13328 });
13329 })
13330 }
13331
13332 pub fn move_to_next_subword_end(
13333 &mut self,
13334 _: &MoveToNextSubwordEnd,
13335 window: &mut Window,
13336 cx: &mut Context<Self>,
13337 ) {
13338 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13339 self.change_selections(Default::default(), window, cx, |s| {
13340 s.move_cursors_with(|map, head, _| {
13341 (movement::next_subword_end(map, head), SelectionGoal::None)
13342 });
13343 })
13344 }
13345
13346 pub fn select_to_next_word_end(
13347 &mut self,
13348 _: &SelectToNextWordEnd,
13349 window: &mut Window,
13350 cx: &mut Context<Self>,
13351 ) {
13352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13353 self.change_selections(Default::default(), window, cx, |s| {
13354 s.move_heads_with(|map, head, _| {
13355 (movement::next_word_end(map, head), SelectionGoal::None)
13356 });
13357 })
13358 }
13359
13360 pub fn select_to_next_subword_end(
13361 &mut self,
13362 _: &SelectToNextSubwordEnd,
13363 window: &mut Window,
13364 cx: &mut Context<Self>,
13365 ) {
13366 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13367 self.change_selections(Default::default(), window, cx, |s| {
13368 s.move_heads_with(|map, head, _| {
13369 (movement::next_subword_end(map, head), SelectionGoal::None)
13370 });
13371 })
13372 }
13373
13374 pub fn delete_to_next_word_end(
13375 &mut self,
13376 action: &DeleteToNextWordEnd,
13377 window: &mut Window,
13378 cx: &mut Context<Self>,
13379 ) {
13380 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13381 self.transact(window, cx, |this, window, cx| {
13382 this.change_selections(Default::default(), window, cx, |s| {
13383 s.move_with(|map, selection| {
13384 if selection.is_empty() {
13385 let mut cursor = if action.ignore_newlines {
13386 movement::next_word_end(map, selection.head())
13387 } else {
13388 movement::next_word_end_or_newline(map, selection.head())
13389 };
13390 cursor = movement::adjust_greedy_deletion(
13391 map,
13392 selection.head(),
13393 cursor,
13394 action.ignore_brackets,
13395 );
13396 selection.set_head(cursor, SelectionGoal::None);
13397 }
13398 });
13399 });
13400 this.insert("", window, cx);
13401 });
13402 }
13403
13404 pub fn delete_to_next_subword_end(
13405 &mut self,
13406 _: &DeleteToNextSubwordEnd,
13407 window: &mut Window,
13408 cx: &mut Context<Self>,
13409 ) {
13410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13411 self.transact(window, cx, |this, window, cx| {
13412 this.change_selections(Default::default(), window, cx, |s| {
13413 s.move_with(|map, selection| {
13414 if selection.is_empty() {
13415 let mut cursor = movement::next_subword_end(map, selection.head());
13416 cursor =
13417 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13418 selection.set_head(cursor, SelectionGoal::None);
13419 }
13420 });
13421 });
13422 this.insert("", window, cx);
13423 });
13424 }
13425
13426 pub fn move_to_beginning_of_line(
13427 &mut self,
13428 action: &MoveToBeginningOfLine,
13429 window: &mut Window,
13430 cx: &mut Context<Self>,
13431 ) {
13432 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13433 self.change_selections(Default::default(), window, cx, |s| {
13434 s.move_cursors_with(|map, head, _| {
13435 (
13436 movement::indented_line_beginning(
13437 map,
13438 head,
13439 action.stop_at_soft_wraps,
13440 action.stop_at_indent,
13441 ),
13442 SelectionGoal::None,
13443 )
13444 });
13445 })
13446 }
13447
13448 pub fn select_to_beginning_of_line(
13449 &mut self,
13450 action: &SelectToBeginningOfLine,
13451 window: &mut Window,
13452 cx: &mut Context<Self>,
13453 ) {
13454 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13455 self.change_selections(Default::default(), window, cx, |s| {
13456 s.move_heads_with(|map, head, _| {
13457 (
13458 movement::indented_line_beginning(
13459 map,
13460 head,
13461 action.stop_at_soft_wraps,
13462 action.stop_at_indent,
13463 ),
13464 SelectionGoal::None,
13465 )
13466 });
13467 });
13468 }
13469
13470 pub fn delete_to_beginning_of_line(
13471 &mut self,
13472 action: &DeleteToBeginningOfLine,
13473 window: &mut Window,
13474 cx: &mut Context<Self>,
13475 ) {
13476 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13477 self.transact(window, cx, |this, window, cx| {
13478 this.change_selections(Default::default(), window, cx, |s| {
13479 s.move_with(|_, selection| {
13480 selection.reversed = true;
13481 });
13482 });
13483
13484 this.select_to_beginning_of_line(
13485 &SelectToBeginningOfLine {
13486 stop_at_soft_wraps: false,
13487 stop_at_indent: action.stop_at_indent,
13488 },
13489 window,
13490 cx,
13491 );
13492 this.backspace(&Backspace, window, cx);
13493 });
13494 }
13495
13496 pub fn move_to_end_of_line(
13497 &mut self,
13498 action: &MoveToEndOfLine,
13499 window: &mut Window,
13500 cx: &mut Context<Self>,
13501 ) {
13502 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13503 self.change_selections(Default::default(), window, cx, |s| {
13504 s.move_cursors_with(|map, head, _| {
13505 (
13506 movement::line_end(map, head, action.stop_at_soft_wraps),
13507 SelectionGoal::None,
13508 )
13509 });
13510 })
13511 }
13512
13513 pub fn select_to_end_of_line(
13514 &mut self,
13515 action: &SelectToEndOfLine,
13516 window: &mut Window,
13517 cx: &mut Context<Self>,
13518 ) {
13519 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13520 self.change_selections(Default::default(), window, cx, |s| {
13521 s.move_heads_with(|map, head, _| {
13522 (
13523 movement::line_end(map, head, action.stop_at_soft_wraps),
13524 SelectionGoal::None,
13525 )
13526 });
13527 })
13528 }
13529
13530 pub fn delete_to_end_of_line(
13531 &mut self,
13532 _: &DeleteToEndOfLine,
13533 window: &mut Window,
13534 cx: &mut Context<Self>,
13535 ) {
13536 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13537 self.transact(window, cx, |this, window, cx| {
13538 this.select_to_end_of_line(
13539 &SelectToEndOfLine {
13540 stop_at_soft_wraps: false,
13541 },
13542 window,
13543 cx,
13544 );
13545 this.delete(&Delete, window, cx);
13546 });
13547 }
13548
13549 pub fn cut_to_end_of_line(
13550 &mut self,
13551 action: &CutToEndOfLine,
13552 window: &mut Window,
13553 cx: &mut Context<Self>,
13554 ) {
13555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13556 self.transact(window, cx, |this, window, cx| {
13557 this.select_to_end_of_line(
13558 &SelectToEndOfLine {
13559 stop_at_soft_wraps: false,
13560 },
13561 window,
13562 cx,
13563 );
13564 if !action.stop_at_newlines {
13565 this.change_selections(Default::default(), window, cx, |s| {
13566 s.move_with(|_, sel| {
13567 if sel.is_empty() {
13568 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13569 }
13570 });
13571 });
13572 }
13573 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13574 let item = this.cut_common(false, window, cx);
13575 cx.write_to_clipboard(item);
13576 });
13577 }
13578
13579 pub fn move_to_start_of_paragraph(
13580 &mut self,
13581 _: &MoveToStartOfParagraph,
13582 window: &mut Window,
13583 cx: &mut Context<Self>,
13584 ) {
13585 if matches!(self.mode, EditorMode::SingleLine) {
13586 cx.propagate();
13587 return;
13588 }
13589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13590 self.change_selections(Default::default(), window, cx, |s| {
13591 s.move_with(|map, selection| {
13592 selection.collapse_to(
13593 movement::start_of_paragraph(map, selection.head(), 1),
13594 SelectionGoal::None,
13595 )
13596 });
13597 })
13598 }
13599
13600 pub fn move_to_end_of_paragraph(
13601 &mut self,
13602 _: &MoveToEndOfParagraph,
13603 window: &mut Window,
13604 cx: &mut Context<Self>,
13605 ) {
13606 if matches!(self.mode, EditorMode::SingleLine) {
13607 cx.propagate();
13608 return;
13609 }
13610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13611 self.change_selections(Default::default(), window, cx, |s| {
13612 s.move_with(|map, selection| {
13613 selection.collapse_to(
13614 movement::end_of_paragraph(map, selection.head(), 1),
13615 SelectionGoal::None,
13616 )
13617 });
13618 })
13619 }
13620
13621 pub fn select_to_start_of_paragraph(
13622 &mut self,
13623 _: &SelectToStartOfParagraph,
13624 window: &mut Window,
13625 cx: &mut Context<Self>,
13626 ) {
13627 if matches!(self.mode, EditorMode::SingleLine) {
13628 cx.propagate();
13629 return;
13630 }
13631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13632 self.change_selections(Default::default(), window, cx, |s| {
13633 s.move_heads_with(|map, head, _| {
13634 (
13635 movement::start_of_paragraph(map, head, 1),
13636 SelectionGoal::None,
13637 )
13638 });
13639 })
13640 }
13641
13642 pub fn select_to_end_of_paragraph(
13643 &mut self,
13644 _: &SelectToEndOfParagraph,
13645 window: &mut Window,
13646 cx: &mut Context<Self>,
13647 ) {
13648 if matches!(self.mode, EditorMode::SingleLine) {
13649 cx.propagate();
13650 return;
13651 }
13652 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13653 self.change_selections(Default::default(), window, cx, |s| {
13654 s.move_heads_with(|map, head, _| {
13655 (
13656 movement::end_of_paragraph(map, head, 1),
13657 SelectionGoal::None,
13658 )
13659 });
13660 })
13661 }
13662
13663 pub fn move_to_start_of_excerpt(
13664 &mut self,
13665 _: &MoveToStartOfExcerpt,
13666 window: &mut Window,
13667 cx: &mut Context<Self>,
13668 ) {
13669 if matches!(self.mode, EditorMode::SingleLine) {
13670 cx.propagate();
13671 return;
13672 }
13673 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13674 self.change_selections(Default::default(), window, cx, |s| {
13675 s.move_with(|map, selection| {
13676 selection.collapse_to(
13677 movement::start_of_excerpt(
13678 map,
13679 selection.head(),
13680 workspace::searchable::Direction::Prev,
13681 ),
13682 SelectionGoal::None,
13683 )
13684 });
13685 })
13686 }
13687
13688 pub fn move_to_start_of_next_excerpt(
13689 &mut self,
13690 _: &MoveToStartOfNextExcerpt,
13691 window: &mut Window,
13692 cx: &mut Context<Self>,
13693 ) {
13694 if matches!(self.mode, EditorMode::SingleLine) {
13695 cx.propagate();
13696 return;
13697 }
13698
13699 self.change_selections(Default::default(), window, cx, |s| {
13700 s.move_with(|map, selection| {
13701 selection.collapse_to(
13702 movement::start_of_excerpt(
13703 map,
13704 selection.head(),
13705 workspace::searchable::Direction::Next,
13706 ),
13707 SelectionGoal::None,
13708 )
13709 });
13710 })
13711 }
13712
13713 pub fn move_to_end_of_excerpt(
13714 &mut self,
13715 _: &MoveToEndOfExcerpt,
13716 window: &mut Window,
13717 cx: &mut Context<Self>,
13718 ) {
13719 if matches!(self.mode, EditorMode::SingleLine) {
13720 cx.propagate();
13721 return;
13722 }
13723 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13724 self.change_selections(Default::default(), window, cx, |s| {
13725 s.move_with(|map, selection| {
13726 selection.collapse_to(
13727 movement::end_of_excerpt(
13728 map,
13729 selection.head(),
13730 workspace::searchable::Direction::Next,
13731 ),
13732 SelectionGoal::None,
13733 )
13734 });
13735 })
13736 }
13737
13738 pub fn move_to_end_of_previous_excerpt(
13739 &mut self,
13740 _: &MoveToEndOfPreviousExcerpt,
13741 window: &mut Window,
13742 cx: &mut Context<Self>,
13743 ) {
13744 if matches!(self.mode, EditorMode::SingleLine) {
13745 cx.propagate();
13746 return;
13747 }
13748 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13749 self.change_selections(Default::default(), window, cx, |s| {
13750 s.move_with(|map, selection| {
13751 selection.collapse_to(
13752 movement::end_of_excerpt(
13753 map,
13754 selection.head(),
13755 workspace::searchable::Direction::Prev,
13756 ),
13757 SelectionGoal::None,
13758 )
13759 });
13760 })
13761 }
13762
13763 pub fn select_to_start_of_excerpt(
13764 &mut self,
13765 _: &SelectToStartOfExcerpt,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 if matches!(self.mode, EditorMode::SingleLine) {
13770 cx.propagate();
13771 return;
13772 }
13773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13774 self.change_selections(Default::default(), window, cx, |s| {
13775 s.move_heads_with(|map, head, _| {
13776 (
13777 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13778 SelectionGoal::None,
13779 )
13780 });
13781 })
13782 }
13783
13784 pub fn select_to_start_of_next_excerpt(
13785 &mut self,
13786 _: &SelectToStartOfNextExcerpt,
13787 window: &mut Window,
13788 cx: &mut Context<Self>,
13789 ) {
13790 if matches!(self.mode, EditorMode::SingleLine) {
13791 cx.propagate();
13792 return;
13793 }
13794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13795 self.change_selections(Default::default(), window, cx, |s| {
13796 s.move_heads_with(|map, head, _| {
13797 (
13798 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13799 SelectionGoal::None,
13800 )
13801 });
13802 })
13803 }
13804
13805 pub fn select_to_end_of_excerpt(
13806 &mut self,
13807 _: &SelectToEndOfExcerpt,
13808 window: &mut Window,
13809 cx: &mut Context<Self>,
13810 ) {
13811 if matches!(self.mode, EditorMode::SingleLine) {
13812 cx.propagate();
13813 return;
13814 }
13815 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13816 self.change_selections(Default::default(), window, cx, |s| {
13817 s.move_heads_with(|map, head, _| {
13818 (
13819 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13820 SelectionGoal::None,
13821 )
13822 });
13823 })
13824 }
13825
13826 pub fn select_to_end_of_previous_excerpt(
13827 &mut self,
13828 _: &SelectToEndOfPreviousExcerpt,
13829 window: &mut Window,
13830 cx: &mut Context<Self>,
13831 ) {
13832 if matches!(self.mode, EditorMode::SingleLine) {
13833 cx.propagate();
13834 return;
13835 }
13836 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13837 self.change_selections(Default::default(), window, cx, |s| {
13838 s.move_heads_with(|map, head, _| {
13839 (
13840 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13841 SelectionGoal::None,
13842 )
13843 });
13844 })
13845 }
13846
13847 pub fn move_to_beginning(
13848 &mut self,
13849 _: &MoveToBeginning,
13850 window: &mut Window,
13851 cx: &mut Context<Self>,
13852 ) {
13853 if matches!(self.mode, EditorMode::SingleLine) {
13854 cx.propagate();
13855 return;
13856 }
13857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13858 self.change_selections(Default::default(), window, cx, |s| {
13859 s.select_ranges(vec![0..0]);
13860 });
13861 }
13862
13863 pub fn select_to_beginning(
13864 &mut self,
13865 _: &SelectToBeginning,
13866 window: &mut Window,
13867 cx: &mut Context<Self>,
13868 ) {
13869 let mut selection = self.selections.last::<Point>(cx);
13870 selection.set_head(Point::zero(), SelectionGoal::None);
13871 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13872 self.change_selections(Default::default(), window, cx, |s| {
13873 s.select(vec![selection]);
13874 });
13875 }
13876
13877 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13878 if matches!(self.mode, EditorMode::SingleLine) {
13879 cx.propagate();
13880 return;
13881 }
13882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13883 let cursor = self.buffer.read(cx).read(cx).len();
13884 self.change_selections(Default::default(), window, cx, |s| {
13885 s.select_ranges(vec![cursor..cursor])
13886 });
13887 }
13888
13889 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13890 self.nav_history = nav_history;
13891 }
13892
13893 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13894 self.nav_history.as_ref()
13895 }
13896
13897 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13898 self.push_to_nav_history(
13899 self.selections.newest_anchor().head(),
13900 None,
13901 false,
13902 true,
13903 cx,
13904 );
13905 }
13906
13907 fn push_to_nav_history(
13908 &mut self,
13909 cursor_anchor: Anchor,
13910 new_position: Option<Point>,
13911 is_deactivate: bool,
13912 always: bool,
13913 cx: &mut Context<Self>,
13914 ) {
13915 if let Some(nav_history) = self.nav_history.as_mut() {
13916 let buffer = self.buffer.read(cx).read(cx);
13917 let cursor_position = cursor_anchor.to_point(&buffer);
13918 let scroll_state = self.scroll_manager.anchor();
13919 let scroll_top_row = scroll_state.top_row(&buffer);
13920 drop(buffer);
13921
13922 if let Some(new_position) = new_position {
13923 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13924 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13925 return;
13926 }
13927 }
13928
13929 nav_history.push(
13930 Some(NavigationData {
13931 cursor_anchor,
13932 cursor_position,
13933 scroll_anchor: scroll_state,
13934 scroll_top_row,
13935 }),
13936 cx,
13937 );
13938 cx.emit(EditorEvent::PushedToNavHistory {
13939 anchor: cursor_anchor,
13940 is_deactivate,
13941 })
13942 }
13943 }
13944
13945 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13947 let buffer = self.buffer.read(cx).snapshot(cx);
13948 let mut selection = self.selections.first::<usize>(cx);
13949 selection.set_head(buffer.len(), SelectionGoal::None);
13950 self.change_selections(Default::default(), window, cx, |s| {
13951 s.select(vec![selection]);
13952 });
13953 }
13954
13955 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
13956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13957 let end = self.buffer.read(cx).read(cx).len();
13958 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13959 s.select_ranges(vec![0..end]);
13960 });
13961 }
13962
13963 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
13964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13965 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13966 let mut selections = self.selections.all::<Point>(cx);
13967 let max_point = display_map.buffer_snapshot.max_point();
13968 for selection in &mut selections {
13969 let rows = selection.spanned_rows(true, &display_map);
13970 selection.start = Point::new(rows.start.0, 0);
13971 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
13972 selection.reversed = false;
13973 }
13974 self.change_selections(Default::default(), window, cx, |s| {
13975 s.select(selections);
13976 });
13977 }
13978
13979 pub fn split_selection_into_lines(
13980 &mut self,
13981 action: &SplitSelectionIntoLines,
13982 window: &mut Window,
13983 cx: &mut Context<Self>,
13984 ) {
13985 let selections = self
13986 .selections
13987 .all::<Point>(cx)
13988 .into_iter()
13989 .map(|selection| selection.start..selection.end)
13990 .collect::<Vec<_>>();
13991 self.unfold_ranges(&selections, true, true, cx);
13992
13993 let mut new_selection_ranges = Vec::new();
13994 {
13995 let buffer = self.buffer.read(cx).read(cx);
13996 for selection in selections {
13997 for row in selection.start.row..selection.end.row {
13998 let line_start = Point::new(row, 0);
13999 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14000
14001 if action.keep_selections {
14002 // Keep the selection range for each line
14003 let selection_start = if row == selection.start.row {
14004 selection.start
14005 } else {
14006 line_start
14007 };
14008 new_selection_ranges.push(selection_start..line_end);
14009 } else {
14010 // Collapse to cursor at end of line
14011 new_selection_ranges.push(line_end..line_end);
14012 }
14013 }
14014
14015 let is_multiline_selection = selection.start.row != selection.end.row;
14016 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14017 // so this action feels more ergonomic when paired with other selection operations
14018 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14019 if !should_skip_last {
14020 if action.keep_selections {
14021 if is_multiline_selection {
14022 let line_start = Point::new(selection.end.row, 0);
14023 new_selection_ranges.push(line_start..selection.end);
14024 } else {
14025 new_selection_ranges.push(selection.start..selection.end);
14026 }
14027 } else {
14028 new_selection_ranges.push(selection.end..selection.end);
14029 }
14030 }
14031 }
14032 }
14033 self.change_selections(Default::default(), window, cx, |s| {
14034 s.select_ranges(new_selection_ranges);
14035 });
14036 }
14037
14038 pub fn add_selection_above(
14039 &mut self,
14040 _: &AddSelectionAbove,
14041 window: &mut Window,
14042 cx: &mut Context<Self>,
14043 ) {
14044 self.add_selection(true, window, cx);
14045 }
14046
14047 pub fn add_selection_below(
14048 &mut self,
14049 _: &AddSelectionBelow,
14050 window: &mut Window,
14051 cx: &mut Context<Self>,
14052 ) {
14053 self.add_selection(false, window, cx);
14054 }
14055
14056 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
14057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14058
14059 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14060 let all_selections = self.selections.all::<Point>(cx);
14061 let text_layout_details = self.text_layout_details(window);
14062
14063 let (mut columnar_selections, new_selections_to_columnarize) = {
14064 if let Some(state) = self.add_selections_state.as_ref() {
14065 let columnar_selection_ids: HashSet<_> = state
14066 .groups
14067 .iter()
14068 .flat_map(|group| group.stack.iter())
14069 .copied()
14070 .collect();
14071
14072 all_selections
14073 .into_iter()
14074 .partition(|s| columnar_selection_ids.contains(&s.id))
14075 } else {
14076 (Vec::new(), all_selections)
14077 }
14078 };
14079
14080 let mut state = self
14081 .add_selections_state
14082 .take()
14083 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14084
14085 for selection in new_selections_to_columnarize {
14086 let range = selection.display_range(&display_map).sorted();
14087 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14088 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14089 let positions = start_x.min(end_x)..start_x.max(end_x);
14090 let mut stack = Vec::new();
14091 for row in range.start.row().0..=range.end.row().0 {
14092 if let Some(selection) = self.selections.build_columnar_selection(
14093 &display_map,
14094 DisplayRow(row),
14095 &positions,
14096 selection.reversed,
14097 &text_layout_details,
14098 ) {
14099 stack.push(selection.id);
14100 columnar_selections.push(selection);
14101 }
14102 }
14103 if !stack.is_empty() {
14104 if above {
14105 stack.reverse();
14106 }
14107 state.groups.push(AddSelectionsGroup { above, stack });
14108 }
14109 }
14110
14111 let mut final_selections = Vec::new();
14112 let end_row = if above {
14113 DisplayRow(0)
14114 } else {
14115 display_map.max_point().row()
14116 };
14117
14118 let mut last_added_item_per_group = HashMap::default();
14119 for group in state.groups.iter_mut() {
14120 if let Some(last_id) = group.stack.last() {
14121 last_added_item_per_group.insert(*last_id, group);
14122 }
14123 }
14124
14125 for selection in columnar_selections {
14126 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14127 if above == group.above {
14128 let range = selection.display_range(&display_map).sorted();
14129 debug_assert_eq!(range.start.row(), range.end.row());
14130 let mut row = range.start.row();
14131 let positions =
14132 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14133 px(start)..px(end)
14134 } else {
14135 let start_x =
14136 display_map.x_for_display_point(range.start, &text_layout_details);
14137 let end_x =
14138 display_map.x_for_display_point(range.end, &text_layout_details);
14139 start_x.min(end_x)..start_x.max(end_x)
14140 };
14141
14142 let mut maybe_new_selection = None;
14143 while row != end_row {
14144 if above {
14145 row.0 -= 1;
14146 } else {
14147 row.0 += 1;
14148 }
14149 if let Some(new_selection) = self.selections.build_columnar_selection(
14150 &display_map,
14151 row,
14152 &positions,
14153 selection.reversed,
14154 &text_layout_details,
14155 ) {
14156 maybe_new_selection = Some(new_selection);
14157 break;
14158 }
14159 }
14160
14161 if let Some(new_selection) = maybe_new_selection {
14162 group.stack.push(new_selection.id);
14163 if above {
14164 final_selections.push(new_selection);
14165 final_selections.push(selection);
14166 } else {
14167 final_selections.push(selection);
14168 final_selections.push(new_selection);
14169 }
14170 } else {
14171 final_selections.push(selection);
14172 }
14173 } else {
14174 group.stack.pop();
14175 }
14176 } else {
14177 final_selections.push(selection);
14178 }
14179 }
14180
14181 self.change_selections(Default::default(), window, cx, |s| {
14182 s.select(final_selections);
14183 });
14184
14185 let final_selection_ids: HashSet<_> = self
14186 .selections
14187 .all::<Point>(cx)
14188 .iter()
14189 .map(|s| s.id)
14190 .collect();
14191 state.groups.retain_mut(|group| {
14192 // selections might get merged above so we remove invalid items from stacks
14193 group.stack.retain(|id| final_selection_ids.contains(id));
14194
14195 // single selection in stack can be treated as initial state
14196 group.stack.len() > 1
14197 });
14198
14199 if !state.groups.is_empty() {
14200 self.add_selections_state = Some(state);
14201 }
14202 }
14203
14204 fn select_match_ranges(
14205 &mut self,
14206 range: Range<usize>,
14207 reversed: bool,
14208 replace_newest: bool,
14209 auto_scroll: Option<Autoscroll>,
14210 window: &mut Window,
14211 cx: &mut Context<Editor>,
14212 ) {
14213 self.unfold_ranges(
14214 std::slice::from_ref(&range),
14215 false,
14216 auto_scroll.is_some(),
14217 cx,
14218 );
14219 let effects = if let Some(scroll) = auto_scroll {
14220 SelectionEffects::scroll(scroll)
14221 } else {
14222 SelectionEffects::no_scroll()
14223 };
14224 self.change_selections(effects, window, cx, |s| {
14225 if replace_newest {
14226 s.delete(s.newest_anchor().id);
14227 }
14228 if reversed {
14229 s.insert_range(range.end..range.start);
14230 } else {
14231 s.insert_range(range);
14232 }
14233 });
14234 }
14235
14236 pub fn select_next_match_internal(
14237 &mut self,
14238 display_map: &DisplaySnapshot,
14239 replace_newest: bool,
14240 autoscroll: Option<Autoscroll>,
14241 window: &mut Window,
14242 cx: &mut Context<Self>,
14243 ) -> Result<()> {
14244 let buffer = &display_map.buffer_snapshot;
14245 let mut selections = self.selections.all::<usize>(cx);
14246 if let Some(mut select_next_state) = self.select_next_state.take() {
14247 let query = &select_next_state.query;
14248 if !select_next_state.done {
14249 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14250 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14251 let mut next_selected_range = None;
14252
14253 let bytes_after_last_selection =
14254 buffer.bytes_in_range(last_selection.end..buffer.len());
14255 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14256 let query_matches = query
14257 .stream_find_iter(bytes_after_last_selection)
14258 .map(|result| (last_selection.end, result))
14259 .chain(
14260 query
14261 .stream_find_iter(bytes_before_first_selection)
14262 .map(|result| (0, result)),
14263 );
14264
14265 for (start_offset, query_match) in query_matches {
14266 let query_match = query_match.unwrap(); // can only fail due to I/O
14267 let offset_range =
14268 start_offset + query_match.start()..start_offset + query_match.end();
14269
14270 if !select_next_state.wordwise
14271 || (!buffer.is_inside_word(offset_range.start, None)
14272 && !buffer.is_inside_word(offset_range.end, None))
14273 {
14274 // TODO: This is n^2, because we might check all the selections
14275 if !selections
14276 .iter()
14277 .any(|selection| selection.range().overlaps(&offset_range))
14278 {
14279 next_selected_range = Some(offset_range);
14280 break;
14281 }
14282 }
14283 }
14284
14285 if let Some(next_selected_range) = next_selected_range {
14286 self.select_match_ranges(
14287 next_selected_range,
14288 last_selection.reversed,
14289 replace_newest,
14290 autoscroll,
14291 window,
14292 cx,
14293 );
14294 } else {
14295 select_next_state.done = true;
14296 }
14297 }
14298
14299 self.select_next_state = Some(select_next_state);
14300 } else {
14301 let mut only_carets = true;
14302 let mut same_text_selected = true;
14303 let mut selected_text = None;
14304
14305 let mut selections_iter = selections.iter().peekable();
14306 while let Some(selection) = selections_iter.next() {
14307 if selection.start != selection.end {
14308 only_carets = false;
14309 }
14310
14311 if same_text_selected {
14312 if selected_text.is_none() {
14313 selected_text =
14314 Some(buffer.text_for_range(selection.range()).collect::<String>());
14315 }
14316
14317 if let Some(next_selection) = selections_iter.peek() {
14318 if next_selection.range().len() == selection.range().len() {
14319 let next_selected_text = buffer
14320 .text_for_range(next_selection.range())
14321 .collect::<String>();
14322 if Some(next_selected_text) != selected_text {
14323 same_text_selected = false;
14324 selected_text = None;
14325 }
14326 } else {
14327 same_text_selected = false;
14328 selected_text = None;
14329 }
14330 }
14331 }
14332 }
14333
14334 if only_carets {
14335 for selection in &mut selections {
14336 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14337 selection.start = word_range.start;
14338 selection.end = word_range.end;
14339 selection.goal = SelectionGoal::None;
14340 selection.reversed = false;
14341 self.select_match_ranges(
14342 selection.start..selection.end,
14343 selection.reversed,
14344 replace_newest,
14345 autoscroll,
14346 window,
14347 cx,
14348 );
14349 }
14350
14351 if selections.len() == 1 {
14352 let selection = selections
14353 .last()
14354 .expect("ensured that there's only one selection");
14355 let query = buffer
14356 .text_for_range(selection.start..selection.end)
14357 .collect::<String>();
14358 let is_empty = query.is_empty();
14359 let select_state = SelectNextState {
14360 query: AhoCorasick::new(&[query])?,
14361 wordwise: true,
14362 done: is_empty,
14363 };
14364 self.select_next_state = Some(select_state);
14365 } else {
14366 self.select_next_state = None;
14367 }
14368 } else if let Some(selected_text) = selected_text {
14369 self.select_next_state = Some(SelectNextState {
14370 query: AhoCorasick::new(&[selected_text])?,
14371 wordwise: false,
14372 done: false,
14373 });
14374 self.select_next_match_internal(
14375 display_map,
14376 replace_newest,
14377 autoscroll,
14378 window,
14379 cx,
14380 )?;
14381 }
14382 }
14383 Ok(())
14384 }
14385
14386 pub fn select_all_matches(
14387 &mut self,
14388 _action: &SelectAllMatches,
14389 window: &mut Window,
14390 cx: &mut Context<Self>,
14391 ) -> Result<()> {
14392 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14393
14394 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14395
14396 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14397 let Some(select_next_state) = self.select_next_state.as_mut() else {
14398 return Ok(());
14399 };
14400 if select_next_state.done {
14401 return Ok(());
14402 }
14403
14404 let mut new_selections = Vec::new();
14405
14406 let reversed = self.selections.oldest::<usize>(cx).reversed;
14407 let buffer = &display_map.buffer_snapshot;
14408 let query_matches = select_next_state
14409 .query
14410 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14411
14412 for query_match in query_matches.into_iter() {
14413 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14414 let offset_range = if reversed {
14415 query_match.end()..query_match.start()
14416 } else {
14417 query_match.start()..query_match.end()
14418 };
14419
14420 if !select_next_state.wordwise
14421 || (!buffer.is_inside_word(offset_range.start, None)
14422 && !buffer.is_inside_word(offset_range.end, None))
14423 {
14424 new_selections.push(offset_range.start..offset_range.end);
14425 }
14426 }
14427
14428 select_next_state.done = true;
14429
14430 if new_selections.is_empty() {
14431 log::error!("bug: new_selections is empty in select_all_matches");
14432 return Ok(());
14433 }
14434
14435 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14436 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14437 selections.select_ranges(new_selections)
14438 });
14439
14440 Ok(())
14441 }
14442
14443 pub fn select_next(
14444 &mut self,
14445 action: &SelectNext,
14446 window: &mut Window,
14447 cx: &mut Context<Self>,
14448 ) -> Result<()> {
14449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14450 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14451 self.select_next_match_internal(
14452 &display_map,
14453 action.replace_newest,
14454 Some(Autoscroll::newest()),
14455 window,
14456 cx,
14457 )?;
14458 Ok(())
14459 }
14460
14461 pub fn select_previous(
14462 &mut self,
14463 action: &SelectPrevious,
14464 window: &mut Window,
14465 cx: &mut Context<Self>,
14466 ) -> Result<()> {
14467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14468 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14469 let buffer = &display_map.buffer_snapshot;
14470 let mut selections = self.selections.all::<usize>(cx);
14471 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14472 let query = &select_prev_state.query;
14473 if !select_prev_state.done {
14474 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14475 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14476 let mut next_selected_range = None;
14477 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14478 let bytes_before_last_selection =
14479 buffer.reversed_bytes_in_range(0..last_selection.start);
14480 let bytes_after_first_selection =
14481 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14482 let query_matches = query
14483 .stream_find_iter(bytes_before_last_selection)
14484 .map(|result| (last_selection.start, result))
14485 .chain(
14486 query
14487 .stream_find_iter(bytes_after_first_selection)
14488 .map(|result| (buffer.len(), result)),
14489 );
14490 for (end_offset, query_match) in query_matches {
14491 let query_match = query_match.unwrap(); // can only fail due to I/O
14492 let offset_range =
14493 end_offset - query_match.end()..end_offset - query_match.start();
14494
14495 if !select_prev_state.wordwise
14496 || (!buffer.is_inside_word(offset_range.start, None)
14497 && !buffer.is_inside_word(offset_range.end, None))
14498 {
14499 next_selected_range = Some(offset_range);
14500 break;
14501 }
14502 }
14503
14504 if let Some(next_selected_range) = next_selected_range {
14505 self.select_match_ranges(
14506 next_selected_range,
14507 last_selection.reversed,
14508 action.replace_newest,
14509 Some(Autoscroll::newest()),
14510 window,
14511 cx,
14512 );
14513 } else {
14514 select_prev_state.done = true;
14515 }
14516 }
14517
14518 self.select_prev_state = Some(select_prev_state);
14519 } else {
14520 let mut only_carets = true;
14521 let mut same_text_selected = true;
14522 let mut selected_text = None;
14523
14524 let mut selections_iter = selections.iter().peekable();
14525 while let Some(selection) = selections_iter.next() {
14526 if selection.start != selection.end {
14527 only_carets = false;
14528 }
14529
14530 if same_text_selected {
14531 if selected_text.is_none() {
14532 selected_text =
14533 Some(buffer.text_for_range(selection.range()).collect::<String>());
14534 }
14535
14536 if let Some(next_selection) = selections_iter.peek() {
14537 if next_selection.range().len() == selection.range().len() {
14538 let next_selected_text = buffer
14539 .text_for_range(next_selection.range())
14540 .collect::<String>();
14541 if Some(next_selected_text) != selected_text {
14542 same_text_selected = false;
14543 selected_text = None;
14544 }
14545 } else {
14546 same_text_selected = false;
14547 selected_text = None;
14548 }
14549 }
14550 }
14551 }
14552
14553 if only_carets {
14554 for selection in &mut selections {
14555 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14556 selection.start = word_range.start;
14557 selection.end = word_range.end;
14558 selection.goal = SelectionGoal::None;
14559 selection.reversed = false;
14560 self.select_match_ranges(
14561 selection.start..selection.end,
14562 selection.reversed,
14563 action.replace_newest,
14564 Some(Autoscroll::newest()),
14565 window,
14566 cx,
14567 );
14568 }
14569 if selections.len() == 1 {
14570 let selection = selections
14571 .last()
14572 .expect("ensured that there's only one selection");
14573 let query = buffer
14574 .text_for_range(selection.start..selection.end)
14575 .collect::<String>();
14576 let is_empty = query.is_empty();
14577 let select_state = SelectNextState {
14578 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14579 wordwise: true,
14580 done: is_empty,
14581 };
14582 self.select_prev_state = Some(select_state);
14583 } else {
14584 self.select_prev_state = None;
14585 }
14586 } else if let Some(selected_text) = selected_text {
14587 self.select_prev_state = Some(SelectNextState {
14588 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14589 wordwise: false,
14590 done: false,
14591 });
14592 self.select_previous(action, window, cx)?;
14593 }
14594 }
14595 Ok(())
14596 }
14597
14598 pub fn find_next_match(
14599 &mut self,
14600 _: &FindNextMatch,
14601 window: &mut Window,
14602 cx: &mut Context<Self>,
14603 ) -> Result<()> {
14604 let selections = self.selections.disjoint_anchors_arc();
14605 match selections.first() {
14606 Some(first) if selections.len() >= 2 => {
14607 self.change_selections(Default::default(), window, cx, |s| {
14608 s.select_ranges([first.range()]);
14609 });
14610 }
14611 _ => self.select_next(
14612 &SelectNext {
14613 replace_newest: true,
14614 },
14615 window,
14616 cx,
14617 )?,
14618 }
14619 Ok(())
14620 }
14621
14622 pub fn find_previous_match(
14623 &mut self,
14624 _: &FindPreviousMatch,
14625 window: &mut Window,
14626 cx: &mut Context<Self>,
14627 ) -> Result<()> {
14628 let selections = self.selections.disjoint_anchors_arc();
14629 match selections.last() {
14630 Some(last) if selections.len() >= 2 => {
14631 self.change_selections(Default::default(), window, cx, |s| {
14632 s.select_ranges([last.range()]);
14633 });
14634 }
14635 _ => self.select_previous(
14636 &SelectPrevious {
14637 replace_newest: true,
14638 },
14639 window,
14640 cx,
14641 )?,
14642 }
14643 Ok(())
14644 }
14645
14646 pub fn toggle_comments(
14647 &mut self,
14648 action: &ToggleComments,
14649 window: &mut Window,
14650 cx: &mut Context<Self>,
14651 ) {
14652 if self.read_only(cx) {
14653 return;
14654 }
14655 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14656 let text_layout_details = &self.text_layout_details(window);
14657 self.transact(window, cx, |this, window, cx| {
14658 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
14659 let mut edits = Vec::new();
14660 let mut selection_edit_ranges = Vec::new();
14661 let mut last_toggled_row = None;
14662 let snapshot = this.buffer.read(cx).read(cx);
14663 let empty_str: Arc<str> = Arc::default();
14664 let mut suffixes_inserted = Vec::new();
14665 let ignore_indent = action.ignore_indent;
14666
14667 fn comment_prefix_range(
14668 snapshot: &MultiBufferSnapshot,
14669 row: MultiBufferRow,
14670 comment_prefix: &str,
14671 comment_prefix_whitespace: &str,
14672 ignore_indent: bool,
14673 ) -> Range<Point> {
14674 let indent_size = if ignore_indent {
14675 0
14676 } else {
14677 snapshot.indent_size_for_line(row).len
14678 };
14679
14680 let start = Point::new(row.0, indent_size);
14681
14682 let mut line_bytes = snapshot
14683 .bytes_in_range(start..snapshot.max_point())
14684 .flatten()
14685 .copied();
14686
14687 // If this line currently begins with the line comment prefix, then record
14688 // the range containing the prefix.
14689 if line_bytes
14690 .by_ref()
14691 .take(comment_prefix.len())
14692 .eq(comment_prefix.bytes())
14693 {
14694 // Include any whitespace that matches the comment prefix.
14695 let matching_whitespace_len = line_bytes
14696 .zip(comment_prefix_whitespace.bytes())
14697 .take_while(|(a, b)| a == b)
14698 .count() as u32;
14699 let end = Point::new(
14700 start.row,
14701 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14702 );
14703 start..end
14704 } else {
14705 start..start
14706 }
14707 }
14708
14709 fn comment_suffix_range(
14710 snapshot: &MultiBufferSnapshot,
14711 row: MultiBufferRow,
14712 comment_suffix: &str,
14713 comment_suffix_has_leading_space: bool,
14714 ) -> Range<Point> {
14715 let end = Point::new(row.0, snapshot.line_len(row));
14716 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14717
14718 let mut line_end_bytes = snapshot
14719 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14720 .flatten()
14721 .copied();
14722
14723 let leading_space_len = if suffix_start_column > 0
14724 && line_end_bytes.next() == Some(b' ')
14725 && comment_suffix_has_leading_space
14726 {
14727 1
14728 } else {
14729 0
14730 };
14731
14732 // If this line currently begins with the line comment prefix, then record
14733 // the range containing the prefix.
14734 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14735 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14736 start..end
14737 } else {
14738 end..end
14739 }
14740 }
14741
14742 // TODO: Handle selections that cross excerpts
14743 for selection in &mut selections {
14744 let start_column = snapshot
14745 .indent_size_for_line(MultiBufferRow(selection.start.row))
14746 .len;
14747 let language = if let Some(language) =
14748 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14749 {
14750 language
14751 } else {
14752 continue;
14753 };
14754
14755 selection_edit_ranges.clear();
14756
14757 // If multiple selections contain a given row, avoid processing that
14758 // row more than once.
14759 let mut start_row = MultiBufferRow(selection.start.row);
14760 if last_toggled_row == Some(start_row) {
14761 start_row = start_row.next_row();
14762 }
14763 let end_row =
14764 if selection.end.row > selection.start.row && selection.end.column == 0 {
14765 MultiBufferRow(selection.end.row - 1)
14766 } else {
14767 MultiBufferRow(selection.end.row)
14768 };
14769 last_toggled_row = Some(end_row);
14770
14771 if start_row > end_row {
14772 continue;
14773 }
14774
14775 // If the language has line comments, toggle those.
14776 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14777
14778 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14779 if ignore_indent {
14780 full_comment_prefixes = full_comment_prefixes
14781 .into_iter()
14782 .map(|s| Arc::from(s.trim_end()))
14783 .collect();
14784 }
14785
14786 if !full_comment_prefixes.is_empty() {
14787 let first_prefix = full_comment_prefixes
14788 .first()
14789 .expect("prefixes is non-empty");
14790 let prefix_trimmed_lengths = full_comment_prefixes
14791 .iter()
14792 .map(|p| p.trim_end_matches(' ').len())
14793 .collect::<SmallVec<[usize; 4]>>();
14794
14795 let mut all_selection_lines_are_comments = true;
14796
14797 for row in start_row.0..=end_row.0 {
14798 let row = MultiBufferRow(row);
14799 if start_row < end_row && snapshot.is_line_blank(row) {
14800 continue;
14801 }
14802
14803 let prefix_range = full_comment_prefixes
14804 .iter()
14805 .zip(prefix_trimmed_lengths.iter().copied())
14806 .map(|(prefix, trimmed_prefix_len)| {
14807 comment_prefix_range(
14808 snapshot.deref(),
14809 row,
14810 &prefix[..trimmed_prefix_len],
14811 &prefix[trimmed_prefix_len..],
14812 ignore_indent,
14813 )
14814 })
14815 .max_by_key(|range| range.end.column - range.start.column)
14816 .expect("prefixes is non-empty");
14817
14818 if prefix_range.is_empty() {
14819 all_selection_lines_are_comments = false;
14820 }
14821
14822 selection_edit_ranges.push(prefix_range);
14823 }
14824
14825 if all_selection_lines_are_comments {
14826 edits.extend(
14827 selection_edit_ranges
14828 .iter()
14829 .cloned()
14830 .map(|range| (range, empty_str.clone())),
14831 );
14832 } else {
14833 let min_column = selection_edit_ranges
14834 .iter()
14835 .map(|range| range.start.column)
14836 .min()
14837 .unwrap_or(0);
14838 edits.extend(selection_edit_ranges.iter().map(|range| {
14839 let position = Point::new(range.start.row, min_column);
14840 (position..position, first_prefix.clone())
14841 }));
14842 }
14843 } else if let Some(BlockCommentConfig {
14844 start: full_comment_prefix,
14845 end: comment_suffix,
14846 ..
14847 }) = language.block_comment()
14848 {
14849 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14850 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14851 let prefix_range = comment_prefix_range(
14852 snapshot.deref(),
14853 start_row,
14854 comment_prefix,
14855 comment_prefix_whitespace,
14856 ignore_indent,
14857 );
14858 let suffix_range = comment_suffix_range(
14859 snapshot.deref(),
14860 end_row,
14861 comment_suffix.trim_start_matches(' '),
14862 comment_suffix.starts_with(' '),
14863 );
14864
14865 if prefix_range.is_empty() || suffix_range.is_empty() {
14866 edits.push((
14867 prefix_range.start..prefix_range.start,
14868 full_comment_prefix.clone(),
14869 ));
14870 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14871 suffixes_inserted.push((end_row, comment_suffix.len()));
14872 } else {
14873 edits.push((prefix_range, empty_str.clone()));
14874 edits.push((suffix_range, empty_str.clone()));
14875 }
14876 } else {
14877 continue;
14878 }
14879 }
14880
14881 drop(snapshot);
14882 this.buffer.update(cx, |buffer, cx| {
14883 buffer.edit(edits, None, cx);
14884 });
14885
14886 // Adjust selections so that they end before any comment suffixes that
14887 // were inserted.
14888 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14889 let mut selections = this.selections.all::<Point>(cx);
14890 let snapshot = this.buffer.read(cx).read(cx);
14891 for selection in &mut selections {
14892 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14893 match row.cmp(&MultiBufferRow(selection.end.row)) {
14894 Ordering::Less => {
14895 suffixes_inserted.next();
14896 continue;
14897 }
14898 Ordering::Greater => break,
14899 Ordering::Equal => {
14900 if selection.end.column == snapshot.line_len(row) {
14901 if selection.is_empty() {
14902 selection.start.column -= suffix_len as u32;
14903 }
14904 selection.end.column -= suffix_len as u32;
14905 }
14906 break;
14907 }
14908 }
14909 }
14910 }
14911
14912 drop(snapshot);
14913 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
14914
14915 let selections = this.selections.all::<Point>(cx);
14916 let selections_on_single_row = selections.windows(2).all(|selections| {
14917 selections[0].start.row == selections[1].start.row
14918 && selections[0].end.row == selections[1].end.row
14919 && selections[0].start.row == selections[0].end.row
14920 });
14921 let selections_selecting = selections
14922 .iter()
14923 .any(|selection| selection.start != selection.end);
14924 let advance_downwards = action.advance_downwards
14925 && selections_on_single_row
14926 && !selections_selecting
14927 && !matches!(this.mode, EditorMode::SingleLine);
14928
14929 if advance_downwards {
14930 let snapshot = this.buffer.read(cx).snapshot(cx);
14931
14932 this.change_selections(Default::default(), window, cx, |s| {
14933 s.move_cursors_with(|display_snapshot, display_point, _| {
14934 let mut point = display_point.to_point(display_snapshot);
14935 point.row += 1;
14936 point = snapshot.clip_point(point, Bias::Left);
14937 let display_point = point.to_display_point(display_snapshot);
14938 let goal = SelectionGoal::HorizontalPosition(
14939 display_snapshot
14940 .x_for_display_point(display_point, text_layout_details)
14941 .into(),
14942 );
14943 (display_point, goal)
14944 })
14945 });
14946 }
14947 });
14948 }
14949
14950 pub fn select_enclosing_symbol(
14951 &mut self,
14952 _: &SelectEnclosingSymbol,
14953 window: &mut Window,
14954 cx: &mut Context<Self>,
14955 ) {
14956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14957
14958 let buffer = self.buffer.read(cx).snapshot(cx);
14959 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
14960
14961 fn update_selection(
14962 selection: &Selection<usize>,
14963 buffer_snap: &MultiBufferSnapshot,
14964 ) -> Option<Selection<usize>> {
14965 let cursor = selection.head();
14966 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
14967 for symbol in symbols.iter().rev() {
14968 let start = symbol.range.start.to_offset(buffer_snap);
14969 let end = symbol.range.end.to_offset(buffer_snap);
14970 let new_range = start..end;
14971 if start < selection.start || end > selection.end {
14972 return Some(Selection {
14973 id: selection.id,
14974 start: new_range.start,
14975 end: new_range.end,
14976 goal: SelectionGoal::None,
14977 reversed: selection.reversed,
14978 });
14979 }
14980 }
14981 None
14982 }
14983
14984 let mut selected_larger_symbol = false;
14985 let new_selections = old_selections
14986 .iter()
14987 .map(|selection| match update_selection(selection, &buffer) {
14988 Some(new_selection) => {
14989 if new_selection.range() != selection.range() {
14990 selected_larger_symbol = true;
14991 }
14992 new_selection
14993 }
14994 None => selection.clone(),
14995 })
14996 .collect::<Vec<_>>();
14997
14998 if selected_larger_symbol {
14999 self.change_selections(Default::default(), window, cx, |s| {
15000 s.select(new_selections);
15001 });
15002 }
15003 }
15004
15005 pub fn select_larger_syntax_node(
15006 &mut self,
15007 _: &SelectLargerSyntaxNode,
15008 window: &mut Window,
15009 cx: &mut Context<Self>,
15010 ) {
15011 let Some(visible_row_count) = self.visible_row_count() else {
15012 return;
15013 };
15014 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15015 if old_selections.is_empty() {
15016 return;
15017 }
15018
15019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15020
15021 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15022 let buffer = self.buffer.read(cx).snapshot(cx);
15023
15024 let mut selected_larger_node = false;
15025 let mut new_selections = old_selections
15026 .iter()
15027 .map(|selection| {
15028 let old_range = selection.start..selection.end;
15029
15030 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15031 // manually select word at selection
15032 if ["string_content", "inline"].contains(&node.kind()) {
15033 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15034 // ignore if word is already selected
15035 if !word_range.is_empty() && old_range != word_range {
15036 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15037 // only select word if start and end point belongs to same word
15038 if word_range == last_word_range {
15039 selected_larger_node = true;
15040 return Selection {
15041 id: selection.id,
15042 start: word_range.start,
15043 end: word_range.end,
15044 goal: SelectionGoal::None,
15045 reversed: selection.reversed,
15046 };
15047 }
15048 }
15049 }
15050 }
15051
15052 let mut new_range = old_range.clone();
15053 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15054 new_range = range;
15055 if !node.is_named() {
15056 continue;
15057 }
15058 if !display_map.intersects_fold(new_range.start)
15059 && !display_map.intersects_fold(new_range.end)
15060 {
15061 break;
15062 }
15063 }
15064
15065 selected_larger_node |= new_range != old_range;
15066 Selection {
15067 id: selection.id,
15068 start: new_range.start,
15069 end: new_range.end,
15070 goal: SelectionGoal::None,
15071 reversed: selection.reversed,
15072 }
15073 })
15074 .collect::<Vec<_>>();
15075
15076 if !selected_larger_node {
15077 return; // don't put this call in the history
15078 }
15079
15080 // scroll based on transformation done to the last selection created by the user
15081 let (last_old, last_new) = old_selections
15082 .last()
15083 .zip(new_selections.last().cloned())
15084 .expect("old_selections isn't empty");
15085
15086 // revert selection
15087 let is_selection_reversed = {
15088 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15089 new_selections.last_mut().expect("checked above").reversed =
15090 should_newest_selection_be_reversed;
15091 should_newest_selection_be_reversed
15092 };
15093
15094 if selected_larger_node {
15095 self.select_syntax_node_history.disable_clearing = true;
15096 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15097 s.select(new_selections.clone());
15098 });
15099 self.select_syntax_node_history.disable_clearing = false;
15100 }
15101
15102 let start_row = last_new.start.to_display_point(&display_map).row().0;
15103 let end_row = last_new.end.to_display_point(&display_map).row().0;
15104 let selection_height = end_row - start_row + 1;
15105 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15106
15107 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15108 let scroll_behavior = if fits_on_the_screen {
15109 self.request_autoscroll(Autoscroll::fit(), cx);
15110 SelectSyntaxNodeScrollBehavior::FitSelection
15111 } else if is_selection_reversed {
15112 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15113 SelectSyntaxNodeScrollBehavior::CursorTop
15114 } else {
15115 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15116 SelectSyntaxNodeScrollBehavior::CursorBottom
15117 };
15118
15119 self.select_syntax_node_history.push((
15120 old_selections,
15121 scroll_behavior,
15122 is_selection_reversed,
15123 ));
15124 }
15125
15126 pub fn select_smaller_syntax_node(
15127 &mut self,
15128 _: &SelectSmallerSyntaxNode,
15129 window: &mut Window,
15130 cx: &mut Context<Self>,
15131 ) {
15132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15133
15134 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15135 self.select_syntax_node_history.pop()
15136 {
15137 if let Some(selection) = selections.last_mut() {
15138 selection.reversed = is_selection_reversed;
15139 }
15140
15141 self.select_syntax_node_history.disable_clearing = true;
15142 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15143 s.select(selections.to_vec());
15144 });
15145 self.select_syntax_node_history.disable_clearing = false;
15146
15147 match scroll_behavior {
15148 SelectSyntaxNodeScrollBehavior::CursorTop => {
15149 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15150 }
15151 SelectSyntaxNodeScrollBehavior::FitSelection => {
15152 self.request_autoscroll(Autoscroll::fit(), cx);
15153 }
15154 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15155 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15156 }
15157 }
15158 }
15159 }
15160
15161 pub fn unwrap_syntax_node(
15162 &mut self,
15163 _: &UnwrapSyntaxNode,
15164 window: &mut Window,
15165 cx: &mut Context<Self>,
15166 ) {
15167 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15168
15169 let buffer = self.buffer.read(cx).snapshot(cx);
15170 let selections = self
15171 .selections
15172 .all::<usize>(cx)
15173 .into_iter()
15174 // subtracting the offset requires sorting
15175 .sorted_by_key(|i| i.start);
15176
15177 let full_edits = selections
15178 .into_iter()
15179 .filter_map(|selection| {
15180 let child = if selection.is_empty()
15181 && let Some((_, ancestor_range)) =
15182 buffer.syntax_ancestor(selection.start..selection.end)
15183 {
15184 ancestor_range
15185 } else {
15186 selection.range()
15187 };
15188
15189 let mut parent = child.clone();
15190 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15191 parent = ancestor_range;
15192 if parent.start < child.start || parent.end > child.end {
15193 break;
15194 }
15195 }
15196
15197 if parent == child {
15198 return None;
15199 }
15200 let text = buffer.text_for_range(child).collect::<String>();
15201 Some((selection.id, parent, text))
15202 })
15203 .collect::<Vec<_>>();
15204 if full_edits.is_empty() {
15205 return;
15206 }
15207
15208 self.transact(window, cx, |this, window, cx| {
15209 this.buffer.update(cx, |buffer, cx| {
15210 buffer.edit(
15211 full_edits
15212 .iter()
15213 .map(|(_, p, t)| (p.clone(), t.clone()))
15214 .collect::<Vec<_>>(),
15215 None,
15216 cx,
15217 );
15218 });
15219 this.change_selections(Default::default(), window, cx, |s| {
15220 let mut offset = 0;
15221 let mut selections = vec![];
15222 for (id, parent, text) in full_edits {
15223 let start = parent.start - offset;
15224 offset += parent.len() - text.len();
15225 selections.push(Selection {
15226 id,
15227 start,
15228 end: start + text.len(),
15229 reversed: false,
15230 goal: Default::default(),
15231 });
15232 }
15233 s.select(selections);
15234 });
15235 });
15236 }
15237
15238 pub fn select_next_syntax_node(
15239 &mut self,
15240 _: &SelectNextSyntaxNode,
15241 window: &mut Window,
15242 cx: &mut Context<Self>,
15243 ) {
15244 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15245 if old_selections.is_empty() {
15246 return;
15247 }
15248
15249 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15250
15251 let buffer = self.buffer.read(cx).snapshot(cx);
15252 let mut selected_sibling = false;
15253
15254 let new_selections = old_selections
15255 .iter()
15256 .map(|selection| {
15257 let old_range = selection.start..selection.end;
15258
15259 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15260 let new_range = node.byte_range();
15261 selected_sibling = true;
15262 Selection {
15263 id: selection.id,
15264 start: new_range.start,
15265 end: new_range.end,
15266 goal: SelectionGoal::None,
15267 reversed: selection.reversed,
15268 }
15269 } else {
15270 selection.clone()
15271 }
15272 })
15273 .collect::<Vec<_>>();
15274
15275 if selected_sibling {
15276 self.change_selections(
15277 SelectionEffects::scroll(Autoscroll::fit()),
15278 window,
15279 cx,
15280 |s| {
15281 s.select(new_selections);
15282 },
15283 );
15284 }
15285 }
15286
15287 pub fn select_prev_syntax_node(
15288 &mut self,
15289 _: &SelectPreviousSyntaxNode,
15290 window: &mut Window,
15291 cx: &mut Context<Self>,
15292 ) {
15293 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
15294 if old_selections.is_empty() {
15295 return;
15296 }
15297
15298 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15299
15300 let buffer = self.buffer.read(cx).snapshot(cx);
15301 let mut selected_sibling = false;
15302
15303 let new_selections = old_selections
15304 .iter()
15305 .map(|selection| {
15306 let old_range = selection.start..selection.end;
15307
15308 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15309 let new_range = node.byte_range();
15310 selected_sibling = true;
15311 Selection {
15312 id: selection.id,
15313 start: new_range.start,
15314 end: new_range.end,
15315 goal: SelectionGoal::None,
15316 reversed: selection.reversed,
15317 }
15318 } else {
15319 selection.clone()
15320 }
15321 })
15322 .collect::<Vec<_>>();
15323
15324 if selected_sibling {
15325 self.change_selections(
15326 SelectionEffects::scroll(Autoscroll::fit()),
15327 window,
15328 cx,
15329 |s| {
15330 s.select(new_selections);
15331 },
15332 );
15333 }
15334 }
15335
15336 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15337 if !EditorSettings::get_global(cx).gutter.runnables {
15338 self.clear_tasks();
15339 return Task::ready(());
15340 }
15341 let project = self.project().map(Entity::downgrade);
15342 let task_sources = self.lsp_task_sources(cx);
15343 let multi_buffer = self.buffer.downgrade();
15344 cx.spawn_in(window, async move |editor, cx| {
15345 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15346 let Some(project) = project.and_then(|p| p.upgrade()) else {
15347 return;
15348 };
15349 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15350 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15351 }) else {
15352 return;
15353 };
15354
15355 let hide_runnables = project
15356 .update(cx, |project, _| project.is_via_collab())
15357 .unwrap_or(true);
15358 if hide_runnables {
15359 return;
15360 }
15361 let new_rows =
15362 cx.background_spawn({
15363 let snapshot = display_snapshot.clone();
15364 async move {
15365 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15366 }
15367 })
15368 .await;
15369 let Ok(lsp_tasks) =
15370 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15371 else {
15372 return;
15373 };
15374 let lsp_tasks = lsp_tasks.await;
15375
15376 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15377 lsp_tasks
15378 .into_iter()
15379 .flat_map(|(kind, tasks)| {
15380 tasks.into_iter().filter_map(move |(location, task)| {
15381 Some((kind.clone(), location?, task))
15382 })
15383 })
15384 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15385 let buffer = location.target.buffer;
15386 let buffer_snapshot = buffer.read(cx).snapshot();
15387 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
15388 |(excerpt_id, snapshot, _)| {
15389 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15390 display_snapshot
15391 .buffer_snapshot
15392 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15393 } else {
15394 None
15395 }
15396 },
15397 );
15398 if let Some(offset) = offset {
15399 let task_buffer_range =
15400 location.target.range.to_point(&buffer_snapshot);
15401 let context_buffer_range =
15402 task_buffer_range.to_offset(&buffer_snapshot);
15403 let context_range = BufferOffset(context_buffer_range.start)
15404 ..BufferOffset(context_buffer_range.end);
15405
15406 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15407 .or_insert_with(|| RunnableTasks {
15408 templates: Vec::new(),
15409 offset,
15410 column: task_buffer_range.start.column,
15411 extra_variables: HashMap::default(),
15412 context_range,
15413 })
15414 .templates
15415 .push((kind, task.original_task().clone()));
15416 }
15417
15418 acc
15419 })
15420 }) else {
15421 return;
15422 };
15423
15424 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15425 buffer.language_settings(cx).tasks.prefer_lsp
15426 }) else {
15427 return;
15428 };
15429
15430 let rows = Self::runnable_rows(
15431 project,
15432 display_snapshot,
15433 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15434 new_rows,
15435 cx.clone(),
15436 )
15437 .await;
15438 editor
15439 .update(cx, |editor, _| {
15440 editor.clear_tasks();
15441 for (key, mut value) in rows {
15442 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15443 value.templates.extend(lsp_tasks.templates);
15444 }
15445
15446 editor.insert_tasks(key, value);
15447 }
15448 for (key, value) in lsp_tasks_by_rows {
15449 editor.insert_tasks(key, value);
15450 }
15451 })
15452 .ok();
15453 })
15454 }
15455 fn fetch_runnable_ranges(
15456 snapshot: &DisplaySnapshot,
15457 range: Range<Anchor>,
15458 ) -> Vec<language::RunnableRange> {
15459 snapshot.buffer_snapshot.runnable_ranges(range).collect()
15460 }
15461
15462 fn runnable_rows(
15463 project: Entity<Project>,
15464 snapshot: DisplaySnapshot,
15465 prefer_lsp: bool,
15466 runnable_ranges: Vec<RunnableRange>,
15467 cx: AsyncWindowContext,
15468 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15469 cx.spawn(async move |cx| {
15470 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15471 for mut runnable in runnable_ranges {
15472 let Some(tasks) = cx
15473 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15474 .ok()
15475 else {
15476 continue;
15477 };
15478 let mut tasks = tasks.await;
15479
15480 if prefer_lsp {
15481 tasks.retain(|(task_kind, _)| {
15482 !matches!(task_kind, TaskSourceKind::Language { .. })
15483 });
15484 }
15485 if tasks.is_empty() {
15486 continue;
15487 }
15488
15489 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
15490 let Some(row) = snapshot
15491 .buffer_snapshot
15492 .buffer_line_for_row(MultiBufferRow(point.row))
15493 .map(|(_, range)| range.start.row)
15494 else {
15495 continue;
15496 };
15497
15498 let context_range =
15499 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15500 runnable_rows.push((
15501 (runnable.buffer_id, row),
15502 RunnableTasks {
15503 templates: tasks,
15504 offset: snapshot
15505 .buffer_snapshot
15506 .anchor_before(runnable.run_range.start),
15507 context_range,
15508 column: point.column,
15509 extra_variables: runnable.extra_captures,
15510 },
15511 ));
15512 }
15513 runnable_rows
15514 })
15515 }
15516
15517 fn templates_with_tags(
15518 project: &Entity<Project>,
15519 runnable: &mut Runnable,
15520 cx: &mut App,
15521 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15522 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15523 let (worktree_id, file) = project
15524 .buffer_for_id(runnable.buffer, cx)
15525 .and_then(|buffer| buffer.read(cx).file())
15526 .map(|file| (file.worktree_id(cx), file.clone()))
15527 .unzip();
15528
15529 (
15530 project.task_store().read(cx).task_inventory().cloned(),
15531 worktree_id,
15532 file,
15533 )
15534 });
15535
15536 let tags = mem::take(&mut runnable.tags);
15537 let language = runnable.language.clone();
15538 cx.spawn(async move |cx| {
15539 let mut templates_with_tags = Vec::new();
15540 if let Some(inventory) = inventory {
15541 for RunnableTag(tag) in tags {
15542 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15543 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15544 }) else {
15545 return templates_with_tags;
15546 };
15547 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15548 move |(_, template)| {
15549 template.tags.iter().any(|source_tag| source_tag == &tag)
15550 },
15551 ));
15552 }
15553 }
15554 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15555
15556 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15557 // Strongest source wins; if we have worktree tag binding, prefer that to
15558 // global and language bindings;
15559 // if we have a global binding, prefer that to language binding.
15560 let first_mismatch = templates_with_tags
15561 .iter()
15562 .position(|(tag_source, _)| tag_source != leading_tag_source);
15563 if let Some(index) = first_mismatch {
15564 templates_with_tags.truncate(index);
15565 }
15566 }
15567
15568 templates_with_tags
15569 })
15570 }
15571
15572 pub fn move_to_enclosing_bracket(
15573 &mut self,
15574 _: &MoveToEnclosingBracket,
15575 window: &mut Window,
15576 cx: &mut Context<Self>,
15577 ) {
15578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15579 self.change_selections(Default::default(), window, cx, |s| {
15580 s.move_offsets_with(|snapshot, selection| {
15581 let Some(enclosing_bracket_ranges) =
15582 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15583 else {
15584 return;
15585 };
15586
15587 let mut best_length = usize::MAX;
15588 let mut best_inside = false;
15589 let mut best_in_bracket_range = false;
15590 let mut best_destination = None;
15591 for (open, close) in enclosing_bracket_ranges {
15592 let close = close.to_inclusive();
15593 let length = close.end() - open.start;
15594 let inside = selection.start >= open.end && selection.end <= *close.start();
15595 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15596 || close.contains(&selection.head());
15597
15598 // If best is next to a bracket and current isn't, skip
15599 if !in_bracket_range && best_in_bracket_range {
15600 continue;
15601 }
15602
15603 // Prefer smaller lengths unless best is inside and current isn't
15604 if length > best_length && (best_inside || !inside) {
15605 continue;
15606 }
15607
15608 best_length = length;
15609 best_inside = inside;
15610 best_in_bracket_range = in_bracket_range;
15611 best_destination = Some(
15612 if close.contains(&selection.start) && close.contains(&selection.end) {
15613 if inside { open.end } else { open.start }
15614 } else if inside {
15615 *close.start()
15616 } else {
15617 *close.end()
15618 },
15619 );
15620 }
15621
15622 if let Some(destination) = best_destination {
15623 selection.collapse_to(destination, SelectionGoal::None);
15624 }
15625 })
15626 });
15627 }
15628
15629 pub fn undo_selection(
15630 &mut self,
15631 _: &UndoSelection,
15632 window: &mut Window,
15633 cx: &mut Context<Self>,
15634 ) {
15635 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15636 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15637 self.selection_history.mode = SelectionHistoryMode::Undoing;
15638 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15639 this.end_selection(window, cx);
15640 this.change_selections(
15641 SelectionEffects::scroll(Autoscroll::newest()),
15642 window,
15643 cx,
15644 |s| s.select_anchors(entry.selections.to_vec()),
15645 );
15646 });
15647 self.selection_history.mode = SelectionHistoryMode::Normal;
15648
15649 self.select_next_state = entry.select_next_state;
15650 self.select_prev_state = entry.select_prev_state;
15651 self.add_selections_state = entry.add_selections_state;
15652 }
15653 }
15654
15655 pub fn redo_selection(
15656 &mut self,
15657 _: &RedoSelection,
15658 window: &mut Window,
15659 cx: &mut Context<Self>,
15660 ) {
15661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15662 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15663 self.selection_history.mode = SelectionHistoryMode::Redoing;
15664 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15665 this.end_selection(window, cx);
15666 this.change_selections(
15667 SelectionEffects::scroll(Autoscroll::newest()),
15668 window,
15669 cx,
15670 |s| s.select_anchors(entry.selections.to_vec()),
15671 );
15672 });
15673 self.selection_history.mode = SelectionHistoryMode::Normal;
15674
15675 self.select_next_state = entry.select_next_state;
15676 self.select_prev_state = entry.select_prev_state;
15677 self.add_selections_state = entry.add_selections_state;
15678 }
15679 }
15680
15681 pub fn expand_excerpts(
15682 &mut self,
15683 action: &ExpandExcerpts,
15684 _: &mut Window,
15685 cx: &mut Context<Self>,
15686 ) {
15687 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15688 }
15689
15690 pub fn expand_excerpts_down(
15691 &mut self,
15692 action: &ExpandExcerptsDown,
15693 _: &mut Window,
15694 cx: &mut Context<Self>,
15695 ) {
15696 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15697 }
15698
15699 pub fn expand_excerpts_up(
15700 &mut self,
15701 action: &ExpandExcerptsUp,
15702 _: &mut Window,
15703 cx: &mut Context<Self>,
15704 ) {
15705 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15706 }
15707
15708 pub fn expand_excerpts_for_direction(
15709 &mut self,
15710 lines: u32,
15711 direction: ExpandExcerptDirection,
15712
15713 cx: &mut Context<Self>,
15714 ) {
15715 let selections = self.selections.disjoint_anchors_arc();
15716
15717 let lines = if lines == 0 {
15718 EditorSettings::get_global(cx).expand_excerpt_lines
15719 } else {
15720 lines
15721 };
15722
15723 self.buffer.update(cx, |buffer, cx| {
15724 let snapshot = buffer.snapshot(cx);
15725 let mut excerpt_ids = selections
15726 .iter()
15727 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15728 .collect::<Vec<_>>();
15729 excerpt_ids.sort();
15730 excerpt_ids.dedup();
15731 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15732 })
15733 }
15734
15735 pub fn expand_excerpt(
15736 &mut self,
15737 excerpt: ExcerptId,
15738 direction: ExpandExcerptDirection,
15739 window: &mut Window,
15740 cx: &mut Context<Self>,
15741 ) {
15742 let current_scroll_position = self.scroll_position(cx);
15743 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15744 let mut should_scroll_up = false;
15745
15746 if direction == ExpandExcerptDirection::Down {
15747 let multi_buffer = self.buffer.read(cx);
15748 let snapshot = multi_buffer.snapshot(cx);
15749 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15750 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15751 && let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt)
15752 {
15753 let buffer_snapshot = buffer.read(cx).snapshot();
15754 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15755 let last_row = buffer_snapshot.max_point().row;
15756 let lines_below = last_row.saturating_sub(excerpt_end_row);
15757 should_scroll_up = lines_below >= lines_to_expand;
15758 }
15759 }
15760
15761 self.buffer.update(cx, |buffer, cx| {
15762 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15763 });
15764
15765 if should_scroll_up {
15766 let new_scroll_position =
15767 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
15768 self.set_scroll_position(new_scroll_position, window, cx);
15769 }
15770 }
15771
15772 pub fn go_to_singleton_buffer_point(
15773 &mut self,
15774 point: Point,
15775 window: &mut Window,
15776 cx: &mut Context<Self>,
15777 ) {
15778 self.go_to_singleton_buffer_range(point..point, window, cx);
15779 }
15780
15781 pub fn go_to_singleton_buffer_range(
15782 &mut self,
15783 range: Range<Point>,
15784 window: &mut Window,
15785 cx: &mut Context<Self>,
15786 ) {
15787 let multibuffer = self.buffer().read(cx);
15788 let Some(buffer) = multibuffer.as_singleton() else {
15789 return;
15790 };
15791 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15792 return;
15793 };
15794 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15795 return;
15796 };
15797 self.change_selections(
15798 SelectionEffects::default().nav_history(true),
15799 window,
15800 cx,
15801 |s| s.select_anchor_ranges([start..end]),
15802 );
15803 }
15804
15805 pub fn go_to_diagnostic(
15806 &mut self,
15807 action: &GoToDiagnostic,
15808 window: &mut Window,
15809 cx: &mut Context<Self>,
15810 ) {
15811 if !self.diagnostics_enabled() {
15812 return;
15813 }
15814 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15815 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15816 }
15817
15818 pub fn go_to_prev_diagnostic(
15819 &mut self,
15820 action: &GoToPreviousDiagnostic,
15821 window: &mut Window,
15822 cx: &mut Context<Self>,
15823 ) {
15824 if !self.diagnostics_enabled() {
15825 return;
15826 }
15827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15828 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15829 }
15830
15831 pub fn go_to_diagnostic_impl(
15832 &mut self,
15833 direction: Direction,
15834 severity: GoToDiagnosticSeverityFilter,
15835 window: &mut Window,
15836 cx: &mut Context<Self>,
15837 ) {
15838 let buffer = self.buffer.read(cx).snapshot(cx);
15839 let selection = self.selections.newest::<usize>(cx);
15840
15841 let mut active_group_id = None;
15842 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15843 && active_group.active_range.start.to_offset(&buffer) == selection.start
15844 {
15845 active_group_id = Some(active_group.group_id);
15846 }
15847
15848 fn filtered(
15849 snapshot: EditorSnapshot,
15850 severity: GoToDiagnosticSeverityFilter,
15851 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
15852 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
15853 diagnostics
15854 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15855 .filter(|entry| entry.range.start != entry.range.end)
15856 .filter(|entry| !entry.diagnostic.is_unnecessary)
15857 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15858 }
15859
15860 let snapshot = self.snapshot(window, cx);
15861 let before = filtered(
15862 snapshot.clone(),
15863 severity,
15864 buffer
15865 .diagnostics_in_range(0..selection.start)
15866 .filter(|entry| entry.range.start <= selection.start),
15867 );
15868 let after = filtered(
15869 snapshot,
15870 severity,
15871 buffer
15872 .diagnostics_in_range(selection.start..buffer.len())
15873 .filter(|entry| entry.range.start >= selection.start),
15874 );
15875
15876 let mut found: Option<DiagnosticEntry<usize>> = None;
15877 if direction == Direction::Prev {
15878 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15879 {
15880 for diagnostic in prev_diagnostics.into_iter().rev() {
15881 if diagnostic.range.start != selection.start
15882 || active_group_id
15883 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15884 {
15885 found = Some(diagnostic);
15886 break 'outer;
15887 }
15888 }
15889 }
15890 } else {
15891 for diagnostic in after.chain(before) {
15892 if diagnostic.range.start != selection.start
15893 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
15894 {
15895 found = Some(diagnostic);
15896 break;
15897 }
15898 }
15899 }
15900 let Some(next_diagnostic) = found else {
15901 return;
15902 };
15903
15904 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
15905 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
15906 return;
15907 };
15908 self.change_selections(Default::default(), window, cx, |s| {
15909 s.select_ranges(vec![
15910 next_diagnostic.range.start..next_diagnostic.range.start,
15911 ])
15912 });
15913 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
15914 self.refresh_edit_prediction(false, true, window, cx);
15915 }
15916
15917 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
15918 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15919 let snapshot = self.snapshot(window, cx);
15920 let selection = self.selections.newest::<Point>(cx);
15921 self.go_to_hunk_before_or_after_position(
15922 &snapshot,
15923 selection.head(),
15924 Direction::Next,
15925 window,
15926 cx,
15927 );
15928 }
15929
15930 pub fn go_to_hunk_before_or_after_position(
15931 &mut self,
15932 snapshot: &EditorSnapshot,
15933 position: Point,
15934 direction: Direction,
15935 window: &mut Window,
15936 cx: &mut Context<Editor>,
15937 ) {
15938 let row = if direction == Direction::Next {
15939 self.hunk_after_position(snapshot, position)
15940 .map(|hunk| hunk.row_range.start)
15941 } else {
15942 self.hunk_before_position(snapshot, position)
15943 };
15944
15945 if let Some(row) = row {
15946 let destination = Point::new(row.0, 0);
15947 let autoscroll = Autoscroll::center();
15948
15949 self.unfold_ranges(&[destination..destination], false, false, cx);
15950 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
15951 s.select_ranges([destination..destination]);
15952 });
15953 }
15954 }
15955
15956 fn hunk_after_position(
15957 &mut self,
15958 snapshot: &EditorSnapshot,
15959 position: Point,
15960 ) -> Option<MultiBufferDiffHunk> {
15961 snapshot
15962 .buffer_snapshot
15963 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
15964 .find(|hunk| hunk.row_range.start.0 > position.row)
15965 .or_else(|| {
15966 snapshot
15967 .buffer_snapshot
15968 .diff_hunks_in_range(Point::zero()..position)
15969 .find(|hunk| hunk.row_range.end.0 < position.row)
15970 })
15971 }
15972
15973 fn go_to_prev_hunk(
15974 &mut self,
15975 _: &GoToPreviousHunk,
15976 window: &mut Window,
15977 cx: &mut Context<Self>,
15978 ) {
15979 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15980 let snapshot = self.snapshot(window, cx);
15981 let selection = self.selections.newest::<Point>(cx);
15982 self.go_to_hunk_before_or_after_position(
15983 &snapshot,
15984 selection.head(),
15985 Direction::Prev,
15986 window,
15987 cx,
15988 );
15989 }
15990
15991 fn hunk_before_position(
15992 &mut self,
15993 snapshot: &EditorSnapshot,
15994 position: Point,
15995 ) -> Option<MultiBufferRow> {
15996 snapshot
15997 .buffer_snapshot
15998 .diff_hunk_before(position)
15999 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
16000 }
16001
16002 fn go_to_next_change(
16003 &mut self,
16004 _: &GoToNextChange,
16005 window: &mut Window,
16006 cx: &mut Context<Self>,
16007 ) {
16008 if let Some(selections) = self
16009 .change_list
16010 .next_change(1, Direction::Next)
16011 .map(|s| s.to_vec())
16012 {
16013 self.change_selections(Default::default(), window, cx, |s| {
16014 let map = s.display_map();
16015 s.select_display_ranges(selections.iter().map(|a| {
16016 let point = a.to_display_point(&map);
16017 point..point
16018 }))
16019 })
16020 }
16021 }
16022
16023 fn go_to_previous_change(
16024 &mut self,
16025 _: &GoToPreviousChange,
16026 window: &mut Window,
16027 cx: &mut Context<Self>,
16028 ) {
16029 if let Some(selections) = self
16030 .change_list
16031 .next_change(1, Direction::Prev)
16032 .map(|s| s.to_vec())
16033 {
16034 self.change_selections(Default::default(), window, cx, |s| {
16035 let map = s.display_map();
16036 s.select_display_ranges(selections.iter().map(|a| {
16037 let point = a.to_display_point(&map);
16038 point..point
16039 }))
16040 })
16041 }
16042 }
16043
16044 pub fn go_to_next_document_highlight(
16045 &mut self,
16046 _: &GoToNextDocumentHighlight,
16047 window: &mut Window,
16048 cx: &mut Context<Self>,
16049 ) {
16050 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16051 }
16052
16053 pub fn go_to_prev_document_highlight(
16054 &mut self,
16055 _: &GoToPreviousDocumentHighlight,
16056 window: &mut Window,
16057 cx: &mut Context<Self>,
16058 ) {
16059 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16060 }
16061
16062 pub fn go_to_document_highlight_before_or_after_position(
16063 &mut self,
16064 direction: Direction,
16065 window: &mut Window,
16066 cx: &mut Context<Editor>,
16067 ) {
16068 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16069 let snapshot = self.snapshot(window, cx);
16070 let buffer = &snapshot.buffer_snapshot;
16071 let position = self.selections.newest::<Point>(cx).head();
16072 let anchor_position = buffer.anchor_after(position);
16073
16074 // Get all document highlights (both read and write)
16075 let mut all_highlights = Vec::new();
16076
16077 if let Some((_, read_highlights)) = self
16078 .background_highlights
16079 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16080 {
16081 all_highlights.extend(read_highlights.iter());
16082 }
16083
16084 if let Some((_, write_highlights)) = self
16085 .background_highlights
16086 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16087 {
16088 all_highlights.extend(write_highlights.iter());
16089 }
16090
16091 if all_highlights.is_empty() {
16092 return;
16093 }
16094
16095 // Sort highlights by position
16096 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16097
16098 let target_highlight = match direction {
16099 Direction::Next => {
16100 // Find the first highlight after the current position
16101 all_highlights
16102 .iter()
16103 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16104 }
16105 Direction::Prev => {
16106 // Find the last highlight before the current position
16107 all_highlights
16108 .iter()
16109 .rev()
16110 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16111 }
16112 };
16113
16114 if let Some(highlight) = target_highlight {
16115 let destination = highlight.start.to_point(buffer);
16116 let autoscroll = Autoscroll::center();
16117
16118 self.unfold_ranges(&[destination..destination], false, false, cx);
16119 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16120 s.select_ranges([destination..destination]);
16121 });
16122 }
16123 }
16124
16125 fn go_to_line<T: 'static>(
16126 &mut self,
16127 position: Anchor,
16128 highlight_color: Option<Hsla>,
16129 window: &mut Window,
16130 cx: &mut Context<Self>,
16131 ) {
16132 let snapshot = self.snapshot(window, cx).display_snapshot;
16133 let position = position.to_point(&snapshot.buffer_snapshot);
16134 let start = snapshot
16135 .buffer_snapshot
16136 .clip_point(Point::new(position.row, 0), Bias::Left);
16137 let end = start + Point::new(1, 0);
16138 let start = snapshot.buffer_snapshot.anchor_before(start);
16139 let end = snapshot.buffer_snapshot.anchor_before(end);
16140
16141 self.highlight_rows::<T>(
16142 start..end,
16143 highlight_color
16144 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16145 Default::default(),
16146 cx,
16147 );
16148
16149 if self.buffer.read(cx).is_singleton() {
16150 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16151 }
16152 }
16153
16154 pub fn go_to_definition(
16155 &mut self,
16156 _: &GoToDefinition,
16157 window: &mut Window,
16158 cx: &mut Context<Self>,
16159 ) -> Task<Result<Navigated>> {
16160 let definition =
16161 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16162 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16163 cx.spawn_in(window, async move |editor, cx| {
16164 if definition.await? == Navigated::Yes {
16165 return Ok(Navigated::Yes);
16166 }
16167 match fallback_strategy {
16168 GoToDefinitionFallback::None => Ok(Navigated::No),
16169 GoToDefinitionFallback::FindAllReferences => {
16170 match editor.update_in(cx, |editor, window, cx| {
16171 editor.find_all_references(&FindAllReferences, window, cx)
16172 })? {
16173 Some(references) => references.await,
16174 None => Ok(Navigated::No),
16175 }
16176 }
16177 }
16178 })
16179 }
16180
16181 pub fn go_to_declaration(
16182 &mut self,
16183 _: &GoToDeclaration,
16184 window: &mut Window,
16185 cx: &mut Context<Self>,
16186 ) -> Task<Result<Navigated>> {
16187 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16188 }
16189
16190 pub fn go_to_declaration_split(
16191 &mut self,
16192 _: &GoToDeclaration,
16193 window: &mut Window,
16194 cx: &mut Context<Self>,
16195 ) -> Task<Result<Navigated>> {
16196 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16197 }
16198
16199 pub fn go_to_implementation(
16200 &mut self,
16201 _: &GoToImplementation,
16202 window: &mut Window,
16203 cx: &mut Context<Self>,
16204 ) -> Task<Result<Navigated>> {
16205 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16206 }
16207
16208 pub fn go_to_implementation_split(
16209 &mut self,
16210 _: &GoToImplementationSplit,
16211 window: &mut Window,
16212 cx: &mut Context<Self>,
16213 ) -> Task<Result<Navigated>> {
16214 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16215 }
16216
16217 pub fn go_to_type_definition(
16218 &mut self,
16219 _: &GoToTypeDefinition,
16220 window: &mut Window,
16221 cx: &mut Context<Self>,
16222 ) -> Task<Result<Navigated>> {
16223 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16224 }
16225
16226 pub fn go_to_definition_split(
16227 &mut self,
16228 _: &GoToDefinitionSplit,
16229 window: &mut Window,
16230 cx: &mut Context<Self>,
16231 ) -> Task<Result<Navigated>> {
16232 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16233 }
16234
16235 pub fn go_to_type_definition_split(
16236 &mut self,
16237 _: &GoToTypeDefinitionSplit,
16238 window: &mut Window,
16239 cx: &mut Context<Self>,
16240 ) -> Task<Result<Navigated>> {
16241 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16242 }
16243
16244 fn go_to_definition_of_kind(
16245 &mut self,
16246 kind: GotoDefinitionKind,
16247 split: bool,
16248 window: &mut Window,
16249 cx: &mut Context<Self>,
16250 ) -> Task<Result<Navigated>> {
16251 let Some(provider) = self.semantics_provider.clone() else {
16252 return Task::ready(Ok(Navigated::No));
16253 };
16254 let head = self.selections.newest::<usize>(cx).head();
16255 let buffer = self.buffer.read(cx);
16256 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16257 return Task::ready(Ok(Navigated::No));
16258 };
16259 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16260 return Task::ready(Ok(Navigated::No));
16261 };
16262
16263 cx.spawn_in(window, async move |editor, cx| {
16264 let Some(definitions) = definitions.await? else {
16265 return Ok(Navigated::No);
16266 };
16267 let navigated = editor
16268 .update_in(cx, |editor, window, cx| {
16269 editor.navigate_to_hover_links(
16270 Some(kind),
16271 definitions
16272 .into_iter()
16273 .filter(|location| {
16274 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16275 })
16276 .map(HoverLink::Text)
16277 .collect::<Vec<_>>(),
16278 split,
16279 window,
16280 cx,
16281 )
16282 })?
16283 .await?;
16284 anyhow::Ok(navigated)
16285 })
16286 }
16287
16288 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16289 let selection = self.selections.newest_anchor();
16290 let head = selection.head();
16291 let tail = selection.tail();
16292
16293 let Some((buffer, start_position)) =
16294 self.buffer.read(cx).text_anchor_for_position(head, cx)
16295 else {
16296 return;
16297 };
16298
16299 let end_position = if head != tail {
16300 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16301 return;
16302 };
16303 Some(pos)
16304 } else {
16305 None
16306 };
16307
16308 let url_finder = cx.spawn_in(window, async move |editor, cx| {
16309 let url = if let Some(end_pos) = end_position {
16310 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16311 } else {
16312 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16313 };
16314
16315 if let Some(url) = url {
16316 editor.update(cx, |_, cx| {
16317 cx.open_url(&url);
16318 })
16319 } else {
16320 Ok(())
16321 }
16322 });
16323
16324 url_finder.detach();
16325 }
16326
16327 pub fn open_selected_filename(
16328 &mut self,
16329 _: &OpenSelectedFilename,
16330 window: &mut Window,
16331 cx: &mut Context<Self>,
16332 ) {
16333 let Some(workspace) = self.workspace() else {
16334 return;
16335 };
16336
16337 let position = self.selections.newest_anchor().head();
16338
16339 let Some((buffer, buffer_position)) =
16340 self.buffer.read(cx).text_anchor_for_position(position, cx)
16341 else {
16342 return;
16343 };
16344
16345 let project = self.project.clone();
16346
16347 cx.spawn_in(window, async move |_, cx| {
16348 let result = find_file(&buffer, project, buffer_position, cx).await;
16349
16350 if let Some((_, path)) = result {
16351 workspace
16352 .update_in(cx, |workspace, window, cx| {
16353 workspace.open_resolved_path(path, window, cx)
16354 })?
16355 .await?;
16356 }
16357 anyhow::Ok(())
16358 })
16359 .detach();
16360 }
16361
16362 pub(crate) fn navigate_to_hover_links(
16363 &mut self,
16364 kind: Option<GotoDefinitionKind>,
16365 definitions: Vec<HoverLink>,
16366 split: bool,
16367 window: &mut Window,
16368 cx: &mut Context<Editor>,
16369 ) -> Task<Result<Navigated>> {
16370 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16371 let mut first_url_or_file = None;
16372 let definitions: Vec<_> = definitions
16373 .into_iter()
16374 .filter_map(|def| match def {
16375 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16376 HoverLink::InlayHint(lsp_location, server_id) => {
16377 let computation =
16378 self.compute_target_location(lsp_location, server_id, window, cx);
16379 Some(cx.background_spawn(computation))
16380 }
16381 HoverLink::Url(url) => {
16382 first_url_or_file = Some(Either::Left(url));
16383 None
16384 }
16385 HoverLink::File(path) => {
16386 first_url_or_file = Some(Either::Right(path));
16387 None
16388 }
16389 })
16390 .collect();
16391
16392 let workspace = self.workspace();
16393
16394 cx.spawn_in(window, async move |editor, cx| {
16395 let locations: Vec<Location> = future::join_all(definitions)
16396 .await
16397 .into_iter()
16398 .filter_map(|location| location.transpose())
16399 .collect::<Result<_>>()
16400 .context("location tasks")?;
16401 let mut locations = cx.update(|_, cx| {
16402 locations
16403 .into_iter()
16404 .map(|location| {
16405 let buffer = location.buffer.read(cx);
16406 (location.buffer, location.range.to_point(buffer))
16407 })
16408 .into_group_map()
16409 })?;
16410 let mut num_locations = 0;
16411 for ranges in locations.values_mut() {
16412 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16413 ranges.dedup();
16414 num_locations += ranges.len();
16415 }
16416
16417 if num_locations > 1 {
16418 let Some(workspace) = workspace else {
16419 return Ok(Navigated::No);
16420 };
16421
16422 let tab_kind = match kind {
16423 Some(GotoDefinitionKind::Implementation) => "Implementations",
16424 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16425 Some(GotoDefinitionKind::Declaration) => "Declarations",
16426 Some(GotoDefinitionKind::Type) => "Types",
16427 };
16428 let title = editor
16429 .update_in(cx, |_, _, cx| {
16430 let target = locations
16431 .iter()
16432 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16433 .map(|(buffer, location)| {
16434 buffer
16435 .read(cx)
16436 .text_for_range(location.clone())
16437 .collect::<String>()
16438 })
16439 .filter(|text| !text.contains('\n'))
16440 .unique()
16441 .take(3)
16442 .join(", ");
16443 if target.is_empty() {
16444 tab_kind.to_owned()
16445 } else {
16446 format!("{tab_kind} for {target}")
16447 }
16448 })
16449 .context("buffer title")?;
16450
16451 let opened = workspace
16452 .update_in(cx, |workspace, window, cx| {
16453 Self::open_locations_in_multibuffer(
16454 workspace,
16455 locations,
16456 title,
16457 split,
16458 MultibufferSelectionMode::First,
16459 window,
16460 cx,
16461 )
16462 })
16463 .is_ok();
16464
16465 anyhow::Ok(Navigated::from_bool(opened))
16466 } else if num_locations == 0 {
16467 // If there is one url or file, open it directly
16468 match first_url_or_file {
16469 Some(Either::Left(url)) => {
16470 cx.update(|_, cx| cx.open_url(&url))?;
16471 Ok(Navigated::Yes)
16472 }
16473 Some(Either::Right(path)) => {
16474 let Some(workspace) = workspace else {
16475 return Ok(Navigated::No);
16476 };
16477
16478 workspace
16479 .update_in(cx, |workspace, window, cx| {
16480 workspace.open_resolved_path(path, window, cx)
16481 })?
16482 .await?;
16483 Ok(Navigated::Yes)
16484 }
16485 None => Ok(Navigated::No),
16486 }
16487 } else {
16488 let Some(workspace) = workspace else {
16489 return Ok(Navigated::No);
16490 };
16491
16492 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16493 let target_range = target_ranges.first().unwrap().clone();
16494
16495 editor.update_in(cx, |editor, window, cx| {
16496 let range = target_range.to_point(target_buffer.read(cx));
16497 let range = editor.range_for_match(&range);
16498 let range = collapse_multiline_range(range);
16499
16500 if !split
16501 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16502 {
16503 editor.go_to_singleton_buffer_range(range, window, cx);
16504 } else {
16505 let pane = workspace.read(cx).active_pane().clone();
16506 window.defer(cx, move |window, cx| {
16507 let target_editor: Entity<Self> =
16508 workspace.update(cx, |workspace, cx| {
16509 let pane = if split {
16510 workspace.adjacent_pane(window, cx)
16511 } else {
16512 workspace.active_pane().clone()
16513 };
16514
16515 workspace.open_project_item(
16516 pane,
16517 target_buffer.clone(),
16518 true,
16519 true,
16520 window,
16521 cx,
16522 )
16523 });
16524 target_editor.update(cx, |target_editor, cx| {
16525 // When selecting a definition in a different buffer, disable the nav history
16526 // to avoid creating a history entry at the previous cursor location.
16527 pane.update(cx, |pane, _| pane.disable_history());
16528 target_editor.go_to_singleton_buffer_range(range, window, cx);
16529 pane.update(cx, |pane, _| pane.enable_history());
16530 });
16531 });
16532 }
16533 Navigated::Yes
16534 })
16535 }
16536 })
16537 }
16538
16539 fn compute_target_location(
16540 &self,
16541 lsp_location: lsp::Location,
16542 server_id: LanguageServerId,
16543 window: &mut Window,
16544 cx: &mut Context<Self>,
16545 ) -> Task<anyhow::Result<Option<Location>>> {
16546 let Some(project) = self.project.clone() else {
16547 return Task::ready(Ok(None));
16548 };
16549
16550 cx.spawn_in(window, async move |editor, cx| {
16551 let location_task = editor.update(cx, |_, cx| {
16552 project.update(cx, |project, cx| {
16553 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16554 })
16555 })?;
16556 let location = Some({
16557 let target_buffer_handle = location_task.await.context("open local buffer")?;
16558 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16559 let target_start = target_buffer
16560 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16561 let target_end = target_buffer
16562 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16563 target_buffer.anchor_after(target_start)
16564 ..target_buffer.anchor_before(target_end)
16565 })?;
16566 Location {
16567 buffer: target_buffer_handle,
16568 range,
16569 }
16570 });
16571 Ok(location)
16572 })
16573 }
16574
16575 pub fn find_all_references(
16576 &mut self,
16577 _: &FindAllReferences,
16578 window: &mut Window,
16579 cx: &mut Context<Self>,
16580 ) -> Option<Task<Result<Navigated>>> {
16581 let selection = self.selections.newest::<usize>(cx);
16582 let multi_buffer = self.buffer.read(cx);
16583 let head = selection.head();
16584
16585 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16586 let head_anchor = multi_buffer_snapshot.anchor_at(
16587 head,
16588 if head < selection.tail() {
16589 Bias::Right
16590 } else {
16591 Bias::Left
16592 },
16593 );
16594
16595 match self
16596 .find_all_references_task_sources
16597 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16598 {
16599 Ok(_) => {
16600 log::info!(
16601 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16602 );
16603 return None;
16604 }
16605 Err(i) => {
16606 self.find_all_references_task_sources.insert(i, head_anchor);
16607 }
16608 }
16609
16610 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16611 let workspace = self.workspace()?;
16612 let project = workspace.read(cx).project().clone();
16613 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16614 Some(cx.spawn_in(window, async move |editor, cx| {
16615 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16616 if let Ok(i) = editor
16617 .find_all_references_task_sources
16618 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16619 {
16620 editor.find_all_references_task_sources.remove(i);
16621 }
16622 });
16623
16624 let Some(locations) = references.await? else {
16625 return anyhow::Ok(Navigated::No);
16626 };
16627 let mut locations = cx.update(|_, cx| {
16628 locations
16629 .into_iter()
16630 .map(|location| {
16631 let buffer = location.buffer.read(cx);
16632 (location.buffer, location.range.to_point(buffer))
16633 })
16634 .into_group_map()
16635 })?;
16636 if locations.is_empty() {
16637 return anyhow::Ok(Navigated::No);
16638 }
16639 for ranges in locations.values_mut() {
16640 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16641 ranges.dedup();
16642 }
16643
16644 workspace.update_in(cx, |workspace, window, cx| {
16645 let target = locations
16646 .iter()
16647 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16648 .map(|(buffer, location)| {
16649 buffer
16650 .read(cx)
16651 .text_for_range(location.clone())
16652 .collect::<String>()
16653 })
16654 .filter(|text| !text.contains('\n'))
16655 .unique()
16656 .take(3)
16657 .join(", ");
16658 let title = if target.is_empty() {
16659 "References".to_owned()
16660 } else {
16661 format!("References to {target}")
16662 };
16663 Self::open_locations_in_multibuffer(
16664 workspace,
16665 locations,
16666 title,
16667 false,
16668 MultibufferSelectionMode::First,
16669 window,
16670 cx,
16671 );
16672 Navigated::Yes
16673 })
16674 }))
16675 }
16676
16677 /// Opens a multibuffer with the given project locations in it
16678 pub fn open_locations_in_multibuffer(
16679 workspace: &mut Workspace,
16680 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16681 title: String,
16682 split: bool,
16683 multibuffer_selection_mode: MultibufferSelectionMode,
16684 window: &mut Window,
16685 cx: &mut Context<Workspace>,
16686 ) {
16687 if locations.is_empty() {
16688 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16689 return;
16690 }
16691
16692 let capability = workspace.project().read(cx).capability();
16693 let mut ranges = <Vec<Range<Anchor>>>::new();
16694
16695 // a key to find existing multibuffer editors with the same set of locations
16696 // to prevent us from opening more and more multibuffer tabs for searches and the like
16697 let mut key = (title.clone(), vec![]);
16698 let excerpt_buffer = cx.new(|cx| {
16699 let key = &mut key.1;
16700 let mut multibuffer = MultiBuffer::new(capability);
16701 for (buffer, mut ranges_for_buffer) in locations {
16702 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16703 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16704 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16705 PathKey::for_buffer(&buffer, cx),
16706 buffer.clone(),
16707 ranges_for_buffer,
16708 multibuffer_context_lines(cx),
16709 cx,
16710 );
16711 ranges.extend(new_ranges)
16712 }
16713
16714 multibuffer.with_title(title)
16715 });
16716 let existing = workspace.active_pane().update(cx, |pane, cx| {
16717 pane.items()
16718 .filter_map(|item| item.downcast::<Editor>())
16719 .find(|editor| {
16720 editor
16721 .read(cx)
16722 .lookup_key
16723 .as_ref()
16724 .and_then(|it| {
16725 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16726 })
16727 .is_some_and(|it| *it == key)
16728 })
16729 });
16730 let editor = existing.unwrap_or_else(|| {
16731 cx.new(|cx| {
16732 let mut editor = Editor::for_multibuffer(
16733 excerpt_buffer,
16734 Some(workspace.project().clone()),
16735 window,
16736 cx,
16737 );
16738 editor.lookup_key = Some(Box::new(key));
16739 editor
16740 })
16741 });
16742 editor.update(cx, |editor, cx| {
16743 match multibuffer_selection_mode {
16744 MultibufferSelectionMode::First => {
16745 if let Some(first_range) = ranges.first() {
16746 editor.change_selections(
16747 SelectionEffects::no_scroll(),
16748 window,
16749 cx,
16750 |selections| {
16751 selections.clear_disjoint();
16752 selections
16753 .select_anchor_ranges(std::iter::once(first_range.clone()));
16754 },
16755 );
16756 }
16757 editor.highlight_background::<Self>(
16758 &ranges,
16759 |theme| theme.colors().editor_highlighted_line_background,
16760 cx,
16761 );
16762 }
16763 MultibufferSelectionMode::All => {
16764 editor.change_selections(
16765 SelectionEffects::no_scroll(),
16766 window,
16767 cx,
16768 |selections| {
16769 selections.clear_disjoint();
16770 selections.select_anchor_ranges(ranges);
16771 },
16772 );
16773 }
16774 }
16775 editor.register_buffers_with_language_servers(cx);
16776 });
16777
16778 let item = Box::new(editor);
16779 let item_id = item.item_id();
16780
16781 if split {
16782 workspace.split_item(SplitDirection::Right, item, window, cx);
16783 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16784 let (preview_item_id, preview_item_idx) =
16785 workspace.active_pane().read_with(cx, |pane, _| {
16786 (pane.preview_item_id(), pane.preview_item_idx())
16787 });
16788
16789 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16790
16791 if let Some(preview_item_id) = preview_item_id {
16792 workspace.active_pane().update(cx, |pane, cx| {
16793 pane.remove_item(preview_item_id, false, false, window, cx);
16794 });
16795 }
16796 } else {
16797 workspace.add_item_to_active_pane(item, None, true, window, cx);
16798 }
16799 workspace.active_pane().update(cx, |pane, cx| {
16800 pane.set_preview_item_id(Some(item_id), cx);
16801 });
16802 }
16803
16804 pub fn rename(
16805 &mut self,
16806 _: &Rename,
16807 window: &mut Window,
16808 cx: &mut Context<Self>,
16809 ) -> Option<Task<Result<()>>> {
16810 use language::ToOffset as _;
16811
16812 let provider = self.semantics_provider.clone()?;
16813 let selection = self.selections.newest_anchor().clone();
16814 let (cursor_buffer, cursor_buffer_position) = self
16815 .buffer
16816 .read(cx)
16817 .text_anchor_for_position(selection.head(), cx)?;
16818 let (tail_buffer, cursor_buffer_position_end) = self
16819 .buffer
16820 .read(cx)
16821 .text_anchor_for_position(selection.tail(), cx)?;
16822 if tail_buffer != cursor_buffer {
16823 return None;
16824 }
16825
16826 let snapshot = cursor_buffer.read(cx).snapshot();
16827 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16828 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16829 let prepare_rename = provider
16830 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16831 .unwrap_or_else(|| Task::ready(Ok(None)));
16832 drop(snapshot);
16833
16834 Some(cx.spawn_in(window, async move |this, cx| {
16835 let rename_range = if let Some(range) = prepare_rename.await? {
16836 Some(range)
16837 } else {
16838 this.update(cx, |this, cx| {
16839 let buffer = this.buffer.read(cx).snapshot(cx);
16840 let mut buffer_highlights = this
16841 .document_highlights_for_position(selection.head(), &buffer)
16842 .filter(|highlight| {
16843 highlight.start.excerpt_id == selection.head().excerpt_id
16844 && highlight.end.excerpt_id == selection.head().excerpt_id
16845 });
16846 buffer_highlights
16847 .next()
16848 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16849 })?
16850 };
16851 if let Some(rename_range) = rename_range {
16852 this.update_in(cx, |this, window, cx| {
16853 let snapshot = cursor_buffer.read(cx).snapshot();
16854 let rename_buffer_range = rename_range.to_offset(&snapshot);
16855 let cursor_offset_in_rename_range =
16856 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16857 let cursor_offset_in_rename_range_end =
16858 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16859
16860 this.take_rename(false, window, cx);
16861 let buffer = this.buffer.read(cx).read(cx);
16862 let cursor_offset = selection.head().to_offset(&buffer);
16863 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16864 let rename_end = rename_start + rename_buffer_range.len();
16865 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16866 let mut old_highlight_id = None;
16867 let old_name: Arc<str> = buffer
16868 .chunks(rename_start..rename_end, true)
16869 .map(|chunk| {
16870 if old_highlight_id.is_none() {
16871 old_highlight_id = chunk.syntax_highlight_id;
16872 }
16873 chunk.text
16874 })
16875 .collect::<String>()
16876 .into();
16877
16878 drop(buffer);
16879
16880 // Position the selection in the rename editor so that it matches the current selection.
16881 this.show_local_selections = false;
16882 let rename_editor = cx.new(|cx| {
16883 let mut editor = Editor::single_line(window, cx);
16884 editor.buffer.update(cx, |buffer, cx| {
16885 buffer.edit([(0..0, old_name.clone())], None, cx)
16886 });
16887 let rename_selection_range = match cursor_offset_in_rename_range
16888 .cmp(&cursor_offset_in_rename_range_end)
16889 {
16890 Ordering::Equal => {
16891 editor.select_all(&SelectAll, window, cx);
16892 return editor;
16893 }
16894 Ordering::Less => {
16895 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
16896 }
16897 Ordering::Greater => {
16898 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
16899 }
16900 };
16901 if rename_selection_range.end > old_name.len() {
16902 editor.select_all(&SelectAll, window, cx);
16903 } else {
16904 editor.change_selections(Default::default(), window, cx, |s| {
16905 s.select_ranges([rename_selection_range]);
16906 });
16907 }
16908 editor
16909 });
16910 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
16911 if e == &EditorEvent::Focused {
16912 cx.emit(EditorEvent::FocusedIn)
16913 }
16914 })
16915 .detach();
16916
16917 let write_highlights =
16918 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
16919 let read_highlights =
16920 this.clear_background_highlights::<DocumentHighlightRead>(cx);
16921 let ranges = write_highlights
16922 .iter()
16923 .flat_map(|(_, ranges)| ranges.iter())
16924 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
16925 .cloned()
16926 .collect();
16927
16928 this.highlight_text::<Rename>(
16929 ranges,
16930 HighlightStyle {
16931 fade_out: Some(0.6),
16932 ..Default::default()
16933 },
16934 cx,
16935 );
16936 let rename_focus_handle = rename_editor.focus_handle(cx);
16937 window.focus(&rename_focus_handle);
16938 let block_id = this.insert_blocks(
16939 [BlockProperties {
16940 style: BlockStyle::Flex,
16941 placement: BlockPlacement::Below(range.start),
16942 height: Some(1),
16943 render: Arc::new({
16944 let rename_editor = rename_editor.clone();
16945 move |cx: &mut BlockContext| {
16946 let mut text_style = cx.editor_style.text.clone();
16947 if let Some(highlight_style) = old_highlight_id
16948 .and_then(|h| h.style(&cx.editor_style.syntax))
16949 {
16950 text_style = text_style.highlight(highlight_style);
16951 }
16952 div()
16953 .block_mouse_except_scroll()
16954 .pl(cx.anchor_x)
16955 .child(EditorElement::new(
16956 &rename_editor,
16957 EditorStyle {
16958 background: cx.theme().system().transparent,
16959 local_player: cx.editor_style.local_player,
16960 text: text_style,
16961 scrollbar_width: cx.editor_style.scrollbar_width,
16962 syntax: cx.editor_style.syntax.clone(),
16963 status: cx.editor_style.status.clone(),
16964 inlay_hints_style: HighlightStyle {
16965 font_weight: Some(FontWeight::BOLD),
16966 ..make_inlay_hints_style(cx.app)
16967 },
16968 edit_prediction_styles: make_suggestion_styles(
16969 cx.app,
16970 ),
16971 ..EditorStyle::default()
16972 },
16973 ))
16974 .into_any_element()
16975 }
16976 }),
16977 priority: 0,
16978 }],
16979 Some(Autoscroll::fit()),
16980 cx,
16981 )[0];
16982 this.pending_rename = Some(RenameState {
16983 range,
16984 old_name,
16985 editor: rename_editor,
16986 block_id,
16987 });
16988 })?;
16989 }
16990
16991 Ok(())
16992 }))
16993 }
16994
16995 pub fn confirm_rename(
16996 &mut self,
16997 _: &ConfirmRename,
16998 window: &mut Window,
16999 cx: &mut Context<Self>,
17000 ) -> Option<Task<Result<()>>> {
17001 let rename = self.take_rename(false, window, cx)?;
17002 let workspace = self.workspace()?.downgrade();
17003 let (buffer, start) = self
17004 .buffer
17005 .read(cx)
17006 .text_anchor_for_position(rename.range.start, cx)?;
17007 let (end_buffer, _) = self
17008 .buffer
17009 .read(cx)
17010 .text_anchor_for_position(rename.range.end, cx)?;
17011 if buffer != end_buffer {
17012 return None;
17013 }
17014
17015 let old_name = rename.old_name;
17016 let new_name = rename.editor.read(cx).text(cx);
17017
17018 let rename = self.semantics_provider.as_ref()?.perform_rename(
17019 &buffer,
17020 start,
17021 new_name.clone(),
17022 cx,
17023 )?;
17024
17025 Some(cx.spawn_in(window, async move |editor, cx| {
17026 let project_transaction = rename.await?;
17027 Self::open_project_transaction(
17028 &editor,
17029 workspace,
17030 project_transaction,
17031 format!("Rename: {} → {}", old_name, new_name),
17032 cx,
17033 )
17034 .await?;
17035
17036 editor.update(cx, |editor, cx| {
17037 editor.refresh_document_highlights(cx);
17038 })?;
17039 Ok(())
17040 }))
17041 }
17042
17043 fn take_rename(
17044 &mut self,
17045 moving_cursor: bool,
17046 window: &mut Window,
17047 cx: &mut Context<Self>,
17048 ) -> Option<RenameState> {
17049 let rename = self.pending_rename.take()?;
17050 if rename.editor.focus_handle(cx).is_focused(window) {
17051 window.focus(&self.focus_handle);
17052 }
17053
17054 self.remove_blocks(
17055 [rename.block_id].into_iter().collect(),
17056 Some(Autoscroll::fit()),
17057 cx,
17058 );
17059 self.clear_highlights::<Rename>(cx);
17060 self.show_local_selections = true;
17061
17062 if moving_cursor {
17063 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17064 editor.selections.newest::<usize>(cx).head()
17065 });
17066
17067 // Update the selection to match the position of the selection inside
17068 // the rename editor.
17069 let snapshot = self.buffer.read(cx).read(cx);
17070 let rename_range = rename.range.to_offset(&snapshot);
17071 let cursor_in_editor = snapshot
17072 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17073 .min(rename_range.end);
17074 drop(snapshot);
17075
17076 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17077 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17078 });
17079 } else {
17080 self.refresh_document_highlights(cx);
17081 }
17082
17083 Some(rename)
17084 }
17085
17086 pub fn pending_rename(&self) -> Option<&RenameState> {
17087 self.pending_rename.as_ref()
17088 }
17089
17090 fn format(
17091 &mut self,
17092 _: &Format,
17093 window: &mut Window,
17094 cx: &mut Context<Self>,
17095 ) -> Option<Task<Result<()>>> {
17096 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17097
17098 let project = match &self.project {
17099 Some(project) => project.clone(),
17100 None => return None,
17101 };
17102
17103 Some(self.perform_format(
17104 project,
17105 FormatTrigger::Manual,
17106 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17107 window,
17108 cx,
17109 ))
17110 }
17111
17112 fn format_selections(
17113 &mut self,
17114 _: &FormatSelections,
17115 window: &mut Window,
17116 cx: &mut Context<Self>,
17117 ) -> Option<Task<Result<()>>> {
17118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17119
17120 let project = match &self.project {
17121 Some(project) => project.clone(),
17122 None => return None,
17123 };
17124
17125 let ranges = self
17126 .selections
17127 .all_adjusted(cx)
17128 .into_iter()
17129 .map(|selection| selection.range())
17130 .collect_vec();
17131
17132 Some(self.perform_format(
17133 project,
17134 FormatTrigger::Manual,
17135 FormatTarget::Ranges(ranges),
17136 window,
17137 cx,
17138 ))
17139 }
17140
17141 fn perform_format(
17142 &mut self,
17143 project: Entity<Project>,
17144 trigger: FormatTrigger,
17145 target: FormatTarget,
17146 window: &mut Window,
17147 cx: &mut Context<Self>,
17148 ) -> Task<Result<()>> {
17149 let buffer = self.buffer.clone();
17150 let (buffers, target) = match target {
17151 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17152 FormatTarget::Ranges(selection_ranges) => {
17153 let multi_buffer = buffer.read(cx);
17154 let snapshot = multi_buffer.read(cx);
17155 let mut buffers = HashSet::default();
17156 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17157 BTreeMap::new();
17158 for selection_range in selection_ranges {
17159 for (buffer, buffer_range, _) in
17160 snapshot.range_to_buffer_ranges(selection_range)
17161 {
17162 let buffer_id = buffer.remote_id();
17163 let start = buffer.anchor_before(buffer_range.start);
17164 let end = buffer.anchor_after(buffer_range.end);
17165 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17166 buffer_id_to_ranges
17167 .entry(buffer_id)
17168 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17169 .or_insert_with(|| vec![start..end]);
17170 }
17171 }
17172 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17173 }
17174 };
17175
17176 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17177 let selections_prev = transaction_id_prev
17178 .and_then(|transaction_id_prev| {
17179 // default to selections as they were after the last edit, if we have them,
17180 // instead of how they are now.
17181 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17182 // will take you back to where you made the last edit, instead of staying where you scrolled
17183 self.selection_history
17184 .transaction(transaction_id_prev)
17185 .map(|t| t.0.clone())
17186 })
17187 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17188
17189 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17190 let format = project.update(cx, |project, cx| {
17191 project.format(buffers, target, true, trigger, cx)
17192 });
17193
17194 cx.spawn_in(window, async move |editor, cx| {
17195 let transaction = futures::select_biased! {
17196 transaction = format.log_err().fuse() => transaction,
17197 () = timeout => {
17198 log::warn!("timed out waiting for formatting");
17199 None
17200 }
17201 };
17202
17203 buffer
17204 .update(cx, |buffer, cx| {
17205 if let Some(transaction) = transaction
17206 && !buffer.is_singleton()
17207 {
17208 buffer.push_transaction(&transaction.0, cx);
17209 }
17210 cx.notify();
17211 })
17212 .ok();
17213
17214 if let Some(transaction_id_now) =
17215 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17216 {
17217 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17218 if has_new_transaction {
17219 _ = editor.update(cx, |editor, _| {
17220 editor
17221 .selection_history
17222 .insert_transaction(transaction_id_now, selections_prev);
17223 });
17224 }
17225 }
17226
17227 Ok(())
17228 })
17229 }
17230
17231 fn organize_imports(
17232 &mut self,
17233 _: &OrganizeImports,
17234 window: &mut Window,
17235 cx: &mut Context<Self>,
17236 ) -> Option<Task<Result<()>>> {
17237 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17238 let project = match &self.project {
17239 Some(project) => project.clone(),
17240 None => return None,
17241 };
17242 Some(self.perform_code_action_kind(
17243 project,
17244 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17245 window,
17246 cx,
17247 ))
17248 }
17249
17250 fn perform_code_action_kind(
17251 &mut self,
17252 project: Entity<Project>,
17253 kind: CodeActionKind,
17254 window: &mut Window,
17255 cx: &mut Context<Self>,
17256 ) -> Task<Result<()>> {
17257 let buffer = self.buffer.clone();
17258 let buffers = buffer.read(cx).all_buffers();
17259 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17260 let apply_action = project.update(cx, |project, cx| {
17261 project.apply_code_action_kind(buffers, kind, true, cx)
17262 });
17263 cx.spawn_in(window, async move |_, cx| {
17264 let transaction = futures::select_biased! {
17265 () = timeout => {
17266 log::warn!("timed out waiting for executing code action");
17267 None
17268 }
17269 transaction = apply_action.log_err().fuse() => transaction,
17270 };
17271 buffer
17272 .update(cx, |buffer, cx| {
17273 // check if we need this
17274 if let Some(transaction) = transaction
17275 && !buffer.is_singleton()
17276 {
17277 buffer.push_transaction(&transaction.0, cx);
17278 }
17279 cx.notify();
17280 })
17281 .ok();
17282 Ok(())
17283 })
17284 }
17285
17286 pub fn restart_language_server(
17287 &mut self,
17288 _: &RestartLanguageServer,
17289 _: &mut Window,
17290 cx: &mut Context<Self>,
17291 ) {
17292 if let Some(project) = self.project.clone() {
17293 self.buffer.update(cx, |multi_buffer, cx| {
17294 project.update(cx, |project, cx| {
17295 project.restart_language_servers_for_buffers(
17296 multi_buffer.all_buffers().into_iter().collect(),
17297 HashSet::default(),
17298 cx,
17299 );
17300 });
17301 })
17302 }
17303 }
17304
17305 pub fn stop_language_server(
17306 &mut self,
17307 _: &StopLanguageServer,
17308 _: &mut Window,
17309 cx: &mut Context<Self>,
17310 ) {
17311 if let Some(project) = self.project.clone() {
17312 self.buffer.update(cx, |multi_buffer, cx| {
17313 project.update(cx, |project, cx| {
17314 project.stop_language_servers_for_buffers(
17315 multi_buffer.all_buffers().into_iter().collect(),
17316 HashSet::default(),
17317 cx,
17318 );
17319 cx.emit(project::Event::RefreshInlayHints);
17320 });
17321 });
17322 }
17323 }
17324
17325 fn cancel_language_server_work(
17326 workspace: &mut Workspace,
17327 _: &actions::CancelLanguageServerWork,
17328 _: &mut Window,
17329 cx: &mut Context<Workspace>,
17330 ) {
17331 let project = workspace.project();
17332 let buffers = workspace
17333 .active_item(cx)
17334 .and_then(|item| item.act_as::<Editor>(cx))
17335 .map_or(HashSet::default(), |editor| {
17336 editor.read(cx).buffer.read(cx).all_buffers()
17337 });
17338 project.update(cx, |project, cx| {
17339 project.cancel_language_server_work_for_buffers(buffers, cx);
17340 });
17341 }
17342
17343 fn show_character_palette(
17344 &mut self,
17345 _: &ShowCharacterPalette,
17346 window: &mut Window,
17347 _: &mut Context<Self>,
17348 ) {
17349 window.show_character_palette();
17350 }
17351
17352 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17353 if !self.diagnostics_enabled() {
17354 return;
17355 }
17356
17357 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17358 let buffer = self.buffer.read(cx).snapshot(cx);
17359 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17360 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17361 let is_valid = buffer
17362 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17363 .any(|entry| {
17364 entry.diagnostic.is_primary
17365 && !entry.range.is_empty()
17366 && entry.range.start == primary_range_start
17367 && entry.diagnostic.message == active_diagnostics.active_message
17368 });
17369
17370 if !is_valid {
17371 self.dismiss_diagnostics(cx);
17372 }
17373 }
17374 }
17375
17376 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17377 match &self.active_diagnostics {
17378 ActiveDiagnostic::Group(group) => Some(group),
17379 _ => None,
17380 }
17381 }
17382
17383 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17384 if !self.diagnostics_enabled() {
17385 return;
17386 }
17387 self.dismiss_diagnostics(cx);
17388 self.active_diagnostics = ActiveDiagnostic::All;
17389 }
17390
17391 fn activate_diagnostics(
17392 &mut self,
17393 buffer_id: BufferId,
17394 diagnostic: DiagnosticEntry<usize>,
17395 window: &mut Window,
17396 cx: &mut Context<Self>,
17397 ) {
17398 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17399 return;
17400 }
17401 self.dismiss_diagnostics(cx);
17402 let snapshot = self.snapshot(window, cx);
17403 let buffer = self.buffer.read(cx).snapshot(cx);
17404 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17405 return;
17406 };
17407
17408 let diagnostic_group = buffer
17409 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17410 .collect::<Vec<_>>();
17411
17412 let blocks =
17413 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17414
17415 let blocks = self.display_map.update(cx, |display_map, cx| {
17416 display_map.insert_blocks(blocks, cx).into_iter().collect()
17417 });
17418 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17419 active_range: buffer.anchor_before(diagnostic.range.start)
17420 ..buffer.anchor_after(diagnostic.range.end),
17421 active_message: diagnostic.diagnostic.message.clone(),
17422 group_id: diagnostic.diagnostic.group_id,
17423 blocks,
17424 });
17425 cx.notify();
17426 }
17427
17428 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17429 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17430 return;
17431 };
17432
17433 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17434 if let ActiveDiagnostic::Group(group) = prev {
17435 self.display_map.update(cx, |display_map, cx| {
17436 display_map.remove_blocks(group.blocks, cx);
17437 });
17438 cx.notify();
17439 }
17440 }
17441
17442 /// Disable inline diagnostics rendering for this editor.
17443 pub fn disable_inline_diagnostics(&mut self) {
17444 self.inline_diagnostics_enabled = false;
17445 self.inline_diagnostics_update = Task::ready(());
17446 self.inline_diagnostics.clear();
17447 }
17448
17449 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17450 self.diagnostics_enabled = false;
17451 self.dismiss_diagnostics(cx);
17452 self.inline_diagnostics_update = Task::ready(());
17453 self.inline_diagnostics.clear();
17454 }
17455
17456 pub fn disable_word_completions(&mut self) {
17457 self.word_completions_enabled = false;
17458 }
17459
17460 pub fn diagnostics_enabled(&self) -> bool {
17461 self.diagnostics_enabled && self.mode.is_full()
17462 }
17463
17464 pub fn inline_diagnostics_enabled(&self) -> bool {
17465 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17466 }
17467
17468 pub fn show_inline_diagnostics(&self) -> bool {
17469 self.show_inline_diagnostics
17470 }
17471
17472 pub fn toggle_inline_diagnostics(
17473 &mut self,
17474 _: &ToggleInlineDiagnostics,
17475 window: &mut Window,
17476 cx: &mut Context<Editor>,
17477 ) {
17478 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17479 self.refresh_inline_diagnostics(false, window, cx);
17480 }
17481
17482 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17483 self.diagnostics_max_severity = severity;
17484 self.display_map.update(cx, |display_map, _| {
17485 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17486 });
17487 }
17488
17489 pub fn toggle_diagnostics(
17490 &mut self,
17491 _: &ToggleDiagnostics,
17492 window: &mut Window,
17493 cx: &mut Context<Editor>,
17494 ) {
17495 if !self.diagnostics_enabled() {
17496 return;
17497 }
17498
17499 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17500 EditorSettings::get_global(cx)
17501 .diagnostics_max_severity
17502 .filter(|severity| severity != &DiagnosticSeverity::Off)
17503 .unwrap_or(DiagnosticSeverity::Hint)
17504 } else {
17505 DiagnosticSeverity::Off
17506 };
17507 self.set_max_diagnostics_severity(new_severity, cx);
17508 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17509 self.active_diagnostics = ActiveDiagnostic::None;
17510 self.inline_diagnostics_update = Task::ready(());
17511 self.inline_diagnostics.clear();
17512 } else {
17513 self.refresh_inline_diagnostics(false, window, cx);
17514 }
17515
17516 cx.notify();
17517 }
17518
17519 pub fn toggle_minimap(
17520 &mut self,
17521 _: &ToggleMinimap,
17522 window: &mut Window,
17523 cx: &mut Context<Editor>,
17524 ) {
17525 if self.supports_minimap(cx) {
17526 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17527 }
17528 }
17529
17530 fn refresh_inline_diagnostics(
17531 &mut self,
17532 debounce: bool,
17533 window: &mut Window,
17534 cx: &mut Context<Self>,
17535 ) {
17536 let max_severity = ProjectSettings::get_global(cx)
17537 .diagnostics
17538 .inline
17539 .max_severity
17540 .unwrap_or(self.diagnostics_max_severity);
17541
17542 if !self.inline_diagnostics_enabled()
17543 || !self.show_inline_diagnostics
17544 || max_severity == DiagnosticSeverity::Off
17545 {
17546 self.inline_diagnostics_update = Task::ready(());
17547 self.inline_diagnostics.clear();
17548 return;
17549 }
17550
17551 let debounce_ms = ProjectSettings::get_global(cx)
17552 .diagnostics
17553 .inline
17554 .update_debounce_ms;
17555 let debounce = if debounce && debounce_ms > 0 {
17556 Some(Duration::from_millis(debounce_ms))
17557 } else {
17558 None
17559 };
17560 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17561 if let Some(debounce) = debounce {
17562 cx.background_executor().timer(debounce).await;
17563 }
17564 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17565 editor
17566 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17567 .ok()
17568 }) else {
17569 return;
17570 };
17571
17572 let new_inline_diagnostics = cx
17573 .background_spawn(async move {
17574 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17575 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17576 let message = diagnostic_entry
17577 .diagnostic
17578 .message
17579 .split_once('\n')
17580 .map(|(line, _)| line)
17581 .map(SharedString::new)
17582 .unwrap_or_else(|| {
17583 SharedString::from(diagnostic_entry.diagnostic.message)
17584 });
17585 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17586 let (Ok(i) | Err(i)) = inline_diagnostics
17587 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17588 inline_diagnostics.insert(
17589 i,
17590 (
17591 start_anchor,
17592 InlineDiagnostic {
17593 message,
17594 group_id: diagnostic_entry.diagnostic.group_id,
17595 start: diagnostic_entry.range.start.to_point(&snapshot),
17596 is_primary: diagnostic_entry.diagnostic.is_primary,
17597 severity: diagnostic_entry.diagnostic.severity,
17598 },
17599 ),
17600 );
17601 }
17602 inline_diagnostics
17603 })
17604 .await;
17605
17606 editor
17607 .update(cx, |editor, cx| {
17608 editor.inline_diagnostics = new_inline_diagnostics;
17609 cx.notify();
17610 })
17611 .ok();
17612 });
17613 }
17614
17615 fn pull_diagnostics(
17616 &mut self,
17617 buffer_id: Option<BufferId>,
17618 window: &Window,
17619 cx: &mut Context<Self>,
17620 ) -> Option<()> {
17621 if !self.mode().is_full() {
17622 return None;
17623 }
17624 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17625 .diagnostics
17626 .lsp_pull_diagnostics;
17627 if !pull_diagnostics_settings.enabled {
17628 return None;
17629 }
17630 let project = self.project()?.downgrade();
17631 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17632 let mut buffers = self.buffer.read(cx).all_buffers();
17633 if let Some(buffer_id) = buffer_id {
17634 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
17635 }
17636
17637 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17638 cx.background_executor().timer(debounce).await;
17639
17640 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17641 buffers
17642 .into_iter()
17643 .filter_map(|buffer| {
17644 project
17645 .update(cx, |project, cx| {
17646 project.lsp_store().update(cx, |lsp_store, cx| {
17647 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17648 })
17649 })
17650 .ok()
17651 })
17652 .collect::<FuturesUnordered<_>>()
17653 }) else {
17654 return;
17655 };
17656
17657 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17658 match pull_task {
17659 Ok(()) => {
17660 if editor
17661 .update_in(cx, |editor, window, cx| {
17662 editor.update_diagnostics_state(window, cx);
17663 })
17664 .is_err()
17665 {
17666 return;
17667 }
17668 }
17669 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17670 }
17671 }
17672 });
17673
17674 Some(())
17675 }
17676
17677 pub fn set_selections_from_remote(
17678 &mut self,
17679 selections: Vec<Selection<Anchor>>,
17680 pending_selection: Option<Selection<Anchor>>,
17681 window: &mut Window,
17682 cx: &mut Context<Self>,
17683 ) {
17684 let old_cursor_position = self.selections.newest_anchor().head();
17685 self.selections.change_with(cx, |s| {
17686 s.select_anchors(selections);
17687 if let Some(pending_selection) = pending_selection {
17688 s.set_pending(pending_selection, SelectMode::Character);
17689 } else {
17690 s.clear_pending();
17691 }
17692 });
17693 self.selections_did_change(
17694 false,
17695 &old_cursor_position,
17696 SelectionEffects::default(),
17697 window,
17698 cx,
17699 );
17700 }
17701
17702 pub fn transact(
17703 &mut self,
17704 window: &mut Window,
17705 cx: &mut Context<Self>,
17706 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17707 ) -> Option<TransactionId> {
17708 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17709 this.start_transaction_at(Instant::now(), window, cx);
17710 update(this, window, cx);
17711 this.end_transaction_at(Instant::now(), cx)
17712 })
17713 }
17714
17715 pub fn start_transaction_at(
17716 &mut self,
17717 now: Instant,
17718 window: &mut Window,
17719 cx: &mut Context<Self>,
17720 ) -> Option<TransactionId> {
17721 self.end_selection(window, cx);
17722 if let Some(tx_id) = self
17723 .buffer
17724 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17725 {
17726 self.selection_history
17727 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17728 cx.emit(EditorEvent::TransactionBegun {
17729 transaction_id: tx_id,
17730 });
17731 Some(tx_id)
17732 } else {
17733 None
17734 }
17735 }
17736
17737 pub fn end_transaction_at(
17738 &mut self,
17739 now: Instant,
17740 cx: &mut Context<Self>,
17741 ) -> Option<TransactionId> {
17742 if let Some(transaction_id) = self
17743 .buffer
17744 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17745 {
17746 if let Some((_, end_selections)) =
17747 self.selection_history.transaction_mut(transaction_id)
17748 {
17749 *end_selections = Some(self.selections.disjoint_anchors_arc());
17750 } else {
17751 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17752 }
17753
17754 cx.emit(EditorEvent::Edited { transaction_id });
17755 Some(transaction_id)
17756 } else {
17757 None
17758 }
17759 }
17760
17761 pub fn modify_transaction_selection_history(
17762 &mut self,
17763 transaction_id: TransactionId,
17764 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17765 ) -> bool {
17766 self.selection_history
17767 .transaction_mut(transaction_id)
17768 .map(modify)
17769 .is_some()
17770 }
17771
17772 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17773 if self.selection_mark_mode {
17774 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17775 s.move_with(|_, sel| {
17776 sel.collapse_to(sel.head(), SelectionGoal::None);
17777 });
17778 })
17779 }
17780 self.selection_mark_mode = true;
17781 cx.notify();
17782 }
17783
17784 pub fn swap_selection_ends(
17785 &mut self,
17786 _: &actions::SwapSelectionEnds,
17787 window: &mut Window,
17788 cx: &mut Context<Self>,
17789 ) {
17790 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17791 s.move_with(|_, sel| {
17792 if sel.start != sel.end {
17793 sel.reversed = !sel.reversed
17794 }
17795 });
17796 });
17797 self.request_autoscroll(Autoscroll::newest(), cx);
17798 cx.notify();
17799 }
17800
17801 pub fn toggle_focus(
17802 workspace: &mut Workspace,
17803 _: &actions::ToggleFocus,
17804 window: &mut Window,
17805 cx: &mut Context<Workspace>,
17806 ) {
17807 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17808 return;
17809 };
17810 workspace.activate_item(&item, true, true, window, cx);
17811 }
17812
17813 pub fn toggle_fold(
17814 &mut self,
17815 _: &actions::ToggleFold,
17816 window: &mut Window,
17817 cx: &mut Context<Self>,
17818 ) {
17819 if self.is_singleton(cx) {
17820 let selection = self.selections.newest::<Point>(cx);
17821
17822 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17823 let range = if selection.is_empty() {
17824 let point = selection.head().to_display_point(&display_map);
17825 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17826 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17827 .to_point(&display_map);
17828 start..end
17829 } else {
17830 selection.range()
17831 };
17832 if display_map.folds_in_range(range).next().is_some() {
17833 self.unfold_lines(&Default::default(), window, cx)
17834 } else {
17835 self.fold(&Default::default(), window, cx)
17836 }
17837 } else {
17838 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17839 let buffer_ids: HashSet<_> = self
17840 .selections
17841 .disjoint_anchor_ranges()
17842 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17843 .collect();
17844
17845 let should_unfold = buffer_ids
17846 .iter()
17847 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17848
17849 for buffer_id in buffer_ids {
17850 if should_unfold {
17851 self.unfold_buffer(buffer_id, cx);
17852 } else {
17853 self.fold_buffer(buffer_id, cx);
17854 }
17855 }
17856 }
17857 }
17858
17859 pub fn toggle_fold_recursive(
17860 &mut self,
17861 _: &actions::ToggleFoldRecursive,
17862 window: &mut Window,
17863 cx: &mut Context<Self>,
17864 ) {
17865 let selection = self.selections.newest::<Point>(cx);
17866
17867 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17868 let range = if selection.is_empty() {
17869 let point = selection.head().to_display_point(&display_map);
17870 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17871 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17872 .to_point(&display_map);
17873 start..end
17874 } else {
17875 selection.range()
17876 };
17877 if display_map.folds_in_range(range).next().is_some() {
17878 self.unfold_recursive(&Default::default(), window, cx)
17879 } else {
17880 self.fold_recursive(&Default::default(), window, cx)
17881 }
17882 }
17883
17884 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
17885 if self.is_singleton(cx) {
17886 let mut to_fold = Vec::new();
17887 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17888 let selections = self.selections.all_adjusted(cx);
17889
17890 for selection in selections {
17891 let range = selection.range().sorted();
17892 let buffer_start_row = range.start.row;
17893
17894 if range.start.row != range.end.row {
17895 let mut found = false;
17896 let mut row = range.start.row;
17897 while row <= range.end.row {
17898 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17899 {
17900 found = true;
17901 row = crease.range().end.row + 1;
17902 to_fold.push(crease);
17903 } else {
17904 row += 1
17905 }
17906 }
17907 if found {
17908 continue;
17909 }
17910 }
17911
17912 for row in (0..=range.start.row).rev() {
17913 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
17914 && crease.range().end.row >= buffer_start_row
17915 {
17916 to_fold.push(crease);
17917 if row <= range.start.row {
17918 break;
17919 }
17920 }
17921 }
17922 }
17923
17924 self.fold_creases(to_fold, true, window, cx);
17925 } else {
17926 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17927 let buffer_ids = self
17928 .selections
17929 .disjoint_anchor_ranges()
17930 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17931 .collect::<HashSet<_>>();
17932 for buffer_id in buffer_ids {
17933 self.fold_buffer(buffer_id, cx);
17934 }
17935 }
17936 }
17937
17938 pub fn toggle_fold_all(
17939 &mut self,
17940 _: &actions::ToggleFoldAll,
17941 window: &mut Window,
17942 cx: &mut Context<Self>,
17943 ) {
17944 if self.buffer.read(cx).is_singleton() {
17945 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17946 let has_folds = display_map
17947 .folds_in_range(0..display_map.buffer_snapshot.len())
17948 .next()
17949 .is_some();
17950
17951 if has_folds {
17952 self.unfold_all(&actions::UnfoldAll, window, cx);
17953 } else {
17954 self.fold_all(&actions::FoldAll, window, cx);
17955 }
17956 } else {
17957 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
17958 let should_unfold = buffer_ids
17959 .iter()
17960 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17961
17962 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
17963 editor
17964 .update_in(cx, |editor, _, cx| {
17965 for buffer_id in buffer_ids {
17966 if should_unfold {
17967 editor.unfold_buffer(buffer_id, cx);
17968 } else {
17969 editor.fold_buffer(buffer_id, cx);
17970 }
17971 }
17972 })
17973 .ok();
17974 });
17975 }
17976 }
17977
17978 fn fold_at_level(
17979 &mut self,
17980 fold_at: &FoldAtLevel,
17981 window: &mut Window,
17982 cx: &mut Context<Self>,
17983 ) {
17984 if !self.buffer.read(cx).is_singleton() {
17985 return;
17986 }
17987
17988 let fold_at_level = fold_at.0;
17989 let snapshot = self.buffer.read(cx).snapshot(cx);
17990 let mut to_fold = Vec::new();
17991 let mut stack = vec![(0, snapshot.max_row().0, 1)];
17992
17993 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
17994 while start_row < end_row {
17995 match self
17996 .snapshot(window, cx)
17997 .crease_for_buffer_row(MultiBufferRow(start_row))
17998 {
17999 Some(crease) => {
18000 let nested_start_row = crease.range().start.row + 1;
18001 let nested_end_row = crease.range().end.row;
18002
18003 if current_level < fold_at_level {
18004 stack.push((nested_start_row, nested_end_row, current_level + 1));
18005 } else if current_level == fold_at_level {
18006 to_fold.push(crease);
18007 }
18008
18009 start_row = nested_end_row + 1;
18010 }
18011 None => start_row += 1,
18012 }
18013 }
18014 }
18015
18016 self.fold_creases(to_fold, true, window, cx);
18017 }
18018
18019 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18020 if self.buffer.read(cx).is_singleton() {
18021 let mut fold_ranges = Vec::new();
18022 let snapshot = self.buffer.read(cx).snapshot(cx);
18023
18024 for row in 0..snapshot.max_row().0 {
18025 if let Some(foldable_range) = self
18026 .snapshot(window, cx)
18027 .crease_for_buffer_row(MultiBufferRow(row))
18028 {
18029 fold_ranges.push(foldable_range);
18030 }
18031 }
18032
18033 self.fold_creases(fold_ranges, true, window, cx);
18034 } else {
18035 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18036 editor
18037 .update_in(cx, |editor, _, cx| {
18038 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18039 editor.fold_buffer(buffer_id, cx);
18040 }
18041 })
18042 .ok();
18043 });
18044 }
18045 }
18046
18047 pub fn fold_function_bodies(
18048 &mut self,
18049 _: &actions::FoldFunctionBodies,
18050 window: &mut Window,
18051 cx: &mut Context<Self>,
18052 ) {
18053 let snapshot = self.buffer.read(cx).snapshot(cx);
18054
18055 let ranges = snapshot
18056 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18057 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18058 .collect::<Vec<_>>();
18059
18060 let creases = ranges
18061 .into_iter()
18062 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18063 .collect();
18064
18065 self.fold_creases(creases, true, window, cx);
18066 }
18067
18068 pub fn fold_recursive(
18069 &mut self,
18070 _: &actions::FoldRecursive,
18071 window: &mut Window,
18072 cx: &mut Context<Self>,
18073 ) {
18074 let mut to_fold = Vec::new();
18075 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18076 let selections = self.selections.all_adjusted(cx);
18077
18078 for selection in selections {
18079 let range = selection.range().sorted();
18080 let buffer_start_row = range.start.row;
18081
18082 if range.start.row != range.end.row {
18083 let mut found = false;
18084 for row in range.start.row..=range.end.row {
18085 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18086 found = true;
18087 to_fold.push(crease);
18088 }
18089 }
18090 if found {
18091 continue;
18092 }
18093 }
18094
18095 for row in (0..=range.start.row).rev() {
18096 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18097 if crease.range().end.row >= buffer_start_row {
18098 to_fold.push(crease);
18099 } else {
18100 break;
18101 }
18102 }
18103 }
18104 }
18105
18106 self.fold_creases(to_fold, true, window, cx);
18107 }
18108
18109 pub fn fold_at(
18110 &mut self,
18111 buffer_row: MultiBufferRow,
18112 window: &mut Window,
18113 cx: &mut Context<Self>,
18114 ) {
18115 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18116
18117 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18118 let autoscroll = self
18119 .selections
18120 .all::<Point>(cx)
18121 .iter()
18122 .any(|selection| crease.range().overlaps(&selection.range()));
18123
18124 self.fold_creases(vec![crease], autoscroll, window, cx);
18125 }
18126 }
18127
18128 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18129 if self.is_singleton(cx) {
18130 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18131 let buffer = &display_map.buffer_snapshot;
18132 let selections = self.selections.all::<Point>(cx);
18133 let ranges = selections
18134 .iter()
18135 .map(|s| {
18136 let range = s.display_range(&display_map).sorted();
18137 let mut start = range.start.to_point(&display_map);
18138 let mut end = range.end.to_point(&display_map);
18139 start.column = 0;
18140 end.column = buffer.line_len(MultiBufferRow(end.row));
18141 start..end
18142 })
18143 .collect::<Vec<_>>();
18144
18145 self.unfold_ranges(&ranges, true, true, cx);
18146 } else {
18147 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18148 let buffer_ids = self
18149 .selections
18150 .disjoint_anchor_ranges()
18151 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18152 .collect::<HashSet<_>>();
18153 for buffer_id in buffer_ids {
18154 self.unfold_buffer(buffer_id, cx);
18155 }
18156 }
18157 }
18158
18159 pub fn unfold_recursive(
18160 &mut self,
18161 _: &UnfoldRecursive,
18162 _window: &mut Window,
18163 cx: &mut Context<Self>,
18164 ) {
18165 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18166 let selections = self.selections.all::<Point>(cx);
18167 let ranges = selections
18168 .iter()
18169 .map(|s| {
18170 let mut range = s.display_range(&display_map).sorted();
18171 *range.start.column_mut() = 0;
18172 *range.end.column_mut() = display_map.line_len(range.end.row());
18173 let start = range.start.to_point(&display_map);
18174 let end = range.end.to_point(&display_map);
18175 start..end
18176 })
18177 .collect::<Vec<_>>();
18178
18179 self.unfold_ranges(&ranges, true, true, cx);
18180 }
18181
18182 pub fn unfold_at(
18183 &mut self,
18184 buffer_row: MultiBufferRow,
18185 _window: &mut Window,
18186 cx: &mut Context<Self>,
18187 ) {
18188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18189
18190 let intersection_range = Point::new(buffer_row.0, 0)
18191 ..Point::new(
18192 buffer_row.0,
18193 display_map.buffer_snapshot.line_len(buffer_row),
18194 );
18195
18196 let autoscroll = self
18197 .selections
18198 .all::<Point>(cx)
18199 .iter()
18200 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18201
18202 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18203 }
18204
18205 pub fn unfold_all(
18206 &mut self,
18207 _: &actions::UnfoldAll,
18208 _window: &mut Window,
18209 cx: &mut Context<Self>,
18210 ) {
18211 if self.buffer.read(cx).is_singleton() {
18212 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18213 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
18214 } else {
18215 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18216 editor
18217 .update(cx, |editor, cx| {
18218 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18219 editor.unfold_buffer(buffer_id, cx);
18220 }
18221 })
18222 .ok();
18223 });
18224 }
18225 }
18226
18227 pub fn fold_selected_ranges(
18228 &mut self,
18229 _: &FoldSelectedRanges,
18230 window: &mut Window,
18231 cx: &mut Context<Self>,
18232 ) {
18233 let selections = self.selections.all_adjusted(cx);
18234 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18235 let ranges = selections
18236 .into_iter()
18237 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18238 .collect::<Vec<_>>();
18239 self.fold_creases(ranges, true, window, cx);
18240 }
18241
18242 pub fn fold_ranges<T: ToOffset + Clone>(
18243 &mut self,
18244 ranges: Vec<Range<T>>,
18245 auto_scroll: bool,
18246 window: &mut Window,
18247 cx: &mut Context<Self>,
18248 ) {
18249 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18250 let ranges = ranges
18251 .into_iter()
18252 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18253 .collect::<Vec<_>>();
18254 self.fold_creases(ranges, auto_scroll, window, cx);
18255 }
18256
18257 pub fn fold_creases<T: ToOffset + Clone>(
18258 &mut self,
18259 creases: Vec<Crease<T>>,
18260 auto_scroll: bool,
18261 _window: &mut Window,
18262 cx: &mut Context<Self>,
18263 ) {
18264 if creases.is_empty() {
18265 return;
18266 }
18267
18268 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18269
18270 if auto_scroll {
18271 self.request_autoscroll(Autoscroll::fit(), cx);
18272 }
18273
18274 cx.notify();
18275
18276 self.scrollbar_marker_state.dirty = true;
18277 self.folds_did_change(cx);
18278 }
18279
18280 /// Removes any folds whose ranges intersect any of the given ranges.
18281 pub fn unfold_ranges<T: ToOffset + Clone>(
18282 &mut self,
18283 ranges: &[Range<T>],
18284 inclusive: bool,
18285 auto_scroll: bool,
18286 cx: &mut Context<Self>,
18287 ) {
18288 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18289 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18290 });
18291 self.folds_did_change(cx);
18292 }
18293
18294 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18295 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18296 return;
18297 }
18298 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18299 self.display_map.update(cx, |display_map, cx| {
18300 display_map.fold_buffers([buffer_id], cx)
18301 });
18302 cx.emit(EditorEvent::BufferFoldToggled {
18303 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18304 folded: true,
18305 });
18306 cx.notify();
18307 }
18308
18309 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18310 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18311 return;
18312 }
18313 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18314 self.display_map.update(cx, |display_map, cx| {
18315 display_map.unfold_buffers([buffer_id], cx);
18316 });
18317 cx.emit(EditorEvent::BufferFoldToggled {
18318 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18319 folded: false,
18320 });
18321 cx.notify();
18322 }
18323
18324 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18325 self.display_map.read(cx).is_buffer_folded(buffer)
18326 }
18327
18328 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18329 self.display_map.read(cx).folded_buffers()
18330 }
18331
18332 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18333 self.display_map.update(cx, |display_map, cx| {
18334 display_map.disable_header_for_buffer(buffer_id, cx);
18335 });
18336 cx.notify();
18337 }
18338
18339 /// Removes any folds with the given ranges.
18340 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18341 &mut self,
18342 ranges: &[Range<T>],
18343 type_id: TypeId,
18344 auto_scroll: bool,
18345 cx: &mut Context<Self>,
18346 ) {
18347 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18348 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18349 });
18350 self.folds_did_change(cx);
18351 }
18352
18353 fn remove_folds_with<T: ToOffset + Clone>(
18354 &mut self,
18355 ranges: &[Range<T>],
18356 auto_scroll: bool,
18357 cx: &mut Context<Self>,
18358 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18359 ) {
18360 if ranges.is_empty() {
18361 return;
18362 }
18363
18364 let mut buffers_affected = HashSet::default();
18365 let multi_buffer = self.buffer().read(cx);
18366 for range in ranges {
18367 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18368 buffers_affected.insert(buffer.read(cx).remote_id());
18369 };
18370 }
18371
18372 self.display_map.update(cx, update);
18373
18374 if auto_scroll {
18375 self.request_autoscroll(Autoscroll::fit(), cx);
18376 }
18377
18378 cx.notify();
18379 self.scrollbar_marker_state.dirty = true;
18380 self.active_indent_guides_state.dirty = true;
18381 }
18382
18383 pub fn update_renderer_widths(
18384 &mut self,
18385 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18386 cx: &mut Context<Self>,
18387 ) -> bool {
18388 self.display_map
18389 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18390 }
18391
18392 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18393 self.display_map.read(cx).fold_placeholder.clone()
18394 }
18395
18396 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18397 self.buffer.update(cx, |buffer, cx| {
18398 buffer.set_all_diff_hunks_expanded(cx);
18399 });
18400 }
18401
18402 pub fn expand_all_diff_hunks(
18403 &mut self,
18404 _: &ExpandAllDiffHunks,
18405 _window: &mut Window,
18406 cx: &mut Context<Self>,
18407 ) {
18408 self.buffer.update(cx, |buffer, cx| {
18409 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18410 });
18411 }
18412
18413 pub fn toggle_selected_diff_hunks(
18414 &mut self,
18415 _: &ToggleSelectedDiffHunks,
18416 _window: &mut Window,
18417 cx: &mut Context<Self>,
18418 ) {
18419 let ranges: Vec<_> = self
18420 .selections
18421 .disjoint_anchors()
18422 .iter()
18423 .map(|s| s.range())
18424 .collect();
18425 self.toggle_diff_hunks_in_ranges(ranges, cx);
18426 }
18427
18428 pub fn diff_hunks_in_ranges<'a>(
18429 &'a self,
18430 ranges: &'a [Range<Anchor>],
18431 buffer: &'a MultiBufferSnapshot,
18432 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18433 ranges.iter().flat_map(move |range| {
18434 let end_excerpt_id = range.end.excerpt_id;
18435 let range = range.to_point(buffer);
18436 let mut peek_end = range.end;
18437 if range.end.row < buffer.max_row().0 {
18438 peek_end = Point::new(range.end.row + 1, 0);
18439 }
18440 buffer
18441 .diff_hunks_in_range(range.start..peek_end)
18442 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18443 })
18444 }
18445
18446 pub fn has_stageable_diff_hunks_in_ranges(
18447 &self,
18448 ranges: &[Range<Anchor>],
18449 snapshot: &MultiBufferSnapshot,
18450 ) -> bool {
18451 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18452 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18453 }
18454
18455 pub fn toggle_staged_selected_diff_hunks(
18456 &mut self,
18457 _: &::git::ToggleStaged,
18458 _: &mut Window,
18459 cx: &mut Context<Self>,
18460 ) {
18461 let snapshot = self.buffer.read(cx).snapshot(cx);
18462 let ranges: Vec<_> = self
18463 .selections
18464 .disjoint_anchors()
18465 .iter()
18466 .map(|s| s.range())
18467 .collect();
18468 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18469 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18470 }
18471
18472 pub fn set_render_diff_hunk_controls(
18473 &mut self,
18474 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18475 cx: &mut Context<Self>,
18476 ) {
18477 self.render_diff_hunk_controls = render_diff_hunk_controls;
18478 cx.notify();
18479 }
18480
18481 pub fn stage_and_next(
18482 &mut self,
18483 _: &::git::StageAndNext,
18484 window: &mut Window,
18485 cx: &mut Context<Self>,
18486 ) {
18487 self.do_stage_or_unstage_and_next(true, window, cx);
18488 }
18489
18490 pub fn unstage_and_next(
18491 &mut self,
18492 _: &::git::UnstageAndNext,
18493 window: &mut Window,
18494 cx: &mut Context<Self>,
18495 ) {
18496 self.do_stage_or_unstage_and_next(false, window, cx);
18497 }
18498
18499 pub fn stage_or_unstage_diff_hunks(
18500 &mut self,
18501 stage: bool,
18502 ranges: Vec<Range<Anchor>>,
18503 cx: &mut Context<Self>,
18504 ) {
18505 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18506 cx.spawn(async move |this, cx| {
18507 task.await?;
18508 this.update(cx, |this, cx| {
18509 let snapshot = this.buffer.read(cx).snapshot(cx);
18510 let chunk_by = this
18511 .diff_hunks_in_ranges(&ranges, &snapshot)
18512 .chunk_by(|hunk| hunk.buffer_id);
18513 for (buffer_id, hunks) in &chunk_by {
18514 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18515 }
18516 })
18517 })
18518 .detach_and_log_err(cx);
18519 }
18520
18521 fn save_buffers_for_ranges_if_needed(
18522 &mut self,
18523 ranges: &[Range<Anchor>],
18524 cx: &mut Context<Editor>,
18525 ) -> Task<Result<()>> {
18526 let multibuffer = self.buffer.read(cx);
18527 let snapshot = multibuffer.read(cx);
18528 let buffer_ids: HashSet<_> = ranges
18529 .iter()
18530 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18531 .collect();
18532 drop(snapshot);
18533
18534 let mut buffers = HashSet::default();
18535 for buffer_id in buffer_ids {
18536 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18537 let buffer = buffer_entity.read(cx);
18538 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18539 {
18540 buffers.insert(buffer_entity);
18541 }
18542 }
18543 }
18544
18545 if let Some(project) = &self.project {
18546 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18547 } else {
18548 Task::ready(Ok(()))
18549 }
18550 }
18551
18552 fn do_stage_or_unstage_and_next(
18553 &mut self,
18554 stage: bool,
18555 window: &mut Window,
18556 cx: &mut Context<Self>,
18557 ) {
18558 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18559
18560 if ranges.iter().any(|range| range.start != range.end) {
18561 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18562 return;
18563 }
18564
18565 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18566 let snapshot = self.snapshot(window, cx);
18567 let position = self.selections.newest::<Point>(cx).head();
18568 let mut row = snapshot
18569 .buffer_snapshot
18570 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
18571 .find(|hunk| hunk.row_range.start.0 > position.row)
18572 .map(|hunk| hunk.row_range.start);
18573
18574 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18575 // Outside of the project diff editor, wrap around to the beginning.
18576 if !all_diff_hunks_expanded {
18577 row = row.or_else(|| {
18578 snapshot
18579 .buffer_snapshot
18580 .diff_hunks_in_range(Point::zero()..position)
18581 .find(|hunk| hunk.row_range.end.0 < position.row)
18582 .map(|hunk| hunk.row_range.start)
18583 });
18584 }
18585
18586 if let Some(row) = row {
18587 let destination = Point::new(row.0, 0);
18588 let autoscroll = Autoscroll::center();
18589
18590 self.unfold_ranges(&[destination..destination], false, false, cx);
18591 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18592 s.select_ranges([destination..destination]);
18593 });
18594 }
18595 }
18596
18597 fn do_stage_or_unstage(
18598 &self,
18599 stage: bool,
18600 buffer_id: BufferId,
18601 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18602 cx: &mut App,
18603 ) -> Option<()> {
18604 let project = self.project()?;
18605 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18606 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18607 let buffer_snapshot = buffer.read(cx).snapshot();
18608 let file_exists = buffer_snapshot
18609 .file()
18610 .is_some_and(|file| file.disk_state().exists());
18611 diff.update(cx, |diff, cx| {
18612 diff.stage_or_unstage_hunks(
18613 stage,
18614 &hunks
18615 .map(|hunk| buffer_diff::DiffHunk {
18616 buffer_range: hunk.buffer_range,
18617 diff_base_byte_range: hunk.diff_base_byte_range,
18618 secondary_status: hunk.secondary_status,
18619 range: Point::zero()..Point::zero(), // unused
18620 })
18621 .collect::<Vec<_>>(),
18622 &buffer_snapshot,
18623 file_exists,
18624 cx,
18625 )
18626 });
18627 None
18628 }
18629
18630 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18631 let ranges: Vec<_> = self
18632 .selections
18633 .disjoint_anchors()
18634 .iter()
18635 .map(|s| s.range())
18636 .collect();
18637 self.buffer
18638 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18639 }
18640
18641 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18642 self.buffer.update(cx, |buffer, cx| {
18643 let ranges = vec![Anchor::min()..Anchor::max()];
18644 if !buffer.all_diff_hunks_expanded()
18645 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18646 {
18647 buffer.collapse_diff_hunks(ranges, cx);
18648 true
18649 } else {
18650 false
18651 }
18652 })
18653 }
18654
18655 fn toggle_diff_hunks_in_ranges(
18656 &mut self,
18657 ranges: Vec<Range<Anchor>>,
18658 cx: &mut Context<Editor>,
18659 ) {
18660 self.buffer.update(cx, |buffer, cx| {
18661 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18662 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18663 })
18664 }
18665
18666 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18667 self.buffer.update(cx, |buffer, cx| {
18668 let snapshot = buffer.snapshot(cx);
18669 let excerpt_id = range.end.excerpt_id;
18670 let point_range = range.to_point(&snapshot);
18671 let expand = !buffer.single_hunk_is_expanded(range, cx);
18672 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18673 })
18674 }
18675
18676 pub(crate) fn apply_all_diff_hunks(
18677 &mut self,
18678 _: &ApplyAllDiffHunks,
18679 window: &mut Window,
18680 cx: &mut Context<Self>,
18681 ) {
18682 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18683
18684 let buffers = self.buffer.read(cx).all_buffers();
18685 for branch_buffer in buffers {
18686 branch_buffer.update(cx, |branch_buffer, cx| {
18687 branch_buffer.merge_into_base(Vec::new(), cx);
18688 });
18689 }
18690
18691 if let Some(project) = self.project.clone() {
18692 self.save(
18693 SaveOptions {
18694 format: true,
18695 autosave: false,
18696 },
18697 project,
18698 window,
18699 cx,
18700 )
18701 .detach_and_log_err(cx);
18702 }
18703 }
18704
18705 pub(crate) fn apply_selected_diff_hunks(
18706 &mut self,
18707 _: &ApplyDiffHunk,
18708 window: &mut Window,
18709 cx: &mut Context<Self>,
18710 ) {
18711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18712 let snapshot = self.snapshot(window, cx);
18713 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
18714 let mut ranges_by_buffer = HashMap::default();
18715 self.transact(window, cx, |editor, _window, cx| {
18716 for hunk in hunks {
18717 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18718 ranges_by_buffer
18719 .entry(buffer.clone())
18720 .or_insert_with(Vec::new)
18721 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18722 }
18723 }
18724
18725 for (buffer, ranges) in ranges_by_buffer {
18726 buffer.update(cx, |buffer, cx| {
18727 buffer.merge_into_base(ranges, cx);
18728 });
18729 }
18730 });
18731
18732 if let Some(project) = self.project.clone() {
18733 self.save(
18734 SaveOptions {
18735 format: true,
18736 autosave: false,
18737 },
18738 project,
18739 window,
18740 cx,
18741 )
18742 .detach_and_log_err(cx);
18743 }
18744 }
18745
18746 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18747 if hovered != self.gutter_hovered {
18748 self.gutter_hovered = hovered;
18749 cx.notify();
18750 }
18751 }
18752
18753 pub fn insert_blocks(
18754 &mut self,
18755 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18756 autoscroll: Option<Autoscroll>,
18757 cx: &mut Context<Self>,
18758 ) -> Vec<CustomBlockId> {
18759 let blocks = self
18760 .display_map
18761 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18762 if let Some(autoscroll) = autoscroll {
18763 self.request_autoscroll(autoscroll, cx);
18764 }
18765 cx.notify();
18766 blocks
18767 }
18768
18769 pub fn resize_blocks(
18770 &mut self,
18771 heights: HashMap<CustomBlockId, u32>,
18772 autoscroll: Option<Autoscroll>,
18773 cx: &mut Context<Self>,
18774 ) {
18775 self.display_map
18776 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
18777 if let Some(autoscroll) = autoscroll {
18778 self.request_autoscroll(autoscroll, cx);
18779 }
18780 cx.notify();
18781 }
18782
18783 pub fn replace_blocks(
18784 &mut self,
18785 renderers: HashMap<CustomBlockId, RenderBlock>,
18786 autoscroll: Option<Autoscroll>,
18787 cx: &mut Context<Self>,
18788 ) {
18789 self.display_map
18790 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
18791 if let Some(autoscroll) = autoscroll {
18792 self.request_autoscroll(autoscroll, cx);
18793 }
18794 cx.notify();
18795 }
18796
18797 pub fn remove_blocks(
18798 &mut self,
18799 block_ids: HashSet<CustomBlockId>,
18800 autoscroll: Option<Autoscroll>,
18801 cx: &mut Context<Self>,
18802 ) {
18803 self.display_map.update(cx, |display_map, cx| {
18804 display_map.remove_blocks(block_ids, cx)
18805 });
18806 if let Some(autoscroll) = autoscroll {
18807 self.request_autoscroll(autoscroll, cx);
18808 }
18809 cx.notify();
18810 }
18811
18812 pub fn row_for_block(
18813 &self,
18814 block_id: CustomBlockId,
18815 cx: &mut Context<Self>,
18816 ) -> Option<DisplayRow> {
18817 self.display_map
18818 .update(cx, |map, cx| map.row_for_block(block_id, cx))
18819 }
18820
18821 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
18822 self.focused_block = Some(focused_block);
18823 }
18824
18825 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
18826 self.focused_block.take()
18827 }
18828
18829 pub fn insert_creases(
18830 &mut self,
18831 creases: impl IntoIterator<Item = Crease<Anchor>>,
18832 cx: &mut Context<Self>,
18833 ) -> Vec<CreaseId> {
18834 self.display_map
18835 .update(cx, |map, cx| map.insert_creases(creases, cx))
18836 }
18837
18838 pub fn remove_creases(
18839 &mut self,
18840 ids: impl IntoIterator<Item = CreaseId>,
18841 cx: &mut Context<Self>,
18842 ) -> Vec<(CreaseId, Range<Anchor>)> {
18843 self.display_map
18844 .update(cx, |map, cx| map.remove_creases(ids, cx))
18845 }
18846
18847 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
18848 self.display_map
18849 .update(cx, |map, cx| map.snapshot(cx))
18850 .longest_row()
18851 }
18852
18853 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
18854 self.display_map
18855 .update(cx, |map, cx| map.snapshot(cx))
18856 .max_point()
18857 }
18858
18859 pub fn text(&self, cx: &App) -> String {
18860 self.buffer.read(cx).read(cx).text()
18861 }
18862
18863 pub fn is_empty(&self, cx: &App) -> bool {
18864 self.buffer.read(cx).read(cx).is_empty()
18865 }
18866
18867 pub fn text_option(&self, cx: &App) -> Option<String> {
18868 let text = self.text(cx);
18869 let text = text.trim();
18870
18871 if text.is_empty() {
18872 return None;
18873 }
18874
18875 Some(text.to_string())
18876 }
18877
18878 pub fn set_text(
18879 &mut self,
18880 text: impl Into<Arc<str>>,
18881 window: &mut Window,
18882 cx: &mut Context<Self>,
18883 ) {
18884 self.transact(window, cx, |this, _, cx| {
18885 this.buffer
18886 .read(cx)
18887 .as_singleton()
18888 .expect("you can only call set_text on editors for singleton buffers")
18889 .update(cx, |buffer, cx| buffer.set_text(text, cx));
18890 });
18891 }
18892
18893 pub fn display_text(&self, cx: &mut App) -> String {
18894 self.display_map
18895 .update(cx, |map, cx| map.snapshot(cx))
18896 .text()
18897 }
18898
18899 fn create_minimap(
18900 &self,
18901 minimap_settings: MinimapSettings,
18902 window: &mut Window,
18903 cx: &mut Context<Self>,
18904 ) -> Option<Entity<Self>> {
18905 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
18906 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
18907 }
18908
18909 fn initialize_new_minimap(
18910 &self,
18911 minimap_settings: MinimapSettings,
18912 window: &mut Window,
18913 cx: &mut Context<Self>,
18914 ) -> Entity<Self> {
18915 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
18916
18917 let mut minimap = Editor::new_internal(
18918 EditorMode::Minimap {
18919 parent: cx.weak_entity(),
18920 },
18921 self.buffer.clone(),
18922 None,
18923 Some(self.display_map.clone()),
18924 window,
18925 cx,
18926 );
18927 minimap.scroll_manager.clone_state(&self.scroll_manager);
18928 minimap.set_text_style_refinement(TextStyleRefinement {
18929 font_size: Some(MINIMAP_FONT_SIZE),
18930 font_weight: Some(MINIMAP_FONT_WEIGHT),
18931 ..Default::default()
18932 });
18933 minimap.update_minimap_configuration(minimap_settings, cx);
18934 cx.new(|_| minimap)
18935 }
18936
18937 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
18938 let current_line_highlight = minimap_settings
18939 .current_line_highlight
18940 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
18941 self.set_current_line_highlight(Some(current_line_highlight));
18942 }
18943
18944 pub fn minimap(&self) -> Option<&Entity<Self>> {
18945 self.minimap
18946 .as_ref()
18947 .filter(|_| self.minimap_visibility.visible())
18948 }
18949
18950 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
18951 let mut wrap_guides = smallvec![];
18952
18953 if self.show_wrap_guides == Some(false) {
18954 return wrap_guides;
18955 }
18956
18957 let settings = self.buffer.read(cx).language_settings(cx);
18958 if settings.show_wrap_guides {
18959 match self.soft_wrap_mode(cx) {
18960 SoftWrap::Column(soft_wrap) => {
18961 wrap_guides.push((soft_wrap as usize, true));
18962 }
18963 SoftWrap::Bounded(soft_wrap) => {
18964 wrap_guides.push((soft_wrap as usize, true));
18965 }
18966 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
18967 }
18968 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
18969 }
18970
18971 wrap_guides
18972 }
18973
18974 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
18975 let settings = self.buffer.read(cx).language_settings(cx);
18976 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
18977 match mode {
18978 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
18979 SoftWrap::None
18980 }
18981 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
18982 language_settings::SoftWrap::PreferredLineLength => {
18983 SoftWrap::Column(settings.preferred_line_length)
18984 }
18985 language_settings::SoftWrap::Bounded => {
18986 SoftWrap::Bounded(settings.preferred_line_length)
18987 }
18988 }
18989 }
18990
18991 pub fn set_soft_wrap_mode(
18992 &mut self,
18993 mode: language_settings::SoftWrap,
18994
18995 cx: &mut Context<Self>,
18996 ) {
18997 self.soft_wrap_mode_override = Some(mode);
18998 cx.notify();
18999 }
19000
19001 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19002 self.hard_wrap = hard_wrap;
19003 cx.notify();
19004 }
19005
19006 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19007 self.text_style_refinement = Some(style);
19008 }
19009
19010 /// called by the Element so we know what style we were most recently rendered with.
19011 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19012 // We intentionally do not inform the display map about the minimap style
19013 // so that wrapping is not recalculated and stays consistent for the editor
19014 // and its linked minimap.
19015 if !self.mode.is_minimap() {
19016 let font = style.text.font();
19017 let font_size = style.text.font_size.to_pixels(window.rem_size());
19018 let display_map = self
19019 .placeholder_display_map
19020 .as_ref()
19021 .filter(|_| self.is_empty(cx))
19022 .unwrap_or(&self.display_map);
19023
19024 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19025 }
19026 self.style = Some(style);
19027 }
19028
19029 pub fn style(&self) -> Option<&EditorStyle> {
19030 self.style.as_ref()
19031 }
19032
19033 // Called by the element. This method is not designed to be called outside of the editor
19034 // element's layout code because it does not notify when rewrapping is computed synchronously.
19035 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19036 if self.is_empty(cx) {
19037 self.placeholder_display_map
19038 .as_ref()
19039 .map_or(false, |display_map| {
19040 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19041 })
19042 } else {
19043 self.display_map
19044 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19045 }
19046 }
19047
19048 pub fn set_soft_wrap(&mut self) {
19049 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19050 }
19051
19052 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19053 if self.soft_wrap_mode_override.is_some() {
19054 self.soft_wrap_mode_override.take();
19055 } else {
19056 let soft_wrap = match self.soft_wrap_mode(cx) {
19057 SoftWrap::GitDiff => return,
19058 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19059 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19060 language_settings::SoftWrap::None
19061 }
19062 };
19063 self.soft_wrap_mode_override = Some(soft_wrap);
19064 }
19065 cx.notify();
19066 }
19067
19068 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19069 let Some(workspace) = self.workspace() else {
19070 return;
19071 };
19072 let fs = workspace.read(cx).app_state().fs.clone();
19073 let current_show = TabBarSettings::get_global(cx).show;
19074 update_settings_file(fs, cx, move |setting, _| {
19075 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19076 });
19077 }
19078
19079 pub fn toggle_indent_guides(
19080 &mut self,
19081 _: &ToggleIndentGuides,
19082 _: &mut Window,
19083 cx: &mut Context<Self>,
19084 ) {
19085 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19086 self.buffer
19087 .read(cx)
19088 .language_settings(cx)
19089 .indent_guides
19090 .enabled
19091 });
19092 self.show_indent_guides = Some(!currently_enabled);
19093 cx.notify();
19094 }
19095
19096 fn should_show_indent_guides(&self) -> Option<bool> {
19097 self.show_indent_guides
19098 }
19099
19100 pub fn toggle_line_numbers(
19101 &mut self,
19102 _: &ToggleLineNumbers,
19103 _: &mut Window,
19104 cx: &mut Context<Self>,
19105 ) {
19106 let mut editor_settings = EditorSettings::get_global(cx).clone();
19107 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19108 EditorSettings::override_global(editor_settings, cx);
19109 }
19110
19111 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19112 if let Some(show_line_numbers) = self.show_line_numbers {
19113 return show_line_numbers;
19114 }
19115 EditorSettings::get_global(cx).gutter.line_numbers
19116 }
19117
19118 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19119 self.use_relative_line_numbers
19120 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19121 }
19122
19123 pub fn toggle_relative_line_numbers(
19124 &mut self,
19125 _: &ToggleRelativeLineNumbers,
19126 _: &mut Window,
19127 cx: &mut Context<Self>,
19128 ) {
19129 let is_relative = self.should_use_relative_line_numbers(cx);
19130 self.set_relative_line_number(Some(!is_relative), cx)
19131 }
19132
19133 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19134 self.use_relative_line_numbers = is_relative;
19135 cx.notify();
19136 }
19137
19138 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19139 self.show_gutter = show_gutter;
19140 cx.notify();
19141 }
19142
19143 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19144 self.show_scrollbars = ScrollbarAxes {
19145 horizontal: show,
19146 vertical: show,
19147 };
19148 cx.notify();
19149 }
19150
19151 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19152 self.show_scrollbars.vertical = show;
19153 cx.notify();
19154 }
19155
19156 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19157 self.show_scrollbars.horizontal = show;
19158 cx.notify();
19159 }
19160
19161 pub fn set_minimap_visibility(
19162 &mut self,
19163 minimap_visibility: MinimapVisibility,
19164 window: &mut Window,
19165 cx: &mut Context<Self>,
19166 ) {
19167 if self.minimap_visibility != minimap_visibility {
19168 if minimap_visibility.visible() && self.minimap.is_none() {
19169 let minimap_settings = EditorSettings::get_global(cx).minimap;
19170 self.minimap =
19171 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19172 }
19173 self.minimap_visibility = minimap_visibility;
19174 cx.notify();
19175 }
19176 }
19177
19178 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19179 self.set_show_scrollbars(false, cx);
19180 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19181 }
19182
19183 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19184 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19185 }
19186
19187 /// Normally the text in full mode and auto height editors is padded on the
19188 /// left side by roughly half a character width for improved hit testing.
19189 ///
19190 /// Use this method to disable this for cases where this is not wanted (e.g.
19191 /// if you want to align the editor text with some other text above or below)
19192 /// or if you want to add this padding to single-line editors.
19193 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19194 self.offset_content = offset_content;
19195 cx.notify();
19196 }
19197
19198 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19199 self.show_line_numbers = Some(show_line_numbers);
19200 cx.notify();
19201 }
19202
19203 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19204 self.disable_expand_excerpt_buttons = true;
19205 cx.notify();
19206 }
19207
19208 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19209 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19210 cx.notify();
19211 }
19212
19213 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19214 self.show_code_actions = Some(show_code_actions);
19215 cx.notify();
19216 }
19217
19218 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19219 self.show_runnables = Some(show_runnables);
19220 cx.notify();
19221 }
19222
19223 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19224 self.show_breakpoints = Some(show_breakpoints);
19225 cx.notify();
19226 }
19227
19228 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19229 if self.display_map.read(cx).masked != masked {
19230 self.display_map.update(cx, |map, _| map.masked = masked);
19231 }
19232 cx.notify()
19233 }
19234
19235 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19236 self.show_wrap_guides = Some(show_wrap_guides);
19237 cx.notify();
19238 }
19239
19240 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19241 self.show_indent_guides = Some(show_indent_guides);
19242 cx.notify();
19243 }
19244
19245 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19246 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19247 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19248 && let Some(dir) = file.abs_path(cx).parent()
19249 {
19250 return Some(dir.to_owned());
19251 }
19252
19253 if let Some(project_path) = buffer.read(cx).project_path(cx) {
19254 return Some(project_path.path.to_path_buf());
19255 }
19256 }
19257
19258 None
19259 }
19260
19261 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19262 self.active_excerpt(cx)?
19263 .1
19264 .read(cx)
19265 .file()
19266 .and_then(|f| f.as_local())
19267 }
19268
19269 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19270 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19271 let buffer = buffer.read(cx);
19272 if let Some(project_path) = buffer.project_path(cx) {
19273 let project = self.project()?.read(cx);
19274 project.absolute_path(&project_path, cx)
19275 } else {
19276 buffer
19277 .file()
19278 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19279 }
19280 })
19281 }
19282
19283 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19284 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19285 let project_path = buffer.read(cx).project_path(cx)?;
19286 let project = self.project()?.read(cx);
19287 let entry = project.entry_for_path(&project_path, cx)?;
19288 let path = entry.path.to_path_buf();
19289 Some(path)
19290 })
19291 }
19292
19293 pub fn reveal_in_finder(
19294 &mut self,
19295 _: &RevealInFileManager,
19296 _window: &mut Window,
19297 cx: &mut Context<Self>,
19298 ) {
19299 if let Some(target) = self.target_file(cx) {
19300 cx.reveal_path(&target.abs_path(cx));
19301 }
19302 }
19303
19304 pub fn copy_path(
19305 &mut self,
19306 _: &zed_actions::workspace::CopyPath,
19307 _window: &mut Window,
19308 cx: &mut Context<Self>,
19309 ) {
19310 if let Some(path) = self.target_file_abs_path(cx)
19311 && let Some(path) = path.to_str()
19312 {
19313 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19314 } else {
19315 cx.propagate();
19316 }
19317 }
19318
19319 pub fn copy_relative_path(
19320 &mut self,
19321 _: &zed_actions::workspace::CopyRelativePath,
19322 _window: &mut Window,
19323 cx: &mut Context<Self>,
19324 ) {
19325 if let Some(path) = self.target_file_path(cx)
19326 && let Some(path) = path.to_str()
19327 {
19328 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19329 } else {
19330 cx.propagate();
19331 }
19332 }
19333
19334 /// Returns the project path for the editor's buffer, if any buffer is
19335 /// opened in the editor.
19336 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19337 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19338 buffer.read(cx).project_path(cx)
19339 } else {
19340 None
19341 }
19342 }
19343
19344 // Returns true if the editor handled a go-to-line request
19345 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19346 maybe!({
19347 let breakpoint_store = self.breakpoint_store.as_ref()?;
19348
19349 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19350 else {
19351 self.clear_row_highlights::<ActiveDebugLine>();
19352 return None;
19353 };
19354
19355 let position = active_stack_frame.position;
19356 let buffer_id = position.buffer_id?;
19357 let snapshot = self
19358 .project
19359 .as_ref()?
19360 .read(cx)
19361 .buffer_for_id(buffer_id, cx)?
19362 .read(cx)
19363 .snapshot();
19364
19365 let mut handled = false;
19366 for (id, ExcerptRange { context, .. }) in
19367 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19368 {
19369 if context.start.cmp(&position, &snapshot).is_ge()
19370 || context.end.cmp(&position, &snapshot).is_lt()
19371 {
19372 continue;
19373 }
19374 let snapshot = self.buffer.read(cx).snapshot(cx);
19375 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19376
19377 handled = true;
19378 self.clear_row_highlights::<ActiveDebugLine>();
19379
19380 self.go_to_line::<ActiveDebugLine>(
19381 multibuffer_anchor,
19382 Some(cx.theme().colors().editor_debugger_active_line_background),
19383 window,
19384 cx,
19385 );
19386
19387 cx.notify();
19388 }
19389
19390 handled.then_some(())
19391 })
19392 .is_some()
19393 }
19394
19395 pub fn copy_file_name_without_extension(
19396 &mut self,
19397 _: &CopyFileNameWithoutExtension,
19398 _: &mut Window,
19399 cx: &mut Context<Self>,
19400 ) {
19401 if let Some(file) = self.target_file(cx)
19402 && let Some(file_stem) = file.path().file_stem()
19403 && let Some(name) = file_stem.to_str()
19404 {
19405 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19406 }
19407 }
19408
19409 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19410 if let Some(file) = self.target_file(cx)
19411 && let Some(file_name) = file.path().file_name()
19412 && let Some(name) = file_name.to_str()
19413 {
19414 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19415 }
19416 }
19417
19418 pub fn toggle_git_blame(
19419 &mut self,
19420 _: &::git::Blame,
19421 window: &mut Window,
19422 cx: &mut Context<Self>,
19423 ) {
19424 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19425
19426 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19427 self.start_git_blame(true, window, cx);
19428 }
19429
19430 cx.notify();
19431 }
19432
19433 pub fn toggle_git_blame_inline(
19434 &mut self,
19435 _: &ToggleGitBlameInline,
19436 window: &mut Window,
19437 cx: &mut Context<Self>,
19438 ) {
19439 self.toggle_git_blame_inline_internal(true, window, cx);
19440 cx.notify();
19441 }
19442
19443 pub fn open_git_blame_commit(
19444 &mut self,
19445 _: &OpenGitBlameCommit,
19446 window: &mut Window,
19447 cx: &mut Context<Self>,
19448 ) {
19449 self.open_git_blame_commit_internal(window, cx);
19450 }
19451
19452 fn open_git_blame_commit_internal(
19453 &mut self,
19454 window: &mut Window,
19455 cx: &mut Context<Self>,
19456 ) -> Option<()> {
19457 let blame = self.blame.as_ref()?;
19458 let snapshot = self.snapshot(window, cx);
19459 let cursor = self.selections.newest::<Point>(cx).head();
19460 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
19461 let (_, blame_entry) = blame
19462 .update(cx, |blame, cx| {
19463 blame
19464 .blame_for_rows(
19465 &[RowInfo {
19466 buffer_id: Some(buffer.remote_id()),
19467 buffer_row: Some(point.row),
19468 ..Default::default()
19469 }],
19470 cx,
19471 )
19472 .next()
19473 })
19474 .flatten()?;
19475 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19476 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19477 let workspace = self.workspace()?.downgrade();
19478 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19479 None
19480 }
19481
19482 pub fn git_blame_inline_enabled(&self) -> bool {
19483 self.git_blame_inline_enabled
19484 }
19485
19486 pub fn toggle_selection_menu(
19487 &mut self,
19488 _: &ToggleSelectionMenu,
19489 _: &mut Window,
19490 cx: &mut Context<Self>,
19491 ) {
19492 self.show_selection_menu = self
19493 .show_selection_menu
19494 .map(|show_selections_menu| !show_selections_menu)
19495 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19496
19497 cx.notify();
19498 }
19499
19500 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19501 self.show_selection_menu
19502 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19503 }
19504
19505 fn start_git_blame(
19506 &mut self,
19507 user_triggered: bool,
19508 window: &mut Window,
19509 cx: &mut Context<Self>,
19510 ) {
19511 if let Some(project) = self.project() {
19512 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19513 && buffer.read(cx).file().is_none()
19514 {
19515 return;
19516 }
19517
19518 let focused = self.focus_handle(cx).contains_focused(window, cx);
19519
19520 let project = project.clone();
19521 let blame = cx
19522 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19523 self.blame_subscription =
19524 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19525 self.blame = Some(blame);
19526 }
19527 }
19528
19529 fn toggle_git_blame_inline_internal(
19530 &mut self,
19531 user_triggered: bool,
19532 window: &mut Window,
19533 cx: &mut Context<Self>,
19534 ) {
19535 if self.git_blame_inline_enabled {
19536 self.git_blame_inline_enabled = false;
19537 self.show_git_blame_inline = false;
19538 self.show_git_blame_inline_delay_task.take();
19539 } else {
19540 self.git_blame_inline_enabled = true;
19541 self.start_git_blame_inline(user_triggered, window, cx);
19542 }
19543
19544 cx.notify();
19545 }
19546
19547 fn start_git_blame_inline(
19548 &mut self,
19549 user_triggered: bool,
19550 window: &mut Window,
19551 cx: &mut Context<Self>,
19552 ) {
19553 self.start_git_blame(user_triggered, window, cx);
19554
19555 if ProjectSettings::get_global(cx)
19556 .git
19557 .inline_blame_delay()
19558 .is_some()
19559 {
19560 self.start_inline_blame_timer(window, cx);
19561 } else {
19562 self.show_git_blame_inline = true
19563 }
19564 }
19565
19566 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19567 self.blame.as_ref()
19568 }
19569
19570 pub fn show_git_blame_gutter(&self) -> bool {
19571 self.show_git_blame_gutter
19572 }
19573
19574 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19575 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19576 }
19577
19578 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19579 self.show_git_blame_inline
19580 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19581 && !self.newest_selection_head_on_empty_line(cx)
19582 && self.has_blame_entries(cx)
19583 }
19584
19585 fn has_blame_entries(&self, cx: &App) -> bool {
19586 self.blame()
19587 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19588 }
19589
19590 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19591 let cursor_anchor = self.selections.newest_anchor().head();
19592
19593 let snapshot = self.buffer.read(cx).snapshot(cx);
19594 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19595
19596 snapshot.line_len(buffer_row) == 0
19597 }
19598
19599 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19600 let buffer_and_selection = maybe!({
19601 let selection = self.selections.newest::<Point>(cx);
19602 let selection_range = selection.range();
19603
19604 let multi_buffer = self.buffer().read(cx);
19605 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19606 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19607
19608 let (buffer, range, _) = if selection.reversed {
19609 buffer_ranges.first()
19610 } else {
19611 buffer_ranges.last()
19612 }?;
19613
19614 let selection = text::ToPoint::to_point(&range.start, buffer).row
19615 ..text::ToPoint::to_point(&range.end, buffer).row;
19616 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19617 });
19618
19619 let Some((buffer, selection)) = buffer_and_selection else {
19620 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19621 };
19622
19623 let Some(project) = self.project() else {
19624 return Task::ready(Err(anyhow!("editor does not have project")));
19625 };
19626
19627 project.update(cx, |project, cx| {
19628 project.get_permalink_to_line(&buffer, selection, cx)
19629 })
19630 }
19631
19632 pub fn copy_permalink_to_line(
19633 &mut self,
19634 _: &CopyPermalinkToLine,
19635 window: &mut Window,
19636 cx: &mut Context<Self>,
19637 ) {
19638 let permalink_task = self.get_permalink_to_line(cx);
19639 let workspace = self.workspace();
19640
19641 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19642 Ok(permalink) => {
19643 cx.update(|_, cx| {
19644 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19645 })
19646 .ok();
19647 }
19648 Err(err) => {
19649 let message = format!("Failed to copy permalink: {err}");
19650
19651 anyhow::Result::<()>::Err(err).log_err();
19652
19653 if let Some(workspace) = workspace {
19654 workspace
19655 .update_in(cx, |workspace, _, cx| {
19656 struct CopyPermalinkToLine;
19657
19658 workspace.show_toast(
19659 Toast::new(
19660 NotificationId::unique::<CopyPermalinkToLine>(),
19661 message,
19662 ),
19663 cx,
19664 )
19665 })
19666 .ok();
19667 }
19668 }
19669 })
19670 .detach();
19671 }
19672
19673 pub fn copy_file_location(
19674 &mut self,
19675 _: &CopyFileLocation,
19676 _: &mut Window,
19677 cx: &mut Context<Self>,
19678 ) {
19679 let selection = self.selections.newest::<Point>(cx).start.row + 1;
19680 if let Some(file) = self.target_file(cx)
19681 && let Some(path) = file.path().to_str()
19682 {
19683 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19684 }
19685 }
19686
19687 pub fn open_permalink_to_line(
19688 &mut self,
19689 _: &OpenPermalinkToLine,
19690 window: &mut Window,
19691 cx: &mut Context<Self>,
19692 ) {
19693 let permalink_task = self.get_permalink_to_line(cx);
19694 let workspace = self.workspace();
19695
19696 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19697 Ok(permalink) => {
19698 cx.update(|_, cx| {
19699 cx.open_url(permalink.as_ref());
19700 })
19701 .ok();
19702 }
19703 Err(err) => {
19704 let message = format!("Failed to open permalink: {err}");
19705
19706 anyhow::Result::<()>::Err(err).log_err();
19707
19708 if let Some(workspace) = workspace {
19709 workspace
19710 .update(cx, |workspace, cx| {
19711 struct OpenPermalinkToLine;
19712
19713 workspace.show_toast(
19714 Toast::new(
19715 NotificationId::unique::<OpenPermalinkToLine>(),
19716 message,
19717 ),
19718 cx,
19719 )
19720 })
19721 .ok();
19722 }
19723 }
19724 })
19725 .detach();
19726 }
19727
19728 pub fn insert_uuid_v4(
19729 &mut self,
19730 _: &InsertUuidV4,
19731 window: &mut Window,
19732 cx: &mut Context<Self>,
19733 ) {
19734 self.insert_uuid(UuidVersion::V4, window, cx);
19735 }
19736
19737 pub fn insert_uuid_v7(
19738 &mut self,
19739 _: &InsertUuidV7,
19740 window: &mut Window,
19741 cx: &mut Context<Self>,
19742 ) {
19743 self.insert_uuid(UuidVersion::V7, window, cx);
19744 }
19745
19746 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19747 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19748 self.transact(window, cx, |this, window, cx| {
19749 let edits = this
19750 .selections
19751 .all::<Point>(cx)
19752 .into_iter()
19753 .map(|selection| {
19754 let uuid = match version {
19755 UuidVersion::V4 => uuid::Uuid::new_v4(),
19756 UuidVersion::V7 => uuid::Uuid::now_v7(),
19757 };
19758
19759 (selection.range(), uuid.to_string())
19760 });
19761 this.edit(edits, cx);
19762 this.refresh_edit_prediction(true, false, window, cx);
19763 });
19764 }
19765
19766 pub fn open_selections_in_multibuffer(
19767 &mut self,
19768 _: &OpenSelectionsInMultibuffer,
19769 window: &mut Window,
19770 cx: &mut Context<Self>,
19771 ) {
19772 let multibuffer = self.buffer.read(cx);
19773
19774 let Some(buffer) = multibuffer.as_singleton() else {
19775 return;
19776 };
19777
19778 let Some(workspace) = self.workspace() else {
19779 return;
19780 };
19781
19782 let title = multibuffer.title(cx).to_string();
19783
19784 let locations = self
19785 .selections
19786 .all_anchors(cx)
19787 .iter()
19788 .map(|selection| {
19789 (
19790 buffer.clone(),
19791 (selection.start.text_anchor..selection.end.text_anchor)
19792 .to_point(buffer.read(cx)),
19793 )
19794 })
19795 .into_group_map();
19796
19797 cx.spawn_in(window, async move |_, cx| {
19798 workspace.update_in(cx, |workspace, window, cx| {
19799 Self::open_locations_in_multibuffer(
19800 workspace,
19801 locations,
19802 format!("Selections for '{title}'"),
19803 false,
19804 MultibufferSelectionMode::All,
19805 window,
19806 cx,
19807 );
19808 })
19809 })
19810 .detach();
19811 }
19812
19813 /// Adds a row highlight for the given range. If a row has multiple highlights, the
19814 /// last highlight added will be used.
19815 ///
19816 /// If the range ends at the beginning of a line, then that line will not be highlighted.
19817 pub fn highlight_rows<T: 'static>(
19818 &mut self,
19819 range: Range<Anchor>,
19820 color: Hsla,
19821 options: RowHighlightOptions,
19822 cx: &mut Context<Self>,
19823 ) {
19824 let snapshot = self.buffer().read(cx).snapshot(cx);
19825 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19826 let ix = row_highlights.binary_search_by(|highlight| {
19827 Ordering::Equal
19828 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
19829 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
19830 });
19831
19832 if let Err(mut ix) = ix {
19833 let index = post_inc(&mut self.highlight_order);
19834
19835 // If this range intersects with the preceding highlight, then merge it with
19836 // the preceding highlight. Otherwise insert a new highlight.
19837 let mut merged = false;
19838 if ix > 0 {
19839 let prev_highlight = &mut row_highlights[ix - 1];
19840 if prev_highlight
19841 .range
19842 .end
19843 .cmp(&range.start, &snapshot)
19844 .is_ge()
19845 {
19846 ix -= 1;
19847 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
19848 prev_highlight.range.end = range.end;
19849 }
19850 merged = true;
19851 prev_highlight.index = index;
19852 prev_highlight.color = color;
19853 prev_highlight.options = options;
19854 }
19855 }
19856
19857 if !merged {
19858 row_highlights.insert(
19859 ix,
19860 RowHighlight {
19861 range,
19862 index,
19863 color,
19864 options,
19865 type_id: TypeId::of::<T>(),
19866 },
19867 );
19868 }
19869
19870 // If any of the following highlights intersect with this one, merge them.
19871 while let Some(next_highlight) = row_highlights.get(ix + 1) {
19872 let highlight = &row_highlights[ix];
19873 if next_highlight
19874 .range
19875 .start
19876 .cmp(&highlight.range.end, &snapshot)
19877 .is_le()
19878 {
19879 if next_highlight
19880 .range
19881 .end
19882 .cmp(&highlight.range.end, &snapshot)
19883 .is_gt()
19884 {
19885 row_highlights[ix].range.end = next_highlight.range.end;
19886 }
19887 row_highlights.remove(ix + 1);
19888 } else {
19889 break;
19890 }
19891 }
19892 }
19893 }
19894
19895 /// Remove any highlighted row ranges of the given type that intersect the
19896 /// given ranges.
19897 pub fn remove_highlighted_rows<T: 'static>(
19898 &mut self,
19899 ranges_to_remove: Vec<Range<Anchor>>,
19900 cx: &mut Context<Self>,
19901 ) {
19902 let snapshot = self.buffer().read(cx).snapshot(cx);
19903 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
19904 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
19905 row_highlights.retain(|highlight| {
19906 while let Some(range_to_remove) = ranges_to_remove.peek() {
19907 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
19908 Ordering::Less | Ordering::Equal => {
19909 ranges_to_remove.next();
19910 }
19911 Ordering::Greater => {
19912 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
19913 Ordering::Less | Ordering::Equal => {
19914 return false;
19915 }
19916 Ordering::Greater => break,
19917 }
19918 }
19919 }
19920 }
19921
19922 true
19923 })
19924 }
19925
19926 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
19927 pub fn clear_row_highlights<T: 'static>(&mut self) {
19928 self.highlighted_rows.remove(&TypeId::of::<T>());
19929 }
19930
19931 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
19932 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
19933 self.highlighted_rows
19934 .get(&TypeId::of::<T>())
19935 .map_or(&[] as &[_], |vec| vec.as_slice())
19936 .iter()
19937 .map(|highlight| (highlight.range.clone(), highlight.color))
19938 }
19939
19940 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
19941 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
19942 /// Allows to ignore certain kinds of highlights.
19943 pub fn highlighted_display_rows(
19944 &self,
19945 window: &mut Window,
19946 cx: &mut App,
19947 ) -> BTreeMap<DisplayRow, LineHighlight> {
19948 let snapshot = self.snapshot(window, cx);
19949 let mut used_highlight_orders = HashMap::default();
19950 self.highlighted_rows
19951 .iter()
19952 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
19953 .fold(
19954 BTreeMap::<DisplayRow, LineHighlight>::new(),
19955 |mut unique_rows, highlight| {
19956 let start = highlight.range.start.to_display_point(&snapshot);
19957 let end = highlight.range.end.to_display_point(&snapshot);
19958 let start_row = start.row().0;
19959 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
19960 && end.column() == 0
19961 {
19962 end.row().0.saturating_sub(1)
19963 } else {
19964 end.row().0
19965 };
19966 for row in start_row..=end_row {
19967 let used_index =
19968 used_highlight_orders.entry(row).or_insert(highlight.index);
19969 if highlight.index >= *used_index {
19970 *used_index = highlight.index;
19971 unique_rows.insert(
19972 DisplayRow(row),
19973 LineHighlight {
19974 include_gutter: highlight.options.include_gutter,
19975 border: None,
19976 background: highlight.color.into(),
19977 type_id: Some(highlight.type_id),
19978 },
19979 );
19980 }
19981 }
19982 unique_rows
19983 },
19984 )
19985 }
19986
19987 pub fn highlighted_display_row_for_autoscroll(
19988 &self,
19989 snapshot: &DisplaySnapshot,
19990 ) -> Option<DisplayRow> {
19991 self.highlighted_rows
19992 .values()
19993 .flat_map(|highlighted_rows| highlighted_rows.iter())
19994 .filter_map(|highlight| {
19995 if highlight.options.autoscroll {
19996 Some(highlight.range.start.to_display_point(snapshot).row())
19997 } else {
19998 None
19999 }
20000 })
20001 .min()
20002 }
20003
20004 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20005 self.highlight_background::<SearchWithinRange>(
20006 ranges,
20007 |colors| colors.colors().editor_document_highlight_read_background,
20008 cx,
20009 )
20010 }
20011
20012 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20013 self.breadcrumb_header = Some(new_header);
20014 }
20015
20016 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20017 self.clear_background_highlights::<SearchWithinRange>(cx);
20018 }
20019
20020 pub fn highlight_background<T: 'static>(
20021 &mut self,
20022 ranges: &[Range<Anchor>],
20023 color_fetcher: fn(&Theme) -> Hsla,
20024 cx: &mut Context<Self>,
20025 ) {
20026 self.background_highlights.insert(
20027 HighlightKey::Type(TypeId::of::<T>()),
20028 (color_fetcher, Arc::from(ranges)),
20029 );
20030 self.scrollbar_marker_state.dirty = true;
20031 cx.notify();
20032 }
20033
20034 pub fn highlight_background_key<T: 'static>(
20035 &mut self,
20036 key: usize,
20037 ranges: &[Range<Anchor>],
20038 color_fetcher: fn(&Theme) -> Hsla,
20039 cx: &mut Context<Self>,
20040 ) {
20041 self.background_highlights.insert(
20042 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20043 (color_fetcher, Arc::from(ranges)),
20044 );
20045 self.scrollbar_marker_state.dirty = true;
20046 cx.notify();
20047 }
20048
20049 pub fn clear_background_highlights<T: 'static>(
20050 &mut self,
20051 cx: &mut Context<Self>,
20052 ) -> Option<BackgroundHighlight> {
20053 let text_highlights = self
20054 .background_highlights
20055 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20056 if !text_highlights.1.is_empty() {
20057 self.scrollbar_marker_state.dirty = true;
20058 cx.notify();
20059 }
20060 Some(text_highlights)
20061 }
20062
20063 pub fn highlight_gutter<T: 'static>(
20064 &mut self,
20065 ranges: impl Into<Vec<Range<Anchor>>>,
20066 color_fetcher: fn(&App) -> Hsla,
20067 cx: &mut Context<Self>,
20068 ) {
20069 self.gutter_highlights
20070 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20071 cx.notify();
20072 }
20073
20074 pub fn clear_gutter_highlights<T: 'static>(
20075 &mut self,
20076 cx: &mut Context<Self>,
20077 ) -> Option<GutterHighlight> {
20078 cx.notify();
20079 self.gutter_highlights.remove(&TypeId::of::<T>())
20080 }
20081
20082 pub fn insert_gutter_highlight<T: 'static>(
20083 &mut self,
20084 range: Range<Anchor>,
20085 color_fetcher: fn(&App) -> Hsla,
20086 cx: &mut Context<Self>,
20087 ) {
20088 let snapshot = self.buffer().read(cx).snapshot(cx);
20089 let mut highlights = self
20090 .gutter_highlights
20091 .remove(&TypeId::of::<T>())
20092 .map(|(_, highlights)| highlights)
20093 .unwrap_or_default();
20094 let ix = highlights.binary_search_by(|highlight| {
20095 Ordering::Equal
20096 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20097 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20098 });
20099 if let Err(ix) = ix {
20100 highlights.insert(ix, range);
20101 }
20102 self.gutter_highlights
20103 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20104 }
20105
20106 pub fn remove_gutter_highlights<T: 'static>(
20107 &mut self,
20108 ranges_to_remove: Vec<Range<Anchor>>,
20109 cx: &mut Context<Self>,
20110 ) {
20111 let snapshot = self.buffer().read(cx).snapshot(cx);
20112 let Some((color_fetcher, mut gutter_highlights)) =
20113 self.gutter_highlights.remove(&TypeId::of::<T>())
20114 else {
20115 return;
20116 };
20117 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20118 gutter_highlights.retain(|highlight| {
20119 while let Some(range_to_remove) = ranges_to_remove.peek() {
20120 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20121 Ordering::Less | Ordering::Equal => {
20122 ranges_to_remove.next();
20123 }
20124 Ordering::Greater => {
20125 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20126 Ordering::Less | Ordering::Equal => {
20127 return false;
20128 }
20129 Ordering::Greater => break,
20130 }
20131 }
20132 }
20133 }
20134
20135 true
20136 });
20137 self.gutter_highlights
20138 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20139 }
20140
20141 #[cfg(feature = "test-support")]
20142 pub fn all_text_highlights(
20143 &self,
20144 window: &mut Window,
20145 cx: &mut Context<Self>,
20146 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20147 let snapshot = self.snapshot(window, cx);
20148 self.display_map.update(cx, |display_map, _| {
20149 display_map
20150 .all_text_highlights()
20151 .map(|highlight| {
20152 let (style, ranges) = highlight.as_ref();
20153 (
20154 *style,
20155 ranges
20156 .iter()
20157 .map(|range| range.clone().to_display_points(&snapshot))
20158 .collect(),
20159 )
20160 })
20161 .collect()
20162 })
20163 }
20164
20165 #[cfg(feature = "test-support")]
20166 pub fn all_text_background_highlights(
20167 &self,
20168 window: &mut Window,
20169 cx: &mut Context<Self>,
20170 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20171 let snapshot = self.snapshot(window, cx);
20172 let buffer = &snapshot.buffer_snapshot;
20173 let start = buffer.anchor_before(0);
20174 let end = buffer.anchor_after(buffer.len());
20175 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20176 }
20177
20178 #[cfg(any(test, feature = "test-support"))]
20179 pub fn sorted_background_highlights_in_range(
20180 &self,
20181 search_range: Range<Anchor>,
20182 display_snapshot: &DisplaySnapshot,
20183 theme: &Theme,
20184 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20185 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20186 res.sort_by(|a, b| {
20187 a.0.start
20188 .cmp(&b.0.start)
20189 .then_with(|| a.0.end.cmp(&b.0.end))
20190 .then_with(|| a.1.cmp(&b.1))
20191 });
20192 res
20193 }
20194
20195 #[cfg(feature = "test-support")]
20196 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20197 let snapshot = self.buffer().read(cx).snapshot(cx);
20198
20199 let highlights = self
20200 .background_highlights
20201 .get(&HighlightKey::Type(TypeId::of::<
20202 items::BufferSearchHighlights,
20203 >()));
20204
20205 if let Some((_color, ranges)) = highlights {
20206 ranges
20207 .iter()
20208 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20209 .collect_vec()
20210 } else {
20211 vec![]
20212 }
20213 }
20214
20215 fn document_highlights_for_position<'a>(
20216 &'a self,
20217 position: Anchor,
20218 buffer: &'a MultiBufferSnapshot,
20219 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20220 let read_highlights = self
20221 .background_highlights
20222 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20223 .map(|h| &h.1);
20224 let write_highlights = self
20225 .background_highlights
20226 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20227 .map(|h| &h.1);
20228 let left_position = position.bias_left(buffer);
20229 let right_position = position.bias_right(buffer);
20230 read_highlights
20231 .into_iter()
20232 .chain(write_highlights)
20233 .flat_map(move |ranges| {
20234 let start_ix = match ranges.binary_search_by(|probe| {
20235 let cmp = probe.end.cmp(&left_position, buffer);
20236 if cmp.is_ge() {
20237 Ordering::Greater
20238 } else {
20239 Ordering::Less
20240 }
20241 }) {
20242 Ok(i) | Err(i) => i,
20243 };
20244
20245 ranges[start_ix..]
20246 .iter()
20247 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20248 })
20249 }
20250
20251 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20252 self.background_highlights
20253 .get(&HighlightKey::Type(TypeId::of::<T>()))
20254 .is_some_and(|(_, highlights)| !highlights.is_empty())
20255 }
20256
20257 /// Returns all background highlights for a given range.
20258 ///
20259 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20260 pub fn background_highlights_in_range(
20261 &self,
20262 search_range: Range<Anchor>,
20263 display_snapshot: &DisplaySnapshot,
20264 theme: &Theme,
20265 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20266 let mut results = Vec::new();
20267 for (color_fetcher, ranges) in self.background_highlights.values() {
20268 let color = color_fetcher(theme);
20269 let start_ix = match ranges.binary_search_by(|probe| {
20270 let cmp = probe
20271 .end
20272 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20273 if cmp.is_gt() {
20274 Ordering::Greater
20275 } else {
20276 Ordering::Less
20277 }
20278 }) {
20279 Ok(i) | Err(i) => i,
20280 };
20281 for range in &ranges[start_ix..] {
20282 if range
20283 .start
20284 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20285 .is_ge()
20286 {
20287 break;
20288 }
20289
20290 let start = range.start.to_display_point(display_snapshot);
20291 let end = range.end.to_display_point(display_snapshot);
20292 results.push((start..end, color))
20293 }
20294 }
20295 results
20296 }
20297
20298 pub fn gutter_highlights_in_range(
20299 &self,
20300 search_range: Range<Anchor>,
20301 display_snapshot: &DisplaySnapshot,
20302 cx: &App,
20303 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20304 let mut results = Vec::new();
20305 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20306 let color = color_fetcher(cx);
20307 let start_ix = match ranges.binary_search_by(|probe| {
20308 let cmp = probe
20309 .end
20310 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
20311 if cmp.is_gt() {
20312 Ordering::Greater
20313 } else {
20314 Ordering::Less
20315 }
20316 }) {
20317 Ok(i) | Err(i) => i,
20318 };
20319 for range in &ranges[start_ix..] {
20320 if range
20321 .start
20322 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
20323 .is_ge()
20324 {
20325 break;
20326 }
20327
20328 let start = range.start.to_display_point(display_snapshot);
20329 let end = range.end.to_display_point(display_snapshot);
20330 results.push((start..end, color))
20331 }
20332 }
20333 results
20334 }
20335
20336 /// Get the text ranges corresponding to the redaction query
20337 pub fn redacted_ranges(
20338 &self,
20339 search_range: Range<Anchor>,
20340 display_snapshot: &DisplaySnapshot,
20341 cx: &App,
20342 ) -> Vec<Range<DisplayPoint>> {
20343 display_snapshot
20344 .buffer_snapshot
20345 .redacted_ranges(search_range, |file| {
20346 if let Some(file) = file {
20347 file.is_private()
20348 && EditorSettings::get(
20349 Some(SettingsLocation {
20350 worktree_id: file.worktree_id(cx),
20351 path: file.path().as_ref(),
20352 }),
20353 cx,
20354 )
20355 .redact_private_values
20356 } else {
20357 false
20358 }
20359 })
20360 .map(|range| {
20361 range.start.to_display_point(display_snapshot)
20362 ..range.end.to_display_point(display_snapshot)
20363 })
20364 .collect()
20365 }
20366
20367 pub fn highlight_text_key<T: 'static>(
20368 &mut self,
20369 key: usize,
20370 ranges: Vec<Range<Anchor>>,
20371 style: HighlightStyle,
20372 cx: &mut Context<Self>,
20373 ) {
20374 self.display_map.update(cx, |map, _| {
20375 map.highlight_text(
20376 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20377 ranges,
20378 style,
20379 );
20380 });
20381 cx.notify();
20382 }
20383
20384 pub fn highlight_text<T: 'static>(
20385 &mut self,
20386 ranges: Vec<Range<Anchor>>,
20387 style: HighlightStyle,
20388 cx: &mut Context<Self>,
20389 ) {
20390 self.display_map.update(cx, |map, _| {
20391 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20392 });
20393 cx.notify();
20394 }
20395
20396 pub(crate) fn highlight_inlays<T: 'static>(
20397 &mut self,
20398 highlights: Vec<InlayHighlight>,
20399 style: HighlightStyle,
20400 cx: &mut Context<Self>,
20401 ) {
20402 self.display_map.update(cx, |map, _| {
20403 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20404 });
20405 cx.notify();
20406 }
20407
20408 pub fn text_highlights<'a, T: 'static>(
20409 &'a self,
20410 cx: &'a App,
20411 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20412 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20413 }
20414
20415 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20416 let cleared = self
20417 .display_map
20418 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20419 if cleared {
20420 cx.notify();
20421 }
20422 }
20423
20424 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20425 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20426 && self.focus_handle.is_focused(window)
20427 }
20428
20429 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20430 self.show_cursor_when_unfocused = is_enabled;
20431 cx.notify();
20432 }
20433
20434 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20435 cx.notify();
20436 }
20437
20438 fn on_debug_session_event(
20439 &mut self,
20440 _session: Entity<Session>,
20441 event: &SessionEvent,
20442 cx: &mut Context<Self>,
20443 ) {
20444 if let SessionEvent::InvalidateInlineValue = event {
20445 self.refresh_inline_values(cx);
20446 }
20447 }
20448
20449 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20450 let Some(project) = self.project.clone() else {
20451 return;
20452 };
20453
20454 if !self.inline_value_cache.enabled {
20455 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20456 self.splice_inlays(&inlays, Vec::new(), cx);
20457 return;
20458 }
20459
20460 let current_execution_position = self
20461 .highlighted_rows
20462 .get(&TypeId::of::<ActiveDebugLine>())
20463 .and_then(|lines| lines.last().map(|line| line.range.end));
20464
20465 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20466 let inline_values = editor
20467 .update(cx, |editor, cx| {
20468 let Some(current_execution_position) = current_execution_position else {
20469 return Some(Task::ready(Ok(Vec::new())));
20470 };
20471
20472 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20473 let snapshot = buffer.snapshot(cx);
20474
20475 let excerpt = snapshot.excerpt_containing(
20476 current_execution_position..current_execution_position,
20477 )?;
20478
20479 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20480 })?;
20481
20482 let range =
20483 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20484
20485 project.inline_values(buffer, range, cx)
20486 })
20487 .ok()
20488 .flatten()?
20489 .await
20490 .context("refreshing debugger inlays")
20491 .log_err()?;
20492
20493 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20494
20495 for (buffer_id, inline_value) in inline_values
20496 .into_iter()
20497 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20498 {
20499 buffer_inline_values
20500 .entry(buffer_id)
20501 .or_default()
20502 .push(inline_value);
20503 }
20504
20505 editor
20506 .update(cx, |editor, cx| {
20507 let snapshot = editor.buffer.read(cx).snapshot(cx);
20508 let mut new_inlays = Vec::default();
20509
20510 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20511 let buffer_id = buffer_snapshot.remote_id();
20512 buffer_inline_values
20513 .get(&buffer_id)
20514 .into_iter()
20515 .flatten()
20516 .for_each(|hint| {
20517 let inlay = Inlay::debugger(
20518 post_inc(&mut editor.next_inlay_id),
20519 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20520 hint.text(),
20521 );
20522 if !inlay.text.chars().contains(&'\n') {
20523 new_inlays.push(inlay);
20524 }
20525 });
20526 }
20527
20528 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20529 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20530
20531 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20532 })
20533 .ok()?;
20534 Some(())
20535 });
20536 }
20537
20538 fn on_buffer_event(
20539 &mut self,
20540 multibuffer: &Entity<MultiBuffer>,
20541 event: &multi_buffer::Event,
20542 window: &mut Window,
20543 cx: &mut Context<Self>,
20544 ) {
20545 match event {
20546 multi_buffer::Event::Edited {
20547 singleton_buffer_edited,
20548 edited_buffer,
20549 } => {
20550 self.scrollbar_marker_state.dirty = true;
20551 self.active_indent_guides_state.dirty = true;
20552 self.refresh_active_diagnostics(cx);
20553 self.refresh_code_actions(window, cx);
20554 self.refresh_selected_text_highlights(true, window, cx);
20555 self.refresh_single_line_folds(window, cx);
20556 refresh_matching_bracket_highlights(self, window, cx);
20557 if self.has_active_edit_prediction() {
20558 self.update_visible_edit_prediction(window, cx);
20559 }
20560 if let Some(project) = self.project.as_ref()
20561 && let Some(edited_buffer) = edited_buffer
20562 {
20563 project.update(cx, |project, cx| {
20564 self.registered_buffers
20565 .entry(edited_buffer.read(cx).remote_id())
20566 .or_insert_with(|| {
20567 project.register_buffer_with_language_servers(edited_buffer, cx)
20568 });
20569 });
20570 }
20571 cx.emit(EditorEvent::BufferEdited);
20572 cx.emit(SearchEvent::MatchesInvalidated);
20573
20574 if let Some(buffer) = edited_buffer {
20575 self.update_lsp_data(false, Some(buffer.read(cx).remote_id()), window, cx);
20576 }
20577
20578 if *singleton_buffer_edited {
20579 if let Some(buffer) = edited_buffer
20580 && buffer.read(cx).file().is_none()
20581 {
20582 cx.emit(EditorEvent::TitleChanged);
20583 }
20584 if let Some(project) = &self.project {
20585 #[allow(clippy::mutable_key_type)]
20586 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20587 multibuffer
20588 .all_buffers()
20589 .into_iter()
20590 .filter_map(|buffer| {
20591 buffer.update(cx, |buffer, cx| {
20592 let language = buffer.language()?;
20593 let should_discard = project.update(cx, |project, cx| {
20594 project.is_local()
20595 && !project.has_language_servers_for(buffer, cx)
20596 });
20597 should_discard.not().then_some(language.clone())
20598 })
20599 })
20600 .collect::<HashSet<_>>()
20601 });
20602 if !languages_affected.is_empty() {
20603 self.refresh_inlay_hints(
20604 InlayHintRefreshReason::BufferEdited(languages_affected),
20605 cx,
20606 );
20607 }
20608 }
20609 }
20610
20611 let Some(project) = &self.project else { return };
20612 let (telemetry, is_via_ssh) = {
20613 let project = project.read(cx);
20614 let telemetry = project.client().telemetry().clone();
20615 let is_via_ssh = project.is_via_remote_server();
20616 (telemetry, is_via_ssh)
20617 };
20618 refresh_linked_ranges(self, window, cx);
20619 telemetry.log_edit_event("editor", is_via_ssh);
20620 }
20621 multi_buffer::Event::ExcerptsAdded {
20622 buffer,
20623 predecessor,
20624 excerpts,
20625 } => {
20626 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20627 let buffer_id = buffer.read(cx).remote_id();
20628 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20629 && let Some(project) = &self.project
20630 {
20631 update_uncommitted_diff_for_buffer(
20632 cx.entity(),
20633 project,
20634 [buffer.clone()],
20635 self.buffer.clone(),
20636 cx,
20637 )
20638 .detach();
20639 }
20640 if self.active_diagnostics != ActiveDiagnostic::All {
20641 self.update_lsp_data(false, Some(buffer_id), window, cx);
20642 }
20643 cx.emit(EditorEvent::ExcerptsAdded {
20644 buffer: buffer.clone(),
20645 predecessor: *predecessor,
20646 excerpts: excerpts.clone(),
20647 });
20648 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20649 }
20650 multi_buffer::Event::ExcerptsRemoved {
20651 ids,
20652 removed_buffer_ids,
20653 } => {
20654 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20655 let buffer = self.buffer.read(cx);
20656 self.registered_buffers
20657 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
20658 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20659 cx.emit(EditorEvent::ExcerptsRemoved {
20660 ids: ids.clone(),
20661 removed_buffer_ids: removed_buffer_ids.clone(),
20662 });
20663 }
20664 multi_buffer::Event::ExcerptsEdited {
20665 excerpt_ids,
20666 buffer_ids,
20667 } => {
20668 self.display_map.update(cx, |map, cx| {
20669 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20670 });
20671 cx.emit(EditorEvent::ExcerptsEdited {
20672 ids: excerpt_ids.clone(),
20673 });
20674 }
20675 multi_buffer::Event::ExcerptsExpanded { ids } => {
20676 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20677 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20678 }
20679 multi_buffer::Event::Reparsed(buffer_id) => {
20680 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20681 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20682
20683 cx.emit(EditorEvent::Reparsed(*buffer_id));
20684 }
20685 multi_buffer::Event::DiffHunksToggled => {
20686 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20687 }
20688 multi_buffer::Event::LanguageChanged(buffer_id) => {
20689 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
20690 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20691 cx.emit(EditorEvent::Reparsed(*buffer_id));
20692 cx.notify();
20693 }
20694 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20695 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20696 multi_buffer::Event::FileHandleChanged
20697 | multi_buffer::Event::Reloaded
20698 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20699 multi_buffer::Event::DiagnosticsUpdated => {
20700 self.update_diagnostics_state(window, cx);
20701 }
20702 _ => {}
20703 };
20704 }
20705
20706 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20707 if !self.diagnostics_enabled() {
20708 return;
20709 }
20710 self.refresh_active_diagnostics(cx);
20711 self.refresh_inline_diagnostics(true, window, cx);
20712 self.scrollbar_marker_state.dirty = true;
20713 cx.notify();
20714 }
20715
20716 pub fn start_temporary_diff_override(&mut self) {
20717 self.load_diff_task.take();
20718 self.temporary_diff_override = true;
20719 }
20720
20721 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20722 self.temporary_diff_override = false;
20723 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20724 self.buffer.update(cx, |buffer, cx| {
20725 buffer.set_all_diff_hunks_collapsed(cx);
20726 });
20727
20728 if let Some(project) = self.project.clone() {
20729 self.load_diff_task = Some(
20730 update_uncommitted_diff_for_buffer(
20731 cx.entity(),
20732 &project,
20733 self.buffer.read(cx).all_buffers(),
20734 self.buffer.clone(),
20735 cx,
20736 )
20737 .shared(),
20738 );
20739 }
20740 }
20741
20742 fn on_display_map_changed(
20743 &mut self,
20744 _: Entity<DisplayMap>,
20745 _: &mut Window,
20746 cx: &mut Context<Self>,
20747 ) {
20748 cx.notify();
20749 }
20750
20751 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20752 if self.diagnostics_enabled() {
20753 let new_severity = EditorSettings::get_global(cx)
20754 .diagnostics_max_severity
20755 .unwrap_or(DiagnosticSeverity::Hint);
20756 self.set_max_diagnostics_severity(new_severity, cx);
20757 }
20758 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20759 self.update_edit_prediction_settings(cx);
20760 self.refresh_edit_prediction(true, false, window, cx);
20761 self.refresh_inline_values(cx);
20762 self.refresh_inlay_hints(
20763 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20764 self.selections.newest_anchor().head(),
20765 &self.buffer.read(cx).snapshot(cx),
20766 cx,
20767 )),
20768 cx,
20769 );
20770
20771 let old_cursor_shape = self.cursor_shape;
20772 let old_show_breadcrumbs = self.show_breadcrumbs;
20773
20774 {
20775 let editor_settings = EditorSettings::get_global(cx);
20776 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20777 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20778 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20779 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20780 }
20781
20782 if old_cursor_shape != self.cursor_shape {
20783 cx.emit(EditorEvent::CursorShapeChanged);
20784 }
20785
20786 if old_show_breadcrumbs != self.show_breadcrumbs {
20787 cx.emit(EditorEvent::BreadcrumbsChanged);
20788 }
20789
20790 let project_settings = ProjectSettings::get_global(cx);
20791 self.serialize_dirty_buffers =
20792 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20793
20794 if self.mode.is_full() {
20795 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20796 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20797 if self.show_inline_diagnostics != show_inline_diagnostics {
20798 self.show_inline_diagnostics = show_inline_diagnostics;
20799 self.refresh_inline_diagnostics(false, window, cx);
20800 }
20801
20802 if self.git_blame_inline_enabled != inline_blame_enabled {
20803 self.toggle_git_blame_inline_internal(false, window, cx);
20804 }
20805
20806 let minimap_settings = EditorSettings::get_global(cx).minimap;
20807 if self.minimap_visibility != MinimapVisibility::Disabled {
20808 if self.minimap_visibility.settings_visibility()
20809 != minimap_settings.minimap_enabled()
20810 {
20811 self.set_minimap_visibility(
20812 MinimapVisibility::for_mode(self.mode(), cx),
20813 window,
20814 cx,
20815 );
20816 } else if let Some(minimap_entity) = self.minimap.as_ref() {
20817 minimap_entity.update(cx, |minimap_editor, cx| {
20818 minimap_editor.update_minimap_configuration(minimap_settings, cx)
20819 })
20820 }
20821 }
20822 }
20823
20824 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
20825 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
20826 }) {
20827 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
20828 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
20829 }
20830 self.refresh_colors(false, None, window, cx);
20831 }
20832
20833 cx.notify();
20834 }
20835
20836 pub fn set_searchable(&mut self, searchable: bool) {
20837 self.searchable = searchable;
20838 }
20839
20840 pub fn searchable(&self) -> bool {
20841 self.searchable
20842 }
20843
20844 fn open_proposed_changes_editor(
20845 &mut self,
20846 _: &OpenProposedChangesEditor,
20847 window: &mut Window,
20848 cx: &mut Context<Self>,
20849 ) {
20850 let Some(workspace) = self.workspace() else {
20851 cx.propagate();
20852 return;
20853 };
20854
20855 let selections = self.selections.all::<usize>(cx);
20856 let multi_buffer = self.buffer.read(cx);
20857 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20858 let mut new_selections_by_buffer = HashMap::default();
20859 for selection in selections {
20860 for (buffer, range, _) in
20861 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
20862 {
20863 let mut range = range.to_point(buffer);
20864 range.start.column = 0;
20865 range.end.column = buffer.line_len(range.end.row);
20866 new_selections_by_buffer
20867 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
20868 .or_insert(Vec::new())
20869 .push(range)
20870 }
20871 }
20872
20873 let proposed_changes_buffers = new_selections_by_buffer
20874 .into_iter()
20875 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
20876 .collect::<Vec<_>>();
20877 let proposed_changes_editor = cx.new(|cx| {
20878 ProposedChangesEditor::new(
20879 "Proposed changes",
20880 proposed_changes_buffers,
20881 self.project.clone(),
20882 window,
20883 cx,
20884 )
20885 });
20886
20887 window.defer(cx, move |window, cx| {
20888 workspace.update(cx, |workspace, cx| {
20889 workspace.active_pane().update(cx, |pane, cx| {
20890 pane.add_item(
20891 Box::new(proposed_changes_editor),
20892 true,
20893 true,
20894 None,
20895 window,
20896 cx,
20897 );
20898 });
20899 });
20900 });
20901 }
20902
20903 pub fn open_excerpts_in_split(
20904 &mut self,
20905 _: &OpenExcerptsSplit,
20906 window: &mut Window,
20907 cx: &mut Context<Self>,
20908 ) {
20909 self.open_excerpts_common(None, true, window, cx)
20910 }
20911
20912 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
20913 self.open_excerpts_common(None, false, window, cx)
20914 }
20915
20916 fn open_excerpts_common(
20917 &mut self,
20918 jump_data: Option<JumpData>,
20919 split: bool,
20920 window: &mut Window,
20921 cx: &mut Context<Self>,
20922 ) {
20923 let Some(workspace) = self.workspace() else {
20924 cx.propagate();
20925 return;
20926 };
20927
20928 if self.buffer.read(cx).is_singleton() {
20929 cx.propagate();
20930 return;
20931 }
20932
20933 let mut new_selections_by_buffer = HashMap::default();
20934 match &jump_data {
20935 Some(JumpData::MultiBufferPoint {
20936 excerpt_id,
20937 position,
20938 anchor,
20939 line_offset_from_top,
20940 }) => {
20941 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
20942 if let Some(buffer) = multi_buffer_snapshot
20943 .buffer_id_for_excerpt(*excerpt_id)
20944 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
20945 {
20946 let buffer_snapshot = buffer.read(cx).snapshot();
20947 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
20948 language::ToPoint::to_point(anchor, &buffer_snapshot)
20949 } else {
20950 buffer_snapshot.clip_point(*position, Bias::Left)
20951 };
20952 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
20953 new_selections_by_buffer.insert(
20954 buffer,
20955 (
20956 vec![jump_to_offset..jump_to_offset],
20957 Some(*line_offset_from_top),
20958 ),
20959 );
20960 }
20961 }
20962 Some(JumpData::MultiBufferRow {
20963 row,
20964 line_offset_from_top,
20965 }) => {
20966 let point = MultiBufferPoint::new(row.0, 0);
20967 if let Some((buffer, buffer_point, _)) =
20968 self.buffer.read(cx).point_to_buffer_point(point, cx)
20969 {
20970 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
20971 new_selections_by_buffer
20972 .entry(buffer)
20973 .or_insert((Vec::new(), Some(*line_offset_from_top)))
20974 .0
20975 .push(buffer_offset..buffer_offset)
20976 }
20977 }
20978 None => {
20979 let selections = self.selections.all::<usize>(cx);
20980 let multi_buffer = self.buffer.read(cx);
20981 for selection in selections {
20982 for (snapshot, range, _, anchor) in multi_buffer
20983 .snapshot(cx)
20984 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
20985 {
20986 if let Some(anchor) = anchor {
20987 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
20988 else {
20989 continue;
20990 };
20991 let offset = text::ToOffset::to_offset(
20992 &anchor.text_anchor,
20993 &buffer_handle.read(cx).snapshot(),
20994 );
20995 let range = offset..offset;
20996 new_selections_by_buffer
20997 .entry(buffer_handle)
20998 .or_insert((Vec::new(), None))
20999 .0
21000 .push(range)
21001 } else {
21002 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21003 else {
21004 continue;
21005 };
21006 new_selections_by_buffer
21007 .entry(buffer_handle)
21008 .or_insert((Vec::new(), None))
21009 .0
21010 .push(range)
21011 }
21012 }
21013 }
21014 }
21015 }
21016
21017 new_selections_by_buffer
21018 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21019
21020 if new_selections_by_buffer.is_empty() {
21021 return;
21022 }
21023
21024 // We defer the pane interaction because we ourselves are a workspace item
21025 // and activating a new item causes the pane to call a method on us reentrantly,
21026 // which panics if we're on the stack.
21027 window.defer(cx, move |window, cx| {
21028 workspace.update(cx, |workspace, cx| {
21029 let pane = if split {
21030 workspace.adjacent_pane(window, cx)
21031 } else {
21032 workspace.active_pane().clone()
21033 };
21034
21035 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21036 let editor = buffer
21037 .read(cx)
21038 .file()
21039 .is_none()
21040 .then(|| {
21041 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21042 // so `workspace.open_project_item` will never find them, always opening a new editor.
21043 // Instead, we try to activate the existing editor in the pane first.
21044 let (editor, pane_item_index) =
21045 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21046 let editor = item.downcast::<Editor>()?;
21047 let singleton_buffer =
21048 editor.read(cx).buffer().read(cx).as_singleton()?;
21049 if singleton_buffer == buffer {
21050 Some((editor, i))
21051 } else {
21052 None
21053 }
21054 })?;
21055 pane.update(cx, |pane, cx| {
21056 pane.activate_item(pane_item_index, true, true, window, cx)
21057 });
21058 Some(editor)
21059 })
21060 .flatten()
21061 .unwrap_or_else(|| {
21062 workspace.open_project_item::<Self>(
21063 pane.clone(),
21064 buffer,
21065 true,
21066 true,
21067 window,
21068 cx,
21069 )
21070 });
21071
21072 editor.update(cx, |editor, cx| {
21073 let autoscroll = match scroll_offset {
21074 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21075 None => Autoscroll::newest(),
21076 };
21077 let nav_history = editor.nav_history.take();
21078 editor.change_selections(
21079 SelectionEffects::scroll(autoscroll),
21080 window,
21081 cx,
21082 |s| {
21083 s.select_ranges(ranges);
21084 },
21085 );
21086 editor.nav_history = nav_history;
21087 });
21088 }
21089 })
21090 });
21091 }
21092
21093 // For now, don't allow opening excerpts in buffers that aren't backed by
21094 // regular project files.
21095 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21096 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21097 }
21098
21099 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21100 let snapshot = self.buffer.read(cx).read(cx);
21101 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21102 Some(
21103 ranges
21104 .iter()
21105 .map(move |range| {
21106 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21107 })
21108 .collect(),
21109 )
21110 }
21111
21112 fn selection_replacement_ranges(
21113 &self,
21114 range: Range<OffsetUtf16>,
21115 cx: &mut App,
21116 ) -> Vec<Range<OffsetUtf16>> {
21117 let selections = self.selections.all::<OffsetUtf16>(cx);
21118 let newest_selection = selections
21119 .iter()
21120 .max_by_key(|selection| selection.id)
21121 .unwrap();
21122 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21123 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21124 let snapshot = self.buffer.read(cx).read(cx);
21125 selections
21126 .into_iter()
21127 .map(|mut selection| {
21128 selection.start.0 =
21129 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21130 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21131 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21132 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21133 })
21134 .collect()
21135 }
21136
21137 fn report_editor_event(
21138 &self,
21139 reported_event: ReportEditorEvent,
21140 file_extension: Option<String>,
21141 cx: &App,
21142 ) {
21143 if cfg!(any(test, feature = "test-support")) {
21144 return;
21145 }
21146
21147 let Some(project) = &self.project else { return };
21148
21149 // If None, we are in a file without an extension
21150 let file = self
21151 .buffer
21152 .read(cx)
21153 .as_singleton()
21154 .and_then(|b| b.read(cx).file());
21155 let file_extension = file_extension.or(file
21156 .as_ref()
21157 .and_then(|file| Path::new(file.file_name(cx)).extension())
21158 .and_then(|e| e.to_str())
21159 .map(|a| a.to_string()));
21160
21161 let vim_mode = vim_enabled(cx);
21162
21163 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21164 let copilot_enabled = edit_predictions_provider
21165 == language::language_settings::EditPredictionProvider::Copilot;
21166 let copilot_enabled_for_language = self
21167 .buffer
21168 .read(cx)
21169 .language_settings(cx)
21170 .show_edit_predictions;
21171
21172 let project = project.read(cx);
21173 let event_type = reported_event.event_type();
21174
21175 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21176 telemetry::event!(
21177 event_type,
21178 type = if auto_saved {"autosave"} else {"manual"},
21179 file_extension,
21180 vim_mode,
21181 copilot_enabled,
21182 copilot_enabled_for_language,
21183 edit_predictions_provider,
21184 is_via_ssh = project.is_via_remote_server(),
21185 );
21186 } else {
21187 telemetry::event!(
21188 event_type,
21189 file_extension,
21190 vim_mode,
21191 copilot_enabled,
21192 copilot_enabled_for_language,
21193 edit_predictions_provider,
21194 is_via_ssh = project.is_via_remote_server(),
21195 );
21196 };
21197 }
21198
21199 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21200 /// with each line being an array of {text, highlight} objects.
21201 fn copy_highlight_json(
21202 &mut self,
21203 _: &CopyHighlightJson,
21204 window: &mut Window,
21205 cx: &mut Context<Self>,
21206 ) {
21207 #[derive(Serialize)]
21208 struct Chunk<'a> {
21209 text: String,
21210 highlight: Option<&'a str>,
21211 }
21212
21213 let snapshot = self.buffer.read(cx).snapshot(cx);
21214 let range = self
21215 .selected_text_range(false, window, cx)
21216 .and_then(|selection| {
21217 if selection.range.is_empty() {
21218 None
21219 } else {
21220 Some(selection.range)
21221 }
21222 })
21223 .unwrap_or_else(|| 0..snapshot.len());
21224
21225 let chunks = snapshot.chunks(range, true);
21226 let mut lines = Vec::new();
21227 let mut line: VecDeque<Chunk> = VecDeque::new();
21228
21229 let Some(style) = self.style.as_ref() else {
21230 return;
21231 };
21232
21233 for chunk in chunks {
21234 let highlight = chunk
21235 .syntax_highlight_id
21236 .and_then(|id| id.name(&style.syntax));
21237 let mut chunk_lines = chunk.text.split('\n').peekable();
21238 while let Some(text) = chunk_lines.next() {
21239 let mut merged_with_last_token = false;
21240 if let Some(last_token) = line.back_mut()
21241 && last_token.highlight == highlight
21242 {
21243 last_token.text.push_str(text);
21244 merged_with_last_token = true;
21245 }
21246
21247 if !merged_with_last_token {
21248 line.push_back(Chunk {
21249 text: text.into(),
21250 highlight,
21251 });
21252 }
21253
21254 if chunk_lines.peek().is_some() {
21255 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21256 line.pop_front();
21257 }
21258 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21259 line.pop_back();
21260 }
21261
21262 lines.push(mem::take(&mut line));
21263 }
21264 }
21265 }
21266
21267 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21268 return;
21269 };
21270 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21271 }
21272
21273 pub fn open_context_menu(
21274 &mut self,
21275 _: &OpenContextMenu,
21276 window: &mut Window,
21277 cx: &mut Context<Self>,
21278 ) {
21279 self.request_autoscroll(Autoscroll::newest(), cx);
21280 let position = self.selections.newest_display(cx).start;
21281 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21282 }
21283
21284 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21285 &self.inlay_hint_cache
21286 }
21287
21288 pub fn replay_insert_event(
21289 &mut self,
21290 text: &str,
21291 relative_utf16_range: Option<Range<isize>>,
21292 window: &mut Window,
21293 cx: &mut Context<Self>,
21294 ) {
21295 if !self.input_enabled {
21296 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21297 return;
21298 }
21299 if let Some(relative_utf16_range) = relative_utf16_range {
21300 let selections = self.selections.all::<OffsetUtf16>(cx);
21301 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21302 let new_ranges = selections.into_iter().map(|range| {
21303 let start = OffsetUtf16(
21304 range
21305 .head()
21306 .0
21307 .saturating_add_signed(relative_utf16_range.start),
21308 );
21309 let end = OffsetUtf16(
21310 range
21311 .head()
21312 .0
21313 .saturating_add_signed(relative_utf16_range.end),
21314 );
21315 start..end
21316 });
21317 s.select_ranges(new_ranges);
21318 });
21319 }
21320
21321 self.handle_input(text, window, cx);
21322 }
21323
21324 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21325 let Some(provider) = self.semantics_provider.as_ref() else {
21326 return false;
21327 };
21328
21329 let mut supports = false;
21330 self.buffer().update(cx, |this, cx| {
21331 this.for_each_buffer(|buffer| {
21332 supports |= provider.supports_inlay_hints(buffer, cx);
21333 });
21334 });
21335
21336 supports
21337 }
21338
21339 pub fn is_focused(&self, window: &Window) -> bool {
21340 self.focus_handle.is_focused(window)
21341 }
21342
21343 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21344 cx.emit(EditorEvent::Focused);
21345
21346 if let Some(descendant) = self
21347 .last_focused_descendant
21348 .take()
21349 .and_then(|descendant| descendant.upgrade())
21350 {
21351 window.focus(&descendant);
21352 } else {
21353 if let Some(blame) = self.blame.as_ref() {
21354 blame.update(cx, GitBlame::focus)
21355 }
21356
21357 self.blink_manager.update(cx, BlinkManager::enable);
21358 self.show_cursor_names(window, cx);
21359 self.buffer.update(cx, |buffer, cx| {
21360 buffer.finalize_last_transaction(cx);
21361 if self.leader_id.is_none() {
21362 buffer.set_active_selections(
21363 &self.selections.disjoint_anchors_arc(),
21364 self.selections.line_mode,
21365 self.cursor_shape,
21366 cx,
21367 );
21368 }
21369 });
21370 }
21371 }
21372
21373 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21374 cx.emit(EditorEvent::FocusedIn)
21375 }
21376
21377 fn handle_focus_out(
21378 &mut self,
21379 event: FocusOutEvent,
21380 _window: &mut Window,
21381 cx: &mut Context<Self>,
21382 ) {
21383 if event.blurred != self.focus_handle {
21384 self.last_focused_descendant = Some(event.blurred);
21385 }
21386 self.selection_drag_state = SelectionDragState::None;
21387 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21388 }
21389
21390 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21391 self.blink_manager.update(cx, BlinkManager::disable);
21392 self.buffer
21393 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21394
21395 if let Some(blame) = self.blame.as_ref() {
21396 blame.update(cx, GitBlame::blur)
21397 }
21398 if !self.hover_state.focused(window, cx) {
21399 hide_hover(self, cx);
21400 }
21401 if !self
21402 .context_menu
21403 .borrow()
21404 .as_ref()
21405 .is_some_and(|context_menu| context_menu.focused(window, cx))
21406 {
21407 self.hide_context_menu(window, cx);
21408 }
21409 self.discard_edit_prediction(false, cx);
21410 cx.emit(EditorEvent::Blurred);
21411 cx.notify();
21412 }
21413
21414 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21415 let mut pending: String = window
21416 .pending_input_keystrokes()
21417 .into_iter()
21418 .flatten()
21419 .filter_map(|keystroke| {
21420 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21421 keystroke.key_char.clone()
21422 } else {
21423 None
21424 }
21425 })
21426 .collect();
21427
21428 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21429 pending = "".to_string();
21430 }
21431
21432 let existing_pending = self
21433 .text_highlights::<PendingInput>(cx)
21434 .map(|(_, ranges)| ranges.to_vec());
21435 if existing_pending.is_none() && pending.is_empty() {
21436 return;
21437 }
21438 let transaction =
21439 self.transact(window, cx, |this, window, cx| {
21440 let selections = this.selections.all::<usize>(cx);
21441 let edits = selections
21442 .iter()
21443 .map(|selection| (selection.end..selection.end, pending.clone()));
21444 this.edit(edits, cx);
21445 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21446 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21447 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21448 }));
21449 });
21450 if let Some(existing_ranges) = existing_pending {
21451 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21452 this.edit(edits, cx);
21453 }
21454 });
21455
21456 let snapshot = self.snapshot(window, cx);
21457 let ranges = self
21458 .selections
21459 .all::<usize>(cx)
21460 .into_iter()
21461 .map(|selection| {
21462 snapshot.buffer_snapshot.anchor_after(selection.end)
21463 ..snapshot
21464 .buffer_snapshot
21465 .anchor_before(selection.end + pending.len())
21466 })
21467 .collect();
21468
21469 if pending.is_empty() {
21470 self.clear_highlights::<PendingInput>(cx);
21471 } else {
21472 self.highlight_text::<PendingInput>(
21473 ranges,
21474 HighlightStyle {
21475 underline: Some(UnderlineStyle {
21476 thickness: px(1.),
21477 color: None,
21478 wavy: false,
21479 }),
21480 ..Default::default()
21481 },
21482 cx,
21483 );
21484 }
21485
21486 self.ime_transaction = self.ime_transaction.or(transaction);
21487 if let Some(transaction) = self.ime_transaction {
21488 self.buffer.update(cx, |buffer, cx| {
21489 buffer.group_until_transaction(transaction, cx);
21490 });
21491 }
21492
21493 if self.text_highlights::<PendingInput>(cx).is_none() {
21494 self.ime_transaction.take();
21495 }
21496 }
21497
21498 pub fn register_action_renderer(
21499 &mut self,
21500 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21501 ) -> Subscription {
21502 let id = self.next_editor_action_id.post_inc();
21503 self.editor_actions
21504 .borrow_mut()
21505 .insert(id, Box::new(listener));
21506
21507 let editor_actions = self.editor_actions.clone();
21508 Subscription::new(move || {
21509 editor_actions.borrow_mut().remove(&id);
21510 })
21511 }
21512
21513 pub fn register_action<A: Action>(
21514 &mut self,
21515 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21516 ) -> Subscription {
21517 let id = self.next_editor_action_id.post_inc();
21518 let listener = Arc::new(listener);
21519 self.editor_actions.borrow_mut().insert(
21520 id,
21521 Box::new(move |_, window, _| {
21522 let listener = listener.clone();
21523 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21524 let action = action.downcast_ref().unwrap();
21525 if phase == DispatchPhase::Bubble {
21526 listener(action, window, cx)
21527 }
21528 })
21529 }),
21530 );
21531
21532 let editor_actions = self.editor_actions.clone();
21533 Subscription::new(move || {
21534 editor_actions.borrow_mut().remove(&id);
21535 })
21536 }
21537
21538 pub fn file_header_size(&self) -> u32 {
21539 FILE_HEADER_HEIGHT
21540 }
21541
21542 pub fn restore(
21543 &mut self,
21544 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21545 window: &mut Window,
21546 cx: &mut Context<Self>,
21547 ) {
21548 let workspace = self.workspace();
21549 let project = self.project();
21550 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21551 let mut tasks = Vec::new();
21552 for (buffer_id, changes) in revert_changes {
21553 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21554 buffer.update(cx, |buffer, cx| {
21555 buffer.edit(
21556 changes
21557 .into_iter()
21558 .map(|(range, text)| (range, text.to_string())),
21559 None,
21560 cx,
21561 );
21562 });
21563
21564 if let Some(project) =
21565 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21566 {
21567 project.update(cx, |project, cx| {
21568 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21569 })
21570 }
21571 }
21572 }
21573 tasks
21574 });
21575 cx.spawn_in(window, async move |_, cx| {
21576 for (buffer, task) in save_tasks {
21577 let result = task.await;
21578 if result.is_err() {
21579 let Some(path) = buffer
21580 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21581 .ok()
21582 else {
21583 continue;
21584 };
21585 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21586 let Some(task) = cx
21587 .update_window_entity(workspace, |workspace, window, cx| {
21588 workspace
21589 .open_path_preview(path, None, false, false, false, window, cx)
21590 })
21591 .ok()
21592 else {
21593 continue;
21594 };
21595 task.await.log_err();
21596 }
21597 }
21598 }
21599 })
21600 .detach();
21601 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21602 selections.refresh()
21603 });
21604 }
21605
21606 pub fn to_pixel_point(
21607 &self,
21608 source: multi_buffer::Anchor,
21609 editor_snapshot: &EditorSnapshot,
21610 window: &mut Window,
21611 ) -> Option<gpui::Point<Pixels>> {
21612 let source_point = source.to_display_point(editor_snapshot);
21613 self.display_to_pixel_point(source_point, editor_snapshot, window)
21614 }
21615
21616 pub fn display_to_pixel_point(
21617 &self,
21618 source: DisplayPoint,
21619 editor_snapshot: &EditorSnapshot,
21620 window: &mut Window,
21621 ) -> Option<gpui::Point<Pixels>> {
21622 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21623 let text_layout_details = self.text_layout_details(window);
21624 let scroll_top = text_layout_details
21625 .scroll_anchor
21626 .scroll_position(editor_snapshot)
21627 .y;
21628
21629 if source.row().as_f32() < scroll_top.floor() {
21630 return None;
21631 }
21632 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21633 let source_y = line_height * (source.row().as_f32() - scroll_top);
21634 Some(gpui::Point::new(source_x, source_y))
21635 }
21636
21637 pub fn has_visible_completions_menu(&self) -> bool {
21638 !self.edit_prediction_preview_is_active()
21639 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21640 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21641 })
21642 }
21643
21644 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21645 if self.mode.is_minimap() {
21646 return;
21647 }
21648 self.addons
21649 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21650 }
21651
21652 pub fn unregister_addon<T: Addon>(&mut self) {
21653 self.addons.remove(&std::any::TypeId::of::<T>());
21654 }
21655
21656 pub fn addon<T: Addon>(&self) -> Option<&T> {
21657 let type_id = std::any::TypeId::of::<T>();
21658 self.addons
21659 .get(&type_id)
21660 .and_then(|item| item.to_any().downcast_ref::<T>())
21661 }
21662
21663 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21664 let type_id = std::any::TypeId::of::<T>();
21665 self.addons
21666 .get_mut(&type_id)
21667 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21668 }
21669
21670 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21671 let text_layout_details = self.text_layout_details(window);
21672 let style = &text_layout_details.editor_style;
21673 let font_id = window.text_system().resolve_font(&style.text.font());
21674 let font_size = style.text.font_size.to_pixels(window.rem_size());
21675 let line_height = style.text.line_height_in_pixels(window.rem_size());
21676 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21677 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21678
21679 CharacterDimensions {
21680 em_width,
21681 em_advance,
21682 line_height,
21683 }
21684 }
21685
21686 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21687 self.load_diff_task.clone()
21688 }
21689
21690 fn read_metadata_from_db(
21691 &mut self,
21692 item_id: u64,
21693 workspace_id: WorkspaceId,
21694 window: &mut Window,
21695 cx: &mut Context<Editor>,
21696 ) {
21697 if self.is_singleton(cx)
21698 && !self.mode.is_minimap()
21699 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21700 {
21701 let buffer_snapshot = OnceCell::new();
21702
21703 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21704 && !folds.is_empty()
21705 {
21706 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21707 self.fold_ranges(
21708 folds
21709 .into_iter()
21710 .map(|(start, end)| {
21711 snapshot.clip_offset(start, Bias::Left)
21712 ..snapshot.clip_offset(end, Bias::Right)
21713 })
21714 .collect(),
21715 false,
21716 window,
21717 cx,
21718 );
21719 }
21720
21721 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21722 && !selections.is_empty()
21723 {
21724 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21725 // skip adding the initial selection to selection history
21726 self.selection_history.mode = SelectionHistoryMode::Skipping;
21727 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21728 s.select_ranges(selections.into_iter().map(|(start, end)| {
21729 snapshot.clip_offset(start, Bias::Left)
21730 ..snapshot.clip_offset(end, Bias::Right)
21731 }));
21732 });
21733 self.selection_history.mode = SelectionHistoryMode::Normal;
21734 };
21735 }
21736
21737 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21738 }
21739
21740 fn update_lsp_data(
21741 &mut self,
21742 ignore_cache: bool,
21743 for_buffer: Option<BufferId>,
21744 window: &mut Window,
21745 cx: &mut Context<'_, Self>,
21746 ) {
21747 self.pull_diagnostics(for_buffer, window, cx);
21748 self.refresh_colors(ignore_cache, for_buffer, window, cx);
21749 }
21750}
21751
21752fn edit_for_markdown_paste<'a>(
21753 buffer: &MultiBufferSnapshot,
21754 range: Range<usize>,
21755 to_insert: &'a str,
21756 url: Option<url::Url>,
21757) -> (Range<usize>, Cow<'a, str>) {
21758 if url.is_none() {
21759 return (range, Cow::Borrowed(to_insert));
21760 };
21761
21762 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21763
21764 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21765 Cow::Borrowed(to_insert)
21766 } else {
21767 Cow::Owned(format!("[{old_text}]({to_insert})"))
21768 };
21769 (range, new_text)
21770}
21771
21772fn vim_enabled(cx: &App) -> bool {
21773 vim_mode_setting::VimModeSetting::try_get(cx)
21774 .map(|vim_mode| vim_mode.0)
21775 .unwrap_or(false)
21776}
21777
21778fn process_completion_for_edit(
21779 completion: &Completion,
21780 intent: CompletionIntent,
21781 buffer: &Entity<Buffer>,
21782 cursor_position: &text::Anchor,
21783 cx: &mut Context<Editor>,
21784) -> CompletionEdit {
21785 let buffer = buffer.read(cx);
21786 let buffer_snapshot = buffer.snapshot();
21787 let (snippet, new_text) = if completion.is_snippet() {
21788 // Workaround for typescript language server issues so that methods don't expand within
21789 // strings and functions with type expressions. The previous point is used because the query
21790 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21791 let mut snippet_source = completion.new_text.clone();
21792 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
21793 previous_point.column = previous_point.column.saturating_sub(1);
21794 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21795 && scope.prefers_label_for_snippet_in_completion()
21796 && let Some(label) = completion.label()
21797 && matches!(
21798 completion.kind(),
21799 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21800 )
21801 {
21802 snippet_source = label;
21803 }
21804 match Snippet::parse(&snippet_source).log_err() {
21805 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21806 None => (None, completion.new_text.clone()),
21807 }
21808 } else {
21809 (None, completion.new_text.clone())
21810 };
21811
21812 let mut range_to_replace = {
21813 let replace_range = &completion.replace_range;
21814 if let CompletionSource::Lsp {
21815 insert_range: Some(insert_range),
21816 ..
21817 } = &completion.source
21818 {
21819 debug_assert_eq!(
21820 insert_range.start, replace_range.start,
21821 "insert_range and replace_range should start at the same position"
21822 );
21823 debug_assert!(
21824 insert_range
21825 .start
21826 .cmp(cursor_position, &buffer_snapshot)
21827 .is_le(),
21828 "insert_range should start before or at cursor position"
21829 );
21830 debug_assert!(
21831 replace_range
21832 .start
21833 .cmp(cursor_position, &buffer_snapshot)
21834 .is_le(),
21835 "replace_range should start before or at cursor position"
21836 );
21837
21838 let should_replace = match intent {
21839 CompletionIntent::CompleteWithInsert => false,
21840 CompletionIntent::CompleteWithReplace => true,
21841 CompletionIntent::Complete | CompletionIntent::Compose => {
21842 let insert_mode =
21843 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21844 .completions
21845 .lsp_insert_mode;
21846 match insert_mode {
21847 LspInsertMode::Insert => false,
21848 LspInsertMode::Replace => true,
21849 LspInsertMode::ReplaceSubsequence => {
21850 let mut text_to_replace = buffer.chars_for_range(
21851 buffer.anchor_before(replace_range.start)
21852 ..buffer.anchor_after(replace_range.end),
21853 );
21854 let mut current_needle = text_to_replace.next();
21855 for haystack_ch in completion.label.text.chars() {
21856 if let Some(needle_ch) = current_needle
21857 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
21858 {
21859 current_needle = text_to_replace.next();
21860 }
21861 }
21862 current_needle.is_none()
21863 }
21864 LspInsertMode::ReplaceSuffix => {
21865 if replace_range
21866 .end
21867 .cmp(cursor_position, &buffer_snapshot)
21868 .is_gt()
21869 {
21870 let range_after_cursor = *cursor_position..replace_range.end;
21871 let text_after_cursor = buffer
21872 .text_for_range(
21873 buffer.anchor_before(range_after_cursor.start)
21874 ..buffer.anchor_after(range_after_cursor.end),
21875 )
21876 .collect::<String>()
21877 .to_ascii_lowercase();
21878 completion
21879 .label
21880 .text
21881 .to_ascii_lowercase()
21882 .ends_with(&text_after_cursor)
21883 } else {
21884 true
21885 }
21886 }
21887 }
21888 }
21889 };
21890
21891 if should_replace {
21892 replace_range.clone()
21893 } else {
21894 insert_range.clone()
21895 }
21896 } else {
21897 replace_range.clone()
21898 }
21899 };
21900
21901 if range_to_replace
21902 .end
21903 .cmp(cursor_position, &buffer_snapshot)
21904 .is_lt()
21905 {
21906 range_to_replace.end = *cursor_position;
21907 }
21908
21909 CompletionEdit {
21910 new_text,
21911 replace_range: range_to_replace.to_offset(buffer),
21912 snippet,
21913 }
21914}
21915
21916struct CompletionEdit {
21917 new_text: String,
21918 replace_range: Range<usize>,
21919 snippet: Option<Snippet>,
21920}
21921
21922fn insert_extra_newline_brackets(
21923 buffer: &MultiBufferSnapshot,
21924 range: Range<usize>,
21925 language: &language::LanguageScope,
21926) -> bool {
21927 let leading_whitespace_len = buffer
21928 .reversed_chars_at(range.start)
21929 .take_while(|c| c.is_whitespace() && *c != '\n')
21930 .map(|c| c.len_utf8())
21931 .sum::<usize>();
21932 let trailing_whitespace_len = buffer
21933 .chars_at(range.end)
21934 .take_while(|c| c.is_whitespace() && *c != '\n')
21935 .map(|c| c.len_utf8())
21936 .sum::<usize>();
21937 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
21938
21939 language.brackets().any(|(pair, enabled)| {
21940 let pair_start = pair.start.trim_end();
21941 let pair_end = pair.end.trim_start();
21942
21943 enabled
21944 && pair.newline
21945 && buffer.contains_str_at(range.end, pair_end)
21946 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
21947 })
21948}
21949
21950fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
21951 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
21952 [(buffer, range, _)] => (*buffer, range.clone()),
21953 _ => return false,
21954 };
21955 let pair = {
21956 let mut result: Option<BracketMatch> = None;
21957
21958 for pair in buffer
21959 .all_bracket_ranges(range.clone())
21960 .filter(move |pair| {
21961 pair.open_range.start <= range.start && pair.close_range.end >= range.end
21962 })
21963 {
21964 let len = pair.close_range.end - pair.open_range.start;
21965
21966 if let Some(existing) = &result {
21967 let existing_len = existing.close_range.end - existing.open_range.start;
21968 if len > existing_len {
21969 continue;
21970 }
21971 }
21972
21973 result = Some(pair);
21974 }
21975
21976 result
21977 };
21978 let Some(pair) = pair else {
21979 return false;
21980 };
21981 pair.newline_only
21982 && buffer
21983 .chars_for_range(pair.open_range.end..range.start)
21984 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
21985 .all(|c| c.is_whitespace() && c != '\n')
21986}
21987
21988fn update_uncommitted_diff_for_buffer(
21989 editor: Entity<Editor>,
21990 project: &Entity<Project>,
21991 buffers: impl IntoIterator<Item = Entity<Buffer>>,
21992 buffer: Entity<MultiBuffer>,
21993 cx: &mut App,
21994) -> Task<()> {
21995 let mut tasks = Vec::new();
21996 project.update(cx, |project, cx| {
21997 for buffer in buffers {
21998 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
21999 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22000 }
22001 }
22002 });
22003 cx.spawn(async move |cx| {
22004 let diffs = future::join_all(tasks).await;
22005 if editor
22006 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22007 .unwrap_or(false)
22008 {
22009 return;
22010 }
22011
22012 buffer
22013 .update(cx, |buffer, cx| {
22014 for diff in diffs.into_iter().flatten() {
22015 buffer.add_diff(diff, cx);
22016 }
22017 })
22018 .ok();
22019 })
22020}
22021
22022fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22023 let tab_size = tab_size.get() as usize;
22024 let mut width = offset;
22025
22026 for ch in text.chars() {
22027 width += if ch == '\t' {
22028 tab_size - (width % tab_size)
22029 } else {
22030 1
22031 };
22032 }
22033
22034 width - offset
22035}
22036
22037#[cfg(test)]
22038mod tests {
22039 use super::*;
22040
22041 #[test]
22042 fn test_string_size_with_expanded_tabs() {
22043 let nz = |val| NonZeroU32::new(val).unwrap();
22044 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22045 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22046 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22047 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22048 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22049 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22050 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22051 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22052 }
22053}
22054
22055/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22056struct WordBreakingTokenizer<'a> {
22057 input: &'a str,
22058}
22059
22060impl<'a> WordBreakingTokenizer<'a> {
22061 fn new(input: &'a str) -> Self {
22062 Self { input }
22063 }
22064}
22065
22066fn is_char_ideographic(ch: char) -> bool {
22067 use unicode_script::Script::*;
22068 use unicode_script::UnicodeScript;
22069 matches!(ch.script(), Han | Tangut | Yi)
22070}
22071
22072fn is_grapheme_ideographic(text: &str) -> bool {
22073 text.chars().any(is_char_ideographic)
22074}
22075
22076fn is_grapheme_whitespace(text: &str) -> bool {
22077 text.chars().any(|x| x.is_whitespace())
22078}
22079
22080fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22081 text.chars()
22082 .next()
22083 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22084}
22085
22086#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22087enum WordBreakToken<'a> {
22088 Word { token: &'a str, grapheme_len: usize },
22089 InlineWhitespace { token: &'a str, grapheme_len: usize },
22090 Newline,
22091}
22092
22093impl<'a> Iterator for WordBreakingTokenizer<'a> {
22094 /// Yields a span, the count of graphemes in the token, and whether it was
22095 /// whitespace. Note that it also breaks at word boundaries.
22096 type Item = WordBreakToken<'a>;
22097
22098 fn next(&mut self) -> Option<Self::Item> {
22099 use unicode_segmentation::UnicodeSegmentation;
22100 if self.input.is_empty() {
22101 return None;
22102 }
22103
22104 let mut iter = self.input.graphemes(true).peekable();
22105 let mut offset = 0;
22106 let mut grapheme_len = 0;
22107 if let Some(first_grapheme) = iter.next() {
22108 let is_newline = first_grapheme == "\n";
22109 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22110 offset += first_grapheme.len();
22111 grapheme_len += 1;
22112 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22113 if let Some(grapheme) = iter.peek().copied()
22114 && should_stay_with_preceding_ideograph(grapheme)
22115 {
22116 offset += grapheme.len();
22117 grapheme_len += 1;
22118 }
22119 } else {
22120 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22121 let mut next_word_bound = words.peek().copied();
22122 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22123 next_word_bound = words.next();
22124 }
22125 while let Some(grapheme) = iter.peek().copied() {
22126 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22127 break;
22128 };
22129 if is_grapheme_whitespace(grapheme) != is_whitespace
22130 || (grapheme == "\n") != is_newline
22131 {
22132 break;
22133 };
22134 offset += grapheme.len();
22135 grapheme_len += 1;
22136 iter.next();
22137 }
22138 }
22139 let token = &self.input[..offset];
22140 self.input = &self.input[offset..];
22141 if token == "\n" {
22142 Some(WordBreakToken::Newline)
22143 } else if is_whitespace {
22144 Some(WordBreakToken::InlineWhitespace {
22145 token,
22146 grapheme_len,
22147 })
22148 } else {
22149 Some(WordBreakToken::Word {
22150 token,
22151 grapheme_len,
22152 })
22153 }
22154 } else {
22155 None
22156 }
22157 }
22158}
22159
22160#[test]
22161fn test_word_breaking_tokenizer() {
22162 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22163 ("", &[]),
22164 (" ", &[whitespace(" ", 2)]),
22165 ("Ʒ", &[word("Ʒ", 1)]),
22166 ("Ǽ", &[word("Ǽ", 1)]),
22167 ("⋑", &[word("⋑", 1)]),
22168 ("⋑⋑", &[word("⋑⋑", 2)]),
22169 (
22170 "原理,进而",
22171 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22172 ),
22173 (
22174 "hello world",
22175 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22176 ),
22177 (
22178 "hello, world",
22179 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22180 ),
22181 (
22182 " hello world",
22183 &[
22184 whitespace(" ", 2),
22185 word("hello", 5),
22186 whitespace(" ", 1),
22187 word("world", 5),
22188 ],
22189 ),
22190 (
22191 "这是什么 \n 钢笔",
22192 &[
22193 word("这", 1),
22194 word("是", 1),
22195 word("什", 1),
22196 word("么", 1),
22197 whitespace(" ", 1),
22198 newline(),
22199 whitespace(" ", 1),
22200 word("钢", 1),
22201 word("笔", 1),
22202 ],
22203 ),
22204 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22205 ];
22206
22207 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22208 WordBreakToken::Word {
22209 token,
22210 grapheme_len,
22211 }
22212 }
22213
22214 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22215 WordBreakToken::InlineWhitespace {
22216 token,
22217 grapheme_len,
22218 }
22219 }
22220
22221 fn newline() -> WordBreakToken<'static> {
22222 WordBreakToken::Newline
22223 }
22224
22225 for (input, result) in tests {
22226 assert_eq!(
22227 WordBreakingTokenizer::new(input)
22228 .collect::<Vec<_>>()
22229 .as_slice(),
22230 *result,
22231 );
22232 }
22233}
22234
22235fn wrap_with_prefix(
22236 first_line_prefix: String,
22237 subsequent_lines_prefix: String,
22238 unwrapped_text: String,
22239 wrap_column: usize,
22240 tab_size: NonZeroU32,
22241 preserve_existing_whitespace: bool,
22242) -> String {
22243 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22244 let subsequent_lines_prefix_len =
22245 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22246 let mut wrapped_text = String::new();
22247 let mut current_line = first_line_prefix;
22248 let mut is_first_line = true;
22249
22250 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22251 let mut current_line_len = first_line_prefix_len;
22252 let mut in_whitespace = false;
22253 for token in tokenizer {
22254 let have_preceding_whitespace = in_whitespace;
22255 match token {
22256 WordBreakToken::Word {
22257 token,
22258 grapheme_len,
22259 } => {
22260 in_whitespace = false;
22261 let current_prefix_len = if is_first_line {
22262 first_line_prefix_len
22263 } else {
22264 subsequent_lines_prefix_len
22265 };
22266 if current_line_len + grapheme_len > wrap_column
22267 && current_line_len != current_prefix_len
22268 {
22269 wrapped_text.push_str(current_line.trim_end());
22270 wrapped_text.push('\n');
22271 is_first_line = false;
22272 current_line = subsequent_lines_prefix.clone();
22273 current_line_len = subsequent_lines_prefix_len;
22274 }
22275 current_line.push_str(token);
22276 current_line_len += grapheme_len;
22277 }
22278 WordBreakToken::InlineWhitespace {
22279 mut token,
22280 mut grapheme_len,
22281 } => {
22282 in_whitespace = true;
22283 if have_preceding_whitespace && !preserve_existing_whitespace {
22284 continue;
22285 }
22286 if !preserve_existing_whitespace {
22287 token = " ";
22288 grapheme_len = 1;
22289 }
22290 let current_prefix_len = if is_first_line {
22291 first_line_prefix_len
22292 } else {
22293 subsequent_lines_prefix_len
22294 };
22295 if current_line_len + grapheme_len > wrap_column {
22296 wrapped_text.push_str(current_line.trim_end());
22297 wrapped_text.push('\n');
22298 is_first_line = false;
22299 current_line = subsequent_lines_prefix.clone();
22300 current_line_len = subsequent_lines_prefix_len;
22301 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22302 current_line.push_str(token);
22303 current_line_len += grapheme_len;
22304 }
22305 }
22306 WordBreakToken::Newline => {
22307 in_whitespace = true;
22308 let current_prefix_len = if is_first_line {
22309 first_line_prefix_len
22310 } else {
22311 subsequent_lines_prefix_len
22312 };
22313 if preserve_existing_whitespace {
22314 wrapped_text.push_str(current_line.trim_end());
22315 wrapped_text.push('\n');
22316 is_first_line = false;
22317 current_line = subsequent_lines_prefix.clone();
22318 current_line_len = subsequent_lines_prefix_len;
22319 } else if have_preceding_whitespace {
22320 continue;
22321 } else if current_line_len + 1 > wrap_column
22322 && current_line_len != current_prefix_len
22323 {
22324 wrapped_text.push_str(current_line.trim_end());
22325 wrapped_text.push('\n');
22326 is_first_line = false;
22327 current_line = subsequent_lines_prefix.clone();
22328 current_line_len = subsequent_lines_prefix_len;
22329 } else if current_line_len != current_prefix_len {
22330 current_line.push(' ');
22331 current_line_len += 1;
22332 }
22333 }
22334 }
22335 }
22336
22337 if !current_line.is_empty() {
22338 wrapped_text.push_str(¤t_line);
22339 }
22340 wrapped_text
22341}
22342
22343#[test]
22344fn test_wrap_with_prefix() {
22345 assert_eq!(
22346 wrap_with_prefix(
22347 "# ".to_string(),
22348 "# ".to_string(),
22349 "abcdefg".to_string(),
22350 4,
22351 NonZeroU32::new(4).unwrap(),
22352 false,
22353 ),
22354 "# abcdefg"
22355 );
22356 assert_eq!(
22357 wrap_with_prefix(
22358 "".to_string(),
22359 "".to_string(),
22360 "\thello world".to_string(),
22361 8,
22362 NonZeroU32::new(4).unwrap(),
22363 false,
22364 ),
22365 "hello\nworld"
22366 );
22367 assert_eq!(
22368 wrap_with_prefix(
22369 "// ".to_string(),
22370 "// ".to_string(),
22371 "xx \nyy zz aa bb cc".to_string(),
22372 12,
22373 NonZeroU32::new(4).unwrap(),
22374 false,
22375 ),
22376 "// xx yy zz\n// aa bb cc"
22377 );
22378 assert_eq!(
22379 wrap_with_prefix(
22380 String::new(),
22381 String::new(),
22382 "这是什么 \n 钢笔".to_string(),
22383 3,
22384 NonZeroU32::new(4).unwrap(),
22385 false,
22386 ),
22387 "这是什\n么 钢\n笔"
22388 );
22389}
22390
22391pub trait CollaborationHub {
22392 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22393 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22394 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22395}
22396
22397impl CollaborationHub for Entity<Project> {
22398 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22399 self.read(cx).collaborators()
22400 }
22401
22402 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22403 self.read(cx).user_store().read(cx).participant_indices()
22404 }
22405
22406 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22407 let this = self.read(cx);
22408 let user_ids = this.collaborators().values().map(|c| c.user_id);
22409 this.user_store().read(cx).participant_names(user_ids, cx)
22410 }
22411}
22412
22413pub trait SemanticsProvider {
22414 fn hover(
22415 &self,
22416 buffer: &Entity<Buffer>,
22417 position: text::Anchor,
22418 cx: &mut App,
22419 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22420
22421 fn inline_values(
22422 &self,
22423 buffer_handle: Entity<Buffer>,
22424 range: Range<text::Anchor>,
22425 cx: &mut App,
22426 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22427
22428 fn inlay_hints(
22429 &self,
22430 buffer_handle: Entity<Buffer>,
22431 range: Range<text::Anchor>,
22432 cx: &mut App,
22433 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22434
22435 fn resolve_inlay_hint(
22436 &self,
22437 hint: InlayHint,
22438 buffer_handle: Entity<Buffer>,
22439 server_id: LanguageServerId,
22440 cx: &mut App,
22441 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22442
22443 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22444
22445 fn document_highlights(
22446 &self,
22447 buffer: &Entity<Buffer>,
22448 position: text::Anchor,
22449 cx: &mut App,
22450 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22451
22452 fn definitions(
22453 &self,
22454 buffer: &Entity<Buffer>,
22455 position: text::Anchor,
22456 kind: GotoDefinitionKind,
22457 cx: &mut App,
22458 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22459
22460 fn range_for_rename(
22461 &self,
22462 buffer: &Entity<Buffer>,
22463 position: text::Anchor,
22464 cx: &mut App,
22465 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22466
22467 fn perform_rename(
22468 &self,
22469 buffer: &Entity<Buffer>,
22470 position: text::Anchor,
22471 new_name: String,
22472 cx: &mut App,
22473 ) -> Option<Task<Result<ProjectTransaction>>>;
22474}
22475
22476pub trait CompletionProvider {
22477 fn completions(
22478 &self,
22479 excerpt_id: ExcerptId,
22480 buffer: &Entity<Buffer>,
22481 buffer_position: text::Anchor,
22482 trigger: CompletionContext,
22483 window: &mut Window,
22484 cx: &mut Context<Editor>,
22485 ) -> Task<Result<Vec<CompletionResponse>>>;
22486
22487 fn resolve_completions(
22488 &self,
22489 _buffer: Entity<Buffer>,
22490 _completion_indices: Vec<usize>,
22491 _completions: Rc<RefCell<Box<[Completion]>>>,
22492 _cx: &mut Context<Editor>,
22493 ) -> Task<Result<bool>> {
22494 Task::ready(Ok(false))
22495 }
22496
22497 fn apply_additional_edits_for_completion(
22498 &self,
22499 _buffer: Entity<Buffer>,
22500 _completions: Rc<RefCell<Box<[Completion]>>>,
22501 _completion_index: usize,
22502 _push_to_history: bool,
22503 _cx: &mut Context<Editor>,
22504 ) -> Task<Result<Option<language::Transaction>>> {
22505 Task::ready(Ok(None))
22506 }
22507
22508 fn is_completion_trigger(
22509 &self,
22510 buffer: &Entity<Buffer>,
22511 position: language::Anchor,
22512 text: &str,
22513 trigger_in_words: bool,
22514 menu_is_open: bool,
22515 cx: &mut Context<Editor>,
22516 ) -> bool;
22517
22518 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22519
22520 fn sort_completions(&self) -> bool {
22521 true
22522 }
22523
22524 fn filter_completions(&self) -> bool {
22525 true
22526 }
22527}
22528
22529pub trait CodeActionProvider {
22530 fn id(&self) -> Arc<str>;
22531
22532 fn code_actions(
22533 &self,
22534 buffer: &Entity<Buffer>,
22535 range: Range<text::Anchor>,
22536 window: &mut Window,
22537 cx: &mut App,
22538 ) -> Task<Result<Vec<CodeAction>>>;
22539
22540 fn apply_code_action(
22541 &self,
22542 buffer_handle: Entity<Buffer>,
22543 action: CodeAction,
22544 excerpt_id: ExcerptId,
22545 push_to_history: bool,
22546 window: &mut Window,
22547 cx: &mut App,
22548 ) -> Task<Result<ProjectTransaction>>;
22549}
22550
22551impl CodeActionProvider for Entity<Project> {
22552 fn id(&self) -> Arc<str> {
22553 "project".into()
22554 }
22555
22556 fn code_actions(
22557 &self,
22558 buffer: &Entity<Buffer>,
22559 range: Range<text::Anchor>,
22560 _window: &mut Window,
22561 cx: &mut App,
22562 ) -> Task<Result<Vec<CodeAction>>> {
22563 self.update(cx, |project, cx| {
22564 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22565 let code_actions = project.code_actions(buffer, range, None, cx);
22566 cx.background_spawn(async move {
22567 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22568 Ok(code_lens_actions
22569 .context("code lens fetch")?
22570 .into_iter()
22571 .flatten()
22572 .chain(
22573 code_actions
22574 .context("code action fetch")?
22575 .into_iter()
22576 .flatten(),
22577 )
22578 .collect())
22579 })
22580 })
22581 }
22582
22583 fn apply_code_action(
22584 &self,
22585 buffer_handle: Entity<Buffer>,
22586 action: CodeAction,
22587 _excerpt_id: ExcerptId,
22588 push_to_history: bool,
22589 _window: &mut Window,
22590 cx: &mut App,
22591 ) -> Task<Result<ProjectTransaction>> {
22592 self.update(cx, |project, cx| {
22593 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22594 })
22595 }
22596}
22597
22598fn snippet_completions(
22599 project: &Project,
22600 buffer: &Entity<Buffer>,
22601 buffer_position: text::Anchor,
22602 cx: &mut App,
22603) -> Task<Result<CompletionResponse>> {
22604 let languages = buffer.read(cx).languages_at(buffer_position);
22605 let snippet_store = project.snippets().read(cx);
22606
22607 let scopes: Vec<_> = languages
22608 .iter()
22609 .filter_map(|language| {
22610 let language_name = language.lsp_id();
22611 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22612
22613 if snippets.is_empty() {
22614 None
22615 } else {
22616 Some((language.default_scope(), snippets))
22617 }
22618 })
22619 .collect();
22620
22621 if scopes.is_empty() {
22622 return Task::ready(Ok(CompletionResponse {
22623 completions: vec![],
22624 display_options: CompletionDisplayOptions::default(),
22625 is_incomplete: false,
22626 }));
22627 }
22628
22629 let snapshot = buffer.read(cx).text_snapshot();
22630 let chars: String = snapshot
22631 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22632 .collect();
22633 let executor = cx.background_executor().clone();
22634
22635 cx.background_spawn(async move {
22636 let mut is_incomplete = false;
22637 let mut completions: Vec<Completion> = Vec::new();
22638 for (scope, snippets) in scopes.into_iter() {
22639 let classifier =
22640 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22641 let mut last_word = chars
22642 .chars()
22643 .take_while(|c| classifier.is_word(*c))
22644 .collect::<String>();
22645 last_word = last_word.chars().rev().collect();
22646
22647 if last_word.is_empty() {
22648 return Ok(CompletionResponse {
22649 completions: vec![],
22650 display_options: CompletionDisplayOptions::default(),
22651 is_incomplete: true,
22652 });
22653 }
22654
22655 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22656 let to_lsp = |point: &text::Anchor| {
22657 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22658 point_to_lsp(end)
22659 };
22660 let lsp_end = to_lsp(&buffer_position);
22661
22662 let candidates = snippets
22663 .iter()
22664 .enumerate()
22665 .flat_map(|(ix, snippet)| {
22666 snippet
22667 .prefix
22668 .iter()
22669 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22670 })
22671 .collect::<Vec<StringMatchCandidate>>();
22672
22673 const MAX_RESULTS: usize = 100;
22674 let mut matches = fuzzy::match_strings(
22675 &candidates,
22676 &last_word,
22677 last_word.chars().any(|c| c.is_uppercase()),
22678 true,
22679 MAX_RESULTS,
22680 &Default::default(),
22681 executor.clone(),
22682 )
22683 .await;
22684
22685 if matches.len() >= MAX_RESULTS {
22686 is_incomplete = true;
22687 }
22688
22689 // Remove all candidates where the query's start does not match the start of any word in the candidate
22690 if let Some(query_start) = last_word.chars().next() {
22691 matches.retain(|string_match| {
22692 split_words(&string_match.string).any(|word| {
22693 // Check that the first codepoint of the word as lowercase matches the first
22694 // codepoint of the query as lowercase
22695 word.chars()
22696 .flat_map(|codepoint| codepoint.to_lowercase())
22697 .zip(query_start.to_lowercase())
22698 .all(|(word_cp, query_cp)| word_cp == query_cp)
22699 })
22700 });
22701 }
22702
22703 let matched_strings = matches
22704 .into_iter()
22705 .map(|m| m.string)
22706 .collect::<HashSet<_>>();
22707
22708 completions.extend(snippets.iter().filter_map(|snippet| {
22709 let matching_prefix = snippet
22710 .prefix
22711 .iter()
22712 .find(|prefix| matched_strings.contains(*prefix))?;
22713 let start = as_offset - last_word.len();
22714 let start = snapshot.anchor_before(start);
22715 let range = start..buffer_position;
22716 let lsp_start = to_lsp(&start);
22717 let lsp_range = lsp::Range {
22718 start: lsp_start,
22719 end: lsp_end,
22720 };
22721 Some(Completion {
22722 replace_range: range,
22723 new_text: snippet.body.clone(),
22724 source: CompletionSource::Lsp {
22725 insert_range: None,
22726 server_id: LanguageServerId(usize::MAX),
22727 resolved: true,
22728 lsp_completion: Box::new(lsp::CompletionItem {
22729 label: snippet.prefix.first().unwrap().clone(),
22730 kind: Some(CompletionItemKind::SNIPPET),
22731 label_details: snippet.description.as_ref().map(|description| {
22732 lsp::CompletionItemLabelDetails {
22733 detail: Some(description.clone()),
22734 description: None,
22735 }
22736 }),
22737 insert_text_format: Some(InsertTextFormat::SNIPPET),
22738 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22739 lsp::InsertReplaceEdit {
22740 new_text: snippet.body.clone(),
22741 insert: lsp_range,
22742 replace: lsp_range,
22743 },
22744 )),
22745 filter_text: Some(snippet.body.clone()),
22746 sort_text: Some(char::MAX.to_string()),
22747 ..lsp::CompletionItem::default()
22748 }),
22749 lsp_defaults: None,
22750 },
22751 label: CodeLabel {
22752 text: matching_prefix.clone(),
22753 runs: Vec::new(),
22754 filter_range: 0..matching_prefix.len(),
22755 },
22756 icon_path: None,
22757 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22758 single_line: snippet.name.clone().into(),
22759 plain_text: snippet
22760 .description
22761 .clone()
22762 .map(|description| description.into()),
22763 }),
22764 insert_text_mode: None,
22765 confirm: None,
22766 })
22767 }))
22768 }
22769
22770 Ok(CompletionResponse {
22771 completions,
22772 display_options: CompletionDisplayOptions::default(),
22773 is_incomplete,
22774 })
22775 })
22776}
22777
22778impl CompletionProvider for Entity<Project> {
22779 fn completions(
22780 &self,
22781 _excerpt_id: ExcerptId,
22782 buffer: &Entity<Buffer>,
22783 buffer_position: text::Anchor,
22784 options: CompletionContext,
22785 _window: &mut Window,
22786 cx: &mut Context<Editor>,
22787 ) -> Task<Result<Vec<CompletionResponse>>> {
22788 self.update(cx, |project, cx| {
22789 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22790 let project_completions = project.completions(buffer, buffer_position, options, cx);
22791 cx.background_spawn(async move {
22792 let mut responses = project_completions.await?;
22793 let snippets = snippets.await?;
22794 if !snippets.completions.is_empty() {
22795 responses.push(snippets);
22796 }
22797 Ok(responses)
22798 })
22799 })
22800 }
22801
22802 fn resolve_completions(
22803 &self,
22804 buffer: Entity<Buffer>,
22805 completion_indices: Vec<usize>,
22806 completions: Rc<RefCell<Box<[Completion]>>>,
22807 cx: &mut Context<Editor>,
22808 ) -> Task<Result<bool>> {
22809 self.update(cx, |project, cx| {
22810 project.lsp_store().update(cx, |lsp_store, cx| {
22811 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22812 })
22813 })
22814 }
22815
22816 fn apply_additional_edits_for_completion(
22817 &self,
22818 buffer: Entity<Buffer>,
22819 completions: Rc<RefCell<Box<[Completion]>>>,
22820 completion_index: usize,
22821 push_to_history: bool,
22822 cx: &mut Context<Editor>,
22823 ) -> Task<Result<Option<language::Transaction>>> {
22824 self.update(cx, |project, cx| {
22825 project.lsp_store().update(cx, |lsp_store, cx| {
22826 lsp_store.apply_additional_edits_for_completion(
22827 buffer,
22828 completions,
22829 completion_index,
22830 push_to_history,
22831 cx,
22832 )
22833 })
22834 })
22835 }
22836
22837 fn is_completion_trigger(
22838 &self,
22839 buffer: &Entity<Buffer>,
22840 position: language::Anchor,
22841 text: &str,
22842 trigger_in_words: bool,
22843 menu_is_open: bool,
22844 cx: &mut Context<Editor>,
22845 ) -> bool {
22846 let mut chars = text.chars();
22847 let char = if let Some(char) = chars.next() {
22848 char
22849 } else {
22850 return false;
22851 };
22852 if chars.next().is_some() {
22853 return false;
22854 }
22855
22856 let buffer = buffer.read(cx);
22857 let snapshot = buffer.snapshot();
22858 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
22859 return false;
22860 }
22861 let classifier = snapshot
22862 .char_classifier_at(position)
22863 .scope_context(Some(CharScopeContext::Completion));
22864 if trigger_in_words && classifier.is_word(char) {
22865 return true;
22866 }
22867
22868 buffer.completion_triggers().contains(text)
22869 }
22870}
22871
22872impl SemanticsProvider for Entity<Project> {
22873 fn hover(
22874 &self,
22875 buffer: &Entity<Buffer>,
22876 position: text::Anchor,
22877 cx: &mut App,
22878 ) -> Option<Task<Option<Vec<project::Hover>>>> {
22879 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
22880 }
22881
22882 fn document_highlights(
22883 &self,
22884 buffer: &Entity<Buffer>,
22885 position: text::Anchor,
22886 cx: &mut App,
22887 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
22888 Some(self.update(cx, |project, cx| {
22889 project.document_highlights(buffer, position, cx)
22890 }))
22891 }
22892
22893 fn definitions(
22894 &self,
22895 buffer: &Entity<Buffer>,
22896 position: text::Anchor,
22897 kind: GotoDefinitionKind,
22898 cx: &mut App,
22899 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
22900 Some(self.update(cx, |project, cx| match kind {
22901 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
22902 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
22903 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
22904 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
22905 }))
22906 }
22907
22908 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
22909 self.update(cx, |project, cx| {
22910 if project
22911 .active_debug_session(cx)
22912 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
22913 {
22914 return true;
22915 }
22916
22917 buffer.update(cx, |buffer, cx| {
22918 project.any_language_server_supports_inlay_hints(buffer, cx)
22919 })
22920 })
22921 }
22922
22923 fn inline_values(
22924 &self,
22925 buffer_handle: Entity<Buffer>,
22926 range: Range<text::Anchor>,
22927 cx: &mut App,
22928 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22929 self.update(cx, |project, cx| {
22930 let (session, active_stack_frame) = project.active_debug_session(cx)?;
22931
22932 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
22933 })
22934 }
22935
22936 fn inlay_hints(
22937 &self,
22938 buffer_handle: Entity<Buffer>,
22939 range: Range<text::Anchor>,
22940 cx: &mut App,
22941 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
22942 Some(self.update(cx, |project, cx| {
22943 project.inlay_hints(buffer_handle, range, cx)
22944 }))
22945 }
22946
22947 fn resolve_inlay_hint(
22948 &self,
22949 hint: InlayHint,
22950 buffer_handle: Entity<Buffer>,
22951 server_id: LanguageServerId,
22952 cx: &mut App,
22953 ) -> Option<Task<anyhow::Result<InlayHint>>> {
22954 Some(self.update(cx, |project, cx| {
22955 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
22956 }))
22957 }
22958
22959 fn range_for_rename(
22960 &self,
22961 buffer: &Entity<Buffer>,
22962 position: text::Anchor,
22963 cx: &mut App,
22964 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
22965 Some(self.update(cx, |project, cx| {
22966 let buffer = buffer.clone();
22967 let task = project.prepare_rename(buffer.clone(), position, cx);
22968 cx.spawn(async move |_, cx| {
22969 Ok(match task.await? {
22970 PrepareRenameResponse::Success(range) => Some(range),
22971 PrepareRenameResponse::InvalidPosition => None,
22972 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
22973 // Fallback on using TreeSitter info to determine identifier range
22974 buffer.read_with(cx, |buffer, _| {
22975 let snapshot = buffer.snapshot();
22976 let (range, kind) = snapshot.surrounding_word(position, None);
22977 if kind != Some(CharKind::Word) {
22978 return None;
22979 }
22980 Some(
22981 snapshot.anchor_before(range.start)
22982 ..snapshot.anchor_after(range.end),
22983 )
22984 })?
22985 }
22986 })
22987 })
22988 }))
22989 }
22990
22991 fn perform_rename(
22992 &self,
22993 buffer: &Entity<Buffer>,
22994 position: text::Anchor,
22995 new_name: String,
22996 cx: &mut App,
22997 ) -> Option<Task<Result<ProjectTransaction>>> {
22998 Some(self.update(cx, |project, cx| {
22999 project.perform_rename(buffer.clone(), position, new_name, cx)
23000 }))
23001 }
23002}
23003
23004fn inlay_hint_settings(
23005 location: Anchor,
23006 snapshot: &MultiBufferSnapshot,
23007 cx: &mut Context<Editor>,
23008) -> InlayHintSettings {
23009 let file = snapshot.file_at(location);
23010 let language = snapshot.language_at(location).map(|l| l.name());
23011 language_settings(language, file, cx).inlay_hints
23012}
23013
23014fn consume_contiguous_rows(
23015 contiguous_row_selections: &mut Vec<Selection<Point>>,
23016 selection: &Selection<Point>,
23017 display_map: &DisplaySnapshot,
23018 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23019) -> (MultiBufferRow, MultiBufferRow) {
23020 contiguous_row_selections.push(selection.clone());
23021 let start_row = starting_row(selection, display_map);
23022 let mut end_row = ending_row(selection, display_map);
23023
23024 while let Some(next_selection) = selections.peek() {
23025 if next_selection.start.row <= end_row.0 {
23026 end_row = ending_row(next_selection, display_map);
23027 contiguous_row_selections.push(selections.next().unwrap().clone());
23028 } else {
23029 break;
23030 }
23031 }
23032 (start_row, end_row)
23033}
23034
23035fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23036 if selection.start.column > 0 {
23037 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23038 } else {
23039 MultiBufferRow(selection.start.row)
23040 }
23041}
23042
23043fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23044 if next_selection.end.column > 0 || next_selection.is_empty() {
23045 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23046 } else {
23047 MultiBufferRow(next_selection.end.row)
23048 }
23049}
23050
23051impl EditorSnapshot {
23052 pub fn remote_selections_in_range<'a>(
23053 &'a self,
23054 range: &'a Range<Anchor>,
23055 collaboration_hub: &dyn CollaborationHub,
23056 cx: &'a App,
23057 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23058 let participant_names = collaboration_hub.user_names(cx);
23059 let participant_indices = collaboration_hub.user_participant_indices(cx);
23060 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23061 let collaborators_by_replica_id = collaborators_by_peer_id
23062 .values()
23063 .map(|collaborator| (collaborator.replica_id, collaborator))
23064 .collect::<HashMap<_, _>>();
23065 self.buffer_snapshot
23066 .selections_in_range(range, false)
23067 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23068 if replica_id == AGENT_REPLICA_ID {
23069 Some(RemoteSelection {
23070 replica_id,
23071 selection,
23072 cursor_shape,
23073 line_mode,
23074 collaborator_id: CollaboratorId::Agent,
23075 user_name: Some("Agent".into()),
23076 color: cx.theme().players().agent(),
23077 })
23078 } else {
23079 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23080 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23081 let user_name = participant_names.get(&collaborator.user_id).cloned();
23082 Some(RemoteSelection {
23083 replica_id,
23084 selection,
23085 cursor_shape,
23086 line_mode,
23087 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23088 user_name,
23089 color: if let Some(index) = participant_index {
23090 cx.theme().players().color_for_participant(index.0)
23091 } else {
23092 cx.theme().players().absent()
23093 },
23094 })
23095 }
23096 })
23097 }
23098
23099 pub fn hunks_for_ranges(
23100 &self,
23101 ranges: impl IntoIterator<Item = Range<Point>>,
23102 ) -> Vec<MultiBufferDiffHunk> {
23103 let mut hunks = Vec::new();
23104 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23105 HashMap::default();
23106 for query_range in ranges {
23107 let query_rows =
23108 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23109 for hunk in self.buffer_snapshot.diff_hunks_in_range(
23110 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23111 ) {
23112 // Include deleted hunks that are adjacent to the query range, because
23113 // otherwise they would be missed.
23114 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23115 if hunk.status().is_deleted() {
23116 intersects_range |= hunk.row_range.start == query_rows.end;
23117 intersects_range |= hunk.row_range.end == query_rows.start;
23118 }
23119 if intersects_range {
23120 if !processed_buffer_rows
23121 .entry(hunk.buffer_id)
23122 .or_default()
23123 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23124 {
23125 continue;
23126 }
23127 hunks.push(hunk);
23128 }
23129 }
23130 }
23131
23132 hunks
23133 }
23134
23135 fn display_diff_hunks_for_rows<'a>(
23136 &'a self,
23137 display_rows: Range<DisplayRow>,
23138 folded_buffers: &'a HashSet<BufferId>,
23139 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23140 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23141 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23142
23143 self.buffer_snapshot
23144 .diff_hunks_in_range(buffer_start..buffer_end)
23145 .filter_map(|hunk| {
23146 if folded_buffers.contains(&hunk.buffer_id) {
23147 return None;
23148 }
23149
23150 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23151 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23152
23153 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23154 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23155
23156 let display_hunk = if hunk_display_start.column() != 0 {
23157 DisplayDiffHunk::Folded {
23158 display_row: hunk_display_start.row(),
23159 }
23160 } else {
23161 let mut end_row = hunk_display_end.row();
23162 if hunk_display_end.column() > 0 {
23163 end_row.0 += 1;
23164 }
23165 let is_created_file = hunk.is_created_file();
23166 DisplayDiffHunk::Unfolded {
23167 status: hunk.status(),
23168 diff_base_byte_range: hunk.diff_base_byte_range,
23169 display_row_range: hunk_display_start.row()..end_row,
23170 multi_buffer_range: Anchor::range_in_buffer(
23171 hunk.excerpt_id,
23172 hunk.buffer_id,
23173 hunk.buffer_range,
23174 ),
23175 is_created_file,
23176 }
23177 };
23178
23179 Some(display_hunk)
23180 })
23181 }
23182
23183 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23184 self.display_snapshot.buffer_snapshot.language_at(position)
23185 }
23186
23187 pub fn is_focused(&self) -> bool {
23188 self.is_focused
23189 }
23190
23191 pub fn placeholder_text(&self) -> Option<String> {
23192 self.placeholder_display_snapshot
23193 .as_ref()
23194 .map(|display_map| display_map.text())
23195 }
23196
23197 pub fn scroll_position(&self) -> gpui::Point<f32> {
23198 self.scroll_anchor.scroll_position(&self.display_snapshot)
23199 }
23200
23201 fn gutter_dimensions(
23202 &self,
23203 font_id: FontId,
23204 font_size: Pixels,
23205 max_line_number_width: Pixels,
23206 cx: &App,
23207 ) -> Option<GutterDimensions> {
23208 if !self.show_gutter {
23209 return None;
23210 }
23211
23212 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23213 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23214
23215 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23216 matches!(
23217 ProjectSettings::get_global(cx).git.git_gutter,
23218 GitGutterSetting::TrackedFiles
23219 )
23220 });
23221 let gutter_settings = EditorSettings::get_global(cx).gutter;
23222 let show_line_numbers = self
23223 .show_line_numbers
23224 .unwrap_or(gutter_settings.line_numbers);
23225 let line_gutter_width = if show_line_numbers {
23226 // Avoid flicker-like gutter resizes when the line number gains another digit by
23227 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23228 let min_width_for_number_on_gutter =
23229 ch_advance * gutter_settings.min_line_number_digits as f32;
23230 max_line_number_width.max(min_width_for_number_on_gutter)
23231 } else {
23232 0.0.into()
23233 };
23234
23235 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23236 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23237
23238 let git_blame_entries_width =
23239 self.git_blame_gutter_max_author_length
23240 .map(|max_author_length| {
23241 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23242 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23243
23244 /// The number of characters to dedicate to gaps and margins.
23245 const SPACING_WIDTH: usize = 4;
23246
23247 let max_char_count = max_author_length.min(renderer.max_author_length())
23248 + ::git::SHORT_SHA_LENGTH
23249 + MAX_RELATIVE_TIMESTAMP.len()
23250 + SPACING_WIDTH;
23251
23252 ch_advance * max_char_count
23253 });
23254
23255 let is_singleton = self.buffer_snapshot.is_singleton();
23256
23257 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23258 left_padding += if !is_singleton {
23259 ch_width * 4.0
23260 } else if show_runnables || show_breakpoints {
23261 ch_width * 3.0
23262 } else if show_git_gutter && show_line_numbers {
23263 ch_width * 2.0
23264 } else if show_git_gutter || show_line_numbers {
23265 ch_width
23266 } else {
23267 px(0.)
23268 };
23269
23270 let shows_folds = is_singleton && gutter_settings.folds;
23271
23272 let right_padding = if shows_folds && show_line_numbers {
23273 ch_width * 4.0
23274 } else if shows_folds || (!is_singleton && show_line_numbers) {
23275 ch_width * 3.0
23276 } else if show_line_numbers {
23277 ch_width
23278 } else {
23279 px(0.)
23280 };
23281
23282 Some(GutterDimensions {
23283 left_padding,
23284 right_padding,
23285 width: line_gutter_width + left_padding + right_padding,
23286 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23287 git_blame_entries_width,
23288 })
23289 }
23290
23291 pub fn render_crease_toggle(
23292 &self,
23293 buffer_row: MultiBufferRow,
23294 row_contains_cursor: bool,
23295 editor: Entity<Editor>,
23296 window: &mut Window,
23297 cx: &mut App,
23298 ) -> Option<AnyElement> {
23299 let folded = self.is_line_folded(buffer_row);
23300 let mut is_foldable = false;
23301
23302 if let Some(crease) = self
23303 .crease_snapshot
23304 .query_row(buffer_row, &self.buffer_snapshot)
23305 {
23306 is_foldable = true;
23307 match crease {
23308 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23309 if let Some(render_toggle) = render_toggle {
23310 let toggle_callback =
23311 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23312 if folded {
23313 editor.update(cx, |editor, cx| {
23314 editor.fold_at(buffer_row, window, cx)
23315 });
23316 } else {
23317 editor.update(cx, |editor, cx| {
23318 editor.unfold_at(buffer_row, window, cx)
23319 });
23320 }
23321 });
23322 return Some((render_toggle)(
23323 buffer_row,
23324 folded,
23325 toggle_callback,
23326 window,
23327 cx,
23328 ));
23329 }
23330 }
23331 }
23332 }
23333
23334 is_foldable |= self.starts_indent(buffer_row);
23335
23336 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23337 Some(
23338 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23339 .toggle_state(folded)
23340 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23341 if folded {
23342 this.unfold_at(buffer_row, window, cx);
23343 } else {
23344 this.fold_at(buffer_row, window, cx);
23345 }
23346 }))
23347 .into_any_element(),
23348 )
23349 } else {
23350 None
23351 }
23352 }
23353
23354 pub fn render_crease_trailer(
23355 &self,
23356 buffer_row: MultiBufferRow,
23357 window: &mut Window,
23358 cx: &mut App,
23359 ) -> Option<AnyElement> {
23360 let folded = self.is_line_folded(buffer_row);
23361 if let Crease::Inline { render_trailer, .. } = self
23362 .crease_snapshot
23363 .query_row(buffer_row, &self.buffer_snapshot)?
23364 {
23365 let render_trailer = render_trailer.as_ref()?;
23366 Some(render_trailer(buffer_row, folded, window, cx))
23367 } else {
23368 None
23369 }
23370 }
23371}
23372
23373impl Deref for EditorSnapshot {
23374 type Target = DisplaySnapshot;
23375
23376 fn deref(&self) -> &Self::Target {
23377 &self.display_snapshot
23378 }
23379}
23380
23381#[derive(Clone, Debug, PartialEq, Eq)]
23382pub enum EditorEvent {
23383 InputIgnored {
23384 text: Arc<str>,
23385 },
23386 InputHandled {
23387 utf16_range_to_replace: Option<Range<isize>>,
23388 text: Arc<str>,
23389 },
23390 ExcerptsAdded {
23391 buffer: Entity<Buffer>,
23392 predecessor: ExcerptId,
23393 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23394 },
23395 ExcerptsRemoved {
23396 ids: Vec<ExcerptId>,
23397 removed_buffer_ids: Vec<BufferId>,
23398 },
23399 BufferFoldToggled {
23400 ids: Vec<ExcerptId>,
23401 folded: bool,
23402 },
23403 ExcerptsEdited {
23404 ids: Vec<ExcerptId>,
23405 },
23406 ExcerptsExpanded {
23407 ids: Vec<ExcerptId>,
23408 },
23409 BufferEdited,
23410 Edited {
23411 transaction_id: clock::Lamport,
23412 },
23413 Reparsed(BufferId),
23414 Focused,
23415 FocusedIn,
23416 Blurred,
23417 DirtyChanged,
23418 Saved,
23419 TitleChanged,
23420 SelectionsChanged {
23421 local: bool,
23422 },
23423 ScrollPositionChanged {
23424 local: bool,
23425 autoscroll: bool,
23426 },
23427 TransactionUndone {
23428 transaction_id: clock::Lamport,
23429 },
23430 TransactionBegun {
23431 transaction_id: clock::Lamport,
23432 },
23433 CursorShapeChanged,
23434 BreadcrumbsChanged,
23435 PushedToNavHistory {
23436 anchor: Anchor,
23437 is_deactivate: bool,
23438 },
23439}
23440
23441impl EventEmitter<EditorEvent> for Editor {}
23442
23443impl Focusable for Editor {
23444 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23445 self.focus_handle.clone()
23446 }
23447}
23448
23449impl Render for Editor {
23450 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23451 let settings = ThemeSettings::get_global(cx);
23452
23453 let mut text_style = match self.mode {
23454 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23455 color: cx.theme().colors().editor_foreground,
23456 font_family: settings.ui_font.family.clone(),
23457 font_features: settings.ui_font.features.clone(),
23458 font_fallbacks: settings.ui_font.fallbacks.clone(),
23459 font_size: rems(0.875).into(),
23460 font_weight: settings.ui_font.weight,
23461 line_height: relative(settings.buffer_line_height.value()),
23462 ..Default::default()
23463 },
23464 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23465 color: cx.theme().colors().editor_foreground,
23466 font_family: settings.buffer_font.family.clone(),
23467 font_features: settings.buffer_font.features.clone(),
23468 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23469 font_size: settings.buffer_font_size(cx).into(),
23470 font_weight: settings.buffer_font.weight,
23471 line_height: relative(settings.buffer_line_height.value()),
23472 ..Default::default()
23473 },
23474 };
23475 if let Some(text_style_refinement) = &self.text_style_refinement {
23476 text_style.refine(text_style_refinement)
23477 }
23478
23479 let background = match self.mode {
23480 EditorMode::SingleLine => cx.theme().system().transparent,
23481 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23482 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23483 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23484 };
23485
23486 EditorElement::new(
23487 &cx.entity(),
23488 EditorStyle {
23489 background,
23490 border: cx.theme().colors().border,
23491 local_player: cx.theme().players().local(),
23492 text: text_style,
23493 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23494 syntax: cx.theme().syntax().clone(),
23495 status: cx.theme().status().clone(),
23496 inlay_hints_style: make_inlay_hints_style(cx),
23497 edit_prediction_styles: make_suggestion_styles(cx),
23498 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23499 show_underlines: self.diagnostics_enabled(),
23500 },
23501 )
23502 }
23503}
23504
23505impl EntityInputHandler for Editor {
23506 fn text_for_range(
23507 &mut self,
23508 range_utf16: Range<usize>,
23509 adjusted_range: &mut Option<Range<usize>>,
23510 _: &mut Window,
23511 cx: &mut Context<Self>,
23512 ) -> Option<String> {
23513 let snapshot = self.buffer.read(cx).read(cx);
23514 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23515 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23516 if (start.0..end.0) != range_utf16 {
23517 adjusted_range.replace(start.0..end.0);
23518 }
23519 Some(snapshot.text_for_range(start..end).collect())
23520 }
23521
23522 fn selected_text_range(
23523 &mut self,
23524 ignore_disabled_input: bool,
23525 _: &mut Window,
23526 cx: &mut Context<Self>,
23527 ) -> Option<UTF16Selection> {
23528 // Prevent the IME menu from appearing when holding down an alphabetic key
23529 // while input is disabled.
23530 if !ignore_disabled_input && !self.input_enabled {
23531 return None;
23532 }
23533
23534 let selection = self.selections.newest::<OffsetUtf16>(cx);
23535 let range = selection.range();
23536
23537 Some(UTF16Selection {
23538 range: range.start.0..range.end.0,
23539 reversed: selection.reversed,
23540 })
23541 }
23542
23543 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23544 let snapshot = self.buffer.read(cx).read(cx);
23545 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23546 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23547 }
23548
23549 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23550 self.clear_highlights::<InputComposition>(cx);
23551 self.ime_transaction.take();
23552 }
23553
23554 fn replace_text_in_range(
23555 &mut self,
23556 range_utf16: Option<Range<usize>>,
23557 text: &str,
23558 window: &mut Window,
23559 cx: &mut Context<Self>,
23560 ) {
23561 if !self.input_enabled {
23562 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23563 return;
23564 }
23565
23566 self.transact(window, cx, |this, window, cx| {
23567 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23568 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23569 Some(this.selection_replacement_ranges(range_utf16, cx))
23570 } else {
23571 this.marked_text_ranges(cx)
23572 };
23573
23574 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23575 let newest_selection_id = this.selections.newest_anchor().id;
23576 this.selections
23577 .all::<OffsetUtf16>(cx)
23578 .iter()
23579 .zip(ranges_to_replace.iter())
23580 .find_map(|(selection, range)| {
23581 if selection.id == newest_selection_id {
23582 Some(
23583 (range.start.0 as isize - selection.head().0 as isize)
23584 ..(range.end.0 as isize - selection.head().0 as isize),
23585 )
23586 } else {
23587 None
23588 }
23589 })
23590 });
23591
23592 cx.emit(EditorEvent::InputHandled {
23593 utf16_range_to_replace: range_to_replace,
23594 text: text.into(),
23595 });
23596
23597 if let Some(new_selected_ranges) = new_selected_ranges {
23598 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23599 selections.select_ranges(new_selected_ranges)
23600 });
23601 this.backspace(&Default::default(), window, cx);
23602 }
23603
23604 this.handle_input(text, window, cx);
23605 });
23606
23607 if let Some(transaction) = self.ime_transaction {
23608 self.buffer.update(cx, |buffer, cx| {
23609 buffer.group_until_transaction(transaction, cx);
23610 });
23611 }
23612
23613 self.unmark_text(window, cx);
23614 }
23615
23616 fn replace_and_mark_text_in_range(
23617 &mut self,
23618 range_utf16: Option<Range<usize>>,
23619 text: &str,
23620 new_selected_range_utf16: Option<Range<usize>>,
23621 window: &mut Window,
23622 cx: &mut Context<Self>,
23623 ) {
23624 if !self.input_enabled {
23625 return;
23626 }
23627
23628 let transaction = self.transact(window, cx, |this, window, cx| {
23629 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23630 let snapshot = this.buffer.read(cx).read(cx);
23631 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23632 for marked_range in &mut marked_ranges {
23633 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23634 marked_range.start.0 += relative_range_utf16.start;
23635 marked_range.start =
23636 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23637 marked_range.end =
23638 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23639 }
23640 }
23641 Some(marked_ranges)
23642 } else if let Some(range_utf16) = range_utf16 {
23643 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23644 Some(this.selection_replacement_ranges(range_utf16, cx))
23645 } else {
23646 None
23647 };
23648
23649 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23650 let newest_selection_id = this.selections.newest_anchor().id;
23651 this.selections
23652 .all::<OffsetUtf16>(cx)
23653 .iter()
23654 .zip(ranges_to_replace.iter())
23655 .find_map(|(selection, range)| {
23656 if selection.id == newest_selection_id {
23657 Some(
23658 (range.start.0 as isize - selection.head().0 as isize)
23659 ..(range.end.0 as isize - selection.head().0 as isize),
23660 )
23661 } else {
23662 None
23663 }
23664 })
23665 });
23666
23667 cx.emit(EditorEvent::InputHandled {
23668 utf16_range_to_replace: range_to_replace,
23669 text: text.into(),
23670 });
23671
23672 if let Some(ranges) = ranges_to_replace {
23673 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23674 s.select_ranges(ranges)
23675 });
23676 }
23677
23678 let marked_ranges = {
23679 let snapshot = this.buffer.read(cx).read(cx);
23680 this.selections
23681 .disjoint_anchors_arc()
23682 .iter()
23683 .map(|selection| {
23684 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23685 })
23686 .collect::<Vec<_>>()
23687 };
23688
23689 if text.is_empty() {
23690 this.unmark_text(window, cx);
23691 } else {
23692 this.highlight_text::<InputComposition>(
23693 marked_ranges.clone(),
23694 HighlightStyle {
23695 underline: Some(UnderlineStyle {
23696 thickness: px(1.),
23697 color: None,
23698 wavy: false,
23699 }),
23700 ..Default::default()
23701 },
23702 cx,
23703 );
23704 }
23705
23706 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23707 let use_autoclose = this.use_autoclose;
23708 let use_auto_surround = this.use_auto_surround;
23709 this.set_use_autoclose(false);
23710 this.set_use_auto_surround(false);
23711 this.handle_input(text, window, cx);
23712 this.set_use_autoclose(use_autoclose);
23713 this.set_use_auto_surround(use_auto_surround);
23714
23715 if let Some(new_selected_range) = new_selected_range_utf16 {
23716 let snapshot = this.buffer.read(cx).read(cx);
23717 let new_selected_ranges = marked_ranges
23718 .into_iter()
23719 .map(|marked_range| {
23720 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23721 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23722 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23723 snapshot.clip_offset_utf16(new_start, Bias::Left)
23724 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23725 })
23726 .collect::<Vec<_>>();
23727
23728 drop(snapshot);
23729 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23730 selections.select_ranges(new_selected_ranges)
23731 });
23732 }
23733 });
23734
23735 self.ime_transaction = self.ime_transaction.or(transaction);
23736 if let Some(transaction) = self.ime_transaction {
23737 self.buffer.update(cx, |buffer, cx| {
23738 buffer.group_until_transaction(transaction, cx);
23739 });
23740 }
23741
23742 if self.text_highlights::<InputComposition>(cx).is_none() {
23743 self.ime_transaction.take();
23744 }
23745 }
23746
23747 fn bounds_for_range(
23748 &mut self,
23749 range_utf16: Range<usize>,
23750 element_bounds: gpui::Bounds<Pixels>,
23751 window: &mut Window,
23752 cx: &mut Context<Self>,
23753 ) -> Option<gpui::Bounds<Pixels>> {
23754 let text_layout_details = self.text_layout_details(window);
23755 let CharacterDimensions {
23756 em_width,
23757 em_advance,
23758 line_height,
23759 } = self.character_dimensions(window);
23760
23761 let snapshot = self.snapshot(window, cx);
23762 let scroll_position = snapshot.scroll_position();
23763 let scroll_left = scroll_position.x * em_advance;
23764
23765 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23766 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
23767 + self.gutter_dimensions.full_width();
23768 let y = line_height * (start.row().as_f32() - scroll_position.y);
23769
23770 Some(Bounds {
23771 origin: element_bounds.origin + point(x, y),
23772 size: size(em_width, line_height),
23773 })
23774 }
23775
23776 fn character_index_for_point(
23777 &mut self,
23778 point: gpui::Point<Pixels>,
23779 _window: &mut Window,
23780 _cx: &mut Context<Self>,
23781 ) -> Option<usize> {
23782 let position_map = self.last_position_map.as_ref()?;
23783 if !position_map.text_hitbox.contains(&point) {
23784 return None;
23785 }
23786 let display_point = position_map.point_for_position(point).previous_valid;
23787 let anchor = position_map
23788 .snapshot
23789 .display_point_to_anchor(display_point, Bias::Left);
23790 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
23791 Some(utf16_offset.0)
23792 }
23793}
23794
23795trait SelectionExt {
23796 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23797 fn spanned_rows(
23798 &self,
23799 include_end_if_at_line_start: bool,
23800 map: &DisplaySnapshot,
23801 ) -> Range<MultiBufferRow>;
23802}
23803
23804impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23805 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23806 let start = self
23807 .start
23808 .to_point(&map.buffer_snapshot)
23809 .to_display_point(map);
23810 let end = self
23811 .end
23812 .to_point(&map.buffer_snapshot)
23813 .to_display_point(map);
23814 if self.reversed {
23815 end..start
23816 } else {
23817 start..end
23818 }
23819 }
23820
23821 fn spanned_rows(
23822 &self,
23823 include_end_if_at_line_start: bool,
23824 map: &DisplaySnapshot,
23825 ) -> Range<MultiBufferRow> {
23826 let start = self.start.to_point(&map.buffer_snapshot);
23827 let mut end = self.end.to_point(&map.buffer_snapshot);
23828 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
23829 end.row -= 1;
23830 }
23831
23832 let buffer_start = map.prev_line_boundary(start).0;
23833 let buffer_end = map.next_line_boundary(end).0;
23834 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
23835 }
23836}
23837
23838impl<T: InvalidationRegion> InvalidationStack<T> {
23839 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
23840 where
23841 S: Clone + ToOffset,
23842 {
23843 while let Some(region) = self.last() {
23844 let all_selections_inside_invalidation_ranges =
23845 if selections.len() == region.ranges().len() {
23846 selections
23847 .iter()
23848 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
23849 .all(|(selection, invalidation_range)| {
23850 let head = selection.head().to_offset(buffer);
23851 invalidation_range.start <= head && invalidation_range.end >= head
23852 })
23853 } else {
23854 false
23855 };
23856
23857 if all_selections_inside_invalidation_ranges {
23858 break;
23859 } else {
23860 self.pop();
23861 }
23862 }
23863 }
23864}
23865
23866impl<T> Default for InvalidationStack<T> {
23867 fn default() -> Self {
23868 Self(Default::default())
23869 }
23870}
23871
23872impl<T> Deref for InvalidationStack<T> {
23873 type Target = Vec<T>;
23874
23875 fn deref(&self) -> &Self::Target {
23876 &self.0
23877 }
23878}
23879
23880impl<T> DerefMut for InvalidationStack<T> {
23881 fn deref_mut(&mut self) -> &mut Self::Target {
23882 &mut self.0
23883 }
23884}
23885
23886impl InvalidationRegion for SnippetState {
23887 fn ranges(&self) -> &[Range<Anchor>] {
23888 &self.ranges[self.active_index]
23889 }
23890}
23891
23892fn edit_prediction_edit_text(
23893 current_snapshot: &BufferSnapshot,
23894 edits: &[(Range<Anchor>, String)],
23895 edit_preview: &EditPreview,
23896 include_deletions: bool,
23897 cx: &App,
23898) -> HighlightedText {
23899 let edits = edits
23900 .iter()
23901 .map(|(anchor, text)| {
23902 (
23903 anchor.start.text_anchor..anchor.end.text_anchor,
23904 text.clone(),
23905 )
23906 })
23907 .collect::<Vec<_>>();
23908
23909 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
23910}
23911
23912fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
23913 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
23914 // Just show the raw edit text with basic styling
23915 let mut text = String::new();
23916 let mut highlights = Vec::new();
23917
23918 let insertion_highlight_style = HighlightStyle {
23919 color: Some(cx.theme().colors().text),
23920 ..Default::default()
23921 };
23922
23923 for (_, edit_text) in edits {
23924 let start_offset = text.len();
23925 text.push_str(edit_text);
23926 let end_offset = text.len();
23927
23928 if start_offset < end_offset {
23929 highlights.push((start_offset..end_offset, insertion_highlight_style));
23930 }
23931 }
23932
23933 HighlightedText {
23934 text: text.into(),
23935 highlights,
23936 }
23937}
23938
23939pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
23940 match severity {
23941 lsp::DiagnosticSeverity::ERROR => colors.error,
23942 lsp::DiagnosticSeverity::WARNING => colors.warning,
23943 lsp::DiagnosticSeverity::INFORMATION => colors.info,
23944 lsp::DiagnosticSeverity::HINT => colors.info,
23945 _ => colors.ignored,
23946 }
23947}
23948
23949pub fn styled_runs_for_code_label<'a>(
23950 label: &'a CodeLabel,
23951 syntax_theme: &'a theme::SyntaxTheme,
23952) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
23953 let fade_out = HighlightStyle {
23954 fade_out: Some(0.35),
23955 ..Default::default()
23956 };
23957
23958 let mut prev_end = label.filter_range.end;
23959 label
23960 .runs
23961 .iter()
23962 .enumerate()
23963 .flat_map(move |(ix, (range, highlight_id))| {
23964 let style = if let Some(style) = highlight_id.style(syntax_theme) {
23965 style
23966 } else {
23967 return Default::default();
23968 };
23969 let muted_style = style.highlight(fade_out);
23970
23971 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
23972 if range.start >= label.filter_range.end {
23973 if range.start > prev_end {
23974 runs.push((prev_end..range.start, fade_out));
23975 }
23976 runs.push((range.clone(), muted_style));
23977 } else if range.end <= label.filter_range.end {
23978 runs.push((range.clone(), style));
23979 } else {
23980 runs.push((range.start..label.filter_range.end, style));
23981 runs.push((label.filter_range.end..range.end, muted_style));
23982 }
23983 prev_end = cmp::max(prev_end, range.end);
23984
23985 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
23986 runs.push((prev_end..label.text.len(), fade_out));
23987 }
23988
23989 runs
23990 })
23991}
23992
23993pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
23994 let mut prev_index = 0;
23995 let mut prev_codepoint: Option<char> = None;
23996 text.char_indices()
23997 .chain([(text.len(), '\0')])
23998 .filter_map(move |(index, codepoint)| {
23999 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24000 let is_boundary = index == text.len()
24001 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24002 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24003 if is_boundary {
24004 let chunk = &text[prev_index..index];
24005 prev_index = index;
24006 Some(chunk)
24007 } else {
24008 None
24009 }
24010 })
24011}
24012
24013pub trait RangeToAnchorExt: Sized {
24014 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24015
24016 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24017 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
24018 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24019 }
24020}
24021
24022impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24023 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24024 let start_offset = self.start.to_offset(snapshot);
24025 let end_offset = self.end.to_offset(snapshot);
24026 if start_offset == end_offset {
24027 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24028 } else {
24029 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24030 }
24031 }
24032}
24033
24034pub trait RowExt {
24035 fn as_f32(&self) -> f32;
24036
24037 fn next_row(&self) -> Self;
24038
24039 fn previous_row(&self) -> Self;
24040
24041 fn minus(&self, other: Self) -> u32;
24042}
24043
24044impl RowExt for DisplayRow {
24045 fn as_f32(&self) -> f32 {
24046 self.0 as f32
24047 }
24048
24049 fn next_row(&self) -> Self {
24050 Self(self.0 + 1)
24051 }
24052
24053 fn previous_row(&self) -> Self {
24054 Self(self.0.saturating_sub(1))
24055 }
24056
24057 fn minus(&self, other: Self) -> u32 {
24058 self.0 - other.0
24059 }
24060}
24061
24062impl RowExt for MultiBufferRow {
24063 fn as_f32(&self) -> f32 {
24064 self.0 as f32
24065 }
24066
24067 fn next_row(&self) -> Self {
24068 Self(self.0 + 1)
24069 }
24070
24071 fn previous_row(&self) -> Self {
24072 Self(self.0.saturating_sub(1))
24073 }
24074
24075 fn minus(&self, other: Self) -> u32 {
24076 self.0 - other.0
24077 }
24078}
24079
24080trait RowRangeExt {
24081 type Row;
24082
24083 fn len(&self) -> usize;
24084
24085 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24086}
24087
24088impl RowRangeExt for Range<MultiBufferRow> {
24089 type Row = MultiBufferRow;
24090
24091 fn len(&self) -> usize {
24092 (self.end.0 - self.start.0) as usize
24093 }
24094
24095 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24096 (self.start.0..self.end.0).map(MultiBufferRow)
24097 }
24098}
24099
24100impl RowRangeExt for Range<DisplayRow> {
24101 type Row = DisplayRow;
24102
24103 fn len(&self) -> usize {
24104 (self.end.0 - self.start.0) as usize
24105 }
24106
24107 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24108 (self.start.0..self.end.0).map(DisplayRow)
24109 }
24110}
24111
24112/// If select range has more than one line, we
24113/// just point the cursor to range.start.
24114fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24115 if range.start.row == range.end.row {
24116 range
24117 } else {
24118 range.start..range.start
24119 }
24120}
24121pub struct KillRing(ClipboardItem);
24122impl Global for KillRing {}
24123
24124const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24125
24126enum BreakpointPromptEditAction {
24127 Log,
24128 Condition,
24129 HitCondition,
24130}
24131
24132struct BreakpointPromptEditor {
24133 pub(crate) prompt: Entity<Editor>,
24134 editor: WeakEntity<Editor>,
24135 breakpoint_anchor: Anchor,
24136 breakpoint: Breakpoint,
24137 edit_action: BreakpointPromptEditAction,
24138 block_ids: HashSet<CustomBlockId>,
24139 editor_margins: Arc<Mutex<EditorMargins>>,
24140 _subscriptions: Vec<Subscription>,
24141}
24142
24143impl BreakpointPromptEditor {
24144 const MAX_LINES: u8 = 4;
24145
24146 fn new(
24147 editor: WeakEntity<Editor>,
24148 breakpoint_anchor: Anchor,
24149 breakpoint: Breakpoint,
24150 edit_action: BreakpointPromptEditAction,
24151 window: &mut Window,
24152 cx: &mut Context<Self>,
24153 ) -> Self {
24154 let base_text = match edit_action {
24155 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24156 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24157 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24158 }
24159 .map(|msg| msg.to_string())
24160 .unwrap_or_default();
24161
24162 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24163 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24164
24165 let prompt = cx.new(|cx| {
24166 let mut prompt = Editor::new(
24167 EditorMode::AutoHeight {
24168 min_lines: 1,
24169 max_lines: Some(Self::MAX_LINES as usize),
24170 },
24171 buffer,
24172 None,
24173 window,
24174 cx,
24175 );
24176 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24177 prompt.set_show_cursor_when_unfocused(false, cx);
24178 prompt.set_placeholder_text(
24179 match edit_action {
24180 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24181 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24182 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24183 },
24184 window,
24185 cx,
24186 );
24187
24188 prompt
24189 });
24190
24191 Self {
24192 prompt,
24193 editor,
24194 breakpoint_anchor,
24195 breakpoint,
24196 edit_action,
24197 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24198 block_ids: Default::default(),
24199 _subscriptions: vec![],
24200 }
24201 }
24202
24203 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24204 self.block_ids.extend(block_ids)
24205 }
24206
24207 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24208 if let Some(editor) = self.editor.upgrade() {
24209 let message = self
24210 .prompt
24211 .read(cx)
24212 .buffer
24213 .read(cx)
24214 .as_singleton()
24215 .expect("A multi buffer in breakpoint prompt isn't possible")
24216 .read(cx)
24217 .as_rope()
24218 .to_string();
24219
24220 editor.update(cx, |editor, cx| {
24221 editor.edit_breakpoint_at_anchor(
24222 self.breakpoint_anchor,
24223 self.breakpoint.clone(),
24224 match self.edit_action {
24225 BreakpointPromptEditAction::Log => {
24226 BreakpointEditAction::EditLogMessage(message.into())
24227 }
24228 BreakpointPromptEditAction::Condition => {
24229 BreakpointEditAction::EditCondition(message.into())
24230 }
24231 BreakpointPromptEditAction::HitCondition => {
24232 BreakpointEditAction::EditHitCondition(message.into())
24233 }
24234 },
24235 cx,
24236 );
24237
24238 editor.remove_blocks(self.block_ids.clone(), None, cx);
24239 cx.focus_self(window);
24240 });
24241 }
24242 }
24243
24244 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24245 self.editor
24246 .update(cx, |editor, cx| {
24247 editor.remove_blocks(self.block_ids.clone(), None, cx);
24248 window.focus(&editor.focus_handle);
24249 })
24250 .log_err();
24251 }
24252
24253 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24254 let settings = ThemeSettings::get_global(cx);
24255 let text_style = TextStyle {
24256 color: if self.prompt.read(cx).read_only(cx) {
24257 cx.theme().colors().text_disabled
24258 } else {
24259 cx.theme().colors().text
24260 },
24261 font_family: settings.buffer_font.family.clone(),
24262 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24263 font_size: settings.buffer_font_size(cx).into(),
24264 font_weight: settings.buffer_font.weight,
24265 line_height: relative(settings.buffer_line_height.value()),
24266 ..Default::default()
24267 };
24268 EditorElement::new(
24269 &self.prompt,
24270 EditorStyle {
24271 background: cx.theme().colors().editor_background,
24272 local_player: cx.theme().players().local(),
24273 text: text_style,
24274 ..Default::default()
24275 },
24276 )
24277 }
24278}
24279
24280impl Render for BreakpointPromptEditor {
24281 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24282 let editor_margins = *self.editor_margins.lock();
24283 let gutter_dimensions = editor_margins.gutter;
24284 h_flex()
24285 .key_context("Editor")
24286 .bg(cx.theme().colors().editor_background)
24287 .border_y_1()
24288 .border_color(cx.theme().status().info_border)
24289 .size_full()
24290 .py(window.line_height() / 2.5)
24291 .on_action(cx.listener(Self::confirm))
24292 .on_action(cx.listener(Self::cancel))
24293 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24294 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24295 }
24296}
24297
24298impl Focusable for BreakpointPromptEditor {
24299 fn focus_handle(&self, cx: &App) -> FocusHandle {
24300 self.prompt.focus_handle(cx)
24301 }
24302}
24303
24304fn all_edits_insertions_or_deletions(
24305 edits: &Vec<(Range<Anchor>, String)>,
24306 snapshot: &MultiBufferSnapshot,
24307) -> bool {
24308 let mut all_insertions = true;
24309 let mut all_deletions = true;
24310
24311 for (range, new_text) in edits.iter() {
24312 let range_is_empty = range.to_offset(snapshot).is_empty();
24313 let text_is_empty = new_text.is_empty();
24314
24315 if range_is_empty != text_is_empty {
24316 if range_is_empty {
24317 all_deletions = false;
24318 } else {
24319 all_insertions = false;
24320 }
24321 } else {
24322 return false;
24323 }
24324
24325 if !all_insertions && !all_deletions {
24326 return false;
24327 }
24328 }
24329 all_insertions || all_deletions
24330}
24331
24332struct MissingEditPredictionKeybindingTooltip;
24333
24334impl Render for MissingEditPredictionKeybindingTooltip {
24335 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24336 ui::tooltip_container(window, cx, |container, _, cx| {
24337 container
24338 .flex_shrink_0()
24339 .max_w_80()
24340 .min_h(rems_from_px(124.))
24341 .justify_between()
24342 .child(
24343 v_flex()
24344 .flex_1()
24345 .text_ui_sm(cx)
24346 .child(Label::new("Conflict with Accept Keybinding"))
24347 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24348 )
24349 .child(
24350 h_flex()
24351 .pb_1()
24352 .gap_1()
24353 .items_end()
24354 .w_full()
24355 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24356 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
24357 }))
24358 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24359 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24360 })),
24361 )
24362 })
24363 }
24364}
24365
24366#[derive(Debug, Clone, Copy, PartialEq)]
24367pub struct LineHighlight {
24368 pub background: Background,
24369 pub border: Option<gpui::Hsla>,
24370 pub include_gutter: bool,
24371 pub type_id: Option<TypeId>,
24372}
24373
24374struct LineManipulationResult {
24375 pub new_text: String,
24376 pub line_count_before: usize,
24377 pub line_count_after: usize,
24378}
24379
24380fn render_diff_hunk_controls(
24381 row: u32,
24382 status: &DiffHunkStatus,
24383 hunk_range: Range<Anchor>,
24384 is_created_file: bool,
24385 line_height: Pixels,
24386 editor: &Entity<Editor>,
24387 _window: &mut Window,
24388 cx: &mut App,
24389) -> AnyElement {
24390 h_flex()
24391 .h(line_height)
24392 .mr_1()
24393 .gap_1()
24394 .px_0p5()
24395 .pb_1()
24396 .border_x_1()
24397 .border_b_1()
24398 .border_color(cx.theme().colors().border_variant)
24399 .rounded_b_lg()
24400 .bg(cx.theme().colors().editor_background)
24401 .gap_1()
24402 .block_mouse_except_scroll()
24403 .shadow_md()
24404 .child(if status.has_secondary_hunk() {
24405 Button::new(("stage", row as u64), "Stage")
24406 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24407 .tooltip({
24408 let focus_handle = editor.focus_handle(cx);
24409 move |window, cx| {
24410 Tooltip::for_action_in(
24411 "Stage Hunk",
24412 &::git::ToggleStaged,
24413 &focus_handle,
24414 window,
24415 cx,
24416 )
24417 }
24418 })
24419 .on_click({
24420 let editor = editor.clone();
24421 move |_event, _window, cx| {
24422 editor.update(cx, |editor, cx| {
24423 editor.stage_or_unstage_diff_hunks(
24424 true,
24425 vec![hunk_range.start..hunk_range.start],
24426 cx,
24427 );
24428 });
24429 }
24430 })
24431 } else {
24432 Button::new(("unstage", row as u64), "Unstage")
24433 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24434 .tooltip({
24435 let focus_handle = editor.focus_handle(cx);
24436 move |window, cx| {
24437 Tooltip::for_action_in(
24438 "Unstage Hunk",
24439 &::git::ToggleStaged,
24440 &focus_handle,
24441 window,
24442 cx,
24443 )
24444 }
24445 })
24446 .on_click({
24447 let editor = editor.clone();
24448 move |_event, _window, cx| {
24449 editor.update(cx, |editor, cx| {
24450 editor.stage_or_unstage_diff_hunks(
24451 false,
24452 vec![hunk_range.start..hunk_range.start],
24453 cx,
24454 );
24455 });
24456 }
24457 })
24458 })
24459 .child(
24460 Button::new(("restore", row as u64), "Restore")
24461 .tooltip({
24462 let focus_handle = editor.focus_handle(cx);
24463 move |window, cx| {
24464 Tooltip::for_action_in(
24465 "Restore Hunk",
24466 &::git::Restore,
24467 &focus_handle,
24468 window,
24469 cx,
24470 )
24471 }
24472 })
24473 .on_click({
24474 let editor = editor.clone();
24475 move |_event, window, cx| {
24476 editor.update(cx, |editor, cx| {
24477 let snapshot = editor.snapshot(window, cx);
24478 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
24479 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24480 });
24481 }
24482 })
24483 .disabled(is_created_file),
24484 )
24485 .when(
24486 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24487 |el| {
24488 el.child(
24489 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24490 .shape(IconButtonShape::Square)
24491 .icon_size(IconSize::Small)
24492 // .disabled(!has_multiple_hunks)
24493 .tooltip({
24494 let focus_handle = editor.focus_handle(cx);
24495 move |window, cx| {
24496 Tooltip::for_action_in(
24497 "Next Hunk",
24498 &GoToHunk,
24499 &focus_handle,
24500 window,
24501 cx,
24502 )
24503 }
24504 })
24505 .on_click({
24506 let editor = editor.clone();
24507 move |_event, window, cx| {
24508 editor.update(cx, |editor, cx| {
24509 let snapshot = editor.snapshot(window, cx);
24510 let position =
24511 hunk_range.end.to_point(&snapshot.buffer_snapshot);
24512 editor.go_to_hunk_before_or_after_position(
24513 &snapshot,
24514 position,
24515 Direction::Next,
24516 window,
24517 cx,
24518 );
24519 editor.expand_selected_diff_hunks(cx);
24520 });
24521 }
24522 }),
24523 )
24524 .child(
24525 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24526 .shape(IconButtonShape::Square)
24527 .icon_size(IconSize::Small)
24528 // .disabled(!has_multiple_hunks)
24529 .tooltip({
24530 let focus_handle = editor.focus_handle(cx);
24531 move |window, cx| {
24532 Tooltip::for_action_in(
24533 "Previous Hunk",
24534 &GoToPreviousHunk,
24535 &focus_handle,
24536 window,
24537 cx,
24538 )
24539 }
24540 })
24541 .on_click({
24542 let editor = editor.clone();
24543 move |_event, window, cx| {
24544 editor.update(cx, |editor, cx| {
24545 let snapshot = editor.snapshot(window, cx);
24546 let point =
24547 hunk_range.start.to_point(&snapshot.buffer_snapshot);
24548 editor.go_to_hunk_before_or_after_position(
24549 &snapshot,
24550 point,
24551 Direction::Prev,
24552 window,
24553 cx,
24554 );
24555 editor.expand_selected_diff_hunks(cx);
24556 });
24557 }
24558 }),
24559 )
24560 },
24561 )
24562 .into_any_element()
24563}
24564
24565pub fn multibuffer_context_lines(cx: &App) -> u32 {
24566 EditorSettings::try_get(cx)
24567 .map(|settings| settings.excerpt_context_lines)
24568 .unwrap_or(2)
24569 .min(32)
24570}