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_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt,
78 future::{self, Shared, join},
79};
80use fuzzy::{StringMatch, StringMatchCandidate};
81
82use ::git::blame::BlameEntry;
83use ::git::{Restore, blame::ParsedCommitMessage};
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use git::blame::{GitBlame, GlobalBlameRenderer};
89use gpui::{
90 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
91 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
92 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
93 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
94 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
95 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
96 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
97 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
98};
99use highlight_matching_bracket::refresh_matching_bracket_highlights;
100use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
101pub use hover_popover::hover_markdown_style;
102use hover_popover::{HoverState, hide_hover};
103use indent_guides::ActiveIndentGuidesState;
104use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
105pub use inline_completion::Direction;
106use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
107pub use items::MAX_TAB_TITLE_LEN;
108use itertools::Itertools;
109use language::{
110 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
111 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
112 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
113 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
114 language_settings::{
115 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
116 all_language_settings, language_settings,
117 },
118 point_from_lsp, text_diff_with_options,
119};
120use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
121use linked_editing_ranges::refresh_linked_ranges;
122use markdown::Markdown;
123use mouse_context_menu::MouseContextMenu;
124use persistence::DB;
125use project::{
126 BreakpointWithPosition, ProjectPath,
127 debugger::{
128 breakpoint_store::{
129 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
130 BreakpointStoreEvent,
131 },
132 session::{Session, SessionEvent},
133 },
134 project_settings::DiagnosticSeverity,
135};
136
137pub use git::blame::BlameRenderer;
138pub use proposed_changes_editor::{
139 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
140};
141use std::{cell::OnceCell, iter::Peekable, ops::Not};
142use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
143
144pub use lsp::CompletionContext;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId, LanguageServerName,
148};
149
150use language::BufferSnapshot;
151pub use lsp_ext::lsp_tasks;
152use movement::TextLayoutDetails;
153pub use multi_buffer::{
154 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
155 RowInfo, ToOffset, ToPoint,
156};
157use multi_buffer::{
158 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
159 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
160};
161use parking_lot::Mutex;
162use project::{
163 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
164 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
165 TaskSourceKind,
166 debugger::breakpoint_store::Breakpoint,
167 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
168 project_settings::{GitGutterSetting, ProjectSettings},
169};
170use rand::prelude::*;
171use rpc::{ErrorExt, proto::*};
172use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
173use selections_collection::{
174 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
175};
176use serde::{Deserialize, Serialize};
177use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
178use smallvec::{SmallVec, smallvec};
179use snippet::Snippet;
180use std::sync::Arc;
181use std::{
182 any::TypeId,
183 borrow::Cow,
184 cell::RefCell,
185 cmp::{self, Ordering, Reverse},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 time::{Duration, Instant},
192};
193pub use sum_tree::Bias;
194use sum_tree::TreeMap;
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
196use theme::{
197 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc, wrap_with_prefix};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemHandle, PreviewTabsSettings},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::hover_links::{find_url, find_url_from_range};
215use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
216
217pub const FILE_HEADER_HEIGHT: u32 = 2;
218pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
219pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
220const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
221const MAX_LINE_LEN: usize = 1024;
222const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
223const MAX_SELECTION_HISTORY_LEN: usize = 1024;
224pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
225#[doc(hidden)]
226pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
227const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
228
229pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
232
233pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
234pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
235pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
236pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
237
238pub type RenderDiffHunkControlsFn = Arc<
239 dyn Fn(
240 u32,
241 &DiffHunkStatus,
242 Range<Anchor>,
243 bool,
244 Pixels,
245 &Entity<Editor>,
246 &mut Window,
247 &mut App,
248 ) -> AnyElement,
249>;
250
251const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
252 alt: true,
253 shift: true,
254 control: false,
255 platform: false,
256 function: false,
257};
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 Hint(usize),
279 DebuggerValue(usize),
280}
281
282impl InlayId {
283 fn id(&self) -> usize {
284 match self {
285 Self::InlineCompletion(id) => *id,
286 Self::Hint(id) => *id,
287 Self::DebuggerValue(id) => *id,
288 }
289 }
290}
291
292pub enum ActiveDebugLine {}
293pub enum DebugStackFrameLine {}
294enum DocumentHighlightRead {}
295enum DocumentHighlightWrite {}
296enum InputComposition {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
700type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
701
702#[derive(Default)]
703struct ScrollbarMarkerState {
704 scrollbar_size: Size<Pixels>,
705 dirty: bool,
706 markers: Arc<[PaintQuad]>,
707 pending_refresh: Option<Task<Result<()>>>,
708}
709
710impl ScrollbarMarkerState {
711 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
712 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq)]
717pub enum MinimapVisibility {
718 Disabled,
719 Enabled {
720 /// The configuration currently present in the users settings.
721 setting_configuration: bool,
722 /// Whether to override the currently set visibility from the users setting.
723 toggle_override: bool,
724 },
725}
726
727impl MinimapVisibility {
728 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
729 if mode.is_full() {
730 Self::Enabled {
731 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
732 toggle_override: false,
733 }
734 } else {
735 Self::Disabled
736 }
737 }
738
739 fn hidden(&self) -> Self {
740 match *self {
741 Self::Enabled {
742 setting_configuration,
743 ..
744 } => Self::Enabled {
745 setting_configuration,
746 toggle_override: setting_configuration,
747 },
748 Self::Disabled => Self::Disabled,
749 }
750 }
751
752 fn disabled(&self) -> bool {
753 match *self {
754 Self::Disabled => true,
755 _ => false,
756 }
757 }
758
759 fn settings_visibility(&self) -> bool {
760 match *self {
761 Self::Enabled {
762 setting_configuration,
763 ..
764 } => setting_configuration,
765 _ => false,
766 }
767 }
768
769 fn visible(&self) -> bool {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 toggle_override,
774 } => setting_configuration ^ toggle_override,
775 _ => false,
776 }
777 }
778
779 fn toggle_visibility(&self) -> Self {
780 match *self {
781 Self::Enabled {
782 toggle_override,
783 setting_configuration,
784 } => Self::Enabled {
785 setting_configuration,
786 toggle_override: !toggle_override,
787 },
788 Self::Disabled => Self::Disabled,
789 }
790 }
791}
792
793#[derive(Clone, Debug)]
794struct RunnableTasks {
795 templates: Vec<(TaskSourceKind, TaskTemplate)>,
796 offset: multi_buffer::Anchor,
797 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
798 column: u32,
799 // Values of all named captures, including those starting with '_'
800 extra_variables: HashMap<String, String>,
801 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
802 context_range: Range<BufferOffset>,
803}
804
805impl RunnableTasks {
806 fn resolve<'a>(
807 &'a self,
808 cx: &'a task::TaskContext,
809 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
810 self.templates.iter().filter_map(|(kind, template)| {
811 template
812 .resolve_task(&kind.to_id_base(), cx)
813 .map(|task| (kind.clone(), task))
814 })
815 }
816}
817
818#[derive(Clone)]
819pub struct ResolvedTasks {
820 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
821 position: Anchor,
822}
823
824#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
825struct BufferOffset(usize);
826
827// Addons allow storing per-editor state in other crates (e.g. Vim)
828pub trait Addon: 'static {
829 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
830
831 fn render_buffer_header_controls(
832 &self,
833 _: &ExcerptInfo,
834 _: &Window,
835 _: &App,
836 ) -> Option<AnyElement> {
837 None
838 }
839
840 fn to_any(&self) -> &dyn std::any::Any;
841
842 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
843 None
844 }
845}
846
847/// A set of caret positions, registered when the editor was edited.
848pub struct ChangeList {
849 changes: Vec<Vec<Anchor>>,
850 /// Currently "selected" change.
851 position: Option<usize>,
852}
853
854impl ChangeList {
855 pub fn new() -> Self {
856 Self {
857 changes: Vec::new(),
858 position: None,
859 }
860 }
861
862 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
863 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
864 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
865 if self.changes.is_empty() {
866 return None;
867 }
868
869 let prev = self.position.unwrap_or(self.changes.len());
870 let next = if direction == Direction::Prev {
871 prev.saturating_sub(count)
872 } else {
873 (prev + count).min(self.changes.len() - 1)
874 };
875 self.position = Some(next);
876 self.changes.get(next).map(|anchors| anchors.as_slice())
877 }
878
879 /// Adds a new change to the list, resetting the change list position.
880 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
881 self.position.take();
882 if pop_state {
883 self.changes.pop();
884 }
885 self.changes.push(new_positions.clone());
886 }
887
888 pub fn last(&self) -> Option<&[Anchor]> {
889 self.changes.last().map(|anchors| anchors.as_slice())
890 }
891}
892
893#[derive(Clone)]
894struct InlineBlamePopoverState {
895 scroll_handle: ScrollHandle,
896 commit_message: Option<ParsedCommitMessage>,
897 markdown: Entity<Markdown>,
898}
899
900struct InlineBlamePopover {
901 position: gpui::Point<Pixels>,
902 show_task: Option<Task<()>>,
903 hide_task: Option<Task<()>>,
904 popover_bounds: Option<Bounds<Pixels>>,
905 popover_state: InlineBlamePopoverState,
906}
907
908/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
909/// a breakpoint on them.
910#[derive(Clone, Copy, Debug)]
911struct PhantomBreakpointIndicator {
912 display_row: DisplayRow,
913 /// There's a small debounce between hovering over the line and showing the indicator.
914 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
915 is_active: bool,
916 collides_with_existing_breakpoint: bool,
917}
918/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
919///
920/// See the [module level documentation](self) for more information.
921pub struct Editor {
922 focus_handle: FocusHandle,
923 last_focused_descendant: Option<WeakFocusHandle>,
924 /// The text buffer being edited
925 buffer: Entity<MultiBuffer>,
926 /// Map of how text in the buffer should be displayed.
927 /// Handles soft wraps, folds, fake inlay text insertions, etc.
928 pub display_map: Entity<DisplayMap>,
929 pub selections: SelectionsCollection,
930 pub scroll_manager: ScrollManager,
931 /// When inline assist editors are linked, they all render cursors because
932 /// typing enters text into each of them, even the ones that aren't focused.
933 pub(crate) show_cursor_when_unfocused: bool,
934 columnar_selection_tail: Option<Anchor>,
935 add_selections_state: Option<AddSelectionsState>,
936 select_next_state: Option<SelectNextState>,
937 select_prev_state: Option<SelectNextState>,
938 selection_history: SelectionHistory,
939 autoclose_regions: Vec<AutocloseRegion>,
940 snippet_stack: InvalidationStack<SnippetState>,
941 select_syntax_node_history: SelectSyntaxNodeHistory,
942 ime_transaction: Option<TransactionId>,
943 pub diagnostics_max_severity: DiagnosticSeverity,
944 active_diagnostics: ActiveDiagnostic,
945 show_inline_diagnostics: bool,
946 inline_diagnostics_update: Task<()>,
947 inline_diagnostics_enabled: bool,
948 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
949 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
950 hard_wrap: Option<usize>,
951
952 // TODO: make this a access method
953 pub project: Option<Entity<Project>>,
954 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
955 completion_provider: Option<Rc<dyn CompletionProvider>>,
956 collaboration_hub: Option<Box<dyn CollaborationHub>>,
957 blink_manager: Entity<BlinkManager>,
958 show_cursor_names: bool,
959 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
960 pub show_local_selections: bool,
961 mode: EditorMode,
962 show_breadcrumbs: bool,
963 show_gutter: bool,
964 show_scrollbars: ScrollbarAxes,
965 minimap_visibility: MinimapVisibility,
966 offset_content: bool,
967 disable_expand_excerpt_buttons: bool,
968 show_line_numbers: Option<bool>,
969 use_relative_line_numbers: Option<bool>,
970 show_git_diff_gutter: Option<bool>,
971 show_code_actions: Option<bool>,
972 show_runnables: Option<bool>,
973 show_breakpoints: Option<bool>,
974 show_wrap_guides: Option<bool>,
975 show_indent_guides: Option<bool>,
976 placeholder_text: Option<Arc<str>>,
977 highlight_order: usize,
978 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
979 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
980 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
981 scrollbar_marker_state: ScrollbarMarkerState,
982 active_indent_guides_state: ActiveIndentGuidesState,
983 nav_history: Option<ItemNavHistory>,
984 context_menu: RefCell<Option<CodeContextMenu>>,
985 context_menu_options: Option<ContextMenuOptions>,
986 mouse_context_menu: Option<MouseContextMenu>,
987 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
988 inline_blame_popover: Option<InlineBlamePopover>,
989 signature_help_state: SignatureHelpState,
990 auto_signature_help: Option<bool>,
991 find_all_references_task_sources: Vec<Anchor>,
992 next_completion_id: CompletionId,
993 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
994 code_actions_task: Option<Task<Result<()>>>,
995 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
996 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
997 document_highlights_task: Option<Task<()>>,
998 linked_editing_range_task: Option<Task<Option<()>>>,
999 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1000 pending_rename: Option<RenameState>,
1001 searchable: bool,
1002 cursor_shape: CursorShape,
1003 current_line_highlight: Option<CurrentLineHighlight>,
1004 collapse_matches: bool,
1005 autoindent_mode: Option<AutoindentMode>,
1006 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1007 input_enabled: bool,
1008 use_modal_editing: bool,
1009 read_only: bool,
1010 leader_id: Option<CollaboratorId>,
1011 remote_id: Option<ViewId>,
1012 pub hover_state: HoverState,
1013 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1014 gutter_hovered: bool,
1015 hovered_link_state: Option<HoveredLinkState>,
1016 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1017 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1018 active_inline_completion: Option<InlineCompletionState>,
1019 /// Used to prevent flickering as the user types while the menu is open
1020 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1021 edit_prediction_settings: EditPredictionSettings,
1022 inline_completions_hidden_for_vim_mode: bool,
1023 show_inline_completions_override: Option<bool>,
1024 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1025 edit_prediction_preview: EditPredictionPreview,
1026 edit_prediction_indent_conflict: bool,
1027 edit_prediction_requires_modifier_in_indent_conflict: bool,
1028 inlay_hint_cache: InlayHintCache,
1029 next_inlay_id: usize,
1030 _subscriptions: Vec<Subscription>,
1031 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1032 gutter_dimensions: GutterDimensions,
1033 style: Option<EditorStyle>,
1034 text_style_refinement: Option<TextStyleRefinement>,
1035 next_editor_action_id: EditorActionId,
1036 editor_actions:
1037 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
1038 use_autoclose: bool,
1039 use_auto_surround: bool,
1040 auto_replace_emoji_shortcode: bool,
1041 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1042 show_git_blame_gutter: bool,
1043 show_git_blame_inline: bool,
1044 show_git_blame_inline_delay_task: Option<Task<()>>,
1045 git_blame_inline_enabled: bool,
1046 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1047 serialize_dirty_buffers: bool,
1048 show_selection_menu: Option<bool>,
1049 blame: Option<Entity<GitBlame>>,
1050 blame_subscription: Option<Subscription>,
1051 custom_context_menu: Option<
1052 Box<
1053 dyn 'static
1054 + Fn(
1055 &mut Self,
1056 DisplayPoint,
1057 &mut Window,
1058 &mut Context<Self>,
1059 ) -> Option<Entity<ui::ContextMenu>>,
1060 >,
1061 >,
1062 last_bounds: Option<Bounds<Pixels>>,
1063 last_position_map: Option<Rc<PositionMap>>,
1064 expect_bounds_change: Option<Bounds<Pixels>>,
1065 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1066 tasks_update_task: Option<Task<()>>,
1067 breakpoint_store: Option<Entity<BreakpointStore>>,
1068 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1069 in_project_search: bool,
1070 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1071 breadcrumb_header: Option<String>,
1072 focused_block: Option<FocusedBlock>,
1073 next_scroll_position: NextScrollCursorCenterTopBottom,
1074 addons: HashMap<TypeId, Box<dyn Addon>>,
1075 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1076 load_diff_task: Option<Shared<Task<()>>>,
1077 /// Whether we are temporarily displaying a diff other than git's
1078 temporary_diff_override: bool,
1079 selection_mark_mode: bool,
1080 toggle_fold_multiple_buffers: Task<()>,
1081 _scroll_cursor_center_top_bottom_task: Task<()>,
1082 serialize_selections: Task<()>,
1083 serialize_folds: Task<()>,
1084 mouse_cursor_hidden: bool,
1085 minimap: Option<Entity<Self>>,
1086 hide_mouse_mode: HideMouseMode,
1087 pub change_list: ChangeList,
1088 inline_value_cache: InlineValueCache,
1089}
1090
1091#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1092enum NextScrollCursorCenterTopBottom {
1093 #[default]
1094 Center,
1095 Top,
1096 Bottom,
1097}
1098
1099impl NextScrollCursorCenterTopBottom {
1100 fn next(&self) -> Self {
1101 match self {
1102 Self::Center => Self::Top,
1103 Self::Top => Self::Bottom,
1104 Self::Bottom => Self::Center,
1105 }
1106 }
1107}
1108
1109#[derive(Clone)]
1110pub struct EditorSnapshot {
1111 pub mode: EditorMode,
1112 show_gutter: bool,
1113 show_line_numbers: Option<bool>,
1114 show_git_diff_gutter: Option<bool>,
1115 show_code_actions: Option<bool>,
1116 show_runnables: Option<bool>,
1117 show_breakpoints: Option<bool>,
1118 git_blame_gutter_max_author_length: Option<usize>,
1119 pub display_snapshot: DisplaySnapshot,
1120 pub placeholder_text: Option<Arc<str>>,
1121 is_focused: bool,
1122 scroll_anchor: ScrollAnchor,
1123 ongoing_scroll: OngoingScroll,
1124 current_line_highlight: CurrentLineHighlight,
1125 gutter_hovered: bool,
1126}
1127
1128#[derive(Default, Debug, Clone, Copy)]
1129pub struct GutterDimensions {
1130 pub left_padding: Pixels,
1131 pub right_padding: Pixels,
1132 pub width: Pixels,
1133 pub margin: Pixels,
1134 pub git_blame_entries_width: Option<Pixels>,
1135}
1136
1137impl GutterDimensions {
1138 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1139 Self {
1140 margin: Self::default_gutter_margin(font_id, font_size, cx),
1141 ..Default::default()
1142 }
1143 }
1144
1145 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1146 -cx.text_system().descent(font_id, font_size)
1147 }
1148 /// The full width of the space taken up by the gutter.
1149 pub fn full_width(&self) -> Pixels {
1150 self.margin + self.width
1151 }
1152
1153 /// The width of the space reserved for the fold indicators,
1154 /// use alongside 'justify_end' and `gutter_width` to
1155 /// right align content with the line numbers
1156 pub fn fold_area_width(&self) -> Pixels {
1157 self.margin + self.right_padding
1158 }
1159}
1160
1161#[derive(Debug)]
1162pub struct RemoteSelection {
1163 pub replica_id: ReplicaId,
1164 pub selection: Selection<Anchor>,
1165 pub cursor_shape: CursorShape,
1166 pub collaborator_id: CollaboratorId,
1167 pub line_mode: bool,
1168 pub user_name: Option<SharedString>,
1169 pub color: PlayerColor,
1170}
1171
1172#[derive(Clone, Debug)]
1173struct SelectionHistoryEntry {
1174 selections: Arc<[Selection<Anchor>]>,
1175 select_next_state: Option<SelectNextState>,
1176 select_prev_state: Option<SelectNextState>,
1177 add_selections_state: Option<AddSelectionsState>,
1178}
1179
1180enum SelectionHistoryMode {
1181 Normal,
1182 Undoing,
1183 Redoing,
1184}
1185
1186#[derive(Clone, PartialEq, Eq, Hash)]
1187struct HoveredCursor {
1188 replica_id: u16,
1189 selection_id: usize,
1190}
1191
1192impl Default for SelectionHistoryMode {
1193 fn default() -> Self {
1194 Self::Normal
1195 }
1196}
1197
1198#[derive(Default)]
1199struct SelectionHistory {
1200 #[allow(clippy::type_complexity)]
1201 selections_by_transaction:
1202 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1203 mode: SelectionHistoryMode,
1204 undo_stack: VecDeque<SelectionHistoryEntry>,
1205 redo_stack: VecDeque<SelectionHistoryEntry>,
1206}
1207
1208impl SelectionHistory {
1209 fn insert_transaction(
1210 &mut self,
1211 transaction_id: TransactionId,
1212 selections: Arc<[Selection<Anchor>]>,
1213 ) {
1214 self.selections_by_transaction
1215 .insert(transaction_id, (selections, None));
1216 }
1217
1218 #[allow(clippy::type_complexity)]
1219 fn transaction(
1220 &self,
1221 transaction_id: TransactionId,
1222 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1223 self.selections_by_transaction.get(&transaction_id)
1224 }
1225
1226 #[allow(clippy::type_complexity)]
1227 fn transaction_mut(
1228 &mut self,
1229 transaction_id: TransactionId,
1230 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1231 self.selections_by_transaction.get_mut(&transaction_id)
1232 }
1233
1234 fn push(&mut self, entry: SelectionHistoryEntry) {
1235 if !entry.selections.is_empty() {
1236 match self.mode {
1237 SelectionHistoryMode::Normal => {
1238 self.push_undo(entry);
1239 self.redo_stack.clear();
1240 }
1241 SelectionHistoryMode::Undoing => self.push_redo(entry),
1242 SelectionHistoryMode::Redoing => self.push_undo(entry),
1243 }
1244 }
1245 }
1246
1247 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1248 if self
1249 .undo_stack
1250 .back()
1251 .map_or(true, |e| e.selections != entry.selections)
1252 {
1253 self.undo_stack.push_back(entry);
1254 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1255 self.undo_stack.pop_front();
1256 }
1257 }
1258 }
1259
1260 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1261 if self
1262 .redo_stack
1263 .back()
1264 .map_or(true, |e| e.selections != entry.selections)
1265 {
1266 self.redo_stack.push_back(entry);
1267 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1268 self.redo_stack.pop_front();
1269 }
1270 }
1271 }
1272}
1273
1274#[derive(Clone, Copy)]
1275pub struct RowHighlightOptions {
1276 pub autoscroll: bool,
1277 pub include_gutter: bool,
1278}
1279
1280impl Default for RowHighlightOptions {
1281 fn default() -> Self {
1282 Self {
1283 autoscroll: Default::default(),
1284 include_gutter: true,
1285 }
1286 }
1287}
1288
1289struct RowHighlight {
1290 index: usize,
1291 range: Range<Anchor>,
1292 color: Hsla,
1293 options: RowHighlightOptions,
1294 type_id: TypeId,
1295}
1296
1297#[derive(Clone, Debug)]
1298struct AddSelectionsState {
1299 above: bool,
1300 stack: Vec<usize>,
1301}
1302
1303#[derive(Clone)]
1304struct SelectNextState {
1305 query: AhoCorasick,
1306 wordwise: bool,
1307 done: bool,
1308}
1309
1310impl std::fmt::Debug for SelectNextState {
1311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1312 f.debug_struct(std::any::type_name::<Self>())
1313 .field("wordwise", &self.wordwise)
1314 .field("done", &self.done)
1315 .finish()
1316 }
1317}
1318
1319#[derive(Debug)]
1320struct AutocloseRegion {
1321 selection_id: usize,
1322 range: Range<Anchor>,
1323 pair: BracketPair,
1324}
1325
1326#[derive(Debug)]
1327struct SnippetState {
1328 ranges: Vec<Vec<Range<Anchor>>>,
1329 active_index: usize,
1330 choices: Vec<Option<Vec<String>>>,
1331}
1332
1333#[doc(hidden)]
1334pub struct RenameState {
1335 pub range: Range<Anchor>,
1336 pub old_name: Arc<str>,
1337 pub editor: Entity<Editor>,
1338 block_id: CustomBlockId,
1339}
1340
1341struct InvalidationStack<T>(Vec<T>);
1342
1343struct RegisteredInlineCompletionProvider {
1344 provider: Arc<dyn InlineCompletionProviderHandle>,
1345 _subscription: Subscription,
1346}
1347
1348#[derive(Debug, PartialEq, Eq)]
1349pub struct ActiveDiagnosticGroup {
1350 pub active_range: Range<Anchor>,
1351 pub active_message: String,
1352 pub group_id: usize,
1353 pub blocks: HashSet<CustomBlockId>,
1354}
1355
1356#[derive(Debug, PartialEq, Eq)]
1357
1358pub(crate) enum ActiveDiagnostic {
1359 None,
1360 All,
1361 Group(ActiveDiagnosticGroup),
1362}
1363
1364#[derive(Serialize, Deserialize, Clone, Debug)]
1365pub struct ClipboardSelection {
1366 /// The number of bytes in this selection.
1367 pub len: usize,
1368 /// Whether this was a full-line selection.
1369 pub is_entire_line: bool,
1370 /// The indentation of the first line when this content was originally copied.
1371 pub first_line_indent: u32,
1372}
1373
1374// selections, scroll behavior, was newest selection reversed
1375type SelectSyntaxNodeHistoryState = (
1376 Box<[Selection<usize>]>,
1377 SelectSyntaxNodeScrollBehavior,
1378 bool,
1379);
1380
1381#[derive(Default)]
1382struct SelectSyntaxNodeHistory {
1383 stack: Vec<SelectSyntaxNodeHistoryState>,
1384 // disable temporarily to allow changing selections without losing the stack
1385 pub disable_clearing: bool,
1386}
1387
1388impl SelectSyntaxNodeHistory {
1389 pub fn try_clear(&mut self) {
1390 if !self.disable_clearing {
1391 self.stack.clear();
1392 }
1393 }
1394
1395 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1396 self.stack.push(selection);
1397 }
1398
1399 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1400 self.stack.pop()
1401 }
1402}
1403
1404enum SelectSyntaxNodeScrollBehavior {
1405 CursorTop,
1406 FitSelection,
1407 CursorBottom,
1408}
1409
1410#[derive(Debug)]
1411pub(crate) struct NavigationData {
1412 cursor_anchor: Anchor,
1413 cursor_position: Point,
1414 scroll_anchor: ScrollAnchor,
1415 scroll_top_row: u32,
1416}
1417
1418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1419pub enum GotoDefinitionKind {
1420 Symbol,
1421 Declaration,
1422 Type,
1423 Implementation,
1424}
1425
1426#[derive(Debug, Clone)]
1427enum InlayHintRefreshReason {
1428 ModifiersChanged(bool),
1429 Toggle(bool),
1430 SettingsChange(InlayHintSettings),
1431 NewLinesShown,
1432 BufferEdited(HashSet<Arc<Language>>),
1433 RefreshRequested,
1434 ExcerptsRemoved(Vec<ExcerptId>),
1435}
1436
1437impl InlayHintRefreshReason {
1438 fn description(&self) -> &'static str {
1439 match self {
1440 Self::ModifiersChanged(_) => "modifiers changed",
1441 Self::Toggle(_) => "toggle",
1442 Self::SettingsChange(_) => "settings change",
1443 Self::NewLinesShown => "new lines shown",
1444 Self::BufferEdited(_) => "buffer edited",
1445 Self::RefreshRequested => "refresh requested",
1446 Self::ExcerptsRemoved(_) => "excerpts removed",
1447 }
1448 }
1449}
1450
1451pub enum FormatTarget {
1452 Buffers,
1453 Ranges(Vec<Range<MultiBufferPoint>>),
1454}
1455
1456pub(crate) struct FocusedBlock {
1457 id: BlockId,
1458 focus_handle: WeakFocusHandle,
1459}
1460
1461#[derive(Clone)]
1462enum JumpData {
1463 MultiBufferRow {
1464 row: MultiBufferRow,
1465 line_offset_from_top: u32,
1466 },
1467 MultiBufferPoint {
1468 excerpt_id: ExcerptId,
1469 position: Point,
1470 anchor: text::Anchor,
1471 line_offset_from_top: u32,
1472 },
1473}
1474
1475pub enum MultibufferSelectionMode {
1476 First,
1477 All,
1478}
1479
1480#[derive(Clone, Copy, Debug, Default)]
1481pub struct RewrapOptions {
1482 pub override_language_settings: bool,
1483 pub preserve_existing_whitespace: bool,
1484}
1485
1486impl Editor {
1487 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1488 let buffer = cx.new(|cx| Buffer::local("", cx));
1489 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1490 Self::new(
1491 EditorMode::SingleLine { auto_width: false },
1492 buffer,
1493 None,
1494 window,
1495 cx,
1496 )
1497 }
1498
1499 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1500 let buffer = cx.new(|cx| Buffer::local("", cx));
1501 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1502 Self::new(EditorMode::full(), buffer, None, window, cx)
1503 }
1504
1505 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1506 let buffer = cx.new(|cx| Buffer::local("", cx));
1507 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1508 Self::new(
1509 EditorMode::SingleLine { auto_width: true },
1510 buffer,
1511 None,
1512 window,
1513 cx,
1514 )
1515 }
1516
1517 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1518 let buffer = cx.new(|cx| Buffer::local("", cx));
1519 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1520 Self::new(
1521 EditorMode::AutoHeight { max_lines },
1522 buffer,
1523 None,
1524 window,
1525 cx,
1526 )
1527 }
1528
1529 pub fn for_buffer(
1530 buffer: Entity<Buffer>,
1531 project: Option<Entity<Project>>,
1532 window: &mut Window,
1533 cx: &mut Context<Self>,
1534 ) -> Self {
1535 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1536 Self::new(EditorMode::full(), buffer, project, window, cx)
1537 }
1538
1539 pub fn for_multibuffer(
1540 buffer: Entity<MultiBuffer>,
1541 project: Option<Entity<Project>>,
1542 window: &mut Window,
1543 cx: &mut Context<Self>,
1544 ) -> Self {
1545 Self::new(EditorMode::full(), buffer, project, window, cx)
1546 }
1547
1548 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1549 let mut clone = Self::new(
1550 self.mode.clone(),
1551 self.buffer.clone(),
1552 self.project.clone(),
1553 window,
1554 cx,
1555 );
1556 self.display_map.update(cx, |display_map, cx| {
1557 let snapshot = display_map.snapshot(cx);
1558 clone.display_map.update(cx, |display_map, cx| {
1559 display_map.set_state(&snapshot, cx);
1560 });
1561 });
1562 clone.folds_did_change(cx);
1563 clone.selections.clone_state(&self.selections);
1564 clone.scroll_manager.clone_state(&self.scroll_manager);
1565 clone.searchable = self.searchable;
1566 clone.read_only = self.read_only;
1567 clone
1568 }
1569
1570 pub fn new(
1571 mode: EditorMode,
1572 buffer: Entity<MultiBuffer>,
1573 project: Option<Entity<Project>>,
1574 window: &mut Window,
1575 cx: &mut Context<Self>,
1576 ) -> Self {
1577 Editor::new_internal(mode, buffer, project, None, window, cx)
1578 }
1579
1580 fn new_internal(
1581 mode: EditorMode,
1582 buffer: Entity<MultiBuffer>,
1583 project: Option<Entity<Project>>,
1584 display_map: Option<Entity<DisplayMap>>,
1585 window: &mut Window,
1586 cx: &mut Context<Self>,
1587 ) -> Self {
1588 debug_assert!(
1589 display_map.is_none() || mode.is_minimap(),
1590 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1591 );
1592
1593 let full_mode = mode.is_full();
1594 let diagnostics_max_severity = if full_mode {
1595 EditorSettings::get_global(cx)
1596 .diagnostics_max_severity
1597 .unwrap_or(DiagnosticSeverity::Hint)
1598 } else {
1599 DiagnosticSeverity::Off
1600 };
1601 let style = window.text_style();
1602 let font_size = style.font_size.to_pixels(window.rem_size());
1603 let editor = cx.entity().downgrade();
1604 let fold_placeholder = FoldPlaceholder {
1605 constrain_width: true,
1606 render: Arc::new(move |fold_id, fold_range, cx| {
1607 let editor = editor.clone();
1608 div()
1609 .id(fold_id)
1610 .bg(cx.theme().colors().ghost_element_background)
1611 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1612 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1613 .rounded_xs()
1614 .size_full()
1615 .cursor_pointer()
1616 .child("⋯")
1617 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1618 .on_click(move |_, _window, cx| {
1619 editor
1620 .update(cx, |editor, cx| {
1621 editor.unfold_ranges(
1622 &[fold_range.start..fold_range.end],
1623 true,
1624 false,
1625 cx,
1626 );
1627 cx.stop_propagation();
1628 })
1629 .ok();
1630 })
1631 .into_any()
1632 }),
1633 merge_adjacent: true,
1634 ..FoldPlaceholder::default()
1635 };
1636 let display_map = display_map.unwrap_or_else(|| {
1637 cx.new(|cx| {
1638 DisplayMap::new(
1639 buffer.clone(),
1640 style.font(),
1641 font_size,
1642 None,
1643 FILE_HEADER_HEIGHT,
1644 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1645 fold_placeholder,
1646 diagnostics_max_severity,
1647 cx,
1648 )
1649 })
1650 });
1651
1652 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1653
1654 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1655
1656 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1657 .then(|| language_settings::SoftWrap::None);
1658
1659 let mut project_subscriptions = Vec::new();
1660 if mode.is_full() {
1661 if let Some(project) = project.as_ref() {
1662 project_subscriptions.push(cx.subscribe_in(
1663 project,
1664 window,
1665 |editor, _, event, window, cx| match event {
1666 project::Event::RefreshCodeLens => {
1667 // we always query lens with actions, without storing them, always refreshing them
1668 }
1669 project::Event::RefreshInlayHints => {
1670 editor
1671 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1672 }
1673 project::Event::SnippetEdit(id, snippet_edits) => {
1674 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1675 let focus_handle = editor.focus_handle(cx);
1676 if focus_handle.is_focused(window) {
1677 let snapshot = buffer.read(cx).snapshot();
1678 for (range, snippet) in snippet_edits {
1679 let editor_range =
1680 language::range_from_lsp(*range).to_offset(&snapshot);
1681 editor
1682 .insert_snippet(
1683 &[editor_range],
1684 snippet.clone(),
1685 window,
1686 cx,
1687 )
1688 .ok();
1689 }
1690 }
1691 }
1692 }
1693 _ => {}
1694 },
1695 ));
1696 if let Some(task_inventory) = project
1697 .read(cx)
1698 .task_store()
1699 .read(cx)
1700 .task_inventory()
1701 .cloned()
1702 {
1703 project_subscriptions.push(cx.observe_in(
1704 &task_inventory,
1705 window,
1706 |editor, _, window, cx| {
1707 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1708 },
1709 ));
1710 };
1711
1712 project_subscriptions.push(cx.subscribe_in(
1713 &project.read(cx).breakpoint_store(),
1714 window,
1715 |editor, _, event, window, cx| match event {
1716 BreakpointStoreEvent::ClearDebugLines => {
1717 editor.clear_row_highlights::<ActiveDebugLine>();
1718 editor.refresh_inline_values(cx);
1719 }
1720 BreakpointStoreEvent::SetDebugLine => {
1721 if editor.go_to_active_debug_line(window, cx) {
1722 cx.stop_propagation();
1723 }
1724
1725 editor.refresh_inline_values(cx);
1726 }
1727 _ => {}
1728 },
1729 ));
1730 }
1731 }
1732
1733 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1734
1735 let inlay_hint_settings =
1736 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1737 let focus_handle = cx.focus_handle();
1738 cx.on_focus(&focus_handle, window, Self::handle_focus)
1739 .detach();
1740 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1741 .detach();
1742 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1743 .detach();
1744 cx.on_blur(&focus_handle, window, Self::handle_blur)
1745 .detach();
1746
1747 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1748 Some(false)
1749 } else {
1750 None
1751 };
1752
1753 let breakpoint_store = match (&mode, project.as_ref()) {
1754 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1755 _ => None,
1756 };
1757
1758 let mut code_action_providers = Vec::new();
1759 let mut load_uncommitted_diff = None;
1760 if let Some(project) = project.clone() {
1761 load_uncommitted_diff = Some(
1762 update_uncommitted_diff_for_buffer(
1763 cx.entity(),
1764 &project,
1765 buffer.read(cx).all_buffers(),
1766 buffer.clone(),
1767 cx,
1768 )
1769 .shared(),
1770 );
1771 code_action_providers.push(Rc::new(project) as Rc<_>);
1772 }
1773
1774 let mut this = Self {
1775 focus_handle,
1776 show_cursor_when_unfocused: false,
1777 last_focused_descendant: None,
1778 buffer: buffer.clone(),
1779 display_map: display_map.clone(),
1780 selections,
1781 scroll_manager: ScrollManager::new(cx),
1782 columnar_selection_tail: None,
1783 add_selections_state: None,
1784 select_next_state: None,
1785 select_prev_state: None,
1786 selection_history: SelectionHistory::default(),
1787 autoclose_regions: Vec::new(),
1788 snippet_stack: InvalidationStack::default(),
1789 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1790 ime_transaction: None,
1791 active_diagnostics: ActiveDiagnostic::None,
1792 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1793 inline_diagnostics_update: Task::ready(()),
1794 inline_diagnostics: Vec::new(),
1795 soft_wrap_mode_override,
1796 diagnostics_max_severity,
1797 hard_wrap: None,
1798 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1799 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1800 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1801 project,
1802 blink_manager: blink_manager.clone(),
1803 show_local_selections: true,
1804 show_scrollbars: ScrollbarAxes {
1805 horizontal: full_mode,
1806 vertical: full_mode,
1807 },
1808 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1809 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1810 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1811 show_gutter: mode.is_full(),
1812 show_line_numbers: None,
1813 use_relative_line_numbers: None,
1814 disable_expand_excerpt_buttons: false,
1815 show_git_diff_gutter: None,
1816 show_code_actions: None,
1817 show_runnables: None,
1818 show_breakpoints: None,
1819 show_wrap_guides: None,
1820 show_indent_guides,
1821 placeholder_text: None,
1822 highlight_order: 0,
1823 highlighted_rows: HashMap::default(),
1824 background_highlights: TreeMap::default(),
1825 gutter_highlights: TreeMap::default(),
1826 scrollbar_marker_state: ScrollbarMarkerState::default(),
1827 active_indent_guides_state: ActiveIndentGuidesState::default(),
1828 nav_history: None,
1829 context_menu: RefCell::new(None),
1830 context_menu_options: None,
1831 mouse_context_menu: None,
1832 completion_tasks: Vec::new(),
1833 inline_blame_popover: None,
1834 signature_help_state: SignatureHelpState::default(),
1835 auto_signature_help: None,
1836 find_all_references_task_sources: Vec::new(),
1837 next_completion_id: 0,
1838 next_inlay_id: 0,
1839 code_action_providers,
1840 available_code_actions: None,
1841 code_actions_task: None,
1842 quick_selection_highlight_task: None,
1843 debounced_selection_highlight_task: None,
1844 document_highlights_task: None,
1845 linked_editing_range_task: None,
1846 pending_rename: None,
1847 searchable: true,
1848 cursor_shape: EditorSettings::get_global(cx)
1849 .cursor_shape
1850 .unwrap_or_default(),
1851 current_line_highlight: None,
1852 autoindent_mode: Some(AutoindentMode::EachLine),
1853 collapse_matches: false,
1854 workspace: None,
1855 input_enabled: true,
1856 use_modal_editing: mode.is_full(),
1857 read_only: mode.is_minimap(),
1858 use_autoclose: true,
1859 use_auto_surround: true,
1860 auto_replace_emoji_shortcode: false,
1861 jsx_tag_auto_close_enabled_in_any_buffer: false,
1862 leader_id: None,
1863 remote_id: None,
1864 hover_state: HoverState::default(),
1865 pending_mouse_down: None,
1866 hovered_link_state: None,
1867 edit_prediction_provider: None,
1868 active_inline_completion: None,
1869 stale_inline_completion_in_menu: None,
1870 edit_prediction_preview: EditPredictionPreview::Inactive {
1871 released_too_fast: false,
1872 },
1873 inline_diagnostics_enabled: mode.is_full(),
1874 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1875 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1876
1877 gutter_hovered: false,
1878 pixel_position_of_newest_cursor: None,
1879 last_bounds: None,
1880 last_position_map: None,
1881 expect_bounds_change: None,
1882 gutter_dimensions: GutterDimensions::default(),
1883 style: None,
1884 show_cursor_names: false,
1885 hovered_cursors: HashMap::default(),
1886 next_editor_action_id: EditorActionId::default(),
1887 editor_actions: Rc::default(),
1888 inline_completions_hidden_for_vim_mode: false,
1889 show_inline_completions_override: None,
1890 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1891 edit_prediction_settings: EditPredictionSettings::Disabled,
1892 edit_prediction_indent_conflict: false,
1893 edit_prediction_requires_modifier_in_indent_conflict: true,
1894 custom_context_menu: None,
1895 show_git_blame_gutter: false,
1896 show_git_blame_inline: false,
1897 show_selection_menu: None,
1898 show_git_blame_inline_delay_task: None,
1899 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1900 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1901 serialize_dirty_buffers: !mode.is_minimap()
1902 && ProjectSettings::get_global(cx)
1903 .session
1904 .restore_unsaved_buffers,
1905 blame: None,
1906 blame_subscription: None,
1907 tasks: BTreeMap::default(),
1908
1909 breakpoint_store,
1910 gutter_breakpoint_indicator: (None, None),
1911 _subscriptions: vec![
1912 cx.observe(&buffer, Self::on_buffer_changed),
1913 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1914 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1915 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1916 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1917 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1918 cx.observe_window_activation(window, |editor, window, cx| {
1919 let active = window.is_window_active();
1920 editor.blink_manager.update(cx, |blink_manager, cx| {
1921 if active {
1922 blink_manager.enable(cx);
1923 } else {
1924 blink_manager.disable(cx);
1925 }
1926 });
1927 if active {
1928 editor.show_mouse_cursor();
1929 }
1930 }),
1931 ],
1932 tasks_update_task: None,
1933 linked_edit_ranges: Default::default(),
1934 in_project_search: false,
1935 previous_search_ranges: None,
1936 breadcrumb_header: None,
1937 focused_block: None,
1938 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1939 addons: HashMap::default(),
1940 registered_buffers: HashMap::default(),
1941 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1942 selection_mark_mode: false,
1943 toggle_fold_multiple_buffers: Task::ready(()),
1944 serialize_selections: Task::ready(()),
1945 serialize_folds: Task::ready(()),
1946 text_style_refinement: None,
1947 load_diff_task: load_uncommitted_diff,
1948 temporary_diff_override: false,
1949 mouse_cursor_hidden: false,
1950 minimap: None,
1951 hide_mouse_mode: EditorSettings::get_global(cx)
1952 .hide_mouse
1953 .unwrap_or_default(),
1954 change_list: ChangeList::new(),
1955 mode,
1956 };
1957 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1958 this._subscriptions
1959 .push(cx.observe(breakpoints, |_, _, cx| {
1960 cx.notify();
1961 }));
1962 }
1963 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1964 this._subscriptions.extend(project_subscriptions);
1965
1966 this._subscriptions.push(cx.subscribe_in(
1967 &cx.entity(),
1968 window,
1969 |editor, _, e: &EditorEvent, window, cx| match e {
1970 EditorEvent::ScrollPositionChanged { local, .. } => {
1971 if *local {
1972 let new_anchor = editor.scroll_manager.anchor();
1973 let snapshot = editor.snapshot(window, cx);
1974 editor.update_restoration_data(cx, move |data| {
1975 data.scroll_position = (
1976 new_anchor.top_row(&snapshot.buffer_snapshot),
1977 new_anchor.offset,
1978 );
1979 });
1980 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1981 editor.inline_blame_popover.take();
1982 }
1983 }
1984 EditorEvent::Edited { .. } => {
1985 if !vim_enabled(cx) {
1986 let (map, selections) = editor.selections.all_adjusted_display(cx);
1987 let pop_state = editor
1988 .change_list
1989 .last()
1990 .map(|previous| {
1991 previous.len() == selections.len()
1992 && previous.iter().enumerate().all(|(ix, p)| {
1993 p.to_display_point(&map).row()
1994 == selections[ix].head().row()
1995 })
1996 })
1997 .unwrap_or(false);
1998 let new_positions = selections
1999 .into_iter()
2000 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2001 .collect();
2002 editor
2003 .change_list
2004 .push_to_change_list(pop_state, new_positions);
2005 }
2006 }
2007 _ => (),
2008 },
2009 ));
2010
2011 if let Some(dap_store) = this
2012 .project
2013 .as_ref()
2014 .map(|project| project.read(cx).dap_store())
2015 {
2016 let weak_editor = cx.weak_entity();
2017
2018 this._subscriptions
2019 .push(
2020 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2021 let session_entity = cx.entity();
2022 weak_editor
2023 .update(cx, |editor, cx| {
2024 editor._subscriptions.push(
2025 cx.subscribe(&session_entity, Self::on_debug_session_event),
2026 );
2027 })
2028 .ok();
2029 }),
2030 );
2031
2032 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2033 this._subscriptions
2034 .push(cx.subscribe(&session, Self::on_debug_session_event));
2035 }
2036 }
2037
2038 this.end_selection(window, cx);
2039 this.scroll_manager.show_scrollbars(window, cx);
2040 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
2041
2042 if full_mode {
2043 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2044 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2045
2046 if this.git_blame_inline_enabled {
2047 this.start_git_blame_inline(false, window, cx);
2048 }
2049
2050 this.go_to_active_debug_line(window, cx);
2051
2052 if let Some(buffer) = buffer.read(cx).as_singleton() {
2053 if let Some(project) = this.project.as_ref() {
2054 let handle = project.update(cx, |project, cx| {
2055 project.register_buffer_with_language_servers(&buffer, cx)
2056 });
2057 this.registered_buffers
2058 .insert(buffer.read(cx).remote_id(), handle);
2059 }
2060 }
2061
2062 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2063 }
2064
2065 this.report_editor_event("Editor Opened", None, cx);
2066 this
2067 }
2068
2069 pub fn deploy_mouse_context_menu(
2070 &mut self,
2071 position: gpui::Point<Pixels>,
2072 context_menu: Entity<ContextMenu>,
2073 window: &mut Window,
2074 cx: &mut Context<Self>,
2075 ) {
2076 self.mouse_context_menu = Some(MouseContextMenu::new(
2077 self,
2078 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2079 context_menu,
2080 window,
2081 cx,
2082 ));
2083 }
2084
2085 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2086 self.mouse_context_menu
2087 .as_ref()
2088 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2089 }
2090
2091 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2092 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2093 }
2094
2095 fn key_context_internal(
2096 &self,
2097 has_active_edit_prediction: bool,
2098 window: &Window,
2099 cx: &App,
2100 ) -> KeyContext {
2101 let mut key_context = KeyContext::new_with_defaults();
2102 key_context.add("Editor");
2103 let mode = match self.mode {
2104 EditorMode::SingleLine { .. } => "single_line",
2105 EditorMode::AutoHeight { .. } => "auto_height",
2106 EditorMode::Minimap { .. } => "minimap",
2107 EditorMode::Full { .. } => "full",
2108 };
2109
2110 if EditorSettings::jupyter_enabled(cx) {
2111 key_context.add("jupyter");
2112 }
2113
2114 key_context.set("mode", mode);
2115 if self.pending_rename.is_some() {
2116 key_context.add("renaming");
2117 }
2118
2119 match self.context_menu.borrow().as_ref() {
2120 Some(CodeContextMenu::Completions(_)) => {
2121 key_context.add("menu");
2122 key_context.add("showing_completions");
2123 }
2124 Some(CodeContextMenu::CodeActions(_)) => {
2125 key_context.add("menu");
2126 key_context.add("showing_code_actions")
2127 }
2128 None => {}
2129 }
2130
2131 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2132 if !self.focus_handle(cx).contains_focused(window, cx)
2133 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2134 {
2135 for addon in self.addons.values() {
2136 addon.extend_key_context(&mut key_context, cx)
2137 }
2138 }
2139
2140 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2141 if let Some(extension) = singleton_buffer
2142 .read(cx)
2143 .file()
2144 .and_then(|file| file.path().extension()?.to_str())
2145 {
2146 key_context.set("extension", extension.to_string());
2147 }
2148 } else {
2149 key_context.add("multibuffer");
2150 }
2151
2152 if has_active_edit_prediction {
2153 if self.edit_prediction_in_conflict() {
2154 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2155 } else {
2156 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2157 key_context.add("copilot_suggestion");
2158 }
2159 }
2160
2161 if self.selection_mark_mode {
2162 key_context.add("selection_mode");
2163 }
2164
2165 key_context
2166 }
2167
2168 fn show_mouse_cursor(&mut self) {
2169 self.mouse_cursor_hidden = false;
2170 }
2171
2172 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2173 self.mouse_cursor_hidden = match origin {
2174 HideMouseCursorOrigin::TypingAction => {
2175 matches!(
2176 self.hide_mouse_mode,
2177 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2178 )
2179 }
2180 HideMouseCursorOrigin::MovementAction => {
2181 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2182 }
2183 };
2184 }
2185
2186 pub fn edit_prediction_in_conflict(&self) -> bool {
2187 if !self.show_edit_predictions_in_menu() {
2188 return false;
2189 }
2190
2191 let showing_completions = self
2192 .context_menu
2193 .borrow()
2194 .as_ref()
2195 .map_or(false, |context| {
2196 matches!(context, CodeContextMenu::Completions(_))
2197 });
2198
2199 showing_completions
2200 || self.edit_prediction_requires_modifier()
2201 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2202 // bindings to insert tab characters.
2203 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2204 }
2205
2206 pub fn accept_edit_prediction_keybind(
2207 &self,
2208 window: &Window,
2209 cx: &App,
2210 ) -> AcceptEditPredictionBinding {
2211 let key_context = self.key_context_internal(true, window, cx);
2212 let in_conflict = self.edit_prediction_in_conflict();
2213
2214 AcceptEditPredictionBinding(
2215 window
2216 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2217 .into_iter()
2218 .filter(|binding| {
2219 !in_conflict
2220 || binding
2221 .keystrokes()
2222 .first()
2223 .map_or(false, |keystroke| keystroke.modifiers.modified())
2224 })
2225 .rev()
2226 .min_by_key(|binding| {
2227 binding
2228 .keystrokes()
2229 .first()
2230 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2231 }),
2232 )
2233 }
2234
2235 pub fn new_file(
2236 workspace: &mut Workspace,
2237 _: &workspace::NewFile,
2238 window: &mut Window,
2239 cx: &mut Context<Workspace>,
2240 ) {
2241 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2242 "Failed to create buffer",
2243 window,
2244 cx,
2245 |e, _, _| match e.error_code() {
2246 ErrorCode::RemoteUpgradeRequired => Some(format!(
2247 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2248 e.error_tag("required").unwrap_or("the latest version")
2249 )),
2250 _ => None,
2251 },
2252 );
2253 }
2254
2255 pub fn new_in_workspace(
2256 workspace: &mut Workspace,
2257 window: &mut Window,
2258 cx: &mut Context<Workspace>,
2259 ) -> Task<Result<Entity<Editor>>> {
2260 let project = workspace.project().clone();
2261 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2262
2263 cx.spawn_in(window, async move |workspace, cx| {
2264 let buffer = create.await?;
2265 workspace.update_in(cx, |workspace, window, cx| {
2266 let editor =
2267 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2268 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2269 editor
2270 })
2271 })
2272 }
2273
2274 fn new_file_vertical(
2275 workspace: &mut Workspace,
2276 _: &workspace::NewFileSplitVertical,
2277 window: &mut Window,
2278 cx: &mut Context<Workspace>,
2279 ) {
2280 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2281 }
2282
2283 fn new_file_horizontal(
2284 workspace: &mut Workspace,
2285 _: &workspace::NewFileSplitHorizontal,
2286 window: &mut Window,
2287 cx: &mut Context<Workspace>,
2288 ) {
2289 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2290 }
2291
2292 fn new_file_in_direction(
2293 workspace: &mut Workspace,
2294 direction: SplitDirection,
2295 window: &mut Window,
2296 cx: &mut Context<Workspace>,
2297 ) {
2298 let project = workspace.project().clone();
2299 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2300
2301 cx.spawn_in(window, async move |workspace, cx| {
2302 let buffer = create.await?;
2303 workspace.update_in(cx, move |workspace, window, cx| {
2304 workspace.split_item(
2305 direction,
2306 Box::new(
2307 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2308 ),
2309 window,
2310 cx,
2311 )
2312 })?;
2313 anyhow::Ok(())
2314 })
2315 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2316 match e.error_code() {
2317 ErrorCode::RemoteUpgradeRequired => Some(format!(
2318 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2319 e.error_tag("required").unwrap_or("the latest version")
2320 )),
2321 _ => None,
2322 }
2323 });
2324 }
2325
2326 pub fn leader_id(&self) -> Option<CollaboratorId> {
2327 self.leader_id
2328 }
2329
2330 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2331 &self.buffer
2332 }
2333
2334 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2335 self.workspace.as_ref()?.0.upgrade()
2336 }
2337
2338 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2339 self.buffer().read(cx).title(cx)
2340 }
2341
2342 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2343 let git_blame_gutter_max_author_length = self
2344 .render_git_blame_gutter(cx)
2345 .then(|| {
2346 if let Some(blame) = self.blame.as_ref() {
2347 let max_author_length =
2348 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2349 Some(max_author_length)
2350 } else {
2351 None
2352 }
2353 })
2354 .flatten();
2355
2356 EditorSnapshot {
2357 mode: self.mode.clone(),
2358 show_gutter: self.show_gutter,
2359 show_line_numbers: self.show_line_numbers,
2360 show_git_diff_gutter: self.show_git_diff_gutter,
2361 show_code_actions: self.show_code_actions,
2362 show_runnables: self.show_runnables,
2363 show_breakpoints: self.show_breakpoints,
2364 git_blame_gutter_max_author_length,
2365 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2366 scroll_anchor: self.scroll_manager.anchor(),
2367 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2368 placeholder_text: self.placeholder_text.clone(),
2369 is_focused: self.focus_handle.is_focused(window),
2370 current_line_highlight: self
2371 .current_line_highlight
2372 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2373 gutter_hovered: self.gutter_hovered,
2374 }
2375 }
2376
2377 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2378 self.buffer.read(cx).language_at(point, cx)
2379 }
2380
2381 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2382 self.buffer.read(cx).read(cx).file_at(point).cloned()
2383 }
2384
2385 pub fn active_excerpt(
2386 &self,
2387 cx: &App,
2388 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2389 self.buffer
2390 .read(cx)
2391 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2392 }
2393
2394 pub fn mode(&self) -> &EditorMode {
2395 &self.mode
2396 }
2397
2398 pub fn set_mode(&mut self, mode: EditorMode) {
2399 self.mode = mode;
2400 }
2401
2402 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2403 self.collaboration_hub.as_deref()
2404 }
2405
2406 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2407 self.collaboration_hub = Some(hub);
2408 }
2409
2410 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2411 self.in_project_search = in_project_search;
2412 }
2413
2414 pub fn set_custom_context_menu(
2415 &mut self,
2416 f: impl 'static
2417 + Fn(
2418 &mut Self,
2419 DisplayPoint,
2420 &mut Window,
2421 &mut Context<Self>,
2422 ) -> Option<Entity<ui::ContextMenu>>,
2423 ) {
2424 self.custom_context_menu = Some(Box::new(f))
2425 }
2426
2427 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2428 self.completion_provider = provider;
2429 }
2430
2431 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2432 self.semantics_provider.clone()
2433 }
2434
2435 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2436 self.semantics_provider = provider;
2437 }
2438
2439 pub fn set_edit_prediction_provider<T>(
2440 &mut self,
2441 provider: Option<Entity<T>>,
2442 window: &mut Window,
2443 cx: &mut Context<Self>,
2444 ) where
2445 T: EditPredictionProvider,
2446 {
2447 self.edit_prediction_provider =
2448 provider.map(|provider| RegisteredInlineCompletionProvider {
2449 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2450 if this.focus_handle.is_focused(window) {
2451 this.update_visible_inline_completion(window, cx);
2452 }
2453 }),
2454 provider: Arc::new(provider),
2455 });
2456 self.update_edit_prediction_settings(cx);
2457 self.refresh_inline_completion(false, false, window, cx);
2458 }
2459
2460 pub fn placeholder_text(&self) -> Option<&str> {
2461 self.placeholder_text.as_deref()
2462 }
2463
2464 pub fn set_placeholder_text(
2465 &mut self,
2466 placeholder_text: impl Into<Arc<str>>,
2467 cx: &mut Context<Self>,
2468 ) {
2469 let placeholder_text = Some(placeholder_text.into());
2470 if self.placeholder_text != placeholder_text {
2471 self.placeholder_text = placeholder_text;
2472 cx.notify();
2473 }
2474 }
2475
2476 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2477 self.cursor_shape = cursor_shape;
2478
2479 // Disrupt blink for immediate user feedback that the cursor shape has changed
2480 self.blink_manager.update(cx, BlinkManager::show_cursor);
2481
2482 cx.notify();
2483 }
2484
2485 pub fn set_current_line_highlight(
2486 &mut self,
2487 current_line_highlight: Option<CurrentLineHighlight>,
2488 ) {
2489 self.current_line_highlight = current_line_highlight;
2490 }
2491
2492 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2493 self.collapse_matches = collapse_matches;
2494 }
2495
2496 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2497 let buffers = self.buffer.read(cx).all_buffers();
2498 let Some(project) = self.project.as_ref() else {
2499 return;
2500 };
2501 project.update(cx, |project, cx| {
2502 for buffer in buffers {
2503 self.registered_buffers
2504 .entry(buffer.read(cx).remote_id())
2505 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2506 }
2507 })
2508 }
2509
2510 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2511 if self.collapse_matches {
2512 return range.start..range.start;
2513 }
2514 range.clone()
2515 }
2516
2517 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2518 if self.display_map.read(cx).clip_at_line_ends != clip {
2519 self.display_map
2520 .update(cx, |map, _| map.clip_at_line_ends = clip);
2521 }
2522 }
2523
2524 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2525 self.input_enabled = input_enabled;
2526 }
2527
2528 pub fn set_inline_completions_hidden_for_vim_mode(
2529 &mut self,
2530 hidden: bool,
2531 window: &mut Window,
2532 cx: &mut Context<Self>,
2533 ) {
2534 if hidden != self.inline_completions_hidden_for_vim_mode {
2535 self.inline_completions_hidden_for_vim_mode = hidden;
2536 if hidden {
2537 self.update_visible_inline_completion(window, cx);
2538 } else {
2539 self.refresh_inline_completion(true, false, window, cx);
2540 }
2541 }
2542 }
2543
2544 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2545 self.menu_inline_completions_policy = value;
2546 }
2547
2548 pub fn set_autoindent(&mut self, autoindent: bool) {
2549 if autoindent {
2550 self.autoindent_mode = Some(AutoindentMode::EachLine);
2551 } else {
2552 self.autoindent_mode = None;
2553 }
2554 }
2555
2556 pub fn read_only(&self, cx: &App) -> bool {
2557 self.read_only || self.buffer.read(cx).read_only()
2558 }
2559
2560 pub fn set_read_only(&mut self, read_only: bool) {
2561 self.read_only = read_only;
2562 }
2563
2564 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2565 self.use_autoclose = autoclose;
2566 }
2567
2568 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2569 self.use_auto_surround = auto_surround;
2570 }
2571
2572 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2573 self.auto_replace_emoji_shortcode = auto_replace;
2574 }
2575
2576 pub fn toggle_edit_predictions(
2577 &mut self,
2578 _: &ToggleEditPrediction,
2579 window: &mut Window,
2580 cx: &mut Context<Self>,
2581 ) {
2582 if self.show_inline_completions_override.is_some() {
2583 self.set_show_edit_predictions(None, window, cx);
2584 } else {
2585 let show_edit_predictions = !self.edit_predictions_enabled();
2586 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2587 }
2588 }
2589
2590 pub fn set_show_edit_predictions(
2591 &mut self,
2592 show_edit_predictions: Option<bool>,
2593 window: &mut Window,
2594 cx: &mut Context<Self>,
2595 ) {
2596 self.show_inline_completions_override = show_edit_predictions;
2597 self.update_edit_prediction_settings(cx);
2598
2599 if let Some(false) = show_edit_predictions {
2600 self.discard_inline_completion(false, cx);
2601 } else {
2602 self.refresh_inline_completion(false, true, window, cx);
2603 }
2604 }
2605
2606 fn inline_completions_disabled_in_scope(
2607 &self,
2608 buffer: &Entity<Buffer>,
2609 buffer_position: language::Anchor,
2610 cx: &App,
2611 ) -> bool {
2612 let snapshot = buffer.read(cx).snapshot();
2613 let settings = snapshot.settings_at(buffer_position, cx);
2614
2615 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2616 return false;
2617 };
2618
2619 scope.override_name().map_or(false, |scope_name| {
2620 settings
2621 .edit_predictions_disabled_in
2622 .iter()
2623 .any(|s| s == scope_name)
2624 })
2625 }
2626
2627 pub fn set_use_modal_editing(&mut self, to: bool) {
2628 self.use_modal_editing = to;
2629 }
2630
2631 pub fn use_modal_editing(&self) -> bool {
2632 self.use_modal_editing
2633 }
2634
2635 fn selections_did_change(
2636 &mut self,
2637 local: bool,
2638 old_cursor_position: &Anchor,
2639 show_completions: bool,
2640 window: &mut Window,
2641 cx: &mut Context<Self>,
2642 ) {
2643 window.invalidate_character_coordinates();
2644
2645 // Copy selections to primary selection buffer
2646 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2647 if local {
2648 let selections = self.selections.all::<usize>(cx);
2649 let buffer_handle = self.buffer.read(cx).read(cx);
2650
2651 let mut text = String::new();
2652 for (index, selection) in selections.iter().enumerate() {
2653 let text_for_selection = buffer_handle
2654 .text_for_range(selection.start..selection.end)
2655 .collect::<String>();
2656
2657 text.push_str(&text_for_selection);
2658 if index != selections.len() - 1 {
2659 text.push('\n');
2660 }
2661 }
2662
2663 if !text.is_empty() {
2664 cx.write_to_primary(ClipboardItem::new_string(text));
2665 }
2666 }
2667
2668 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2669 self.buffer.update(cx, |buffer, cx| {
2670 buffer.set_active_selections(
2671 &self.selections.disjoint_anchors(),
2672 self.selections.line_mode,
2673 self.cursor_shape,
2674 cx,
2675 )
2676 });
2677 }
2678 let display_map = self
2679 .display_map
2680 .update(cx, |display_map, cx| display_map.snapshot(cx));
2681 let buffer = &display_map.buffer_snapshot;
2682 self.add_selections_state = None;
2683 self.select_next_state = None;
2684 self.select_prev_state = None;
2685 self.select_syntax_node_history.try_clear();
2686 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2687 self.snippet_stack
2688 .invalidate(&self.selections.disjoint_anchors(), buffer);
2689 self.take_rename(false, window, cx);
2690
2691 let new_cursor_position = self.selections.newest_anchor().head();
2692
2693 self.push_to_nav_history(
2694 *old_cursor_position,
2695 Some(new_cursor_position.to_point(buffer)),
2696 false,
2697 cx,
2698 );
2699
2700 if local {
2701 let new_cursor_position = self.selections.newest_anchor().head();
2702 let mut context_menu = self.context_menu.borrow_mut();
2703 let completion_menu = match context_menu.as_ref() {
2704 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2705 _ => {
2706 *context_menu = None;
2707 None
2708 }
2709 };
2710 if let Some(buffer_id) = new_cursor_position.buffer_id {
2711 if !self.registered_buffers.contains_key(&buffer_id) {
2712 if let Some(project) = self.project.as_ref() {
2713 project.update(cx, |project, cx| {
2714 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2715 return;
2716 };
2717 self.registered_buffers.insert(
2718 buffer_id,
2719 project.register_buffer_with_language_servers(&buffer, cx),
2720 );
2721 })
2722 }
2723 }
2724 }
2725
2726 if let Some(completion_menu) = completion_menu {
2727 let cursor_position = new_cursor_position.to_offset(buffer);
2728 let (word_range, kind) =
2729 buffer.surrounding_word(completion_menu.initial_position, true);
2730 if kind == Some(CharKind::Word)
2731 && word_range.to_inclusive().contains(&cursor_position)
2732 {
2733 let mut completion_menu = completion_menu.clone();
2734 drop(context_menu);
2735
2736 let query = Self::completion_query(buffer, cursor_position);
2737 let completion_provider = self.completion_provider.clone();
2738 cx.spawn_in(window, async move |this, cx| {
2739 completion_menu
2740 .filter(query.as_deref(), completion_provider, this.clone(), cx)
2741 .await;
2742
2743 this.update(cx, |this, cx| {
2744 let mut context_menu = this.context_menu.borrow_mut();
2745 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2746 else {
2747 return;
2748 };
2749
2750 if menu.id > completion_menu.id {
2751 return;
2752 }
2753
2754 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2755 drop(context_menu);
2756 cx.notify();
2757 })
2758 })
2759 .detach();
2760
2761 if show_completions {
2762 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2763 }
2764 } else {
2765 drop(context_menu);
2766 self.hide_context_menu(window, cx);
2767 }
2768 } else {
2769 drop(context_menu);
2770 }
2771
2772 hide_hover(self, cx);
2773
2774 if old_cursor_position.to_display_point(&display_map).row()
2775 != new_cursor_position.to_display_point(&display_map).row()
2776 {
2777 self.available_code_actions.take();
2778 }
2779 self.refresh_code_actions(window, cx);
2780 self.refresh_document_highlights(cx);
2781 self.refresh_selected_text_highlights(false, window, cx);
2782 refresh_matching_bracket_highlights(self, window, cx);
2783 self.update_visible_inline_completion(window, cx);
2784 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2785 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2786 self.inline_blame_popover.take();
2787 if self.git_blame_inline_enabled {
2788 self.start_inline_blame_timer(window, cx);
2789 }
2790 }
2791
2792 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2793 cx.emit(EditorEvent::SelectionsChanged { local });
2794
2795 let selections = &self.selections.disjoint;
2796 if selections.len() == 1 {
2797 cx.emit(SearchEvent::ActiveMatchChanged)
2798 }
2799 if local {
2800 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2801 let inmemory_selections = selections
2802 .iter()
2803 .map(|s| {
2804 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2805 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2806 })
2807 .collect();
2808 self.update_restoration_data(cx, |data| {
2809 data.selections = inmemory_selections;
2810 });
2811
2812 if WorkspaceSettings::get(None, cx).restore_on_startup
2813 != RestoreOnStartupBehavior::None
2814 {
2815 if let Some(workspace_id) =
2816 self.workspace.as_ref().and_then(|workspace| workspace.1)
2817 {
2818 let snapshot = self.buffer().read(cx).snapshot(cx);
2819 let selections = selections.clone();
2820 let background_executor = cx.background_executor().clone();
2821 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2822 self.serialize_selections = cx.background_spawn(async move {
2823 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2824 let db_selections = selections
2825 .iter()
2826 .map(|selection| {
2827 (
2828 selection.start.to_offset(&snapshot),
2829 selection.end.to_offset(&snapshot),
2830 )
2831 })
2832 .collect();
2833
2834 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2835 .await
2836 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2837 .log_err();
2838 });
2839 }
2840 }
2841 }
2842 }
2843
2844 cx.notify();
2845 }
2846
2847 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2848 use text::ToOffset as _;
2849 use text::ToPoint as _;
2850
2851 if self.mode.is_minimap()
2852 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2853 {
2854 return;
2855 }
2856
2857 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2858 return;
2859 };
2860
2861 let snapshot = singleton.read(cx).snapshot();
2862 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2863 let display_snapshot = display_map.snapshot(cx);
2864
2865 display_snapshot
2866 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2867 .map(|fold| {
2868 fold.range.start.text_anchor.to_point(&snapshot)
2869 ..fold.range.end.text_anchor.to_point(&snapshot)
2870 })
2871 .collect()
2872 });
2873 self.update_restoration_data(cx, |data| {
2874 data.folds = inmemory_folds;
2875 });
2876
2877 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2878 return;
2879 };
2880 let background_executor = cx.background_executor().clone();
2881 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2882 let db_folds = self.display_map.update(cx, |display_map, cx| {
2883 display_map
2884 .snapshot(cx)
2885 .folds_in_range(0..snapshot.len())
2886 .map(|fold| {
2887 (
2888 fold.range.start.text_anchor.to_offset(&snapshot),
2889 fold.range.end.text_anchor.to_offset(&snapshot),
2890 )
2891 })
2892 .collect()
2893 });
2894 self.serialize_folds = cx.background_spawn(async move {
2895 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2896 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2897 .await
2898 .with_context(|| {
2899 format!(
2900 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2901 )
2902 })
2903 .log_err();
2904 });
2905 }
2906
2907 pub fn sync_selections(
2908 &mut self,
2909 other: Entity<Editor>,
2910 cx: &mut Context<Self>,
2911 ) -> gpui::Subscription {
2912 let other_selections = other.read(cx).selections.disjoint.to_vec();
2913 self.selections.change_with(cx, |selections| {
2914 selections.select_anchors(other_selections);
2915 });
2916
2917 let other_subscription =
2918 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2919 EditorEvent::SelectionsChanged { local: true } => {
2920 let other_selections = other.read(cx).selections.disjoint.to_vec();
2921 if other_selections.is_empty() {
2922 return;
2923 }
2924 this.selections.change_with(cx, |selections| {
2925 selections.select_anchors(other_selections);
2926 });
2927 }
2928 _ => {}
2929 });
2930
2931 let this_subscription =
2932 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2933 EditorEvent::SelectionsChanged { local: true } => {
2934 let these_selections = this.selections.disjoint.to_vec();
2935 if these_selections.is_empty() {
2936 return;
2937 }
2938 other.update(cx, |other_editor, cx| {
2939 other_editor.selections.change_with(cx, |selections| {
2940 selections.select_anchors(these_selections);
2941 })
2942 });
2943 }
2944 _ => {}
2945 });
2946
2947 Subscription::join(other_subscription, this_subscription)
2948 }
2949
2950 pub fn change_selections<R>(
2951 &mut self,
2952 autoscroll: Option<Autoscroll>,
2953 window: &mut Window,
2954 cx: &mut Context<Self>,
2955 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2956 ) -> R {
2957 self.change_selections_inner(autoscroll, true, window, cx, change)
2958 }
2959
2960 fn change_selections_inner<R>(
2961 &mut self,
2962 autoscroll: Option<Autoscroll>,
2963 request_completions: bool,
2964 window: &mut Window,
2965 cx: &mut Context<Self>,
2966 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2967 ) -> R {
2968 let old_cursor_position = self.selections.newest_anchor().head();
2969 self.push_to_selection_history();
2970
2971 let (changed, result) = self.selections.change_with(cx, change);
2972
2973 if changed {
2974 if let Some(autoscroll) = autoscroll {
2975 self.request_autoscroll(autoscroll, cx);
2976 }
2977 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2978
2979 if self.should_open_signature_help_automatically(
2980 &old_cursor_position,
2981 self.signature_help_state.backspace_pressed(),
2982 cx,
2983 ) {
2984 self.show_signature_help(&ShowSignatureHelp, window, cx);
2985 }
2986 self.signature_help_state.set_backspace_pressed(false);
2987 }
2988
2989 result
2990 }
2991
2992 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2993 where
2994 I: IntoIterator<Item = (Range<S>, T)>,
2995 S: ToOffset,
2996 T: Into<Arc<str>>,
2997 {
2998 if self.read_only(cx) {
2999 return;
3000 }
3001
3002 self.buffer
3003 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3004 }
3005
3006 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3007 where
3008 I: IntoIterator<Item = (Range<S>, T)>,
3009 S: ToOffset,
3010 T: Into<Arc<str>>,
3011 {
3012 if self.read_only(cx) {
3013 return;
3014 }
3015
3016 self.buffer.update(cx, |buffer, cx| {
3017 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3018 });
3019 }
3020
3021 pub fn edit_with_block_indent<I, S, T>(
3022 &mut self,
3023 edits: I,
3024 original_indent_columns: Vec<Option<u32>>,
3025 cx: &mut Context<Self>,
3026 ) where
3027 I: IntoIterator<Item = (Range<S>, T)>,
3028 S: ToOffset,
3029 T: Into<Arc<str>>,
3030 {
3031 if self.read_only(cx) {
3032 return;
3033 }
3034
3035 self.buffer.update(cx, |buffer, cx| {
3036 buffer.edit(
3037 edits,
3038 Some(AutoindentMode::Block {
3039 original_indent_columns,
3040 }),
3041 cx,
3042 )
3043 });
3044 }
3045
3046 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3047 self.hide_context_menu(window, cx);
3048
3049 match phase {
3050 SelectPhase::Begin {
3051 position,
3052 add,
3053 click_count,
3054 } => self.begin_selection(position, add, click_count, window, cx),
3055 SelectPhase::BeginColumnar {
3056 position,
3057 goal_column,
3058 reset,
3059 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3060 SelectPhase::Extend {
3061 position,
3062 click_count,
3063 } => self.extend_selection(position, click_count, window, cx),
3064 SelectPhase::Update {
3065 position,
3066 goal_column,
3067 scroll_delta,
3068 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3069 SelectPhase::End => self.end_selection(window, cx),
3070 }
3071 }
3072
3073 fn extend_selection(
3074 &mut self,
3075 position: DisplayPoint,
3076 click_count: usize,
3077 window: &mut Window,
3078 cx: &mut Context<Self>,
3079 ) {
3080 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3081 let tail = self.selections.newest::<usize>(cx).tail();
3082 self.begin_selection(position, false, click_count, window, cx);
3083
3084 let position = position.to_offset(&display_map, Bias::Left);
3085 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3086
3087 let mut pending_selection = self
3088 .selections
3089 .pending_anchor()
3090 .expect("extend_selection not called with pending selection");
3091 if position >= tail {
3092 pending_selection.start = tail_anchor;
3093 } else {
3094 pending_selection.end = tail_anchor;
3095 pending_selection.reversed = true;
3096 }
3097
3098 let mut pending_mode = self.selections.pending_mode().unwrap();
3099 match &mut pending_mode {
3100 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3101 _ => {}
3102 }
3103
3104 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3105
3106 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3107 s.set_pending(pending_selection, pending_mode)
3108 });
3109 }
3110
3111 fn begin_selection(
3112 &mut self,
3113 position: DisplayPoint,
3114 add: bool,
3115 click_count: usize,
3116 window: &mut Window,
3117 cx: &mut Context<Self>,
3118 ) {
3119 if !self.focus_handle.is_focused(window) {
3120 self.last_focused_descendant = None;
3121 window.focus(&self.focus_handle);
3122 }
3123
3124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3125 let buffer = &display_map.buffer_snapshot;
3126 let position = display_map.clip_point(position, Bias::Left);
3127
3128 let start;
3129 let end;
3130 let mode;
3131 let mut auto_scroll;
3132 match click_count {
3133 1 => {
3134 start = buffer.anchor_before(position.to_point(&display_map));
3135 end = start;
3136 mode = SelectMode::Character;
3137 auto_scroll = true;
3138 }
3139 2 => {
3140 let range = movement::surrounding_word(&display_map, position);
3141 start = buffer.anchor_before(range.start.to_point(&display_map));
3142 end = buffer.anchor_before(range.end.to_point(&display_map));
3143 mode = SelectMode::Word(start..end);
3144 auto_scroll = true;
3145 }
3146 3 => {
3147 let position = display_map
3148 .clip_point(position, Bias::Left)
3149 .to_point(&display_map);
3150 let line_start = display_map.prev_line_boundary(position).0;
3151 let next_line_start = buffer.clip_point(
3152 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3153 Bias::Left,
3154 );
3155 start = buffer.anchor_before(line_start);
3156 end = buffer.anchor_before(next_line_start);
3157 mode = SelectMode::Line(start..end);
3158 auto_scroll = true;
3159 }
3160 _ => {
3161 start = buffer.anchor_before(0);
3162 end = buffer.anchor_before(buffer.len());
3163 mode = SelectMode::All;
3164 auto_scroll = false;
3165 }
3166 }
3167 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3168
3169 let point_to_delete: Option<usize> = {
3170 let selected_points: Vec<Selection<Point>> =
3171 self.selections.disjoint_in_range(start..end, cx);
3172
3173 if !add || click_count > 1 {
3174 None
3175 } else if !selected_points.is_empty() {
3176 Some(selected_points[0].id)
3177 } else {
3178 let clicked_point_already_selected =
3179 self.selections.disjoint.iter().find(|selection| {
3180 selection.start.to_point(buffer) == start.to_point(buffer)
3181 || selection.end.to_point(buffer) == end.to_point(buffer)
3182 });
3183
3184 clicked_point_already_selected.map(|selection| selection.id)
3185 }
3186 };
3187
3188 let selections_count = self.selections.count();
3189
3190 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3191 if let Some(point_to_delete) = point_to_delete {
3192 s.delete(point_to_delete);
3193
3194 if selections_count == 1 {
3195 s.set_pending_anchor_range(start..end, mode);
3196 }
3197 } else {
3198 if !add {
3199 s.clear_disjoint();
3200 }
3201
3202 s.set_pending_anchor_range(start..end, mode);
3203 }
3204 });
3205 }
3206
3207 fn begin_columnar_selection(
3208 &mut self,
3209 position: DisplayPoint,
3210 goal_column: u32,
3211 reset: bool,
3212 window: &mut Window,
3213 cx: &mut Context<Self>,
3214 ) {
3215 if !self.focus_handle.is_focused(window) {
3216 self.last_focused_descendant = None;
3217 window.focus(&self.focus_handle);
3218 }
3219
3220 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3221
3222 if reset {
3223 let pointer_position = display_map
3224 .buffer_snapshot
3225 .anchor_before(position.to_point(&display_map));
3226
3227 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3228 s.clear_disjoint();
3229 s.set_pending_anchor_range(
3230 pointer_position..pointer_position,
3231 SelectMode::Character,
3232 );
3233 });
3234 }
3235
3236 let tail = self.selections.newest::<Point>(cx).tail();
3237 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3238
3239 if !reset {
3240 self.select_columns(
3241 tail.to_display_point(&display_map),
3242 position,
3243 goal_column,
3244 &display_map,
3245 window,
3246 cx,
3247 );
3248 }
3249 }
3250
3251 fn update_selection(
3252 &mut self,
3253 position: DisplayPoint,
3254 goal_column: u32,
3255 scroll_delta: gpui::Point<f32>,
3256 window: &mut Window,
3257 cx: &mut Context<Self>,
3258 ) {
3259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3260
3261 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3262 let tail = tail.to_display_point(&display_map);
3263 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3264 } else if let Some(mut pending) = self.selections.pending_anchor() {
3265 let buffer = self.buffer.read(cx).snapshot(cx);
3266 let head;
3267 let tail;
3268 let mode = self.selections.pending_mode().unwrap();
3269 match &mode {
3270 SelectMode::Character => {
3271 head = position.to_point(&display_map);
3272 tail = pending.tail().to_point(&buffer);
3273 }
3274 SelectMode::Word(original_range) => {
3275 let original_display_range = original_range.start.to_display_point(&display_map)
3276 ..original_range.end.to_display_point(&display_map);
3277 let original_buffer_range = original_display_range.start.to_point(&display_map)
3278 ..original_display_range.end.to_point(&display_map);
3279 if movement::is_inside_word(&display_map, position)
3280 || original_display_range.contains(&position)
3281 {
3282 let word_range = movement::surrounding_word(&display_map, position);
3283 if word_range.start < original_display_range.start {
3284 head = word_range.start.to_point(&display_map);
3285 } else {
3286 head = word_range.end.to_point(&display_map);
3287 }
3288 } else {
3289 head = position.to_point(&display_map);
3290 }
3291
3292 if head <= original_buffer_range.start {
3293 tail = original_buffer_range.end;
3294 } else {
3295 tail = original_buffer_range.start;
3296 }
3297 }
3298 SelectMode::Line(original_range) => {
3299 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3300
3301 let position = display_map
3302 .clip_point(position, Bias::Left)
3303 .to_point(&display_map);
3304 let line_start = display_map.prev_line_boundary(position).0;
3305 let next_line_start = buffer.clip_point(
3306 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3307 Bias::Left,
3308 );
3309
3310 if line_start < original_range.start {
3311 head = line_start
3312 } else {
3313 head = next_line_start
3314 }
3315
3316 if head <= original_range.start {
3317 tail = original_range.end;
3318 } else {
3319 tail = original_range.start;
3320 }
3321 }
3322 SelectMode::All => {
3323 return;
3324 }
3325 };
3326
3327 if head < tail {
3328 pending.start = buffer.anchor_before(head);
3329 pending.end = buffer.anchor_before(tail);
3330 pending.reversed = true;
3331 } else {
3332 pending.start = buffer.anchor_before(tail);
3333 pending.end = buffer.anchor_before(head);
3334 pending.reversed = false;
3335 }
3336
3337 self.change_selections(None, window, cx, |s| {
3338 s.set_pending(pending, mode);
3339 });
3340 } else {
3341 log::error!("update_selection dispatched with no pending selection");
3342 return;
3343 }
3344
3345 self.apply_scroll_delta(scroll_delta, window, cx);
3346 cx.notify();
3347 }
3348
3349 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3350 self.columnar_selection_tail.take();
3351 if self.selections.pending_anchor().is_some() {
3352 let selections = self.selections.all::<usize>(cx);
3353 self.change_selections(None, window, cx, |s| {
3354 s.select(selections);
3355 s.clear_pending();
3356 });
3357 }
3358 }
3359
3360 fn select_columns(
3361 &mut self,
3362 tail: DisplayPoint,
3363 head: DisplayPoint,
3364 goal_column: u32,
3365 display_map: &DisplaySnapshot,
3366 window: &mut Window,
3367 cx: &mut Context<Self>,
3368 ) {
3369 let start_row = cmp::min(tail.row(), head.row());
3370 let end_row = cmp::max(tail.row(), head.row());
3371 let start_column = cmp::min(tail.column(), goal_column);
3372 let end_column = cmp::max(tail.column(), goal_column);
3373 let reversed = start_column < tail.column();
3374
3375 let selection_ranges = (start_row.0..=end_row.0)
3376 .map(DisplayRow)
3377 .filter_map(|row| {
3378 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3379 let start = display_map
3380 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3381 .to_point(display_map);
3382 let end = display_map
3383 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3384 .to_point(display_map);
3385 if reversed {
3386 Some(end..start)
3387 } else {
3388 Some(start..end)
3389 }
3390 } else {
3391 None
3392 }
3393 })
3394 .collect::<Vec<_>>();
3395
3396 self.change_selections(None, window, cx, |s| {
3397 s.select_ranges(selection_ranges);
3398 });
3399 cx.notify();
3400 }
3401
3402 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3403 self.selections
3404 .all_adjusted(cx)
3405 .iter()
3406 .any(|selection| !selection.is_empty())
3407 }
3408
3409 pub fn has_pending_nonempty_selection(&self) -> bool {
3410 let pending_nonempty_selection = match self.selections.pending_anchor() {
3411 Some(Selection { start, end, .. }) => start != end,
3412 None => false,
3413 };
3414
3415 pending_nonempty_selection
3416 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3417 }
3418
3419 pub fn has_pending_selection(&self) -> bool {
3420 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3421 }
3422
3423 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3424 self.selection_mark_mode = false;
3425
3426 if self.clear_expanded_diff_hunks(cx) {
3427 cx.notify();
3428 return;
3429 }
3430 if self.dismiss_menus_and_popups(true, window, cx) {
3431 return;
3432 }
3433
3434 if self.mode.is_full()
3435 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3436 {
3437 return;
3438 }
3439
3440 cx.propagate();
3441 }
3442
3443 pub fn dismiss_menus_and_popups(
3444 &mut self,
3445 is_user_requested: bool,
3446 window: &mut Window,
3447 cx: &mut Context<Self>,
3448 ) -> bool {
3449 if self.take_rename(false, window, cx).is_some() {
3450 return true;
3451 }
3452
3453 if hide_hover(self, cx) {
3454 return true;
3455 }
3456
3457 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3458 return true;
3459 }
3460
3461 if self.hide_context_menu(window, cx).is_some() {
3462 return true;
3463 }
3464
3465 if self.mouse_context_menu.take().is_some() {
3466 return true;
3467 }
3468
3469 if is_user_requested && self.discard_inline_completion(true, cx) {
3470 return true;
3471 }
3472
3473 if self.snippet_stack.pop().is_some() {
3474 return true;
3475 }
3476
3477 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3478 self.dismiss_diagnostics(cx);
3479 return true;
3480 }
3481
3482 false
3483 }
3484
3485 fn linked_editing_ranges_for(
3486 &self,
3487 selection: Range<text::Anchor>,
3488 cx: &App,
3489 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3490 if self.linked_edit_ranges.is_empty() {
3491 return None;
3492 }
3493 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3494 selection.end.buffer_id.and_then(|end_buffer_id| {
3495 if selection.start.buffer_id != Some(end_buffer_id) {
3496 return None;
3497 }
3498 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3499 let snapshot = buffer.read(cx).snapshot();
3500 self.linked_edit_ranges
3501 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3502 .map(|ranges| (ranges, snapshot, buffer))
3503 })?;
3504 use text::ToOffset as TO;
3505 // find offset from the start of current range to current cursor position
3506 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3507
3508 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3509 let start_difference = start_offset - start_byte_offset;
3510 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3511 let end_difference = end_offset - start_byte_offset;
3512 // Current range has associated linked ranges.
3513 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3514 for range in linked_ranges.iter() {
3515 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3516 let end_offset = start_offset + end_difference;
3517 let start_offset = start_offset + start_difference;
3518 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3519 continue;
3520 }
3521 if self.selections.disjoint_anchor_ranges().any(|s| {
3522 if s.start.buffer_id != selection.start.buffer_id
3523 || s.end.buffer_id != selection.end.buffer_id
3524 {
3525 return false;
3526 }
3527 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3528 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3529 }) {
3530 continue;
3531 }
3532 let start = buffer_snapshot.anchor_after(start_offset);
3533 let end = buffer_snapshot.anchor_after(end_offset);
3534 linked_edits
3535 .entry(buffer.clone())
3536 .or_default()
3537 .push(start..end);
3538 }
3539 Some(linked_edits)
3540 }
3541
3542 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3543 let text: Arc<str> = text.into();
3544
3545 if self.read_only(cx) {
3546 return;
3547 }
3548
3549 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3550
3551 let selections = self.selections.all_adjusted(cx);
3552 let mut bracket_inserted = false;
3553 let mut edits = Vec::new();
3554 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3555 let mut new_selections = Vec::with_capacity(selections.len());
3556 let mut new_autoclose_regions = Vec::new();
3557 let snapshot = self.buffer.read(cx).read(cx);
3558 let mut clear_linked_edit_ranges = false;
3559
3560 for (selection, autoclose_region) in
3561 self.selections_with_autoclose_regions(selections, &snapshot)
3562 {
3563 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3564 // Determine if the inserted text matches the opening or closing
3565 // bracket of any of this language's bracket pairs.
3566 let mut bracket_pair = None;
3567 let mut is_bracket_pair_start = false;
3568 let mut is_bracket_pair_end = false;
3569 if !text.is_empty() {
3570 let mut bracket_pair_matching_end = None;
3571 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3572 // and they are removing the character that triggered IME popup.
3573 for (pair, enabled) in scope.brackets() {
3574 if !pair.close && !pair.surround {
3575 continue;
3576 }
3577
3578 if enabled && pair.start.ends_with(text.as_ref()) {
3579 let prefix_len = pair.start.len() - text.len();
3580 let preceding_text_matches_prefix = prefix_len == 0
3581 || (selection.start.column >= (prefix_len as u32)
3582 && snapshot.contains_str_at(
3583 Point::new(
3584 selection.start.row,
3585 selection.start.column - (prefix_len as u32),
3586 ),
3587 &pair.start[..prefix_len],
3588 ));
3589 if preceding_text_matches_prefix {
3590 bracket_pair = Some(pair.clone());
3591 is_bracket_pair_start = true;
3592 break;
3593 }
3594 }
3595 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3596 {
3597 // take first bracket pair matching end, but don't break in case a later bracket
3598 // pair matches start
3599 bracket_pair_matching_end = Some(pair.clone());
3600 }
3601 }
3602 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3603 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3604 is_bracket_pair_end = true;
3605 }
3606 }
3607
3608 if let Some(bracket_pair) = bracket_pair {
3609 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3610 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3611 let auto_surround =
3612 self.use_auto_surround && snapshot_settings.use_auto_surround;
3613 if selection.is_empty() {
3614 if is_bracket_pair_start {
3615 // If the inserted text is a suffix of an opening bracket and the
3616 // selection is preceded by the rest of the opening bracket, then
3617 // insert the closing bracket.
3618 let following_text_allows_autoclose = snapshot
3619 .chars_at(selection.start)
3620 .next()
3621 .map_or(true, |c| scope.should_autoclose_before(c));
3622
3623 let preceding_text_allows_autoclose = selection.start.column == 0
3624 || snapshot.reversed_chars_at(selection.start).next().map_or(
3625 true,
3626 |c| {
3627 bracket_pair.start != bracket_pair.end
3628 || !snapshot
3629 .char_classifier_at(selection.start)
3630 .is_word(c)
3631 },
3632 );
3633
3634 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3635 && bracket_pair.start.len() == 1
3636 {
3637 let target = bracket_pair.start.chars().next().unwrap();
3638 let current_line_count = snapshot
3639 .reversed_chars_at(selection.start)
3640 .take_while(|&c| c != '\n')
3641 .filter(|&c| c == target)
3642 .count();
3643 current_line_count % 2 == 1
3644 } else {
3645 false
3646 };
3647
3648 if autoclose
3649 && bracket_pair.close
3650 && following_text_allows_autoclose
3651 && preceding_text_allows_autoclose
3652 && !is_closing_quote
3653 {
3654 let anchor = snapshot.anchor_before(selection.end);
3655 new_selections.push((selection.map(|_| anchor), text.len()));
3656 new_autoclose_regions.push((
3657 anchor,
3658 text.len(),
3659 selection.id,
3660 bracket_pair.clone(),
3661 ));
3662 edits.push((
3663 selection.range(),
3664 format!("{}{}", text, bracket_pair.end).into(),
3665 ));
3666 bracket_inserted = true;
3667 continue;
3668 }
3669 }
3670
3671 if let Some(region) = autoclose_region {
3672 // If the selection is followed by an auto-inserted closing bracket,
3673 // then don't insert that closing bracket again; just move the selection
3674 // past the closing bracket.
3675 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3676 && text.as_ref() == region.pair.end.as_str();
3677 if should_skip {
3678 let anchor = snapshot.anchor_after(selection.end);
3679 new_selections
3680 .push((selection.map(|_| anchor), region.pair.end.len()));
3681 continue;
3682 }
3683 }
3684
3685 let always_treat_brackets_as_autoclosed = snapshot
3686 .language_settings_at(selection.start, cx)
3687 .always_treat_brackets_as_autoclosed;
3688 if always_treat_brackets_as_autoclosed
3689 && is_bracket_pair_end
3690 && snapshot.contains_str_at(selection.end, text.as_ref())
3691 {
3692 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3693 // and the inserted text is a closing bracket and the selection is followed
3694 // by the closing bracket then move the selection past the closing bracket.
3695 let anchor = snapshot.anchor_after(selection.end);
3696 new_selections.push((selection.map(|_| anchor), text.len()));
3697 continue;
3698 }
3699 }
3700 // If an opening bracket is 1 character long and is typed while
3701 // text is selected, then surround that text with the bracket pair.
3702 else if auto_surround
3703 && bracket_pair.surround
3704 && is_bracket_pair_start
3705 && bracket_pair.start.chars().count() == 1
3706 {
3707 edits.push((selection.start..selection.start, text.clone()));
3708 edits.push((
3709 selection.end..selection.end,
3710 bracket_pair.end.as_str().into(),
3711 ));
3712 bracket_inserted = true;
3713 new_selections.push((
3714 Selection {
3715 id: selection.id,
3716 start: snapshot.anchor_after(selection.start),
3717 end: snapshot.anchor_before(selection.end),
3718 reversed: selection.reversed,
3719 goal: selection.goal,
3720 },
3721 0,
3722 ));
3723 continue;
3724 }
3725 }
3726 }
3727
3728 if self.auto_replace_emoji_shortcode
3729 && selection.is_empty()
3730 && text.as_ref().ends_with(':')
3731 {
3732 if let Some(possible_emoji_short_code) =
3733 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3734 {
3735 if !possible_emoji_short_code.is_empty() {
3736 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3737 let emoji_shortcode_start = Point::new(
3738 selection.start.row,
3739 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3740 );
3741
3742 // Remove shortcode from buffer
3743 edits.push((
3744 emoji_shortcode_start..selection.start,
3745 "".to_string().into(),
3746 ));
3747 new_selections.push((
3748 Selection {
3749 id: selection.id,
3750 start: snapshot.anchor_after(emoji_shortcode_start),
3751 end: snapshot.anchor_before(selection.start),
3752 reversed: selection.reversed,
3753 goal: selection.goal,
3754 },
3755 0,
3756 ));
3757
3758 // Insert emoji
3759 let selection_start_anchor = snapshot.anchor_after(selection.start);
3760 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3761 edits.push((selection.start..selection.end, emoji.to_string().into()));
3762
3763 continue;
3764 }
3765 }
3766 }
3767 }
3768
3769 // If not handling any auto-close operation, then just replace the selected
3770 // text with the given input and move the selection to the end of the
3771 // newly inserted text.
3772 let anchor = snapshot.anchor_after(selection.end);
3773 if !self.linked_edit_ranges.is_empty() {
3774 let start_anchor = snapshot.anchor_before(selection.start);
3775
3776 let is_word_char = text.chars().next().map_or(true, |char| {
3777 let classifier = snapshot
3778 .char_classifier_at(start_anchor.to_offset(&snapshot))
3779 .ignore_punctuation(true);
3780 classifier.is_word(char)
3781 });
3782
3783 if is_word_char {
3784 if let Some(ranges) = self
3785 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3786 {
3787 for (buffer, edits) in ranges {
3788 linked_edits
3789 .entry(buffer.clone())
3790 .or_default()
3791 .extend(edits.into_iter().map(|range| (range, text.clone())));
3792 }
3793 }
3794 } else {
3795 clear_linked_edit_ranges = true;
3796 }
3797 }
3798
3799 new_selections.push((selection.map(|_| anchor), 0));
3800 edits.push((selection.start..selection.end, text.clone()));
3801 }
3802
3803 drop(snapshot);
3804
3805 self.transact(window, cx, |this, window, cx| {
3806 if clear_linked_edit_ranges {
3807 this.linked_edit_ranges.clear();
3808 }
3809 let initial_buffer_versions =
3810 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3811
3812 this.buffer.update(cx, |buffer, cx| {
3813 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3814 });
3815 for (buffer, edits) in linked_edits {
3816 buffer.update(cx, |buffer, cx| {
3817 let snapshot = buffer.snapshot();
3818 let edits = edits
3819 .into_iter()
3820 .map(|(range, text)| {
3821 use text::ToPoint as TP;
3822 let end_point = TP::to_point(&range.end, &snapshot);
3823 let start_point = TP::to_point(&range.start, &snapshot);
3824 (start_point..end_point, text)
3825 })
3826 .sorted_by_key(|(range, _)| range.start);
3827 buffer.edit(edits, None, cx);
3828 })
3829 }
3830 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3831 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3832 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3833 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3834 .zip(new_selection_deltas)
3835 .map(|(selection, delta)| Selection {
3836 id: selection.id,
3837 start: selection.start + delta,
3838 end: selection.end + delta,
3839 reversed: selection.reversed,
3840 goal: SelectionGoal::None,
3841 })
3842 .collect::<Vec<_>>();
3843
3844 let mut i = 0;
3845 for (position, delta, selection_id, pair) in new_autoclose_regions {
3846 let position = position.to_offset(&map.buffer_snapshot) + delta;
3847 let start = map.buffer_snapshot.anchor_before(position);
3848 let end = map.buffer_snapshot.anchor_after(position);
3849 while let Some(existing_state) = this.autoclose_regions.get(i) {
3850 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3851 Ordering::Less => i += 1,
3852 Ordering::Greater => break,
3853 Ordering::Equal => {
3854 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3855 Ordering::Less => i += 1,
3856 Ordering::Equal => break,
3857 Ordering::Greater => break,
3858 }
3859 }
3860 }
3861 }
3862 this.autoclose_regions.insert(
3863 i,
3864 AutocloseRegion {
3865 selection_id,
3866 range: start..end,
3867 pair,
3868 },
3869 );
3870 }
3871
3872 let had_active_inline_completion = this.has_active_inline_completion();
3873 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3874 s.select(new_selections)
3875 });
3876
3877 if !bracket_inserted {
3878 if let Some(on_type_format_task) =
3879 this.trigger_on_type_formatting(text.to_string(), window, cx)
3880 {
3881 on_type_format_task.detach_and_log_err(cx);
3882 }
3883 }
3884
3885 let editor_settings = EditorSettings::get_global(cx);
3886 if bracket_inserted
3887 && (editor_settings.auto_signature_help
3888 || editor_settings.show_signature_help_after_edits)
3889 {
3890 this.show_signature_help(&ShowSignatureHelp, window, cx);
3891 }
3892
3893 let trigger_in_words =
3894 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3895 if this.hard_wrap.is_some() {
3896 let latest: Range<Point> = this.selections.newest(cx).range();
3897 if latest.is_empty()
3898 && this
3899 .buffer()
3900 .read(cx)
3901 .snapshot(cx)
3902 .line_len(MultiBufferRow(latest.start.row))
3903 == latest.start.column
3904 {
3905 this.rewrap_impl(
3906 RewrapOptions {
3907 override_language_settings: true,
3908 preserve_existing_whitespace: true,
3909 },
3910 cx,
3911 )
3912 }
3913 }
3914 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3915 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3916 this.refresh_inline_completion(true, false, window, cx);
3917 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3918 });
3919 }
3920
3921 fn find_possible_emoji_shortcode_at_position(
3922 snapshot: &MultiBufferSnapshot,
3923 position: Point,
3924 ) -> Option<String> {
3925 let mut chars = Vec::new();
3926 let mut found_colon = false;
3927 for char in snapshot.reversed_chars_at(position).take(100) {
3928 // Found a possible emoji shortcode in the middle of the buffer
3929 if found_colon {
3930 if char.is_whitespace() {
3931 chars.reverse();
3932 return Some(chars.iter().collect());
3933 }
3934 // If the previous character is not a whitespace, we are in the middle of a word
3935 // and we only want to complete the shortcode if the word is made up of other emojis
3936 let mut containing_word = String::new();
3937 for ch in snapshot
3938 .reversed_chars_at(position)
3939 .skip(chars.len() + 1)
3940 .take(100)
3941 {
3942 if ch.is_whitespace() {
3943 break;
3944 }
3945 containing_word.push(ch);
3946 }
3947 let containing_word = containing_word.chars().rev().collect::<String>();
3948 if util::word_consists_of_emojis(containing_word.as_str()) {
3949 chars.reverse();
3950 return Some(chars.iter().collect());
3951 }
3952 }
3953
3954 if char.is_whitespace() || !char.is_ascii() {
3955 return None;
3956 }
3957 if char == ':' {
3958 found_colon = true;
3959 } else {
3960 chars.push(char);
3961 }
3962 }
3963 // Found a possible emoji shortcode at the beginning of the buffer
3964 chars.reverse();
3965 Some(chars.iter().collect())
3966 }
3967
3968 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3969 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3970 self.transact(window, cx, |this, window, cx| {
3971 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3972 let selections = this.selections.all::<usize>(cx);
3973 let multi_buffer = this.buffer.read(cx);
3974 let buffer = multi_buffer.snapshot(cx);
3975 selections
3976 .iter()
3977 .map(|selection| {
3978 let start_point = selection.start.to_point(&buffer);
3979 let mut existing_indent =
3980 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3981 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3982 let start = selection.start;
3983 let end = selection.end;
3984 let selection_is_empty = start == end;
3985 let language_scope = buffer.language_scope_at(start);
3986 let (
3987 comment_delimiter,
3988 doc_delimiter,
3989 insert_extra_newline,
3990 indent_on_newline,
3991 indent_on_extra_newline,
3992 ) = if let Some(language) = &language_scope {
3993 let mut insert_extra_newline =
3994 insert_extra_newline_brackets(&buffer, start..end, language)
3995 || insert_extra_newline_tree_sitter(&buffer, start..end);
3996
3997 // Comment extension on newline is allowed only for cursor selections
3998 let comment_delimiter = maybe!({
3999 if !selection_is_empty {
4000 return None;
4001 }
4002
4003 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4004 return None;
4005 }
4006
4007 let delimiters = language.line_comment_prefixes();
4008 let max_len_of_delimiter =
4009 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4010 let (snapshot, range) =
4011 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4012
4013 let num_of_whitespaces = snapshot
4014 .chars_for_range(range.clone())
4015 .take_while(|c| c.is_whitespace())
4016 .count();
4017 let comment_candidate = snapshot
4018 .chars_for_range(range)
4019 .skip(num_of_whitespaces)
4020 .take(max_len_of_delimiter)
4021 .collect::<String>();
4022 let (delimiter, trimmed_len) = delimiters
4023 .iter()
4024 .filter_map(|delimiter| {
4025 let prefix = delimiter.trim_end();
4026 if comment_candidate.starts_with(prefix) {
4027 Some((delimiter, prefix.len()))
4028 } else {
4029 None
4030 }
4031 })
4032 .max_by_key(|(_, len)| *len)?;
4033
4034 let cursor_is_placed_after_comment_marker =
4035 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4036 if cursor_is_placed_after_comment_marker {
4037 Some(delimiter.clone())
4038 } else {
4039 None
4040 }
4041 });
4042
4043 let mut indent_on_newline = IndentSize::spaces(0);
4044 let mut indent_on_extra_newline = IndentSize::spaces(0);
4045
4046 let doc_delimiter = maybe!({
4047 if !selection_is_empty {
4048 return None;
4049 }
4050
4051 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4052 return None;
4053 }
4054
4055 let DocumentationConfig {
4056 start: start_tag,
4057 end: end_tag,
4058 prefix: delimiter,
4059 tab_size: len,
4060 } = language.documentation()?;
4061
4062 let is_within_block_comment = buffer
4063 .language_scope_at(start_point)
4064 .is_some_and(|scope| scope.override_name() == Some("comment"));
4065 if !is_within_block_comment {
4066 return None;
4067 }
4068
4069 let (snapshot, range) =
4070 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4071
4072 let num_of_whitespaces = snapshot
4073 .chars_for_range(range.clone())
4074 .take_while(|c| c.is_whitespace())
4075 .count();
4076
4077 // 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.
4078 let column = start_point.column;
4079 let cursor_is_after_start_tag = {
4080 let start_tag_len = start_tag.len();
4081 let start_tag_line = snapshot
4082 .chars_for_range(range.clone())
4083 .skip(num_of_whitespaces)
4084 .take(start_tag_len)
4085 .collect::<String>();
4086 if start_tag_line.starts_with(start_tag.as_ref()) {
4087 num_of_whitespaces + start_tag_len <= column as usize
4088 } else {
4089 false
4090 }
4091 };
4092
4093 let cursor_is_after_delimiter = {
4094 let delimiter_trim = delimiter.trim_end();
4095 let delimiter_line = snapshot
4096 .chars_for_range(range.clone())
4097 .skip(num_of_whitespaces)
4098 .take(delimiter_trim.len())
4099 .collect::<String>();
4100 if delimiter_line.starts_with(delimiter_trim) {
4101 num_of_whitespaces + delimiter_trim.len() <= column as usize
4102 } else {
4103 false
4104 }
4105 };
4106
4107 let cursor_is_before_end_tag_if_exists = {
4108 let mut char_position = 0u32;
4109 let mut end_tag_offset = None;
4110
4111 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4112 if let Some(byte_pos) = chunk.find(&**end_tag) {
4113 let chars_before_match =
4114 chunk[..byte_pos].chars().count() as u32;
4115 end_tag_offset =
4116 Some(char_position + chars_before_match);
4117 break 'outer;
4118 }
4119 char_position += chunk.chars().count() as u32;
4120 }
4121
4122 if let Some(end_tag_offset) = end_tag_offset {
4123 let cursor_is_before_end_tag = column <= end_tag_offset;
4124 if cursor_is_after_start_tag {
4125 if cursor_is_before_end_tag {
4126 insert_extra_newline = true;
4127 }
4128 let cursor_is_at_start_of_end_tag =
4129 column == end_tag_offset;
4130 if cursor_is_at_start_of_end_tag {
4131 indent_on_extra_newline.len = (*len).into();
4132 }
4133 }
4134 cursor_is_before_end_tag
4135 } else {
4136 true
4137 }
4138 };
4139
4140 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4141 && cursor_is_before_end_tag_if_exists
4142 {
4143 if cursor_is_after_start_tag {
4144 indent_on_newline.len = (*len).into();
4145 }
4146 Some(delimiter.clone())
4147 } else {
4148 None
4149 }
4150 });
4151
4152 (
4153 comment_delimiter,
4154 doc_delimiter,
4155 insert_extra_newline,
4156 indent_on_newline,
4157 indent_on_extra_newline,
4158 )
4159 } else {
4160 (
4161 None,
4162 None,
4163 false,
4164 IndentSize::default(),
4165 IndentSize::default(),
4166 )
4167 };
4168
4169 let prevent_auto_indent = doc_delimiter.is_some();
4170 let delimiter = comment_delimiter.or(doc_delimiter);
4171
4172 let capacity_for_delimiter =
4173 delimiter.as_deref().map(str::len).unwrap_or_default();
4174 let mut new_text = String::with_capacity(
4175 1 + capacity_for_delimiter
4176 + existing_indent.len as usize
4177 + indent_on_newline.len as usize
4178 + indent_on_extra_newline.len as usize,
4179 );
4180 new_text.push('\n');
4181 new_text.extend(existing_indent.chars());
4182 new_text.extend(indent_on_newline.chars());
4183
4184 if let Some(delimiter) = &delimiter {
4185 new_text.push_str(delimiter);
4186 }
4187
4188 if insert_extra_newline {
4189 new_text.push('\n');
4190 new_text.extend(existing_indent.chars());
4191 new_text.extend(indent_on_extra_newline.chars());
4192 }
4193
4194 let anchor = buffer.anchor_after(end);
4195 let new_selection = selection.map(|_| anchor);
4196 (
4197 ((start..end, new_text), prevent_auto_indent),
4198 (insert_extra_newline, new_selection),
4199 )
4200 })
4201 .unzip()
4202 };
4203
4204 let mut auto_indent_edits = Vec::new();
4205 let mut edits = Vec::new();
4206 for (edit, prevent_auto_indent) in edits_with_flags {
4207 if prevent_auto_indent {
4208 edits.push(edit);
4209 } else {
4210 auto_indent_edits.push(edit);
4211 }
4212 }
4213 if !edits.is_empty() {
4214 this.edit(edits, cx);
4215 }
4216 if !auto_indent_edits.is_empty() {
4217 this.edit_with_autoindent(auto_indent_edits, cx);
4218 }
4219
4220 let buffer = this.buffer.read(cx).snapshot(cx);
4221 let new_selections = selection_info
4222 .into_iter()
4223 .map(|(extra_newline_inserted, new_selection)| {
4224 let mut cursor = new_selection.end.to_point(&buffer);
4225 if extra_newline_inserted {
4226 cursor.row -= 1;
4227 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4228 }
4229 new_selection.map(|_| cursor)
4230 })
4231 .collect();
4232
4233 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4234 s.select(new_selections)
4235 });
4236 this.refresh_inline_completion(true, false, window, cx);
4237 });
4238 }
4239
4240 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4241 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4242
4243 let buffer = self.buffer.read(cx);
4244 let snapshot = buffer.snapshot(cx);
4245
4246 let mut edits = Vec::new();
4247 let mut rows = Vec::new();
4248
4249 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4250 let cursor = selection.head();
4251 let row = cursor.row;
4252
4253 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4254
4255 let newline = "\n".to_string();
4256 edits.push((start_of_line..start_of_line, newline));
4257
4258 rows.push(row + rows_inserted as u32);
4259 }
4260
4261 self.transact(window, cx, |editor, window, cx| {
4262 editor.edit(edits, cx);
4263
4264 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4265 let mut index = 0;
4266 s.move_cursors_with(|map, _, _| {
4267 let row = rows[index];
4268 index += 1;
4269
4270 let point = Point::new(row, 0);
4271 let boundary = map.next_line_boundary(point).1;
4272 let clipped = map.clip_point(boundary, Bias::Left);
4273
4274 (clipped, SelectionGoal::None)
4275 });
4276 });
4277
4278 let mut indent_edits = Vec::new();
4279 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4280 for row in rows {
4281 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4282 for (row, indent) in indents {
4283 if indent.len == 0 {
4284 continue;
4285 }
4286
4287 let text = match indent.kind {
4288 IndentKind::Space => " ".repeat(indent.len as usize),
4289 IndentKind::Tab => "\t".repeat(indent.len as usize),
4290 };
4291 let point = Point::new(row.0, 0);
4292 indent_edits.push((point..point, text));
4293 }
4294 }
4295 editor.edit(indent_edits, cx);
4296 });
4297 }
4298
4299 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4300 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4301
4302 let buffer = self.buffer.read(cx);
4303 let snapshot = buffer.snapshot(cx);
4304
4305 let mut edits = Vec::new();
4306 let mut rows = Vec::new();
4307 let mut rows_inserted = 0;
4308
4309 for selection in self.selections.all_adjusted(cx) {
4310 let cursor = selection.head();
4311 let row = cursor.row;
4312
4313 let point = Point::new(row + 1, 0);
4314 let start_of_line = snapshot.clip_point(point, Bias::Left);
4315
4316 let newline = "\n".to_string();
4317 edits.push((start_of_line..start_of_line, newline));
4318
4319 rows_inserted += 1;
4320 rows.push(row + rows_inserted);
4321 }
4322
4323 self.transact(window, cx, |editor, window, cx| {
4324 editor.edit(edits, cx);
4325
4326 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4327 let mut index = 0;
4328 s.move_cursors_with(|map, _, _| {
4329 let row = rows[index];
4330 index += 1;
4331
4332 let point = Point::new(row, 0);
4333 let boundary = map.next_line_boundary(point).1;
4334 let clipped = map.clip_point(boundary, Bias::Left);
4335
4336 (clipped, SelectionGoal::None)
4337 });
4338 });
4339
4340 let mut indent_edits = Vec::new();
4341 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4342 for row in rows {
4343 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4344 for (row, indent) in indents {
4345 if indent.len == 0 {
4346 continue;
4347 }
4348
4349 let text = match indent.kind {
4350 IndentKind::Space => " ".repeat(indent.len as usize),
4351 IndentKind::Tab => "\t".repeat(indent.len as usize),
4352 };
4353 let point = Point::new(row.0, 0);
4354 indent_edits.push((point..point, text));
4355 }
4356 }
4357 editor.edit(indent_edits, cx);
4358 });
4359 }
4360
4361 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4362 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4363 original_indent_columns: Vec::new(),
4364 });
4365 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4366 }
4367
4368 fn insert_with_autoindent_mode(
4369 &mut self,
4370 text: &str,
4371 autoindent_mode: Option<AutoindentMode>,
4372 window: &mut Window,
4373 cx: &mut Context<Self>,
4374 ) {
4375 if self.read_only(cx) {
4376 return;
4377 }
4378
4379 let text: Arc<str> = text.into();
4380 self.transact(window, cx, |this, window, cx| {
4381 let old_selections = this.selections.all_adjusted(cx);
4382 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4383 let anchors = {
4384 let snapshot = buffer.read(cx);
4385 old_selections
4386 .iter()
4387 .map(|s| {
4388 let anchor = snapshot.anchor_after(s.head());
4389 s.map(|_| anchor)
4390 })
4391 .collect::<Vec<_>>()
4392 };
4393 buffer.edit(
4394 old_selections
4395 .iter()
4396 .map(|s| (s.start..s.end, text.clone())),
4397 autoindent_mode,
4398 cx,
4399 );
4400 anchors
4401 });
4402
4403 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4404 s.select_anchors(selection_anchors);
4405 });
4406
4407 cx.notify();
4408 });
4409 }
4410
4411 fn trigger_completion_on_input(
4412 &mut self,
4413 text: &str,
4414 trigger_in_words: bool,
4415 window: &mut Window,
4416 cx: &mut Context<Self>,
4417 ) {
4418 let ignore_completion_provider = self
4419 .context_menu
4420 .borrow()
4421 .as_ref()
4422 .map(|menu| match menu {
4423 CodeContextMenu::Completions(completions_menu) => {
4424 completions_menu.ignore_completion_provider
4425 }
4426 CodeContextMenu::CodeActions(_) => false,
4427 })
4428 .unwrap_or(false);
4429
4430 if ignore_completion_provider {
4431 self.show_word_completions(&ShowWordCompletions, window, cx);
4432 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4433 self.show_completions(
4434 &ShowCompletions {
4435 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4436 },
4437 window,
4438 cx,
4439 );
4440 } else {
4441 self.hide_context_menu(window, cx);
4442 }
4443 }
4444
4445 fn is_completion_trigger(
4446 &self,
4447 text: &str,
4448 trigger_in_words: bool,
4449 cx: &mut Context<Self>,
4450 ) -> bool {
4451 let position = self.selections.newest_anchor().head();
4452 let multibuffer = self.buffer.read(cx);
4453 let Some(buffer) = position
4454 .buffer_id
4455 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4456 else {
4457 return false;
4458 };
4459
4460 if let Some(completion_provider) = &self.completion_provider {
4461 completion_provider.is_completion_trigger(
4462 &buffer,
4463 position.text_anchor,
4464 text,
4465 trigger_in_words,
4466 cx,
4467 )
4468 } else {
4469 false
4470 }
4471 }
4472
4473 /// If any empty selections is touching the start of its innermost containing autoclose
4474 /// region, expand it to select the brackets.
4475 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4476 let selections = self.selections.all::<usize>(cx);
4477 let buffer = self.buffer.read(cx).read(cx);
4478 let new_selections = self
4479 .selections_with_autoclose_regions(selections, &buffer)
4480 .map(|(mut selection, region)| {
4481 if !selection.is_empty() {
4482 return selection;
4483 }
4484
4485 if let Some(region) = region {
4486 let mut range = region.range.to_offset(&buffer);
4487 if selection.start == range.start && range.start >= region.pair.start.len() {
4488 range.start -= region.pair.start.len();
4489 if buffer.contains_str_at(range.start, ®ion.pair.start)
4490 && buffer.contains_str_at(range.end, ®ion.pair.end)
4491 {
4492 range.end += region.pair.end.len();
4493 selection.start = range.start;
4494 selection.end = range.end;
4495
4496 return selection;
4497 }
4498 }
4499 }
4500
4501 let always_treat_brackets_as_autoclosed = buffer
4502 .language_settings_at(selection.start, cx)
4503 .always_treat_brackets_as_autoclosed;
4504
4505 if !always_treat_brackets_as_autoclosed {
4506 return selection;
4507 }
4508
4509 if let Some(scope) = buffer.language_scope_at(selection.start) {
4510 for (pair, enabled) in scope.brackets() {
4511 if !enabled || !pair.close {
4512 continue;
4513 }
4514
4515 if buffer.contains_str_at(selection.start, &pair.end) {
4516 let pair_start_len = pair.start.len();
4517 if buffer.contains_str_at(
4518 selection.start.saturating_sub(pair_start_len),
4519 &pair.start,
4520 ) {
4521 selection.start -= pair_start_len;
4522 selection.end += pair.end.len();
4523
4524 return selection;
4525 }
4526 }
4527 }
4528 }
4529
4530 selection
4531 })
4532 .collect();
4533
4534 drop(buffer);
4535 self.change_selections(None, window, cx, |selections| {
4536 selections.select(new_selections)
4537 });
4538 }
4539
4540 /// Iterate the given selections, and for each one, find the smallest surrounding
4541 /// autoclose region. This uses the ordering of the selections and the autoclose
4542 /// regions to avoid repeated comparisons.
4543 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4544 &'a self,
4545 selections: impl IntoIterator<Item = Selection<D>>,
4546 buffer: &'a MultiBufferSnapshot,
4547 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4548 let mut i = 0;
4549 let mut regions = self.autoclose_regions.as_slice();
4550 selections.into_iter().map(move |selection| {
4551 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4552
4553 let mut enclosing = None;
4554 while let Some(pair_state) = regions.get(i) {
4555 if pair_state.range.end.to_offset(buffer) < range.start {
4556 regions = ®ions[i + 1..];
4557 i = 0;
4558 } else if pair_state.range.start.to_offset(buffer) > range.end {
4559 break;
4560 } else {
4561 if pair_state.selection_id == selection.id {
4562 enclosing = Some(pair_state);
4563 }
4564 i += 1;
4565 }
4566 }
4567
4568 (selection, enclosing)
4569 })
4570 }
4571
4572 /// Remove any autoclose regions that no longer contain their selection.
4573 fn invalidate_autoclose_regions(
4574 &mut self,
4575 mut selections: &[Selection<Anchor>],
4576 buffer: &MultiBufferSnapshot,
4577 ) {
4578 self.autoclose_regions.retain(|state| {
4579 let mut i = 0;
4580 while let Some(selection) = selections.get(i) {
4581 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4582 selections = &selections[1..];
4583 continue;
4584 }
4585 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4586 break;
4587 }
4588 if selection.id == state.selection_id {
4589 return true;
4590 } else {
4591 i += 1;
4592 }
4593 }
4594 false
4595 });
4596 }
4597
4598 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4599 let offset = position.to_offset(buffer);
4600 let (word_range, kind) = buffer.surrounding_word(offset, true);
4601 if offset > word_range.start && kind == Some(CharKind::Word) {
4602 Some(
4603 buffer
4604 .text_for_range(word_range.start..offset)
4605 .collect::<String>(),
4606 )
4607 } else {
4608 None
4609 }
4610 }
4611
4612 pub fn toggle_inline_values(
4613 &mut self,
4614 _: &ToggleInlineValues,
4615 _: &mut Window,
4616 cx: &mut Context<Self>,
4617 ) {
4618 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4619
4620 self.refresh_inline_values(cx);
4621 }
4622
4623 pub fn toggle_inlay_hints(
4624 &mut self,
4625 _: &ToggleInlayHints,
4626 _: &mut Window,
4627 cx: &mut Context<Self>,
4628 ) {
4629 self.refresh_inlay_hints(
4630 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4631 cx,
4632 );
4633 }
4634
4635 pub fn inlay_hints_enabled(&self) -> bool {
4636 self.inlay_hint_cache.enabled
4637 }
4638
4639 pub fn inline_values_enabled(&self) -> bool {
4640 self.inline_value_cache.enabled
4641 }
4642
4643 #[cfg(any(test, feature = "test-support"))]
4644 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4645 self.display_map
4646 .read(cx)
4647 .current_inlays()
4648 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4649 .cloned()
4650 .collect()
4651 }
4652
4653 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4654 if self.semantics_provider.is_none() || !self.mode.is_full() {
4655 return;
4656 }
4657
4658 let reason_description = reason.description();
4659 let ignore_debounce = matches!(
4660 reason,
4661 InlayHintRefreshReason::SettingsChange(_)
4662 | InlayHintRefreshReason::Toggle(_)
4663 | InlayHintRefreshReason::ExcerptsRemoved(_)
4664 | InlayHintRefreshReason::ModifiersChanged(_)
4665 );
4666 let (invalidate_cache, required_languages) = match reason {
4667 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4668 match self.inlay_hint_cache.modifiers_override(enabled) {
4669 Some(enabled) => {
4670 if enabled {
4671 (InvalidationStrategy::RefreshRequested, None)
4672 } else {
4673 self.splice_inlays(
4674 &self
4675 .visible_inlay_hints(cx)
4676 .iter()
4677 .map(|inlay| inlay.id)
4678 .collect::<Vec<InlayId>>(),
4679 Vec::new(),
4680 cx,
4681 );
4682 return;
4683 }
4684 }
4685 None => return,
4686 }
4687 }
4688 InlayHintRefreshReason::Toggle(enabled) => {
4689 if self.inlay_hint_cache.toggle(enabled) {
4690 if enabled {
4691 (InvalidationStrategy::RefreshRequested, None)
4692 } else {
4693 self.splice_inlays(
4694 &self
4695 .visible_inlay_hints(cx)
4696 .iter()
4697 .map(|inlay| inlay.id)
4698 .collect::<Vec<InlayId>>(),
4699 Vec::new(),
4700 cx,
4701 );
4702 return;
4703 }
4704 } else {
4705 return;
4706 }
4707 }
4708 InlayHintRefreshReason::SettingsChange(new_settings) => {
4709 match self.inlay_hint_cache.update_settings(
4710 &self.buffer,
4711 new_settings,
4712 self.visible_inlay_hints(cx),
4713 cx,
4714 ) {
4715 ControlFlow::Break(Some(InlaySplice {
4716 to_remove,
4717 to_insert,
4718 })) => {
4719 self.splice_inlays(&to_remove, to_insert, cx);
4720 return;
4721 }
4722 ControlFlow::Break(None) => return,
4723 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4724 }
4725 }
4726 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4727 if let Some(InlaySplice {
4728 to_remove,
4729 to_insert,
4730 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4731 {
4732 self.splice_inlays(&to_remove, to_insert, cx);
4733 }
4734 self.display_map.update(cx, |display_map, _| {
4735 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4736 });
4737 return;
4738 }
4739 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4740 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4741 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4742 }
4743 InlayHintRefreshReason::RefreshRequested => {
4744 (InvalidationStrategy::RefreshRequested, None)
4745 }
4746 };
4747
4748 if let Some(InlaySplice {
4749 to_remove,
4750 to_insert,
4751 }) = self.inlay_hint_cache.spawn_hint_refresh(
4752 reason_description,
4753 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4754 invalidate_cache,
4755 ignore_debounce,
4756 cx,
4757 ) {
4758 self.splice_inlays(&to_remove, to_insert, cx);
4759 }
4760 }
4761
4762 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4763 self.display_map
4764 .read(cx)
4765 .current_inlays()
4766 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4767 .cloned()
4768 .collect()
4769 }
4770
4771 pub fn excerpts_for_inlay_hints_query(
4772 &self,
4773 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4774 cx: &mut Context<Editor>,
4775 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4776 let Some(project) = self.project.as_ref() else {
4777 return HashMap::default();
4778 };
4779 let project = project.read(cx);
4780 let multi_buffer = self.buffer().read(cx);
4781 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4782 let multi_buffer_visible_start = self
4783 .scroll_manager
4784 .anchor()
4785 .anchor
4786 .to_point(&multi_buffer_snapshot);
4787 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4788 multi_buffer_visible_start
4789 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4790 Bias::Left,
4791 );
4792 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4793 multi_buffer_snapshot
4794 .range_to_buffer_ranges(multi_buffer_visible_range)
4795 .into_iter()
4796 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4797 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4798 let buffer_file = project::File::from_dyn(buffer.file())?;
4799 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4800 let worktree_entry = buffer_worktree
4801 .read(cx)
4802 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4803 if worktree_entry.is_ignored {
4804 return None;
4805 }
4806
4807 let language = buffer.language()?;
4808 if let Some(restrict_to_languages) = restrict_to_languages {
4809 if !restrict_to_languages.contains(language) {
4810 return None;
4811 }
4812 }
4813 Some((
4814 excerpt_id,
4815 (
4816 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4817 buffer.version().clone(),
4818 excerpt_visible_range,
4819 ),
4820 ))
4821 })
4822 .collect()
4823 }
4824
4825 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4826 TextLayoutDetails {
4827 text_system: window.text_system().clone(),
4828 editor_style: self.style.clone().unwrap(),
4829 rem_size: window.rem_size(),
4830 scroll_anchor: self.scroll_manager.anchor(),
4831 visible_rows: self.visible_line_count(),
4832 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4833 }
4834 }
4835
4836 pub fn splice_inlays(
4837 &self,
4838 to_remove: &[InlayId],
4839 to_insert: Vec<Inlay>,
4840 cx: &mut Context<Self>,
4841 ) {
4842 self.display_map.update(cx, |display_map, cx| {
4843 display_map.splice_inlays(to_remove, to_insert, cx)
4844 });
4845 cx.notify();
4846 }
4847
4848 fn trigger_on_type_formatting(
4849 &self,
4850 input: String,
4851 window: &mut Window,
4852 cx: &mut Context<Self>,
4853 ) -> Option<Task<Result<()>>> {
4854 if input.len() != 1 {
4855 return None;
4856 }
4857
4858 let project = self.project.as_ref()?;
4859 let position = self.selections.newest_anchor().head();
4860 let (buffer, buffer_position) = self
4861 .buffer
4862 .read(cx)
4863 .text_anchor_for_position(position, cx)?;
4864
4865 let settings = language_settings::language_settings(
4866 buffer
4867 .read(cx)
4868 .language_at(buffer_position)
4869 .map(|l| l.name()),
4870 buffer.read(cx).file(),
4871 cx,
4872 );
4873 if !settings.use_on_type_format {
4874 return None;
4875 }
4876
4877 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4878 // hence we do LSP request & edit on host side only — add formats to host's history.
4879 let push_to_lsp_host_history = true;
4880 // If this is not the host, append its history with new edits.
4881 let push_to_client_history = project.read(cx).is_via_collab();
4882
4883 let on_type_formatting = project.update(cx, |project, cx| {
4884 project.on_type_format(
4885 buffer.clone(),
4886 buffer_position,
4887 input,
4888 push_to_lsp_host_history,
4889 cx,
4890 )
4891 });
4892 Some(cx.spawn_in(window, async move |editor, cx| {
4893 if let Some(transaction) = on_type_formatting.await? {
4894 if push_to_client_history {
4895 buffer
4896 .update(cx, |buffer, _| {
4897 buffer.push_transaction(transaction, Instant::now());
4898 buffer.finalize_last_transaction();
4899 })
4900 .ok();
4901 }
4902 editor.update(cx, |editor, cx| {
4903 editor.refresh_document_highlights(cx);
4904 })?;
4905 }
4906 Ok(())
4907 }))
4908 }
4909
4910 pub fn show_word_completions(
4911 &mut self,
4912 _: &ShowWordCompletions,
4913 window: &mut Window,
4914 cx: &mut Context<Self>,
4915 ) {
4916 self.open_completions_menu(true, None, window, cx);
4917 }
4918
4919 pub fn show_completions(
4920 &mut self,
4921 options: &ShowCompletions,
4922 window: &mut Window,
4923 cx: &mut Context<Self>,
4924 ) {
4925 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4926 }
4927
4928 fn open_completions_menu(
4929 &mut self,
4930 ignore_completion_provider: bool,
4931 trigger: Option<&str>,
4932 window: &mut Window,
4933 cx: &mut Context<Self>,
4934 ) {
4935 if self.pending_rename.is_some() {
4936 return;
4937 }
4938 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4939 return;
4940 }
4941
4942 let position = self.selections.newest_anchor().head();
4943 if position.diff_base_anchor.is_some() {
4944 return;
4945 }
4946 let (buffer, buffer_position) =
4947 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4948 output
4949 } else {
4950 return;
4951 };
4952 let buffer_snapshot = buffer.read(cx).snapshot();
4953 let show_completion_documentation = buffer_snapshot
4954 .settings_at(buffer_position, cx)
4955 .show_completion_documentation;
4956
4957 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4958
4959 let trigger_kind = match trigger {
4960 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4961 CompletionTriggerKind::TRIGGER_CHARACTER
4962 }
4963 _ => CompletionTriggerKind::INVOKED,
4964 };
4965 let completion_context = CompletionContext {
4966 trigger_character: trigger.and_then(|trigger| {
4967 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4968 Some(String::from(trigger))
4969 } else {
4970 None
4971 }
4972 }),
4973 trigger_kind,
4974 };
4975
4976 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4977 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4978 let word_to_exclude = buffer_snapshot
4979 .text_for_range(old_range.clone())
4980 .collect::<String>();
4981 (
4982 buffer_snapshot.anchor_before(old_range.start)
4983 ..buffer_snapshot.anchor_after(old_range.end),
4984 Some(word_to_exclude),
4985 )
4986 } else {
4987 (buffer_position..buffer_position, None)
4988 };
4989
4990 let completion_settings = language_settings(
4991 buffer_snapshot
4992 .language_at(buffer_position)
4993 .map(|language| language.name()),
4994 buffer_snapshot.file(),
4995 cx,
4996 )
4997 .completions;
4998
4999 // The document can be large, so stay in reasonable bounds when searching for words,
5000 // otherwise completion pop-up might be slow to appear.
5001 const WORD_LOOKUP_ROWS: u32 = 5_000;
5002 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5003 let min_word_search = buffer_snapshot.clip_point(
5004 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5005 Bias::Left,
5006 );
5007 let max_word_search = buffer_snapshot.clip_point(
5008 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5009 Bias::Right,
5010 );
5011 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5012 ..buffer_snapshot.point_to_offset(max_word_search);
5013
5014 let provider = if ignore_completion_provider {
5015 None
5016 } else {
5017 self.completion_provider.clone()
5018 };
5019 let skip_digits = query
5020 .as_ref()
5021 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5022
5023 let (mut words, provided_completions) = match &provider {
5024 Some(provider) => {
5025 let completions = provider.completions(
5026 position.excerpt_id,
5027 &buffer,
5028 buffer_position,
5029 completion_context,
5030 window,
5031 cx,
5032 );
5033
5034 let words = match completion_settings.words {
5035 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5036 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5037 .background_spawn(async move {
5038 buffer_snapshot.words_in_range(WordsQuery {
5039 fuzzy_contents: None,
5040 range: word_search_range,
5041 skip_digits,
5042 })
5043 }),
5044 };
5045
5046 (words, completions)
5047 }
5048 None => (
5049 cx.background_spawn(async move {
5050 buffer_snapshot.words_in_range(WordsQuery {
5051 fuzzy_contents: None,
5052 range: word_search_range,
5053 skip_digits,
5054 })
5055 }),
5056 Task::ready(Ok(None)),
5057 ),
5058 };
5059
5060 let sort_completions = provider
5061 .as_ref()
5062 .map_or(false, |provider| provider.sort_completions());
5063
5064 let filter_completions = provider
5065 .as_ref()
5066 .map_or(true, |provider| provider.filter_completions());
5067
5068 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5069
5070 let id = post_inc(&mut self.next_completion_id);
5071 let task = cx.spawn_in(window, async move |editor, cx| {
5072 async move {
5073 editor.update(cx, |this, _| {
5074 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5075 })?;
5076
5077 let mut completions = Vec::new();
5078 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5079 completions.extend(provided_completions);
5080 if completion_settings.words == WordsCompletionMode::Fallback {
5081 words = Task::ready(BTreeMap::default());
5082 }
5083 }
5084
5085 let mut words = words.await;
5086 if let Some(word_to_exclude) = &word_to_exclude {
5087 words.remove(word_to_exclude);
5088 }
5089 for lsp_completion in &completions {
5090 words.remove(&lsp_completion.new_text);
5091 }
5092 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5093 replace_range: old_range.clone(),
5094 new_text: word.clone(),
5095 label: CodeLabel::plain(word, None),
5096 icon_path: None,
5097 documentation: None,
5098 source: CompletionSource::BufferWord {
5099 word_range,
5100 resolved: false,
5101 },
5102 insert_text_mode: Some(InsertTextMode::AS_IS),
5103 confirm: None,
5104 }));
5105
5106 let menu = if completions.is_empty() {
5107 None
5108 } else {
5109 let mut menu = CompletionsMenu::new(
5110 id,
5111 sort_completions,
5112 show_completion_documentation,
5113 ignore_completion_provider,
5114 position,
5115 buffer.clone(),
5116 completions.into(),
5117 snippet_sort_order,
5118 );
5119
5120 menu.filter(
5121 if filter_completions {
5122 query.as_deref()
5123 } else {
5124 None
5125 },
5126 provider,
5127 editor.clone(),
5128 cx,
5129 )
5130 .await;
5131
5132 menu.visible().then_some(menu)
5133 };
5134
5135 editor.update_in(cx, |editor, window, cx| {
5136 match editor.context_menu.borrow().as_ref() {
5137 None => {}
5138 Some(CodeContextMenu::Completions(prev_menu)) => {
5139 if prev_menu.id > id {
5140 return;
5141 }
5142 }
5143 _ => return,
5144 }
5145
5146 if editor.focus_handle.is_focused(window) && menu.is_some() {
5147 let mut menu = menu.unwrap();
5148 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5149 crate::hover_popover::hide_hover(editor, cx);
5150 *editor.context_menu.borrow_mut() =
5151 Some(CodeContextMenu::Completions(menu));
5152
5153 if editor.show_edit_predictions_in_menu() {
5154 editor.update_visible_inline_completion(window, cx);
5155 } else {
5156 editor.discard_inline_completion(false, cx);
5157 }
5158
5159 cx.notify();
5160 } else if editor.completion_tasks.len() <= 1 {
5161 // If there are no more completion tasks and the last menu was
5162 // empty, we should hide it.
5163 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5164 // If it was already hidden and we don't show inline
5165 // completions in the menu, we should also show the
5166 // inline-completion when available.
5167 if was_hidden && editor.show_edit_predictions_in_menu() {
5168 editor.update_visible_inline_completion(window, cx);
5169 }
5170 }
5171 })?;
5172
5173 anyhow::Ok(())
5174 }
5175 .log_err()
5176 .await
5177 });
5178
5179 self.completion_tasks.push((id, task));
5180 }
5181
5182 #[cfg(feature = "test-support")]
5183 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5184 let menu = self.context_menu.borrow();
5185 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5186 let completions = menu.completions.borrow();
5187 Some(completions.to_vec())
5188 } else {
5189 None
5190 }
5191 }
5192
5193 pub fn confirm_completion(
5194 &mut self,
5195 action: &ConfirmCompletion,
5196 window: &mut Window,
5197 cx: &mut Context<Self>,
5198 ) -> Option<Task<Result<()>>> {
5199 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5200 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5201 }
5202
5203 pub fn confirm_completion_insert(
5204 &mut self,
5205 _: &ConfirmCompletionInsert,
5206 window: &mut Window,
5207 cx: &mut Context<Self>,
5208 ) -> Option<Task<Result<()>>> {
5209 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5210 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5211 }
5212
5213 pub fn confirm_completion_replace(
5214 &mut self,
5215 _: &ConfirmCompletionReplace,
5216 window: &mut Window,
5217 cx: &mut Context<Self>,
5218 ) -> Option<Task<Result<()>>> {
5219 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5220 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5221 }
5222
5223 pub fn compose_completion(
5224 &mut self,
5225 action: &ComposeCompletion,
5226 window: &mut Window,
5227 cx: &mut Context<Self>,
5228 ) -> Option<Task<Result<()>>> {
5229 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5230 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5231 }
5232
5233 fn do_completion(
5234 &mut self,
5235 item_ix: Option<usize>,
5236 intent: CompletionIntent,
5237 window: &mut Window,
5238 cx: &mut Context<Editor>,
5239 ) -> Option<Task<Result<()>>> {
5240 use language::ToOffset as _;
5241
5242 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5243 else {
5244 return None;
5245 };
5246
5247 let candidate_id = {
5248 let entries = completions_menu.entries.borrow();
5249 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5250 if self.show_edit_predictions_in_menu() {
5251 self.discard_inline_completion(true, cx);
5252 }
5253 mat.candidate_id
5254 };
5255
5256 let buffer_handle = completions_menu.buffer;
5257 let completion = completions_menu
5258 .completions
5259 .borrow()
5260 .get(candidate_id)?
5261 .clone();
5262 cx.stop_propagation();
5263
5264 let snapshot = self.buffer.read(cx).snapshot(cx);
5265 let newest_anchor = self.selections.newest_anchor();
5266
5267 let snippet;
5268 let new_text;
5269 if completion.is_snippet() {
5270 let mut snippet_source = completion.new_text.clone();
5271 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5272 if scope.prefers_label_for_snippet_in_completion() {
5273 if let Some(label) = completion.label() {
5274 if matches!(
5275 completion.kind(),
5276 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5277 ) {
5278 snippet_source = label;
5279 }
5280 }
5281 }
5282 }
5283 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5284 new_text = snippet.as_ref().unwrap().text.clone();
5285 } else {
5286 snippet = None;
5287 new_text = completion.new_text.clone();
5288 };
5289
5290 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5291 let buffer = buffer_handle.read(cx);
5292 let replace_range_multibuffer = {
5293 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5294 let multibuffer_anchor = snapshot
5295 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5296 .unwrap()
5297 ..snapshot
5298 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5299 .unwrap();
5300 multibuffer_anchor.start.to_offset(&snapshot)
5301 ..multibuffer_anchor.end.to_offset(&snapshot)
5302 };
5303 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5304 return None;
5305 }
5306
5307 let old_text = buffer
5308 .text_for_range(replace_range.clone())
5309 .collect::<String>();
5310 let lookbehind = newest_anchor
5311 .start
5312 .text_anchor
5313 .to_offset(buffer)
5314 .saturating_sub(replace_range.start);
5315 let lookahead = replace_range
5316 .end
5317 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5318 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5319 let suffix = &old_text[lookbehind.min(old_text.len())..];
5320
5321 let selections = self.selections.all::<usize>(cx);
5322 let mut ranges = Vec::new();
5323 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5324
5325 for selection in &selections {
5326 let range = if selection.id == newest_anchor.id {
5327 replace_range_multibuffer.clone()
5328 } else {
5329 let mut range = selection.range();
5330
5331 // if prefix is present, don't duplicate it
5332 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5333 range.start = range.start.saturating_sub(lookbehind);
5334
5335 // if suffix is also present, mimic the newest cursor and replace it
5336 if selection.id != newest_anchor.id
5337 && snapshot.contains_str_at(range.end, suffix)
5338 {
5339 range.end += lookahead;
5340 }
5341 }
5342 range
5343 };
5344
5345 ranges.push(range.clone());
5346
5347 if !self.linked_edit_ranges.is_empty() {
5348 let start_anchor = snapshot.anchor_before(range.start);
5349 let end_anchor = snapshot.anchor_after(range.end);
5350 if let Some(ranges) = self
5351 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5352 {
5353 for (buffer, edits) in ranges {
5354 linked_edits
5355 .entry(buffer.clone())
5356 .or_default()
5357 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5358 }
5359 }
5360 }
5361 }
5362
5363 cx.emit(EditorEvent::InputHandled {
5364 utf16_range_to_replace: None,
5365 text: new_text.clone().into(),
5366 });
5367
5368 self.transact(window, cx, |this, window, cx| {
5369 if let Some(mut snippet) = snippet {
5370 snippet.text = new_text.to_string();
5371 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5372 } else {
5373 this.buffer.update(cx, |buffer, cx| {
5374 let auto_indent = match completion.insert_text_mode {
5375 Some(InsertTextMode::AS_IS) => None,
5376 _ => this.autoindent_mode.clone(),
5377 };
5378 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5379 buffer.edit(edits, auto_indent, cx);
5380 });
5381 }
5382 for (buffer, edits) in linked_edits {
5383 buffer.update(cx, |buffer, cx| {
5384 let snapshot = buffer.snapshot();
5385 let edits = edits
5386 .into_iter()
5387 .map(|(range, text)| {
5388 use text::ToPoint as TP;
5389 let end_point = TP::to_point(&range.end, &snapshot);
5390 let start_point = TP::to_point(&range.start, &snapshot);
5391 (start_point..end_point, text)
5392 })
5393 .sorted_by_key(|(range, _)| range.start);
5394 buffer.edit(edits, None, cx);
5395 })
5396 }
5397
5398 this.refresh_inline_completion(true, false, window, cx);
5399 });
5400
5401 let show_new_completions_on_confirm = completion
5402 .confirm
5403 .as_ref()
5404 .map_or(false, |confirm| confirm(intent, window, cx));
5405 if show_new_completions_on_confirm {
5406 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5407 }
5408
5409 let provider = self.completion_provider.as_ref()?;
5410 drop(completion);
5411 let apply_edits = provider.apply_additional_edits_for_completion(
5412 buffer_handle,
5413 completions_menu.completions.clone(),
5414 candidate_id,
5415 true,
5416 cx,
5417 );
5418
5419 let editor_settings = EditorSettings::get_global(cx);
5420 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5421 // After the code completion is finished, users often want to know what signatures are needed.
5422 // so we should automatically call signature_help
5423 self.show_signature_help(&ShowSignatureHelp, window, cx);
5424 }
5425
5426 Some(cx.foreground_executor().spawn(async move {
5427 apply_edits.await?;
5428 Ok(())
5429 }))
5430 }
5431
5432 pub fn toggle_code_actions(
5433 &mut self,
5434 action: &ToggleCodeActions,
5435 window: &mut Window,
5436 cx: &mut Context<Self>,
5437 ) {
5438 let quick_launch = action.quick_launch;
5439 let mut context_menu = self.context_menu.borrow_mut();
5440 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5441 if code_actions.deployed_from == action.deployed_from {
5442 // Toggle if we're selecting the same one
5443 *context_menu = None;
5444 cx.notify();
5445 return;
5446 } else {
5447 // Otherwise, clear it and start a new one
5448 *context_menu = None;
5449 cx.notify();
5450 }
5451 }
5452 drop(context_menu);
5453 let snapshot = self.snapshot(window, cx);
5454 let deployed_from = action.deployed_from.clone();
5455 let mut task = self.code_actions_task.take();
5456 let action = action.clone();
5457 cx.spawn_in(window, async move |editor, cx| {
5458 while let Some(prev_task) = task {
5459 prev_task.await.log_err();
5460 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5461 }
5462
5463 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5464 if editor.focus_handle.is_focused(window) {
5465 let multibuffer_point = match &action.deployed_from {
5466 Some(CodeActionSource::Indicator(row)) => {
5467 DisplayPoint::new(*row, 0).to_point(&snapshot)
5468 }
5469 _ => editor.selections.newest::<Point>(cx).head(),
5470 };
5471 let (buffer, buffer_row) = snapshot
5472 .buffer_snapshot
5473 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5474 .and_then(|(buffer_snapshot, range)| {
5475 editor
5476 .buffer
5477 .read(cx)
5478 .buffer(buffer_snapshot.remote_id())
5479 .map(|buffer| (buffer, range.start.row))
5480 })?;
5481 let (_, code_actions) = editor
5482 .available_code_actions
5483 .clone()
5484 .and_then(|(location, code_actions)| {
5485 let snapshot = location.buffer.read(cx).snapshot();
5486 let point_range = location.range.to_point(&snapshot);
5487 let point_range = point_range.start.row..=point_range.end.row;
5488 if point_range.contains(&buffer_row) {
5489 Some((location, code_actions))
5490 } else {
5491 None
5492 }
5493 })
5494 .unzip();
5495 let buffer_id = buffer.read(cx).remote_id();
5496 let tasks = editor
5497 .tasks
5498 .get(&(buffer_id, buffer_row))
5499 .map(|t| Arc::new(t.to_owned()));
5500 if tasks.is_none() && code_actions.is_none() {
5501 return None;
5502 }
5503
5504 editor.completion_tasks.clear();
5505 editor.discard_inline_completion(false, cx);
5506 let task_context =
5507 tasks
5508 .as_ref()
5509 .zip(editor.project.clone())
5510 .map(|(tasks, project)| {
5511 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5512 });
5513
5514 Some(cx.spawn_in(window, async move |editor, cx| {
5515 let task_context = match task_context {
5516 Some(task_context) => task_context.await,
5517 None => None,
5518 };
5519 let resolved_tasks =
5520 tasks
5521 .zip(task_context.clone())
5522 .map(|(tasks, task_context)| ResolvedTasks {
5523 templates: tasks.resolve(&task_context).collect(),
5524 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5525 multibuffer_point.row,
5526 tasks.column,
5527 )),
5528 });
5529 let debug_scenarios = editor.update(cx, |editor, cx| {
5530 if cx.has_flag::<DebuggerFeatureFlag>() {
5531 maybe!({
5532 let project = editor.project.as_ref()?;
5533 let dap_store = project.read(cx).dap_store();
5534 let mut scenarios = vec![];
5535 let resolved_tasks = resolved_tasks.as_ref()?;
5536 let buffer = buffer.read(cx);
5537 let language = buffer.language()?;
5538 let file = buffer.file();
5539 let debug_adapter =
5540 language_settings(language.name().into(), file, cx)
5541 .debuggers
5542 .first()
5543 .map(SharedString::from)
5544 .or_else(|| {
5545 language
5546 .config()
5547 .debuggers
5548 .first()
5549 .map(SharedString::from)
5550 })?;
5551
5552 dap_store.update(cx, |dap_store, cx| {
5553 for (_, task) in &resolved_tasks.templates {
5554 if let Some(scenario) = dap_store
5555 .debug_scenario_for_build_task(
5556 task.original_task().clone(),
5557 debug_adapter.clone().into(),
5558 task.display_label().to_owned().into(),
5559 cx,
5560 )
5561 {
5562 scenarios.push(scenario);
5563 }
5564 }
5565 });
5566 Some(scenarios)
5567 })
5568 .unwrap_or_default()
5569 } else {
5570 vec![]
5571 }
5572 })?;
5573 let spawn_straight_away = quick_launch
5574 && resolved_tasks
5575 .as_ref()
5576 .map_or(false, |tasks| tasks.templates.len() == 1)
5577 && code_actions
5578 .as_ref()
5579 .map_or(true, |actions| actions.is_empty())
5580 && debug_scenarios.is_empty();
5581 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5582 crate::hover_popover::hide_hover(editor, cx);
5583 *editor.context_menu.borrow_mut() =
5584 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5585 buffer,
5586 actions: CodeActionContents::new(
5587 resolved_tasks,
5588 code_actions,
5589 debug_scenarios,
5590 task_context.unwrap_or_default(),
5591 ),
5592 selected_item: Default::default(),
5593 scroll_handle: UniformListScrollHandle::default(),
5594 deployed_from,
5595 }));
5596 if spawn_straight_away {
5597 if let Some(task) = editor.confirm_code_action(
5598 &ConfirmCodeAction { item_ix: Some(0) },
5599 window,
5600 cx,
5601 ) {
5602 cx.notify();
5603 return task;
5604 }
5605 }
5606 cx.notify();
5607 Task::ready(Ok(()))
5608 }) {
5609 task.await
5610 } else {
5611 Ok(())
5612 }
5613 }))
5614 } else {
5615 Some(Task::ready(Ok(())))
5616 }
5617 })?;
5618 if let Some(task) = spawned_test_task {
5619 task.await?;
5620 }
5621
5622 anyhow::Ok(())
5623 })
5624 .detach_and_log_err(cx);
5625 }
5626
5627 pub fn confirm_code_action(
5628 &mut self,
5629 action: &ConfirmCodeAction,
5630 window: &mut Window,
5631 cx: &mut Context<Self>,
5632 ) -> Option<Task<Result<()>>> {
5633 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5634
5635 let actions_menu =
5636 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5637 menu
5638 } else {
5639 return None;
5640 };
5641
5642 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5643 let action = actions_menu.actions.get(action_ix)?;
5644 let title = action.label();
5645 let buffer = actions_menu.buffer;
5646 let workspace = self.workspace()?;
5647
5648 match action {
5649 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5650 workspace.update(cx, |workspace, cx| {
5651 workspace.schedule_resolved_task(
5652 task_source_kind,
5653 resolved_task,
5654 false,
5655 window,
5656 cx,
5657 );
5658
5659 Some(Task::ready(Ok(())))
5660 })
5661 }
5662 CodeActionsItem::CodeAction {
5663 excerpt_id,
5664 action,
5665 provider,
5666 } => {
5667 let apply_code_action =
5668 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5669 let workspace = workspace.downgrade();
5670 Some(cx.spawn_in(window, async move |editor, cx| {
5671 let project_transaction = apply_code_action.await?;
5672 Self::open_project_transaction(
5673 &editor,
5674 workspace,
5675 project_transaction,
5676 title,
5677 cx,
5678 )
5679 .await
5680 }))
5681 }
5682 CodeActionsItem::DebugScenario(scenario) => {
5683 let context = actions_menu.actions.context.clone();
5684
5685 workspace.update(cx, |workspace, cx| {
5686 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5687 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5688 });
5689 Some(Task::ready(Ok(())))
5690 }
5691 }
5692 }
5693
5694 pub async fn open_project_transaction(
5695 this: &WeakEntity<Editor>,
5696 workspace: WeakEntity<Workspace>,
5697 transaction: ProjectTransaction,
5698 title: String,
5699 cx: &mut AsyncWindowContext,
5700 ) -> Result<()> {
5701 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5702 cx.update(|_, cx| {
5703 entries.sort_unstable_by_key(|(buffer, _)| {
5704 buffer.read(cx).file().map(|f| f.path().clone())
5705 });
5706 })?;
5707
5708 // If the project transaction's edits are all contained within this editor, then
5709 // avoid opening a new editor to display them.
5710
5711 if let Some((buffer, transaction)) = entries.first() {
5712 if entries.len() == 1 {
5713 let excerpt = this.update(cx, |editor, cx| {
5714 editor
5715 .buffer()
5716 .read(cx)
5717 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5718 })?;
5719 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5720 if excerpted_buffer == *buffer {
5721 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5722 let excerpt_range = excerpt_range.to_offset(buffer);
5723 buffer
5724 .edited_ranges_for_transaction::<usize>(transaction)
5725 .all(|range| {
5726 excerpt_range.start <= range.start
5727 && excerpt_range.end >= range.end
5728 })
5729 })?;
5730
5731 if all_edits_within_excerpt {
5732 return Ok(());
5733 }
5734 }
5735 }
5736 }
5737 } else {
5738 return Ok(());
5739 }
5740
5741 let mut ranges_to_highlight = Vec::new();
5742 let excerpt_buffer = cx.new(|cx| {
5743 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5744 for (buffer_handle, transaction) in &entries {
5745 let edited_ranges = buffer_handle
5746 .read(cx)
5747 .edited_ranges_for_transaction::<Point>(transaction)
5748 .collect::<Vec<_>>();
5749 let (ranges, _) = multibuffer.set_excerpts_for_path(
5750 PathKey::for_buffer(buffer_handle, cx),
5751 buffer_handle.clone(),
5752 edited_ranges,
5753 DEFAULT_MULTIBUFFER_CONTEXT,
5754 cx,
5755 );
5756
5757 ranges_to_highlight.extend(ranges);
5758 }
5759 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5760 multibuffer
5761 })?;
5762
5763 workspace.update_in(cx, |workspace, window, cx| {
5764 let project = workspace.project().clone();
5765 let editor =
5766 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5767 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5768 editor.update(cx, |editor, cx| {
5769 editor.highlight_background::<Self>(
5770 &ranges_to_highlight,
5771 |theme| theme.editor_highlighted_line_background,
5772 cx,
5773 );
5774 });
5775 })?;
5776
5777 Ok(())
5778 }
5779
5780 pub fn clear_code_action_providers(&mut self) {
5781 self.code_action_providers.clear();
5782 self.available_code_actions.take();
5783 }
5784
5785 pub fn add_code_action_provider(
5786 &mut self,
5787 provider: Rc<dyn CodeActionProvider>,
5788 window: &mut Window,
5789 cx: &mut Context<Self>,
5790 ) {
5791 if self
5792 .code_action_providers
5793 .iter()
5794 .any(|existing_provider| existing_provider.id() == provider.id())
5795 {
5796 return;
5797 }
5798
5799 self.code_action_providers.push(provider);
5800 self.refresh_code_actions(window, cx);
5801 }
5802
5803 pub fn remove_code_action_provider(
5804 &mut self,
5805 id: Arc<str>,
5806 window: &mut Window,
5807 cx: &mut Context<Self>,
5808 ) {
5809 self.code_action_providers
5810 .retain(|provider| provider.id() != id);
5811 self.refresh_code_actions(window, cx);
5812 }
5813
5814 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
5815 !self.code_action_providers.is_empty()
5816 && EditorSettings::get_global(cx).toolbar.code_actions
5817 }
5818
5819 pub fn has_available_code_actions(&self) -> bool {
5820 self.available_code_actions
5821 .as_ref()
5822 .is_some_and(|(_, actions)| !actions.is_empty())
5823 }
5824
5825 fn render_inline_code_actions(
5826 &self,
5827 icon_size: ui::IconSize,
5828 display_row: DisplayRow,
5829 is_active: bool,
5830 cx: &mut Context<Self>,
5831 ) -> AnyElement {
5832 let show_tooltip = !self.context_menu_visible();
5833 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
5834 .icon_size(icon_size)
5835 .shape(ui::IconButtonShape::Square)
5836 .style(ButtonStyle::Transparent)
5837 .icon_color(ui::Color::Hidden)
5838 .toggle_state(is_active)
5839 .when(show_tooltip, |this| {
5840 this.tooltip({
5841 let focus_handle = self.focus_handle.clone();
5842 move |window, cx| {
5843 Tooltip::for_action_in(
5844 "Toggle Code Actions",
5845 &ToggleCodeActions {
5846 deployed_from: None,
5847 quick_launch: false,
5848 },
5849 &focus_handle,
5850 window,
5851 cx,
5852 )
5853 }
5854 })
5855 })
5856 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
5857 window.focus(&editor.focus_handle(cx));
5858 editor.toggle_code_actions(
5859 &crate::actions::ToggleCodeActions {
5860 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
5861 display_row,
5862 )),
5863 quick_launch: false,
5864 },
5865 window,
5866 cx,
5867 );
5868 }))
5869 .into_any_element()
5870 }
5871
5872 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
5873 &self.context_menu
5874 }
5875
5876 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5877 let newest_selection = self.selections.newest_anchor().clone();
5878 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5879 let buffer = self.buffer.read(cx);
5880 if newest_selection.head().diff_base_anchor.is_some() {
5881 return None;
5882 }
5883 let (start_buffer, start) =
5884 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5885 let (end_buffer, end) =
5886 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5887 if start_buffer != end_buffer {
5888 return None;
5889 }
5890
5891 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5892 cx.background_executor()
5893 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5894 .await;
5895
5896 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5897 let providers = this.code_action_providers.clone();
5898 let tasks = this
5899 .code_action_providers
5900 .iter()
5901 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5902 .collect::<Vec<_>>();
5903 (providers, tasks)
5904 })?;
5905
5906 let mut actions = Vec::new();
5907 for (provider, provider_actions) in
5908 providers.into_iter().zip(future::join_all(tasks).await)
5909 {
5910 if let Some(provider_actions) = provider_actions.log_err() {
5911 actions.extend(provider_actions.into_iter().map(|action| {
5912 AvailableCodeAction {
5913 excerpt_id: newest_selection.start.excerpt_id,
5914 action,
5915 provider: provider.clone(),
5916 }
5917 }));
5918 }
5919 }
5920
5921 this.update(cx, |this, cx| {
5922 this.available_code_actions = if actions.is_empty() {
5923 None
5924 } else {
5925 Some((
5926 Location {
5927 buffer: start_buffer,
5928 range: start..end,
5929 },
5930 actions.into(),
5931 ))
5932 };
5933 cx.notify();
5934 })
5935 }));
5936 None
5937 }
5938
5939 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5940 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5941 self.show_git_blame_inline = false;
5942
5943 self.show_git_blame_inline_delay_task =
5944 Some(cx.spawn_in(window, async move |this, cx| {
5945 cx.background_executor().timer(delay).await;
5946
5947 this.update(cx, |this, cx| {
5948 this.show_git_blame_inline = true;
5949 cx.notify();
5950 })
5951 .log_err();
5952 }));
5953 }
5954 }
5955
5956 fn show_blame_popover(
5957 &mut self,
5958 blame_entry: &BlameEntry,
5959 position: gpui::Point<Pixels>,
5960 cx: &mut Context<Self>,
5961 ) {
5962 if let Some(state) = &mut self.inline_blame_popover {
5963 state.hide_task.take();
5964 cx.notify();
5965 } else {
5966 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5967 let show_task = cx.spawn(async move |editor, cx| {
5968 cx.background_executor()
5969 .timer(std::time::Duration::from_millis(delay))
5970 .await;
5971 editor
5972 .update(cx, |editor, cx| {
5973 if let Some(state) = &mut editor.inline_blame_popover {
5974 state.show_task = None;
5975 cx.notify();
5976 }
5977 })
5978 .ok();
5979 });
5980 let Some(blame) = self.blame.as_ref() else {
5981 return;
5982 };
5983 let blame = blame.read(cx);
5984 let details = blame.details_for_entry(&blame_entry);
5985 let markdown = cx.new(|cx| {
5986 Markdown::new(
5987 details
5988 .as_ref()
5989 .map(|message| message.message.clone())
5990 .unwrap_or_default(),
5991 None,
5992 None,
5993 cx,
5994 )
5995 });
5996 self.inline_blame_popover = Some(InlineBlamePopover {
5997 position,
5998 show_task: Some(show_task),
5999 hide_task: None,
6000 popover_bounds: None,
6001 popover_state: InlineBlamePopoverState {
6002 scroll_handle: ScrollHandle::new(),
6003 commit_message: details,
6004 markdown,
6005 },
6006 });
6007 }
6008 }
6009
6010 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6011 if let Some(state) = &mut self.inline_blame_popover {
6012 if state.show_task.is_some() {
6013 self.inline_blame_popover.take();
6014 cx.notify();
6015 } else {
6016 let hide_task = cx.spawn(async move |editor, cx| {
6017 cx.background_executor()
6018 .timer(std::time::Duration::from_millis(100))
6019 .await;
6020 editor
6021 .update(cx, |editor, cx| {
6022 editor.inline_blame_popover.take();
6023 cx.notify();
6024 })
6025 .ok();
6026 });
6027 state.hide_task = Some(hide_task);
6028 }
6029 }
6030 }
6031
6032 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6033 if self.pending_rename.is_some() {
6034 return None;
6035 }
6036
6037 let provider = self.semantics_provider.clone()?;
6038 let buffer = self.buffer.read(cx);
6039 let newest_selection = self.selections.newest_anchor().clone();
6040 let cursor_position = newest_selection.head();
6041 let (cursor_buffer, cursor_buffer_position) =
6042 buffer.text_anchor_for_position(cursor_position, cx)?;
6043 let (tail_buffer, tail_buffer_position) =
6044 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6045 if cursor_buffer != tail_buffer {
6046 return None;
6047 }
6048
6049 let snapshot = cursor_buffer.read(cx).snapshot();
6050 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6051 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6052 if start_word_range != end_word_range {
6053 self.document_highlights_task.take();
6054 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6055 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6056 return None;
6057 }
6058
6059 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6060 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6061 cx.background_executor()
6062 .timer(Duration::from_millis(debounce))
6063 .await;
6064
6065 let highlights = if let Some(highlights) = cx
6066 .update(|cx| {
6067 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6068 })
6069 .ok()
6070 .flatten()
6071 {
6072 highlights.await.log_err()
6073 } else {
6074 None
6075 };
6076
6077 if let Some(highlights) = highlights {
6078 this.update(cx, |this, cx| {
6079 if this.pending_rename.is_some() {
6080 return;
6081 }
6082
6083 let buffer_id = cursor_position.buffer_id;
6084 let buffer = this.buffer.read(cx);
6085 if !buffer
6086 .text_anchor_for_position(cursor_position, cx)
6087 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6088 {
6089 return;
6090 }
6091
6092 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6093 let mut write_ranges = Vec::new();
6094 let mut read_ranges = Vec::new();
6095 for highlight in highlights {
6096 for (excerpt_id, excerpt_range) in
6097 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6098 {
6099 let start = highlight
6100 .range
6101 .start
6102 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6103 let end = highlight
6104 .range
6105 .end
6106 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6107 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6108 continue;
6109 }
6110
6111 let range = Anchor {
6112 buffer_id,
6113 excerpt_id,
6114 text_anchor: start,
6115 diff_base_anchor: None,
6116 }..Anchor {
6117 buffer_id,
6118 excerpt_id,
6119 text_anchor: end,
6120 diff_base_anchor: None,
6121 };
6122 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6123 write_ranges.push(range);
6124 } else {
6125 read_ranges.push(range);
6126 }
6127 }
6128 }
6129
6130 this.highlight_background::<DocumentHighlightRead>(
6131 &read_ranges,
6132 |theme| theme.editor_document_highlight_read_background,
6133 cx,
6134 );
6135 this.highlight_background::<DocumentHighlightWrite>(
6136 &write_ranges,
6137 |theme| theme.editor_document_highlight_write_background,
6138 cx,
6139 );
6140 cx.notify();
6141 })
6142 .log_err();
6143 }
6144 }));
6145 None
6146 }
6147
6148 fn prepare_highlight_query_from_selection(
6149 &mut self,
6150 cx: &mut Context<Editor>,
6151 ) -> Option<(String, Range<Anchor>)> {
6152 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6153 return None;
6154 }
6155 if !EditorSettings::get_global(cx).selection_highlight {
6156 return None;
6157 }
6158 if self.selections.count() != 1 || self.selections.line_mode {
6159 return None;
6160 }
6161 let selection = self.selections.newest::<Point>(cx);
6162 if selection.is_empty() || selection.start.row != selection.end.row {
6163 return None;
6164 }
6165 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6166 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6167 let query = multi_buffer_snapshot
6168 .text_for_range(selection_anchor_range.clone())
6169 .collect::<String>();
6170 if query.trim().is_empty() {
6171 return None;
6172 }
6173 Some((query, selection_anchor_range))
6174 }
6175
6176 fn update_selection_occurrence_highlights(
6177 &mut self,
6178 query_text: String,
6179 query_range: Range<Anchor>,
6180 multi_buffer_range_to_query: Range<Point>,
6181 use_debounce: bool,
6182 window: &mut Window,
6183 cx: &mut Context<Editor>,
6184 ) -> Task<()> {
6185 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6186 cx.spawn_in(window, async move |editor, cx| {
6187 if use_debounce {
6188 cx.background_executor()
6189 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6190 .await;
6191 }
6192 let match_task = cx.background_spawn(async move {
6193 let buffer_ranges = multi_buffer_snapshot
6194 .range_to_buffer_ranges(multi_buffer_range_to_query)
6195 .into_iter()
6196 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6197 let mut match_ranges = Vec::new();
6198 let Ok(regex) = project::search::SearchQuery::text(
6199 query_text.clone(),
6200 false,
6201 false,
6202 false,
6203 Default::default(),
6204 Default::default(),
6205 false,
6206 None,
6207 ) else {
6208 return Vec::default();
6209 };
6210 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6211 match_ranges.extend(
6212 regex
6213 .search(&buffer_snapshot, Some(search_range.clone()))
6214 .await
6215 .into_iter()
6216 .filter_map(|match_range| {
6217 let match_start = buffer_snapshot
6218 .anchor_after(search_range.start + match_range.start);
6219 let match_end = buffer_snapshot
6220 .anchor_before(search_range.start + match_range.end);
6221 let match_anchor_range = Anchor::range_in_buffer(
6222 excerpt_id,
6223 buffer_snapshot.remote_id(),
6224 match_start..match_end,
6225 );
6226 (match_anchor_range != query_range).then_some(match_anchor_range)
6227 }),
6228 );
6229 }
6230 match_ranges
6231 });
6232 let match_ranges = match_task.await;
6233 editor
6234 .update_in(cx, |editor, _, cx| {
6235 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6236 if !match_ranges.is_empty() {
6237 editor.highlight_background::<SelectedTextHighlight>(
6238 &match_ranges,
6239 |theme| theme.editor_document_highlight_bracket_background,
6240 cx,
6241 )
6242 }
6243 })
6244 .log_err();
6245 })
6246 }
6247
6248 fn refresh_selected_text_highlights(
6249 &mut self,
6250 on_buffer_edit: bool,
6251 window: &mut Window,
6252 cx: &mut Context<Editor>,
6253 ) {
6254 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6255 else {
6256 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6257 self.quick_selection_highlight_task.take();
6258 self.debounced_selection_highlight_task.take();
6259 return;
6260 };
6261 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6262 if on_buffer_edit
6263 || self
6264 .quick_selection_highlight_task
6265 .as_ref()
6266 .map_or(true, |(prev_anchor_range, _)| {
6267 prev_anchor_range != &query_range
6268 })
6269 {
6270 let multi_buffer_visible_start = self
6271 .scroll_manager
6272 .anchor()
6273 .anchor
6274 .to_point(&multi_buffer_snapshot);
6275 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6276 multi_buffer_visible_start
6277 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6278 Bias::Left,
6279 );
6280 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6281 self.quick_selection_highlight_task = Some((
6282 query_range.clone(),
6283 self.update_selection_occurrence_highlights(
6284 query_text.clone(),
6285 query_range.clone(),
6286 multi_buffer_visible_range,
6287 false,
6288 window,
6289 cx,
6290 ),
6291 ));
6292 }
6293 if on_buffer_edit
6294 || self
6295 .debounced_selection_highlight_task
6296 .as_ref()
6297 .map_or(true, |(prev_anchor_range, _)| {
6298 prev_anchor_range != &query_range
6299 })
6300 {
6301 let multi_buffer_start = multi_buffer_snapshot
6302 .anchor_before(0)
6303 .to_point(&multi_buffer_snapshot);
6304 let multi_buffer_end = multi_buffer_snapshot
6305 .anchor_after(multi_buffer_snapshot.len())
6306 .to_point(&multi_buffer_snapshot);
6307 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6308 self.debounced_selection_highlight_task = Some((
6309 query_range.clone(),
6310 self.update_selection_occurrence_highlights(
6311 query_text,
6312 query_range,
6313 multi_buffer_full_range,
6314 true,
6315 window,
6316 cx,
6317 ),
6318 ));
6319 }
6320 }
6321
6322 pub fn refresh_inline_completion(
6323 &mut self,
6324 debounce: bool,
6325 user_requested: bool,
6326 window: &mut Window,
6327 cx: &mut Context<Self>,
6328 ) -> Option<()> {
6329 let provider = self.edit_prediction_provider()?;
6330 let cursor = self.selections.newest_anchor().head();
6331 let (buffer, cursor_buffer_position) =
6332 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6333
6334 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6335 self.discard_inline_completion(false, cx);
6336 return None;
6337 }
6338
6339 if !user_requested
6340 && (!self.should_show_edit_predictions()
6341 || !self.is_focused(window)
6342 || buffer.read(cx).is_empty())
6343 {
6344 self.discard_inline_completion(false, cx);
6345 return None;
6346 }
6347
6348 self.update_visible_inline_completion(window, cx);
6349 provider.refresh(
6350 self.project.clone(),
6351 buffer,
6352 cursor_buffer_position,
6353 debounce,
6354 cx,
6355 );
6356 Some(())
6357 }
6358
6359 fn show_edit_predictions_in_menu(&self) -> bool {
6360 match self.edit_prediction_settings {
6361 EditPredictionSettings::Disabled => false,
6362 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6363 }
6364 }
6365
6366 pub fn edit_predictions_enabled(&self) -> bool {
6367 match self.edit_prediction_settings {
6368 EditPredictionSettings::Disabled => false,
6369 EditPredictionSettings::Enabled { .. } => true,
6370 }
6371 }
6372
6373 fn edit_prediction_requires_modifier(&self) -> bool {
6374 match self.edit_prediction_settings {
6375 EditPredictionSettings::Disabled => false,
6376 EditPredictionSettings::Enabled {
6377 preview_requires_modifier,
6378 ..
6379 } => preview_requires_modifier,
6380 }
6381 }
6382
6383 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6384 if self.edit_prediction_provider.is_none() {
6385 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6386 } else {
6387 let selection = self.selections.newest_anchor();
6388 let cursor = selection.head();
6389
6390 if let Some((buffer, cursor_buffer_position)) =
6391 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6392 {
6393 self.edit_prediction_settings =
6394 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6395 }
6396 }
6397 }
6398
6399 fn edit_prediction_settings_at_position(
6400 &self,
6401 buffer: &Entity<Buffer>,
6402 buffer_position: language::Anchor,
6403 cx: &App,
6404 ) -> EditPredictionSettings {
6405 if !self.mode.is_full()
6406 || !self.show_inline_completions_override.unwrap_or(true)
6407 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6408 {
6409 return EditPredictionSettings::Disabled;
6410 }
6411
6412 let buffer = buffer.read(cx);
6413
6414 let file = buffer.file();
6415
6416 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6417 return EditPredictionSettings::Disabled;
6418 };
6419
6420 let by_provider = matches!(
6421 self.menu_inline_completions_policy,
6422 MenuInlineCompletionsPolicy::ByProvider
6423 );
6424
6425 let show_in_menu = by_provider
6426 && self
6427 .edit_prediction_provider
6428 .as_ref()
6429 .map_or(false, |provider| {
6430 provider.provider.show_completions_in_menu()
6431 });
6432
6433 let preview_requires_modifier =
6434 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6435
6436 EditPredictionSettings::Enabled {
6437 show_in_menu,
6438 preview_requires_modifier,
6439 }
6440 }
6441
6442 fn should_show_edit_predictions(&self) -> bool {
6443 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6444 }
6445
6446 pub fn edit_prediction_preview_is_active(&self) -> bool {
6447 matches!(
6448 self.edit_prediction_preview,
6449 EditPredictionPreview::Active { .. }
6450 )
6451 }
6452
6453 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6454 let cursor = self.selections.newest_anchor().head();
6455 if let Some((buffer, cursor_position)) =
6456 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6457 {
6458 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6459 } else {
6460 false
6461 }
6462 }
6463
6464 pub fn supports_minimap(&self, cx: &App) -> bool {
6465 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6466 }
6467
6468 fn edit_predictions_enabled_in_buffer(
6469 &self,
6470 buffer: &Entity<Buffer>,
6471 buffer_position: language::Anchor,
6472 cx: &App,
6473 ) -> bool {
6474 maybe!({
6475 if self.read_only(cx) {
6476 return Some(false);
6477 }
6478 let provider = self.edit_prediction_provider()?;
6479 if !provider.is_enabled(&buffer, buffer_position, cx) {
6480 return Some(false);
6481 }
6482 let buffer = buffer.read(cx);
6483 let Some(file) = buffer.file() else {
6484 return Some(true);
6485 };
6486 let settings = all_language_settings(Some(file), cx);
6487 Some(settings.edit_predictions_enabled_for_file(file, cx))
6488 })
6489 .unwrap_or(false)
6490 }
6491
6492 fn cycle_inline_completion(
6493 &mut self,
6494 direction: Direction,
6495 window: &mut Window,
6496 cx: &mut Context<Self>,
6497 ) -> Option<()> {
6498 let provider = self.edit_prediction_provider()?;
6499 let cursor = self.selections.newest_anchor().head();
6500 let (buffer, cursor_buffer_position) =
6501 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6502 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6503 return None;
6504 }
6505
6506 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6507 self.update_visible_inline_completion(window, cx);
6508
6509 Some(())
6510 }
6511
6512 pub fn show_inline_completion(
6513 &mut self,
6514 _: &ShowEditPrediction,
6515 window: &mut Window,
6516 cx: &mut Context<Self>,
6517 ) {
6518 if !self.has_active_inline_completion() {
6519 self.refresh_inline_completion(false, true, window, cx);
6520 return;
6521 }
6522
6523 self.update_visible_inline_completion(window, cx);
6524 }
6525
6526 pub fn display_cursor_names(
6527 &mut self,
6528 _: &DisplayCursorNames,
6529 window: &mut Window,
6530 cx: &mut Context<Self>,
6531 ) {
6532 self.show_cursor_names(window, cx);
6533 }
6534
6535 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6536 self.show_cursor_names = true;
6537 cx.notify();
6538 cx.spawn_in(window, async move |this, cx| {
6539 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6540 this.update(cx, |this, cx| {
6541 this.show_cursor_names = false;
6542 cx.notify()
6543 })
6544 .ok()
6545 })
6546 .detach();
6547 }
6548
6549 pub fn next_edit_prediction(
6550 &mut self,
6551 _: &NextEditPrediction,
6552 window: &mut Window,
6553 cx: &mut Context<Self>,
6554 ) {
6555 if self.has_active_inline_completion() {
6556 self.cycle_inline_completion(Direction::Next, window, cx);
6557 } else {
6558 let is_copilot_disabled = self
6559 .refresh_inline_completion(false, true, window, cx)
6560 .is_none();
6561 if is_copilot_disabled {
6562 cx.propagate();
6563 }
6564 }
6565 }
6566
6567 pub fn previous_edit_prediction(
6568 &mut self,
6569 _: &PreviousEditPrediction,
6570 window: &mut Window,
6571 cx: &mut Context<Self>,
6572 ) {
6573 if self.has_active_inline_completion() {
6574 self.cycle_inline_completion(Direction::Prev, window, cx);
6575 } else {
6576 let is_copilot_disabled = self
6577 .refresh_inline_completion(false, true, window, cx)
6578 .is_none();
6579 if is_copilot_disabled {
6580 cx.propagate();
6581 }
6582 }
6583 }
6584
6585 pub fn accept_edit_prediction(
6586 &mut self,
6587 _: &AcceptEditPrediction,
6588 window: &mut Window,
6589 cx: &mut Context<Self>,
6590 ) {
6591 if self.show_edit_predictions_in_menu() {
6592 self.hide_context_menu(window, cx);
6593 }
6594
6595 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6596 return;
6597 };
6598
6599 self.report_inline_completion_event(
6600 active_inline_completion.completion_id.clone(),
6601 true,
6602 cx,
6603 );
6604
6605 match &active_inline_completion.completion {
6606 InlineCompletion::Move { target, .. } => {
6607 let target = *target;
6608
6609 if let Some(position_map) = &self.last_position_map {
6610 if position_map
6611 .visible_row_range
6612 .contains(&target.to_display_point(&position_map.snapshot).row())
6613 || !self.edit_prediction_requires_modifier()
6614 {
6615 self.unfold_ranges(&[target..target], true, false, cx);
6616 // Note that this is also done in vim's handler of the Tab action.
6617 self.change_selections(
6618 Some(Autoscroll::newest()),
6619 window,
6620 cx,
6621 |selections| {
6622 selections.select_anchor_ranges([target..target]);
6623 },
6624 );
6625 self.clear_row_highlights::<EditPredictionPreview>();
6626
6627 self.edit_prediction_preview
6628 .set_previous_scroll_position(None);
6629 } else {
6630 self.edit_prediction_preview
6631 .set_previous_scroll_position(Some(
6632 position_map.snapshot.scroll_anchor,
6633 ));
6634
6635 self.highlight_rows::<EditPredictionPreview>(
6636 target..target,
6637 cx.theme().colors().editor_highlighted_line_background,
6638 RowHighlightOptions {
6639 autoscroll: true,
6640 ..Default::default()
6641 },
6642 cx,
6643 );
6644 self.request_autoscroll(Autoscroll::fit(), cx);
6645 }
6646 }
6647 }
6648 InlineCompletion::Edit { edits, .. } => {
6649 if let Some(provider) = self.edit_prediction_provider() {
6650 provider.accept(cx);
6651 }
6652
6653 // Store the transaction ID and selections before applying the edit
6654 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6655
6656 let snapshot = self.buffer.read(cx).snapshot(cx);
6657 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6658
6659 self.buffer.update(cx, |buffer, cx| {
6660 buffer.edit(edits.iter().cloned(), None, cx)
6661 });
6662
6663 self.change_selections(None, window, cx, |s| {
6664 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6665 });
6666
6667 let selections = self.selections.disjoint_anchors();
6668 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6669 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6670 if has_new_transaction {
6671 self.selection_history
6672 .insert_transaction(transaction_id_now, selections);
6673 }
6674 }
6675
6676 self.update_visible_inline_completion(window, cx);
6677 if self.active_inline_completion.is_none() {
6678 self.refresh_inline_completion(true, true, window, cx);
6679 }
6680
6681 cx.notify();
6682 }
6683 }
6684
6685 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6686 }
6687
6688 pub fn accept_partial_inline_completion(
6689 &mut self,
6690 _: &AcceptPartialEditPrediction,
6691 window: &mut Window,
6692 cx: &mut Context<Self>,
6693 ) {
6694 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6695 return;
6696 };
6697 if self.selections.count() != 1 {
6698 return;
6699 }
6700
6701 self.report_inline_completion_event(
6702 active_inline_completion.completion_id.clone(),
6703 true,
6704 cx,
6705 );
6706
6707 match &active_inline_completion.completion {
6708 InlineCompletion::Move { target, .. } => {
6709 let target = *target;
6710 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6711 selections.select_anchor_ranges([target..target]);
6712 });
6713 }
6714 InlineCompletion::Edit { edits, .. } => {
6715 // Find an insertion that starts at the cursor position.
6716 let snapshot = self.buffer.read(cx).snapshot(cx);
6717 let cursor_offset = self.selections.newest::<usize>(cx).head();
6718 let insertion = edits.iter().find_map(|(range, text)| {
6719 let range = range.to_offset(&snapshot);
6720 if range.is_empty() && range.start == cursor_offset {
6721 Some(text)
6722 } else {
6723 None
6724 }
6725 });
6726
6727 if let Some(text) = insertion {
6728 let mut partial_completion = text
6729 .chars()
6730 .by_ref()
6731 .take_while(|c| c.is_alphabetic())
6732 .collect::<String>();
6733 if partial_completion.is_empty() {
6734 partial_completion = text
6735 .chars()
6736 .by_ref()
6737 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6738 .collect::<String>();
6739 }
6740
6741 cx.emit(EditorEvent::InputHandled {
6742 utf16_range_to_replace: None,
6743 text: partial_completion.clone().into(),
6744 });
6745
6746 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6747
6748 self.refresh_inline_completion(true, true, window, cx);
6749 cx.notify();
6750 } else {
6751 self.accept_edit_prediction(&Default::default(), window, cx);
6752 }
6753 }
6754 }
6755 }
6756
6757 fn discard_inline_completion(
6758 &mut self,
6759 should_report_inline_completion_event: bool,
6760 cx: &mut Context<Self>,
6761 ) -> bool {
6762 if should_report_inline_completion_event {
6763 let completion_id = self
6764 .active_inline_completion
6765 .as_ref()
6766 .and_then(|active_completion| active_completion.completion_id.clone());
6767
6768 self.report_inline_completion_event(completion_id, false, cx);
6769 }
6770
6771 if let Some(provider) = self.edit_prediction_provider() {
6772 provider.discard(cx);
6773 }
6774
6775 self.take_active_inline_completion(cx)
6776 }
6777
6778 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6779 let Some(provider) = self.edit_prediction_provider() else {
6780 return;
6781 };
6782
6783 let Some((_, buffer, _)) = self
6784 .buffer
6785 .read(cx)
6786 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6787 else {
6788 return;
6789 };
6790
6791 let extension = buffer
6792 .read(cx)
6793 .file()
6794 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6795
6796 let event_type = match accepted {
6797 true => "Edit Prediction Accepted",
6798 false => "Edit Prediction Discarded",
6799 };
6800 telemetry::event!(
6801 event_type,
6802 provider = provider.name(),
6803 prediction_id = id,
6804 suggestion_accepted = accepted,
6805 file_extension = extension,
6806 );
6807 }
6808
6809 pub fn has_active_inline_completion(&self) -> bool {
6810 self.active_inline_completion.is_some()
6811 }
6812
6813 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6814 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6815 return false;
6816 };
6817
6818 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6819 self.clear_highlights::<InlineCompletionHighlight>(cx);
6820 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6821 true
6822 }
6823
6824 /// Returns true when we're displaying the edit prediction popover below the cursor
6825 /// like we are not previewing and the LSP autocomplete menu is visible
6826 /// or we are in `when_holding_modifier` mode.
6827 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6828 if self.edit_prediction_preview_is_active()
6829 || !self.show_edit_predictions_in_menu()
6830 || !self.edit_predictions_enabled()
6831 {
6832 return false;
6833 }
6834
6835 if self.has_visible_completions_menu() {
6836 return true;
6837 }
6838
6839 has_completion && self.edit_prediction_requires_modifier()
6840 }
6841
6842 fn handle_modifiers_changed(
6843 &mut self,
6844 modifiers: Modifiers,
6845 position_map: &PositionMap,
6846 window: &mut Window,
6847 cx: &mut Context<Self>,
6848 ) {
6849 if self.show_edit_predictions_in_menu() {
6850 self.update_edit_prediction_preview(&modifiers, window, cx);
6851 }
6852
6853 self.update_selection_mode(&modifiers, position_map, window, cx);
6854
6855 let mouse_position = window.mouse_position();
6856 if !position_map.text_hitbox.is_hovered(window) {
6857 return;
6858 }
6859
6860 self.update_hovered_link(
6861 position_map.point_for_position(mouse_position),
6862 &position_map.snapshot,
6863 modifiers,
6864 window,
6865 cx,
6866 )
6867 }
6868
6869 fn update_selection_mode(
6870 &mut self,
6871 modifiers: &Modifiers,
6872 position_map: &PositionMap,
6873 window: &mut Window,
6874 cx: &mut Context<Self>,
6875 ) {
6876 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6877 return;
6878 }
6879
6880 let mouse_position = window.mouse_position();
6881 let point_for_position = position_map.point_for_position(mouse_position);
6882 let position = point_for_position.previous_valid;
6883
6884 self.select(
6885 SelectPhase::BeginColumnar {
6886 position,
6887 reset: false,
6888 goal_column: point_for_position.exact_unclipped.column(),
6889 },
6890 window,
6891 cx,
6892 );
6893 }
6894
6895 fn update_edit_prediction_preview(
6896 &mut self,
6897 modifiers: &Modifiers,
6898 window: &mut Window,
6899 cx: &mut Context<Self>,
6900 ) {
6901 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6902 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6903 return;
6904 };
6905
6906 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6907 if matches!(
6908 self.edit_prediction_preview,
6909 EditPredictionPreview::Inactive { .. }
6910 ) {
6911 self.edit_prediction_preview = EditPredictionPreview::Active {
6912 previous_scroll_position: None,
6913 since: Instant::now(),
6914 };
6915
6916 self.update_visible_inline_completion(window, cx);
6917 cx.notify();
6918 }
6919 } else if let EditPredictionPreview::Active {
6920 previous_scroll_position,
6921 since,
6922 } = self.edit_prediction_preview
6923 {
6924 if let (Some(previous_scroll_position), Some(position_map)) =
6925 (previous_scroll_position, self.last_position_map.as_ref())
6926 {
6927 self.set_scroll_position(
6928 previous_scroll_position
6929 .scroll_position(&position_map.snapshot.display_snapshot),
6930 window,
6931 cx,
6932 );
6933 }
6934
6935 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6936 released_too_fast: since.elapsed() < Duration::from_millis(200),
6937 };
6938 self.clear_row_highlights::<EditPredictionPreview>();
6939 self.update_visible_inline_completion(window, cx);
6940 cx.notify();
6941 }
6942 }
6943
6944 fn update_visible_inline_completion(
6945 &mut self,
6946 _window: &mut Window,
6947 cx: &mut Context<Self>,
6948 ) -> Option<()> {
6949 let selection = self.selections.newest_anchor();
6950 let cursor = selection.head();
6951 let multibuffer = self.buffer.read(cx).snapshot(cx);
6952 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6953 let excerpt_id = cursor.excerpt_id;
6954
6955 let show_in_menu = self.show_edit_predictions_in_menu();
6956 let completions_menu_has_precedence = !show_in_menu
6957 && (self.context_menu.borrow().is_some()
6958 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6959
6960 if completions_menu_has_precedence
6961 || !offset_selection.is_empty()
6962 || self
6963 .active_inline_completion
6964 .as_ref()
6965 .map_or(false, |completion| {
6966 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6967 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6968 !invalidation_range.contains(&offset_selection.head())
6969 })
6970 {
6971 self.discard_inline_completion(false, cx);
6972 return None;
6973 }
6974
6975 self.take_active_inline_completion(cx);
6976 let Some(provider) = self.edit_prediction_provider() else {
6977 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6978 return None;
6979 };
6980
6981 let (buffer, cursor_buffer_position) =
6982 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6983
6984 self.edit_prediction_settings =
6985 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6986
6987 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6988
6989 if self.edit_prediction_indent_conflict {
6990 let cursor_point = cursor.to_point(&multibuffer);
6991
6992 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6993
6994 if let Some((_, indent)) = indents.iter().next() {
6995 if indent.len == cursor_point.column {
6996 self.edit_prediction_indent_conflict = false;
6997 }
6998 }
6999 }
7000
7001 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7002 let edits = inline_completion
7003 .edits
7004 .into_iter()
7005 .flat_map(|(range, new_text)| {
7006 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7007 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7008 Some((start..end, new_text))
7009 })
7010 .collect::<Vec<_>>();
7011 if edits.is_empty() {
7012 return None;
7013 }
7014
7015 let first_edit_start = edits.first().unwrap().0.start;
7016 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7017 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7018
7019 let last_edit_end = edits.last().unwrap().0.end;
7020 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7021 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7022
7023 let cursor_row = cursor.to_point(&multibuffer).row;
7024
7025 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7026
7027 let mut inlay_ids = Vec::new();
7028 let invalidation_row_range;
7029 let move_invalidation_row_range = if cursor_row < edit_start_row {
7030 Some(cursor_row..edit_end_row)
7031 } else if cursor_row > edit_end_row {
7032 Some(edit_start_row..cursor_row)
7033 } else {
7034 None
7035 };
7036 let is_move =
7037 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7038 let completion = if is_move {
7039 invalidation_row_range =
7040 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7041 let target = first_edit_start;
7042 InlineCompletion::Move { target, snapshot }
7043 } else {
7044 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7045 && !self.inline_completions_hidden_for_vim_mode;
7046
7047 if show_completions_in_buffer {
7048 if edits
7049 .iter()
7050 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7051 {
7052 let mut inlays = Vec::new();
7053 for (range, new_text) in &edits {
7054 let inlay = Inlay::inline_completion(
7055 post_inc(&mut self.next_inlay_id),
7056 range.start,
7057 new_text.as_str(),
7058 );
7059 inlay_ids.push(inlay.id);
7060 inlays.push(inlay);
7061 }
7062
7063 self.splice_inlays(&[], inlays, cx);
7064 } else {
7065 let background_color = cx.theme().status().deleted_background;
7066 self.highlight_text::<InlineCompletionHighlight>(
7067 edits.iter().map(|(range, _)| range.clone()).collect(),
7068 HighlightStyle {
7069 background_color: Some(background_color),
7070 ..Default::default()
7071 },
7072 cx,
7073 );
7074 }
7075 }
7076
7077 invalidation_row_range = edit_start_row..edit_end_row;
7078
7079 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7080 if provider.show_tab_accept_marker() {
7081 EditDisplayMode::TabAccept
7082 } else {
7083 EditDisplayMode::Inline
7084 }
7085 } else {
7086 EditDisplayMode::DiffPopover
7087 };
7088
7089 InlineCompletion::Edit {
7090 edits,
7091 edit_preview: inline_completion.edit_preview,
7092 display_mode,
7093 snapshot,
7094 }
7095 };
7096
7097 let invalidation_range = multibuffer
7098 .anchor_before(Point::new(invalidation_row_range.start, 0))
7099 ..multibuffer.anchor_after(Point::new(
7100 invalidation_row_range.end,
7101 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7102 ));
7103
7104 self.stale_inline_completion_in_menu = None;
7105 self.active_inline_completion = Some(InlineCompletionState {
7106 inlay_ids,
7107 completion,
7108 completion_id: inline_completion.id,
7109 invalidation_range,
7110 });
7111
7112 cx.notify();
7113
7114 Some(())
7115 }
7116
7117 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7118 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7119 }
7120
7121 fn clear_tasks(&mut self) {
7122 self.tasks.clear()
7123 }
7124
7125 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7126 if self.tasks.insert(key, value).is_some() {
7127 // This case should hopefully be rare, but just in case...
7128 log::error!(
7129 "multiple different run targets found on a single line, only the last target will be rendered"
7130 )
7131 }
7132 }
7133
7134 /// Get all display points of breakpoints that will be rendered within editor
7135 ///
7136 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7137 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7138 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7139 fn active_breakpoints(
7140 &self,
7141 range: Range<DisplayRow>,
7142 window: &mut Window,
7143 cx: &mut Context<Self>,
7144 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7145 let mut breakpoint_display_points = HashMap::default();
7146
7147 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7148 return breakpoint_display_points;
7149 };
7150
7151 let snapshot = self.snapshot(window, cx);
7152
7153 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7154 let Some(project) = self.project.as_ref() else {
7155 return breakpoint_display_points;
7156 };
7157
7158 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7159 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7160
7161 for (buffer_snapshot, range, excerpt_id) in
7162 multi_buffer_snapshot.range_to_buffer_ranges(range)
7163 {
7164 let Some(buffer) = project
7165 .read(cx)
7166 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7167 else {
7168 continue;
7169 };
7170 let breakpoints = breakpoint_store.read(cx).breakpoints(
7171 &buffer,
7172 Some(
7173 buffer_snapshot.anchor_before(range.start)
7174 ..buffer_snapshot.anchor_after(range.end),
7175 ),
7176 buffer_snapshot,
7177 cx,
7178 );
7179 for (breakpoint, state) in breakpoints {
7180 let multi_buffer_anchor =
7181 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7182 let position = multi_buffer_anchor
7183 .to_point(&multi_buffer_snapshot)
7184 .to_display_point(&snapshot);
7185
7186 breakpoint_display_points.insert(
7187 position.row(),
7188 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7189 );
7190 }
7191 }
7192
7193 breakpoint_display_points
7194 }
7195
7196 fn breakpoint_context_menu(
7197 &self,
7198 anchor: Anchor,
7199 window: &mut Window,
7200 cx: &mut Context<Self>,
7201 ) -> Entity<ui::ContextMenu> {
7202 let weak_editor = cx.weak_entity();
7203 let focus_handle = self.focus_handle(cx);
7204
7205 let row = self
7206 .buffer
7207 .read(cx)
7208 .snapshot(cx)
7209 .summary_for_anchor::<Point>(&anchor)
7210 .row;
7211
7212 let breakpoint = self
7213 .breakpoint_at_row(row, window, cx)
7214 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7215
7216 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7217 "Edit Log Breakpoint"
7218 } else {
7219 "Set Log Breakpoint"
7220 };
7221
7222 let condition_breakpoint_msg = if breakpoint
7223 .as_ref()
7224 .is_some_and(|bp| bp.1.condition.is_some())
7225 {
7226 "Edit Condition Breakpoint"
7227 } else {
7228 "Set Condition Breakpoint"
7229 };
7230
7231 let hit_condition_breakpoint_msg = if breakpoint
7232 .as_ref()
7233 .is_some_and(|bp| bp.1.hit_condition.is_some())
7234 {
7235 "Edit Hit Condition Breakpoint"
7236 } else {
7237 "Set Hit Condition Breakpoint"
7238 };
7239
7240 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7241 "Unset Breakpoint"
7242 } else {
7243 "Set Breakpoint"
7244 };
7245
7246 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7247 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7248
7249 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7250 BreakpointState::Enabled => Some("Disable"),
7251 BreakpointState::Disabled => Some("Enable"),
7252 });
7253
7254 let (anchor, breakpoint) =
7255 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7256
7257 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7258 menu.on_blur_subscription(Subscription::new(|| {}))
7259 .context(focus_handle)
7260 .when(run_to_cursor, |this| {
7261 let weak_editor = weak_editor.clone();
7262 this.entry("Run to cursor", None, move |window, cx| {
7263 weak_editor
7264 .update(cx, |editor, cx| {
7265 editor.change_selections(None, window, cx, |s| {
7266 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7267 });
7268 })
7269 .ok();
7270
7271 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7272 })
7273 .separator()
7274 })
7275 .when_some(toggle_state_msg, |this, msg| {
7276 this.entry(msg, None, {
7277 let weak_editor = weak_editor.clone();
7278 let breakpoint = breakpoint.clone();
7279 move |_window, cx| {
7280 weak_editor
7281 .update(cx, |this, cx| {
7282 this.edit_breakpoint_at_anchor(
7283 anchor,
7284 breakpoint.as_ref().clone(),
7285 BreakpointEditAction::InvertState,
7286 cx,
7287 );
7288 })
7289 .log_err();
7290 }
7291 })
7292 })
7293 .entry(set_breakpoint_msg, None, {
7294 let weak_editor = weak_editor.clone();
7295 let breakpoint = breakpoint.clone();
7296 move |_window, cx| {
7297 weak_editor
7298 .update(cx, |this, cx| {
7299 this.edit_breakpoint_at_anchor(
7300 anchor,
7301 breakpoint.as_ref().clone(),
7302 BreakpointEditAction::Toggle,
7303 cx,
7304 );
7305 })
7306 .log_err();
7307 }
7308 })
7309 .entry(log_breakpoint_msg, None, {
7310 let breakpoint = breakpoint.clone();
7311 let weak_editor = weak_editor.clone();
7312 move |window, cx| {
7313 weak_editor
7314 .update(cx, |this, cx| {
7315 this.add_edit_breakpoint_block(
7316 anchor,
7317 breakpoint.as_ref(),
7318 BreakpointPromptEditAction::Log,
7319 window,
7320 cx,
7321 );
7322 })
7323 .log_err();
7324 }
7325 })
7326 .entry(condition_breakpoint_msg, None, {
7327 let breakpoint = breakpoint.clone();
7328 let weak_editor = weak_editor.clone();
7329 move |window, cx| {
7330 weak_editor
7331 .update(cx, |this, cx| {
7332 this.add_edit_breakpoint_block(
7333 anchor,
7334 breakpoint.as_ref(),
7335 BreakpointPromptEditAction::Condition,
7336 window,
7337 cx,
7338 );
7339 })
7340 .log_err();
7341 }
7342 })
7343 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7344 weak_editor
7345 .update(cx, |this, cx| {
7346 this.add_edit_breakpoint_block(
7347 anchor,
7348 breakpoint.as_ref(),
7349 BreakpointPromptEditAction::HitCondition,
7350 window,
7351 cx,
7352 );
7353 })
7354 .log_err();
7355 })
7356 })
7357 }
7358
7359 fn render_breakpoint(
7360 &self,
7361 position: Anchor,
7362 row: DisplayRow,
7363 breakpoint: &Breakpoint,
7364 state: Option<BreakpointSessionState>,
7365 cx: &mut Context<Self>,
7366 ) -> IconButton {
7367 let is_rejected = state.is_some_and(|s| !s.verified);
7368 // Is it a breakpoint that shows up when hovering over gutter?
7369 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7370 (false, false),
7371 |PhantomBreakpointIndicator {
7372 is_active,
7373 display_row,
7374 collides_with_existing_breakpoint,
7375 }| {
7376 (
7377 is_active && display_row == row,
7378 collides_with_existing_breakpoint,
7379 )
7380 },
7381 );
7382
7383 let (color, icon) = {
7384 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7385 (false, false) => ui::IconName::DebugBreakpoint,
7386 (true, false) => ui::IconName::DebugLogBreakpoint,
7387 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7388 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7389 };
7390
7391 let color = if is_phantom {
7392 Color::Hint
7393 } else if is_rejected {
7394 Color::Disabled
7395 } else {
7396 Color::Debugger
7397 };
7398
7399 (color, icon)
7400 };
7401
7402 let breakpoint = Arc::from(breakpoint.clone());
7403
7404 let alt_as_text = gpui::Keystroke {
7405 modifiers: Modifiers::secondary_key(),
7406 ..Default::default()
7407 };
7408 let primary_action_text = if breakpoint.is_disabled() {
7409 "Enable breakpoint"
7410 } else if is_phantom && !collides_with_existing {
7411 "Set breakpoint"
7412 } else {
7413 "Unset breakpoint"
7414 };
7415 let focus_handle = self.focus_handle.clone();
7416
7417 let meta = if is_rejected {
7418 SharedString::from("No executable code is associated with this line.")
7419 } else if collides_with_existing && !breakpoint.is_disabled() {
7420 SharedString::from(format!(
7421 "{alt_as_text}-click to disable,\nright-click for more options."
7422 ))
7423 } else {
7424 SharedString::from("Right-click for more options.")
7425 };
7426 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7427 .icon_size(IconSize::XSmall)
7428 .size(ui::ButtonSize::None)
7429 .when(is_rejected, |this| {
7430 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7431 })
7432 .icon_color(color)
7433 .style(ButtonStyle::Transparent)
7434 .on_click(cx.listener({
7435 let breakpoint = breakpoint.clone();
7436
7437 move |editor, event: &ClickEvent, window, cx| {
7438 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7439 BreakpointEditAction::InvertState
7440 } else {
7441 BreakpointEditAction::Toggle
7442 };
7443
7444 window.focus(&editor.focus_handle(cx));
7445 editor.edit_breakpoint_at_anchor(
7446 position,
7447 breakpoint.as_ref().clone(),
7448 edit_action,
7449 cx,
7450 );
7451 }
7452 }))
7453 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7454 editor.set_breakpoint_context_menu(
7455 row,
7456 Some(position),
7457 event.down.position,
7458 window,
7459 cx,
7460 );
7461 }))
7462 .tooltip(move |window, cx| {
7463 Tooltip::with_meta_in(
7464 primary_action_text,
7465 Some(&ToggleBreakpoint),
7466 meta.clone(),
7467 &focus_handle,
7468 window,
7469 cx,
7470 )
7471 })
7472 }
7473
7474 fn build_tasks_context(
7475 project: &Entity<Project>,
7476 buffer: &Entity<Buffer>,
7477 buffer_row: u32,
7478 tasks: &Arc<RunnableTasks>,
7479 cx: &mut Context<Self>,
7480 ) -> Task<Option<task::TaskContext>> {
7481 let position = Point::new(buffer_row, tasks.column);
7482 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7483 let location = Location {
7484 buffer: buffer.clone(),
7485 range: range_start..range_start,
7486 };
7487 // Fill in the environmental variables from the tree-sitter captures
7488 let mut captured_task_variables = TaskVariables::default();
7489 for (capture_name, value) in tasks.extra_variables.clone() {
7490 captured_task_variables.insert(
7491 task::VariableName::Custom(capture_name.into()),
7492 value.clone(),
7493 );
7494 }
7495 project.update(cx, |project, cx| {
7496 project.task_store().update(cx, |task_store, cx| {
7497 task_store.task_context_for_location(captured_task_variables, location, cx)
7498 })
7499 })
7500 }
7501
7502 pub fn spawn_nearest_task(
7503 &mut self,
7504 action: &SpawnNearestTask,
7505 window: &mut Window,
7506 cx: &mut Context<Self>,
7507 ) {
7508 let Some((workspace, _)) = self.workspace.clone() else {
7509 return;
7510 };
7511 let Some(project) = self.project.clone() else {
7512 return;
7513 };
7514
7515 // Try to find a closest, enclosing node using tree-sitter that has a
7516 // task
7517 let Some((buffer, buffer_row, tasks)) = self
7518 .find_enclosing_node_task(cx)
7519 // Or find the task that's closest in row-distance.
7520 .or_else(|| self.find_closest_task(cx))
7521 else {
7522 return;
7523 };
7524
7525 let reveal_strategy = action.reveal;
7526 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7527 cx.spawn_in(window, async move |_, cx| {
7528 let context = task_context.await?;
7529 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7530
7531 let resolved = &mut resolved_task.resolved;
7532 resolved.reveal = reveal_strategy;
7533
7534 workspace
7535 .update_in(cx, |workspace, window, cx| {
7536 workspace.schedule_resolved_task(
7537 task_source_kind,
7538 resolved_task,
7539 false,
7540 window,
7541 cx,
7542 );
7543 })
7544 .ok()
7545 })
7546 .detach();
7547 }
7548
7549 fn find_closest_task(
7550 &mut self,
7551 cx: &mut Context<Self>,
7552 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7553 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7554
7555 let ((buffer_id, row), tasks) = self
7556 .tasks
7557 .iter()
7558 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7559
7560 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7561 let tasks = Arc::new(tasks.to_owned());
7562 Some((buffer, *row, tasks))
7563 }
7564
7565 fn find_enclosing_node_task(
7566 &mut self,
7567 cx: &mut Context<Self>,
7568 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7569 let snapshot = self.buffer.read(cx).snapshot(cx);
7570 let offset = self.selections.newest::<usize>(cx).head();
7571 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7572 let buffer_id = excerpt.buffer().remote_id();
7573
7574 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7575 let mut cursor = layer.node().walk();
7576
7577 while cursor.goto_first_child_for_byte(offset).is_some() {
7578 if cursor.node().end_byte() == offset {
7579 cursor.goto_next_sibling();
7580 }
7581 }
7582
7583 // Ascend to the smallest ancestor that contains the range and has a task.
7584 loop {
7585 let node = cursor.node();
7586 let node_range = node.byte_range();
7587 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7588
7589 // Check if this node contains our offset
7590 if node_range.start <= offset && node_range.end >= offset {
7591 // If it contains offset, check for task
7592 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7593 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7594 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7595 }
7596 }
7597
7598 if !cursor.goto_parent() {
7599 break;
7600 }
7601 }
7602 None
7603 }
7604
7605 fn render_run_indicator(
7606 &self,
7607 _style: &EditorStyle,
7608 is_active: bool,
7609 row: DisplayRow,
7610 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7611 cx: &mut Context<Self>,
7612 ) -> IconButton {
7613 let color = Color::Muted;
7614 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7615
7616 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7617 .shape(ui::IconButtonShape::Square)
7618 .icon_size(IconSize::XSmall)
7619 .icon_color(color)
7620 .toggle_state(is_active)
7621 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7622 let quick_launch = e.down.button == MouseButton::Left;
7623 window.focus(&editor.focus_handle(cx));
7624 editor.toggle_code_actions(
7625 &ToggleCodeActions {
7626 deployed_from: Some(CodeActionSource::Indicator(row)),
7627 quick_launch,
7628 },
7629 window,
7630 cx,
7631 );
7632 }))
7633 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7634 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7635 }))
7636 }
7637
7638 pub fn context_menu_visible(&self) -> bool {
7639 !self.edit_prediction_preview_is_active()
7640 && self
7641 .context_menu
7642 .borrow()
7643 .as_ref()
7644 .map_or(false, |menu| menu.visible())
7645 }
7646
7647 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7648 self.context_menu
7649 .borrow()
7650 .as_ref()
7651 .map(|menu| menu.origin())
7652 }
7653
7654 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7655 self.context_menu_options = Some(options);
7656 }
7657
7658 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7659 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7660
7661 fn render_edit_prediction_popover(
7662 &mut self,
7663 text_bounds: &Bounds<Pixels>,
7664 content_origin: gpui::Point<Pixels>,
7665 right_margin: Pixels,
7666 editor_snapshot: &EditorSnapshot,
7667 visible_row_range: Range<DisplayRow>,
7668 scroll_top: f32,
7669 scroll_bottom: f32,
7670 line_layouts: &[LineWithInvisibles],
7671 line_height: Pixels,
7672 scroll_pixel_position: gpui::Point<Pixels>,
7673 newest_selection_head: Option<DisplayPoint>,
7674 editor_width: Pixels,
7675 style: &EditorStyle,
7676 window: &mut Window,
7677 cx: &mut App,
7678 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7679 if self.mode().is_minimap() {
7680 return None;
7681 }
7682 let active_inline_completion = self.active_inline_completion.as_ref()?;
7683
7684 if self.edit_prediction_visible_in_cursor_popover(true) {
7685 return None;
7686 }
7687
7688 match &active_inline_completion.completion {
7689 InlineCompletion::Move { target, .. } => {
7690 let target_display_point = target.to_display_point(editor_snapshot);
7691
7692 if self.edit_prediction_requires_modifier() {
7693 if !self.edit_prediction_preview_is_active() {
7694 return None;
7695 }
7696
7697 self.render_edit_prediction_modifier_jump_popover(
7698 text_bounds,
7699 content_origin,
7700 visible_row_range,
7701 line_layouts,
7702 line_height,
7703 scroll_pixel_position,
7704 newest_selection_head,
7705 target_display_point,
7706 window,
7707 cx,
7708 )
7709 } else {
7710 self.render_edit_prediction_eager_jump_popover(
7711 text_bounds,
7712 content_origin,
7713 editor_snapshot,
7714 visible_row_range,
7715 scroll_top,
7716 scroll_bottom,
7717 line_height,
7718 scroll_pixel_position,
7719 target_display_point,
7720 editor_width,
7721 window,
7722 cx,
7723 )
7724 }
7725 }
7726 InlineCompletion::Edit {
7727 display_mode: EditDisplayMode::Inline,
7728 ..
7729 } => None,
7730 InlineCompletion::Edit {
7731 display_mode: EditDisplayMode::TabAccept,
7732 edits,
7733 ..
7734 } => {
7735 let range = &edits.first()?.0;
7736 let target_display_point = range.end.to_display_point(editor_snapshot);
7737
7738 self.render_edit_prediction_end_of_line_popover(
7739 "Accept",
7740 editor_snapshot,
7741 visible_row_range,
7742 target_display_point,
7743 line_height,
7744 scroll_pixel_position,
7745 content_origin,
7746 editor_width,
7747 window,
7748 cx,
7749 )
7750 }
7751 InlineCompletion::Edit {
7752 edits,
7753 edit_preview,
7754 display_mode: EditDisplayMode::DiffPopover,
7755 snapshot,
7756 } => self.render_edit_prediction_diff_popover(
7757 text_bounds,
7758 content_origin,
7759 right_margin,
7760 editor_snapshot,
7761 visible_row_range,
7762 line_layouts,
7763 line_height,
7764 scroll_pixel_position,
7765 newest_selection_head,
7766 editor_width,
7767 style,
7768 edits,
7769 edit_preview,
7770 snapshot,
7771 window,
7772 cx,
7773 ),
7774 }
7775 }
7776
7777 fn render_edit_prediction_modifier_jump_popover(
7778 &mut self,
7779 text_bounds: &Bounds<Pixels>,
7780 content_origin: gpui::Point<Pixels>,
7781 visible_row_range: Range<DisplayRow>,
7782 line_layouts: &[LineWithInvisibles],
7783 line_height: Pixels,
7784 scroll_pixel_position: gpui::Point<Pixels>,
7785 newest_selection_head: Option<DisplayPoint>,
7786 target_display_point: DisplayPoint,
7787 window: &mut Window,
7788 cx: &mut App,
7789 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7790 let scrolled_content_origin =
7791 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7792
7793 const SCROLL_PADDING_Y: Pixels = px(12.);
7794
7795 if target_display_point.row() < visible_row_range.start {
7796 return self.render_edit_prediction_scroll_popover(
7797 |_| SCROLL_PADDING_Y,
7798 IconName::ArrowUp,
7799 visible_row_range,
7800 line_layouts,
7801 newest_selection_head,
7802 scrolled_content_origin,
7803 window,
7804 cx,
7805 );
7806 } else if target_display_point.row() >= visible_row_range.end {
7807 return self.render_edit_prediction_scroll_popover(
7808 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7809 IconName::ArrowDown,
7810 visible_row_range,
7811 line_layouts,
7812 newest_selection_head,
7813 scrolled_content_origin,
7814 window,
7815 cx,
7816 );
7817 }
7818
7819 const POLE_WIDTH: Pixels = px(2.);
7820
7821 let line_layout =
7822 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7823 let target_column = target_display_point.column() as usize;
7824
7825 let target_x = line_layout.x_for_index(target_column);
7826 let target_y =
7827 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7828
7829 let flag_on_right = target_x < text_bounds.size.width / 2.;
7830
7831 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7832 border_color.l += 0.001;
7833
7834 let mut element = v_flex()
7835 .items_end()
7836 .when(flag_on_right, |el| el.items_start())
7837 .child(if flag_on_right {
7838 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7839 .rounded_bl(px(0.))
7840 .rounded_tl(px(0.))
7841 .border_l_2()
7842 .border_color(border_color)
7843 } else {
7844 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7845 .rounded_br(px(0.))
7846 .rounded_tr(px(0.))
7847 .border_r_2()
7848 .border_color(border_color)
7849 })
7850 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7851 .into_any();
7852
7853 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7854
7855 let mut origin = scrolled_content_origin + point(target_x, target_y)
7856 - point(
7857 if flag_on_right {
7858 POLE_WIDTH
7859 } else {
7860 size.width - POLE_WIDTH
7861 },
7862 size.height - line_height,
7863 );
7864
7865 origin.x = origin.x.max(content_origin.x);
7866
7867 element.prepaint_at(origin, window, cx);
7868
7869 Some((element, origin))
7870 }
7871
7872 fn render_edit_prediction_scroll_popover(
7873 &mut self,
7874 to_y: impl Fn(Size<Pixels>) -> Pixels,
7875 scroll_icon: IconName,
7876 visible_row_range: Range<DisplayRow>,
7877 line_layouts: &[LineWithInvisibles],
7878 newest_selection_head: Option<DisplayPoint>,
7879 scrolled_content_origin: gpui::Point<Pixels>,
7880 window: &mut Window,
7881 cx: &mut App,
7882 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7883 let mut element = self
7884 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7885 .into_any();
7886
7887 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7888
7889 let cursor = newest_selection_head?;
7890 let cursor_row_layout =
7891 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7892 let cursor_column = cursor.column() as usize;
7893
7894 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7895
7896 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7897
7898 element.prepaint_at(origin, window, cx);
7899 Some((element, origin))
7900 }
7901
7902 fn render_edit_prediction_eager_jump_popover(
7903 &mut self,
7904 text_bounds: &Bounds<Pixels>,
7905 content_origin: gpui::Point<Pixels>,
7906 editor_snapshot: &EditorSnapshot,
7907 visible_row_range: Range<DisplayRow>,
7908 scroll_top: f32,
7909 scroll_bottom: f32,
7910 line_height: Pixels,
7911 scroll_pixel_position: gpui::Point<Pixels>,
7912 target_display_point: DisplayPoint,
7913 editor_width: Pixels,
7914 window: &mut Window,
7915 cx: &mut App,
7916 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7917 if target_display_point.row().as_f32() < scroll_top {
7918 let mut element = self
7919 .render_edit_prediction_line_popover(
7920 "Jump to Edit",
7921 Some(IconName::ArrowUp),
7922 window,
7923 cx,
7924 )?
7925 .into_any();
7926
7927 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7928 let offset = point(
7929 (text_bounds.size.width - size.width) / 2.,
7930 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7931 );
7932
7933 let origin = text_bounds.origin + offset;
7934 element.prepaint_at(origin, window, cx);
7935 Some((element, origin))
7936 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7937 let mut element = self
7938 .render_edit_prediction_line_popover(
7939 "Jump to Edit",
7940 Some(IconName::ArrowDown),
7941 window,
7942 cx,
7943 )?
7944 .into_any();
7945
7946 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7947 let offset = point(
7948 (text_bounds.size.width - size.width) / 2.,
7949 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7950 );
7951
7952 let origin = text_bounds.origin + offset;
7953 element.prepaint_at(origin, window, cx);
7954 Some((element, origin))
7955 } else {
7956 self.render_edit_prediction_end_of_line_popover(
7957 "Jump to Edit",
7958 editor_snapshot,
7959 visible_row_range,
7960 target_display_point,
7961 line_height,
7962 scroll_pixel_position,
7963 content_origin,
7964 editor_width,
7965 window,
7966 cx,
7967 )
7968 }
7969 }
7970
7971 fn render_edit_prediction_end_of_line_popover(
7972 self: &mut Editor,
7973 label: &'static str,
7974 editor_snapshot: &EditorSnapshot,
7975 visible_row_range: Range<DisplayRow>,
7976 target_display_point: DisplayPoint,
7977 line_height: Pixels,
7978 scroll_pixel_position: gpui::Point<Pixels>,
7979 content_origin: gpui::Point<Pixels>,
7980 editor_width: Pixels,
7981 window: &mut Window,
7982 cx: &mut App,
7983 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7984 let target_line_end = DisplayPoint::new(
7985 target_display_point.row(),
7986 editor_snapshot.line_len(target_display_point.row()),
7987 );
7988
7989 let mut element = self
7990 .render_edit_prediction_line_popover(label, None, window, cx)?
7991 .into_any();
7992
7993 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7994
7995 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7996
7997 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7998 let mut origin = start_point
7999 + line_origin
8000 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8001 origin.x = origin.x.max(content_origin.x);
8002
8003 let max_x = content_origin.x + editor_width - size.width;
8004
8005 if origin.x > max_x {
8006 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8007
8008 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8009 origin.y += offset;
8010 IconName::ArrowUp
8011 } else {
8012 origin.y -= offset;
8013 IconName::ArrowDown
8014 };
8015
8016 element = self
8017 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8018 .into_any();
8019
8020 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8021
8022 origin.x = content_origin.x + editor_width - size.width - px(2.);
8023 }
8024
8025 element.prepaint_at(origin, window, cx);
8026 Some((element, origin))
8027 }
8028
8029 fn render_edit_prediction_diff_popover(
8030 self: &Editor,
8031 text_bounds: &Bounds<Pixels>,
8032 content_origin: gpui::Point<Pixels>,
8033 right_margin: Pixels,
8034 editor_snapshot: &EditorSnapshot,
8035 visible_row_range: Range<DisplayRow>,
8036 line_layouts: &[LineWithInvisibles],
8037 line_height: Pixels,
8038 scroll_pixel_position: gpui::Point<Pixels>,
8039 newest_selection_head: Option<DisplayPoint>,
8040 editor_width: Pixels,
8041 style: &EditorStyle,
8042 edits: &Vec<(Range<Anchor>, String)>,
8043 edit_preview: &Option<language::EditPreview>,
8044 snapshot: &language::BufferSnapshot,
8045 window: &mut Window,
8046 cx: &mut App,
8047 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8048 let edit_start = edits
8049 .first()
8050 .unwrap()
8051 .0
8052 .start
8053 .to_display_point(editor_snapshot);
8054 let edit_end = edits
8055 .last()
8056 .unwrap()
8057 .0
8058 .end
8059 .to_display_point(editor_snapshot);
8060
8061 let is_visible = visible_row_range.contains(&edit_start.row())
8062 || visible_row_range.contains(&edit_end.row());
8063 if !is_visible {
8064 return None;
8065 }
8066
8067 let highlighted_edits =
8068 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8069
8070 let styled_text = highlighted_edits.to_styled_text(&style.text);
8071 let line_count = highlighted_edits.text.lines().count();
8072
8073 const BORDER_WIDTH: Pixels = px(1.);
8074
8075 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8076 let has_keybind = keybind.is_some();
8077
8078 let mut element = h_flex()
8079 .items_start()
8080 .child(
8081 h_flex()
8082 .bg(cx.theme().colors().editor_background)
8083 .border(BORDER_WIDTH)
8084 .shadow_sm()
8085 .border_color(cx.theme().colors().border)
8086 .rounded_l_lg()
8087 .when(line_count > 1, |el| el.rounded_br_lg())
8088 .pr_1()
8089 .child(styled_text),
8090 )
8091 .child(
8092 h_flex()
8093 .h(line_height + BORDER_WIDTH * 2.)
8094 .px_1p5()
8095 .gap_1()
8096 // Workaround: For some reason, there's a gap if we don't do this
8097 .ml(-BORDER_WIDTH)
8098 .shadow(vec![gpui::BoxShadow {
8099 color: gpui::black().opacity(0.05),
8100 offset: point(px(1.), px(1.)),
8101 blur_radius: px(2.),
8102 spread_radius: px(0.),
8103 }])
8104 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8105 .border(BORDER_WIDTH)
8106 .border_color(cx.theme().colors().border)
8107 .rounded_r_lg()
8108 .id("edit_prediction_diff_popover_keybind")
8109 .when(!has_keybind, |el| {
8110 let status_colors = cx.theme().status();
8111
8112 el.bg(status_colors.error_background)
8113 .border_color(status_colors.error.opacity(0.6))
8114 .child(Icon::new(IconName::Info).color(Color::Error))
8115 .cursor_default()
8116 .hoverable_tooltip(move |_window, cx| {
8117 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8118 })
8119 })
8120 .children(keybind),
8121 )
8122 .into_any();
8123
8124 let longest_row =
8125 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8126 let longest_line_width = if visible_row_range.contains(&longest_row) {
8127 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8128 } else {
8129 layout_line(
8130 longest_row,
8131 editor_snapshot,
8132 style,
8133 editor_width,
8134 |_| false,
8135 window,
8136 cx,
8137 )
8138 .width
8139 };
8140
8141 let viewport_bounds =
8142 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8143 right: -right_margin,
8144 ..Default::default()
8145 });
8146
8147 let x_after_longest =
8148 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8149 - scroll_pixel_position.x;
8150
8151 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8152
8153 // Fully visible if it can be displayed within the window (allow overlapping other
8154 // panes). However, this is only allowed if the popover starts within text_bounds.
8155 let can_position_to_the_right = x_after_longest < text_bounds.right()
8156 && x_after_longest + element_bounds.width < viewport_bounds.right();
8157
8158 let mut origin = if can_position_to_the_right {
8159 point(
8160 x_after_longest,
8161 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8162 - scroll_pixel_position.y,
8163 )
8164 } else {
8165 let cursor_row = newest_selection_head.map(|head| head.row());
8166 let above_edit = edit_start
8167 .row()
8168 .0
8169 .checked_sub(line_count as u32)
8170 .map(DisplayRow);
8171 let below_edit = Some(edit_end.row() + 1);
8172 let above_cursor =
8173 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8174 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8175
8176 // Place the edit popover adjacent to the edit if there is a location
8177 // available that is onscreen and does not obscure the cursor. Otherwise,
8178 // place it adjacent to the cursor.
8179 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8180 .into_iter()
8181 .flatten()
8182 .find(|&start_row| {
8183 let end_row = start_row + line_count as u32;
8184 visible_row_range.contains(&start_row)
8185 && visible_row_range.contains(&end_row)
8186 && cursor_row.map_or(true, |cursor_row| {
8187 !((start_row..end_row).contains(&cursor_row))
8188 })
8189 })?;
8190
8191 content_origin
8192 + point(
8193 -scroll_pixel_position.x,
8194 row_target.as_f32() * line_height - scroll_pixel_position.y,
8195 )
8196 };
8197
8198 origin.x -= BORDER_WIDTH;
8199
8200 window.defer_draw(element, origin, 1);
8201
8202 // Do not return an element, since it will already be drawn due to defer_draw.
8203 None
8204 }
8205
8206 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8207 px(30.)
8208 }
8209
8210 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8211 if self.read_only(cx) {
8212 cx.theme().players().read_only()
8213 } else {
8214 self.style.as_ref().unwrap().local_player
8215 }
8216 }
8217
8218 fn render_edit_prediction_accept_keybind(
8219 &self,
8220 window: &mut Window,
8221 cx: &App,
8222 ) -> Option<AnyElement> {
8223 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8224 let accept_keystroke = accept_binding.keystroke()?;
8225
8226 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8227
8228 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8229 Color::Accent
8230 } else {
8231 Color::Muted
8232 };
8233
8234 h_flex()
8235 .px_0p5()
8236 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8237 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8238 .text_size(TextSize::XSmall.rems(cx))
8239 .child(h_flex().children(ui::render_modifiers(
8240 &accept_keystroke.modifiers,
8241 PlatformStyle::platform(),
8242 Some(modifiers_color),
8243 Some(IconSize::XSmall.rems().into()),
8244 true,
8245 )))
8246 .when(is_platform_style_mac, |parent| {
8247 parent.child(accept_keystroke.key.clone())
8248 })
8249 .when(!is_platform_style_mac, |parent| {
8250 parent.child(
8251 Key::new(
8252 util::capitalize(&accept_keystroke.key),
8253 Some(Color::Default),
8254 )
8255 .size(Some(IconSize::XSmall.rems().into())),
8256 )
8257 })
8258 .into_any()
8259 .into()
8260 }
8261
8262 fn render_edit_prediction_line_popover(
8263 &self,
8264 label: impl Into<SharedString>,
8265 icon: Option<IconName>,
8266 window: &mut Window,
8267 cx: &App,
8268 ) -> Option<Stateful<Div>> {
8269 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8270
8271 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8272 let has_keybind = keybind.is_some();
8273
8274 let result = h_flex()
8275 .id("ep-line-popover")
8276 .py_0p5()
8277 .pl_1()
8278 .pr(padding_right)
8279 .gap_1()
8280 .rounded_md()
8281 .border_1()
8282 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8283 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8284 .shadow_sm()
8285 .when(!has_keybind, |el| {
8286 let status_colors = cx.theme().status();
8287
8288 el.bg(status_colors.error_background)
8289 .border_color(status_colors.error.opacity(0.6))
8290 .pl_2()
8291 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8292 .cursor_default()
8293 .hoverable_tooltip(move |_window, cx| {
8294 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8295 })
8296 })
8297 .children(keybind)
8298 .child(
8299 Label::new(label)
8300 .size(LabelSize::Small)
8301 .when(!has_keybind, |el| {
8302 el.color(cx.theme().status().error.into()).strikethrough()
8303 }),
8304 )
8305 .when(!has_keybind, |el| {
8306 el.child(
8307 h_flex().ml_1().child(
8308 Icon::new(IconName::Info)
8309 .size(IconSize::Small)
8310 .color(cx.theme().status().error.into()),
8311 ),
8312 )
8313 })
8314 .when_some(icon, |element, icon| {
8315 element.child(
8316 div()
8317 .mt(px(1.5))
8318 .child(Icon::new(icon).size(IconSize::Small)),
8319 )
8320 });
8321
8322 Some(result)
8323 }
8324
8325 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8326 let accent_color = cx.theme().colors().text_accent;
8327 let editor_bg_color = cx.theme().colors().editor_background;
8328 editor_bg_color.blend(accent_color.opacity(0.1))
8329 }
8330
8331 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8332 let accent_color = cx.theme().colors().text_accent;
8333 let editor_bg_color = cx.theme().colors().editor_background;
8334 editor_bg_color.blend(accent_color.opacity(0.6))
8335 }
8336
8337 fn render_edit_prediction_cursor_popover(
8338 &self,
8339 min_width: Pixels,
8340 max_width: Pixels,
8341 cursor_point: Point,
8342 style: &EditorStyle,
8343 accept_keystroke: Option<&gpui::Keystroke>,
8344 _window: &Window,
8345 cx: &mut Context<Editor>,
8346 ) -> Option<AnyElement> {
8347 let provider = self.edit_prediction_provider.as_ref()?;
8348
8349 if provider.provider.needs_terms_acceptance(cx) {
8350 return Some(
8351 h_flex()
8352 .min_w(min_width)
8353 .flex_1()
8354 .px_2()
8355 .py_1()
8356 .gap_3()
8357 .elevation_2(cx)
8358 .hover(|style| style.bg(cx.theme().colors().element_hover))
8359 .id("accept-terms")
8360 .cursor_pointer()
8361 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8362 .on_click(cx.listener(|this, _event, window, cx| {
8363 cx.stop_propagation();
8364 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8365 window.dispatch_action(
8366 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8367 cx,
8368 );
8369 }))
8370 .child(
8371 h_flex()
8372 .flex_1()
8373 .gap_2()
8374 .child(Icon::new(IconName::ZedPredict))
8375 .child(Label::new("Accept Terms of Service"))
8376 .child(div().w_full())
8377 .child(
8378 Icon::new(IconName::ArrowUpRight)
8379 .color(Color::Muted)
8380 .size(IconSize::Small),
8381 )
8382 .into_any_element(),
8383 )
8384 .into_any(),
8385 );
8386 }
8387
8388 let is_refreshing = provider.provider.is_refreshing(cx);
8389
8390 fn pending_completion_container() -> Div {
8391 h_flex()
8392 .h_full()
8393 .flex_1()
8394 .gap_2()
8395 .child(Icon::new(IconName::ZedPredict))
8396 }
8397
8398 let completion = match &self.active_inline_completion {
8399 Some(prediction) => {
8400 if !self.has_visible_completions_menu() {
8401 const RADIUS: Pixels = px(6.);
8402 const BORDER_WIDTH: Pixels = px(1.);
8403
8404 return Some(
8405 h_flex()
8406 .elevation_2(cx)
8407 .border(BORDER_WIDTH)
8408 .border_color(cx.theme().colors().border)
8409 .when(accept_keystroke.is_none(), |el| {
8410 el.border_color(cx.theme().status().error)
8411 })
8412 .rounded(RADIUS)
8413 .rounded_tl(px(0.))
8414 .overflow_hidden()
8415 .child(div().px_1p5().child(match &prediction.completion {
8416 InlineCompletion::Move { target, snapshot } => {
8417 use text::ToPoint as _;
8418 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8419 {
8420 Icon::new(IconName::ZedPredictDown)
8421 } else {
8422 Icon::new(IconName::ZedPredictUp)
8423 }
8424 }
8425 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8426 }))
8427 .child(
8428 h_flex()
8429 .gap_1()
8430 .py_1()
8431 .px_2()
8432 .rounded_r(RADIUS - BORDER_WIDTH)
8433 .border_l_1()
8434 .border_color(cx.theme().colors().border)
8435 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8436 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8437 el.child(
8438 Label::new("Hold")
8439 .size(LabelSize::Small)
8440 .when(accept_keystroke.is_none(), |el| {
8441 el.strikethrough()
8442 })
8443 .line_height_style(LineHeightStyle::UiLabel),
8444 )
8445 })
8446 .id("edit_prediction_cursor_popover_keybind")
8447 .when(accept_keystroke.is_none(), |el| {
8448 let status_colors = cx.theme().status();
8449
8450 el.bg(status_colors.error_background)
8451 .border_color(status_colors.error.opacity(0.6))
8452 .child(Icon::new(IconName::Info).color(Color::Error))
8453 .cursor_default()
8454 .hoverable_tooltip(move |_window, cx| {
8455 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8456 .into()
8457 })
8458 })
8459 .when_some(
8460 accept_keystroke.as_ref(),
8461 |el, accept_keystroke| {
8462 el.child(h_flex().children(ui::render_modifiers(
8463 &accept_keystroke.modifiers,
8464 PlatformStyle::platform(),
8465 Some(Color::Default),
8466 Some(IconSize::XSmall.rems().into()),
8467 false,
8468 )))
8469 },
8470 ),
8471 )
8472 .into_any(),
8473 );
8474 }
8475
8476 self.render_edit_prediction_cursor_popover_preview(
8477 prediction,
8478 cursor_point,
8479 style,
8480 cx,
8481 )?
8482 }
8483
8484 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8485 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8486 stale_completion,
8487 cursor_point,
8488 style,
8489 cx,
8490 )?,
8491
8492 None => {
8493 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8494 }
8495 },
8496
8497 None => pending_completion_container().child(Label::new("No Prediction")),
8498 };
8499
8500 let completion = if is_refreshing {
8501 completion
8502 .with_animation(
8503 "loading-completion",
8504 Animation::new(Duration::from_secs(2))
8505 .repeat()
8506 .with_easing(pulsating_between(0.4, 0.8)),
8507 |label, delta| label.opacity(delta),
8508 )
8509 .into_any_element()
8510 } else {
8511 completion.into_any_element()
8512 };
8513
8514 let has_completion = self.active_inline_completion.is_some();
8515
8516 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8517 Some(
8518 h_flex()
8519 .min_w(min_width)
8520 .max_w(max_width)
8521 .flex_1()
8522 .elevation_2(cx)
8523 .border_color(cx.theme().colors().border)
8524 .child(
8525 div()
8526 .flex_1()
8527 .py_1()
8528 .px_2()
8529 .overflow_hidden()
8530 .child(completion),
8531 )
8532 .when_some(accept_keystroke, |el, accept_keystroke| {
8533 if !accept_keystroke.modifiers.modified() {
8534 return el;
8535 }
8536
8537 el.child(
8538 h_flex()
8539 .h_full()
8540 .border_l_1()
8541 .rounded_r_lg()
8542 .border_color(cx.theme().colors().border)
8543 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8544 .gap_1()
8545 .py_1()
8546 .px_2()
8547 .child(
8548 h_flex()
8549 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8550 .when(is_platform_style_mac, |parent| parent.gap_1())
8551 .child(h_flex().children(ui::render_modifiers(
8552 &accept_keystroke.modifiers,
8553 PlatformStyle::platform(),
8554 Some(if !has_completion {
8555 Color::Muted
8556 } else {
8557 Color::Default
8558 }),
8559 None,
8560 false,
8561 ))),
8562 )
8563 .child(Label::new("Preview").into_any_element())
8564 .opacity(if has_completion { 1.0 } else { 0.4 }),
8565 )
8566 })
8567 .into_any(),
8568 )
8569 }
8570
8571 fn render_edit_prediction_cursor_popover_preview(
8572 &self,
8573 completion: &InlineCompletionState,
8574 cursor_point: Point,
8575 style: &EditorStyle,
8576 cx: &mut Context<Editor>,
8577 ) -> Option<Div> {
8578 use text::ToPoint as _;
8579
8580 fn render_relative_row_jump(
8581 prefix: impl Into<String>,
8582 current_row: u32,
8583 target_row: u32,
8584 ) -> Div {
8585 let (row_diff, arrow) = if target_row < current_row {
8586 (current_row - target_row, IconName::ArrowUp)
8587 } else {
8588 (target_row - current_row, IconName::ArrowDown)
8589 };
8590
8591 h_flex()
8592 .child(
8593 Label::new(format!("{}{}", prefix.into(), row_diff))
8594 .color(Color::Muted)
8595 .size(LabelSize::Small),
8596 )
8597 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8598 }
8599
8600 match &completion.completion {
8601 InlineCompletion::Move {
8602 target, snapshot, ..
8603 } => Some(
8604 h_flex()
8605 .px_2()
8606 .gap_2()
8607 .flex_1()
8608 .child(
8609 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8610 Icon::new(IconName::ZedPredictDown)
8611 } else {
8612 Icon::new(IconName::ZedPredictUp)
8613 },
8614 )
8615 .child(Label::new("Jump to Edit")),
8616 ),
8617
8618 InlineCompletion::Edit {
8619 edits,
8620 edit_preview,
8621 snapshot,
8622 display_mode: _,
8623 } => {
8624 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8625
8626 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8627 &snapshot,
8628 &edits,
8629 edit_preview.as_ref()?,
8630 true,
8631 cx,
8632 )
8633 .first_line_preview();
8634
8635 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8636 .with_default_highlights(&style.text, highlighted_edits.highlights);
8637
8638 let preview = h_flex()
8639 .gap_1()
8640 .min_w_16()
8641 .child(styled_text)
8642 .when(has_more_lines, |parent| parent.child("…"));
8643
8644 let left = if first_edit_row != cursor_point.row {
8645 render_relative_row_jump("", cursor_point.row, first_edit_row)
8646 .into_any_element()
8647 } else {
8648 Icon::new(IconName::ZedPredict).into_any_element()
8649 };
8650
8651 Some(
8652 h_flex()
8653 .h_full()
8654 .flex_1()
8655 .gap_2()
8656 .pr_1()
8657 .overflow_x_hidden()
8658 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8659 .child(left)
8660 .child(preview),
8661 )
8662 }
8663 }
8664 }
8665
8666 pub fn render_context_menu(
8667 &self,
8668 style: &EditorStyle,
8669 max_height_in_lines: u32,
8670 window: &mut Window,
8671 cx: &mut Context<Editor>,
8672 ) -> Option<AnyElement> {
8673 let menu = self.context_menu.borrow();
8674 let menu = menu.as_ref()?;
8675 if !menu.visible() {
8676 return None;
8677 };
8678 Some(menu.render(style, max_height_in_lines, window, cx))
8679 }
8680
8681 fn render_context_menu_aside(
8682 &mut self,
8683 max_size: Size<Pixels>,
8684 window: &mut Window,
8685 cx: &mut Context<Editor>,
8686 ) -> Option<AnyElement> {
8687 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8688 if menu.visible() {
8689 menu.render_aside(self, max_size, window, cx)
8690 } else {
8691 None
8692 }
8693 })
8694 }
8695
8696 fn hide_context_menu(
8697 &mut self,
8698 window: &mut Window,
8699 cx: &mut Context<Self>,
8700 ) -> Option<CodeContextMenu> {
8701 cx.notify();
8702 self.completion_tasks.clear();
8703 let context_menu = self.context_menu.borrow_mut().take();
8704 self.stale_inline_completion_in_menu.take();
8705 self.update_visible_inline_completion(window, cx);
8706 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8707 if let Some(completion_provider) = &self.completion_provider {
8708 completion_provider.selection_changed(None, window, cx);
8709 }
8710 }
8711 context_menu
8712 }
8713
8714 fn show_snippet_choices(
8715 &mut self,
8716 choices: &Vec<String>,
8717 selection: Range<Anchor>,
8718 cx: &mut Context<Self>,
8719 ) {
8720 if selection.start.buffer_id.is_none() {
8721 return;
8722 }
8723 let buffer_id = selection.start.buffer_id.unwrap();
8724 let buffer = self.buffer().read(cx).buffer(buffer_id);
8725 let id = post_inc(&mut self.next_completion_id);
8726 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8727
8728 if let Some(buffer) = buffer {
8729 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8730 CompletionsMenu::new_snippet_choices(
8731 id,
8732 true,
8733 choices,
8734 selection,
8735 buffer,
8736 snippet_sort_order,
8737 ),
8738 ));
8739 }
8740 }
8741
8742 pub fn insert_snippet(
8743 &mut self,
8744 insertion_ranges: &[Range<usize>],
8745 snippet: Snippet,
8746 window: &mut Window,
8747 cx: &mut Context<Self>,
8748 ) -> Result<()> {
8749 struct Tabstop<T> {
8750 is_end_tabstop: bool,
8751 ranges: Vec<Range<T>>,
8752 choices: Option<Vec<String>>,
8753 }
8754
8755 let tabstops = self.buffer.update(cx, |buffer, cx| {
8756 let snippet_text: Arc<str> = snippet.text.clone().into();
8757 let edits = insertion_ranges
8758 .iter()
8759 .cloned()
8760 .map(|range| (range, snippet_text.clone()));
8761 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8762
8763 let snapshot = &*buffer.read(cx);
8764 let snippet = &snippet;
8765 snippet
8766 .tabstops
8767 .iter()
8768 .map(|tabstop| {
8769 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8770 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8771 });
8772 let mut tabstop_ranges = tabstop
8773 .ranges
8774 .iter()
8775 .flat_map(|tabstop_range| {
8776 let mut delta = 0_isize;
8777 insertion_ranges.iter().map(move |insertion_range| {
8778 let insertion_start = insertion_range.start as isize + delta;
8779 delta +=
8780 snippet.text.len() as isize - insertion_range.len() as isize;
8781
8782 let start = ((insertion_start + tabstop_range.start) as usize)
8783 .min(snapshot.len());
8784 let end = ((insertion_start + tabstop_range.end) as usize)
8785 .min(snapshot.len());
8786 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8787 })
8788 })
8789 .collect::<Vec<_>>();
8790 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8791
8792 Tabstop {
8793 is_end_tabstop,
8794 ranges: tabstop_ranges,
8795 choices: tabstop.choices.clone(),
8796 }
8797 })
8798 .collect::<Vec<_>>()
8799 });
8800 if let Some(tabstop) = tabstops.first() {
8801 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8802 s.select_ranges(tabstop.ranges.iter().cloned());
8803 });
8804
8805 if let Some(choices) = &tabstop.choices {
8806 if let Some(selection) = tabstop.ranges.first() {
8807 self.show_snippet_choices(choices, selection.clone(), cx)
8808 }
8809 }
8810
8811 // If we're already at the last tabstop and it's at the end of the snippet,
8812 // we're done, we don't need to keep the state around.
8813 if !tabstop.is_end_tabstop {
8814 let choices = tabstops
8815 .iter()
8816 .map(|tabstop| tabstop.choices.clone())
8817 .collect();
8818
8819 let ranges = tabstops
8820 .into_iter()
8821 .map(|tabstop| tabstop.ranges)
8822 .collect::<Vec<_>>();
8823
8824 self.snippet_stack.push(SnippetState {
8825 active_index: 0,
8826 ranges,
8827 choices,
8828 });
8829 }
8830
8831 // Check whether the just-entered snippet ends with an auto-closable bracket.
8832 if self.autoclose_regions.is_empty() {
8833 let snapshot = self.buffer.read(cx).snapshot(cx);
8834 for selection in &mut self.selections.all::<Point>(cx) {
8835 let selection_head = selection.head();
8836 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8837 continue;
8838 };
8839
8840 let mut bracket_pair = None;
8841 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8842 let prev_chars = snapshot
8843 .reversed_chars_at(selection_head)
8844 .collect::<String>();
8845 for (pair, enabled) in scope.brackets() {
8846 if enabled
8847 && pair.close
8848 && prev_chars.starts_with(pair.start.as_str())
8849 && next_chars.starts_with(pair.end.as_str())
8850 {
8851 bracket_pair = Some(pair.clone());
8852 break;
8853 }
8854 }
8855 if let Some(pair) = bracket_pair {
8856 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8857 let autoclose_enabled =
8858 self.use_autoclose && snapshot_settings.use_autoclose;
8859 if autoclose_enabled {
8860 let start = snapshot.anchor_after(selection_head);
8861 let end = snapshot.anchor_after(selection_head);
8862 self.autoclose_regions.push(AutocloseRegion {
8863 selection_id: selection.id,
8864 range: start..end,
8865 pair,
8866 });
8867 }
8868 }
8869 }
8870 }
8871 }
8872 Ok(())
8873 }
8874
8875 pub fn move_to_next_snippet_tabstop(
8876 &mut self,
8877 window: &mut Window,
8878 cx: &mut Context<Self>,
8879 ) -> bool {
8880 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8881 }
8882
8883 pub fn move_to_prev_snippet_tabstop(
8884 &mut self,
8885 window: &mut Window,
8886 cx: &mut Context<Self>,
8887 ) -> bool {
8888 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8889 }
8890
8891 pub fn move_to_snippet_tabstop(
8892 &mut self,
8893 bias: Bias,
8894 window: &mut Window,
8895 cx: &mut Context<Self>,
8896 ) -> bool {
8897 if let Some(mut snippet) = self.snippet_stack.pop() {
8898 match bias {
8899 Bias::Left => {
8900 if snippet.active_index > 0 {
8901 snippet.active_index -= 1;
8902 } else {
8903 self.snippet_stack.push(snippet);
8904 return false;
8905 }
8906 }
8907 Bias::Right => {
8908 if snippet.active_index + 1 < snippet.ranges.len() {
8909 snippet.active_index += 1;
8910 } else {
8911 self.snippet_stack.push(snippet);
8912 return false;
8913 }
8914 }
8915 }
8916 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8917 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8918 s.select_anchor_ranges(current_ranges.iter().cloned())
8919 });
8920
8921 if let Some(choices) = &snippet.choices[snippet.active_index] {
8922 if let Some(selection) = current_ranges.first() {
8923 self.show_snippet_choices(&choices, selection.clone(), cx);
8924 }
8925 }
8926
8927 // If snippet state is not at the last tabstop, push it back on the stack
8928 if snippet.active_index + 1 < snippet.ranges.len() {
8929 self.snippet_stack.push(snippet);
8930 }
8931 return true;
8932 }
8933 }
8934
8935 false
8936 }
8937
8938 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8939 self.transact(window, cx, |this, window, cx| {
8940 this.select_all(&SelectAll, window, cx);
8941 this.insert("", window, cx);
8942 });
8943 }
8944
8945 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8946 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8947 self.transact(window, cx, |this, window, cx| {
8948 this.select_autoclose_pair(window, cx);
8949 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8950 if !this.linked_edit_ranges.is_empty() {
8951 let selections = this.selections.all::<MultiBufferPoint>(cx);
8952 let snapshot = this.buffer.read(cx).snapshot(cx);
8953
8954 for selection in selections.iter() {
8955 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8956 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8957 if selection_start.buffer_id != selection_end.buffer_id {
8958 continue;
8959 }
8960 if let Some(ranges) =
8961 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8962 {
8963 for (buffer, entries) in ranges {
8964 linked_ranges.entry(buffer).or_default().extend(entries);
8965 }
8966 }
8967 }
8968 }
8969
8970 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8971 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8972 for selection in &mut selections {
8973 if selection.is_empty() {
8974 let old_head = selection.head();
8975 let mut new_head =
8976 movement::left(&display_map, old_head.to_display_point(&display_map))
8977 .to_point(&display_map);
8978 if let Some((buffer, line_buffer_range)) = display_map
8979 .buffer_snapshot
8980 .buffer_line_for_row(MultiBufferRow(old_head.row))
8981 {
8982 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8983 let indent_len = match indent_size.kind {
8984 IndentKind::Space => {
8985 buffer.settings_at(line_buffer_range.start, cx).tab_size
8986 }
8987 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8988 };
8989 if old_head.column <= indent_size.len && old_head.column > 0 {
8990 let indent_len = indent_len.get();
8991 new_head = cmp::min(
8992 new_head,
8993 MultiBufferPoint::new(
8994 old_head.row,
8995 ((old_head.column - 1) / indent_len) * indent_len,
8996 ),
8997 );
8998 }
8999 }
9000
9001 selection.set_head(new_head, SelectionGoal::None);
9002 }
9003 }
9004
9005 this.signature_help_state.set_backspace_pressed(true);
9006 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9007 s.select(selections)
9008 });
9009 this.insert("", window, cx);
9010 let empty_str: Arc<str> = Arc::from("");
9011 for (buffer, edits) in linked_ranges {
9012 let snapshot = buffer.read(cx).snapshot();
9013 use text::ToPoint as TP;
9014
9015 let edits = edits
9016 .into_iter()
9017 .map(|range| {
9018 let end_point = TP::to_point(&range.end, &snapshot);
9019 let mut start_point = TP::to_point(&range.start, &snapshot);
9020
9021 if end_point == start_point {
9022 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9023 .saturating_sub(1);
9024 start_point =
9025 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9026 };
9027
9028 (start_point..end_point, empty_str.clone())
9029 })
9030 .sorted_by_key(|(range, _)| range.start)
9031 .collect::<Vec<_>>();
9032 buffer.update(cx, |this, cx| {
9033 this.edit(edits, None, cx);
9034 })
9035 }
9036 this.refresh_inline_completion(true, false, window, cx);
9037 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9038 });
9039 }
9040
9041 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9042 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9043 self.transact(window, cx, |this, window, cx| {
9044 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9045 s.move_with(|map, selection| {
9046 if selection.is_empty() {
9047 let cursor = movement::right(map, selection.head());
9048 selection.end = cursor;
9049 selection.reversed = true;
9050 selection.goal = SelectionGoal::None;
9051 }
9052 })
9053 });
9054 this.insert("", window, cx);
9055 this.refresh_inline_completion(true, false, window, cx);
9056 });
9057 }
9058
9059 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9060 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9061 if self.move_to_prev_snippet_tabstop(window, cx) {
9062 return;
9063 }
9064 self.outdent(&Outdent, window, cx);
9065 }
9066
9067 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9068 if self.move_to_next_snippet_tabstop(window, cx) {
9069 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9070 return;
9071 }
9072 if self.read_only(cx) {
9073 return;
9074 }
9075 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9076 let mut selections = self.selections.all_adjusted(cx);
9077 let buffer = self.buffer.read(cx);
9078 let snapshot = buffer.snapshot(cx);
9079 let rows_iter = selections.iter().map(|s| s.head().row);
9080 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9081
9082 let has_some_cursor_in_whitespace = selections
9083 .iter()
9084 .filter(|selection| selection.is_empty())
9085 .any(|selection| {
9086 let cursor = selection.head();
9087 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9088 cursor.column < current_indent.len
9089 });
9090
9091 let mut edits = Vec::new();
9092 let mut prev_edited_row = 0;
9093 let mut row_delta = 0;
9094 for selection in &mut selections {
9095 if selection.start.row != prev_edited_row {
9096 row_delta = 0;
9097 }
9098 prev_edited_row = selection.end.row;
9099
9100 // If the selection is non-empty, then increase the indentation of the selected lines.
9101 if !selection.is_empty() {
9102 row_delta =
9103 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9104 continue;
9105 }
9106
9107 let cursor = selection.head();
9108 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9109 if let Some(suggested_indent) =
9110 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9111 {
9112 // Don't do anything if already at suggested indent
9113 // and there is any other cursor which is not
9114 if has_some_cursor_in_whitespace
9115 && cursor.column == current_indent.len
9116 && current_indent.len == suggested_indent.len
9117 {
9118 continue;
9119 }
9120
9121 // Adjust line and move cursor to suggested indent
9122 // if cursor is not at suggested indent
9123 if cursor.column < suggested_indent.len
9124 && cursor.column <= current_indent.len
9125 && current_indent.len <= suggested_indent.len
9126 {
9127 selection.start = Point::new(cursor.row, suggested_indent.len);
9128 selection.end = selection.start;
9129 if row_delta == 0 {
9130 edits.extend(Buffer::edit_for_indent_size_adjustment(
9131 cursor.row,
9132 current_indent,
9133 suggested_indent,
9134 ));
9135 row_delta = suggested_indent.len - current_indent.len;
9136 }
9137 continue;
9138 }
9139
9140 // If current indent is more than suggested indent
9141 // only move cursor to current indent and skip indent
9142 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9143 selection.start = Point::new(cursor.row, current_indent.len);
9144 selection.end = selection.start;
9145 continue;
9146 }
9147 }
9148
9149 // Otherwise, insert a hard or soft tab.
9150 let settings = buffer.language_settings_at(cursor, cx);
9151 let tab_size = if settings.hard_tabs {
9152 IndentSize::tab()
9153 } else {
9154 let tab_size = settings.tab_size.get();
9155 let indent_remainder = snapshot
9156 .text_for_range(Point::new(cursor.row, 0)..cursor)
9157 .flat_map(str::chars)
9158 .fold(row_delta % tab_size, |counter: u32, c| {
9159 if c == '\t' {
9160 0
9161 } else {
9162 (counter + 1) % tab_size
9163 }
9164 });
9165
9166 let chars_to_next_tab_stop = tab_size - indent_remainder;
9167 IndentSize::spaces(chars_to_next_tab_stop)
9168 };
9169 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9170 selection.end = selection.start;
9171 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9172 row_delta += tab_size.len;
9173 }
9174
9175 self.transact(window, cx, |this, window, cx| {
9176 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9177 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9178 s.select(selections)
9179 });
9180 this.refresh_inline_completion(true, false, window, cx);
9181 });
9182 }
9183
9184 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9185 if self.read_only(cx) {
9186 return;
9187 }
9188 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9189 let mut selections = self.selections.all::<Point>(cx);
9190 let mut prev_edited_row = 0;
9191 let mut row_delta = 0;
9192 let mut edits = Vec::new();
9193 let buffer = self.buffer.read(cx);
9194 let snapshot = buffer.snapshot(cx);
9195 for selection in &mut selections {
9196 if selection.start.row != prev_edited_row {
9197 row_delta = 0;
9198 }
9199 prev_edited_row = selection.end.row;
9200
9201 row_delta =
9202 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9203 }
9204
9205 self.transact(window, cx, |this, window, cx| {
9206 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9207 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9208 s.select(selections)
9209 });
9210 });
9211 }
9212
9213 fn indent_selection(
9214 buffer: &MultiBuffer,
9215 snapshot: &MultiBufferSnapshot,
9216 selection: &mut Selection<Point>,
9217 edits: &mut Vec<(Range<Point>, String)>,
9218 delta_for_start_row: u32,
9219 cx: &App,
9220 ) -> u32 {
9221 let settings = buffer.language_settings_at(selection.start, cx);
9222 let tab_size = settings.tab_size.get();
9223 let indent_kind = if settings.hard_tabs {
9224 IndentKind::Tab
9225 } else {
9226 IndentKind::Space
9227 };
9228 let mut start_row = selection.start.row;
9229 let mut end_row = selection.end.row + 1;
9230
9231 // If a selection ends at the beginning of a line, don't indent
9232 // that last line.
9233 if selection.end.column == 0 && selection.end.row > selection.start.row {
9234 end_row -= 1;
9235 }
9236
9237 // Avoid re-indenting a row that has already been indented by a
9238 // previous selection, but still update this selection's column
9239 // to reflect that indentation.
9240 if delta_for_start_row > 0 {
9241 start_row += 1;
9242 selection.start.column += delta_for_start_row;
9243 if selection.end.row == selection.start.row {
9244 selection.end.column += delta_for_start_row;
9245 }
9246 }
9247
9248 let mut delta_for_end_row = 0;
9249 let has_multiple_rows = start_row + 1 != end_row;
9250 for row in start_row..end_row {
9251 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9252 let indent_delta = match (current_indent.kind, indent_kind) {
9253 (IndentKind::Space, IndentKind::Space) => {
9254 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9255 IndentSize::spaces(columns_to_next_tab_stop)
9256 }
9257 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9258 (_, IndentKind::Tab) => IndentSize::tab(),
9259 };
9260
9261 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9262 0
9263 } else {
9264 selection.start.column
9265 };
9266 let row_start = Point::new(row, start);
9267 edits.push((
9268 row_start..row_start,
9269 indent_delta.chars().collect::<String>(),
9270 ));
9271
9272 // Update this selection's endpoints to reflect the indentation.
9273 if row == selection.start.row {
9274 selection.start.column += indent_delta.len;
9275 }
9276 if row == selection.end.row {
9277 selection.end.column += indent_delta.len;
9278 delta_for_end_row = indent_delta.len;
9279 }
9280 }
9281
9282 if selection.start.row == selection.end.row {
9283 delta_for_start_row + delta_for_end_row
9284 } else {
9285 delta_for_end_row
9286 }
9287 }
9288
9289 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9290 if self.read_only(cx) {
9291 return;
9292 }
9293 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9294 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9295 let selections = self.selections.all::<Point>(cx);
9296 let mut deletion_ranges = Vec::new();
9297 let mut last_outdent = None;
9298 {
9299 let buffer = self.buffer.read(cx);
9300 let snapshot = buffer.snapshot(cx);
9301 for selection in &selections {
9302 let settings = buffer.language_settings_at(selection.start, cx);
9303 let tab_size = settings.tab_size.get();
9304 let mut rows = selection.spanned_rows(false, &display_map);
9305
9306 // Avoid re-outdenting a row that has already been outdented by a
9307 // previous selection.
9308 if let Some(last_row) = last_outdent {
9309 if last_row == rows.start {
9310 rows.start = rows.start.next_row();
9311 }
9312 }
9313 let has_multiple_rows = rows.len() > 1;
9314 for row in rows.iter_rows() {
9315 let indent_size = snapshot.indent_size_for_line(row);
9316 if indent_size.len > 0 {
9317 let deletion_len = match indent_size.kind {
9318 IndentKind::Space => {
9319 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9320 if columns_to_prev_tab_stop == 0 {
9321 tab_size
9322 } else {
9323 columns_to_prev_tab_stop
9324 }
9325 }
9326 IndentKind::Tab => 1,
9327 };
9328 let start = if has_multiple_rows
9329 || deletion_len > selection.start.column
9330 || indent_size.len < selection.start.column
9331 {
9332 0
9333 } else {
9334 selection.start.column - deletion_len
9335 };
9336 deletion_ranges.push(
9337 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9338 );
9339 last_outdent = Some(row);
9340 }
9341 }
9342 }
9343 }
9344
9345 self.transact(window, cx, |this, window, cx| {
9346 this.buffer.update(cx, |buffer, cx| {
9347 let empty_str: Arc<str> = Arc::default();
9348 buffer.edit(
9349 deletion_ranges
9350 .into_iter()
9351 .map(|range| (range, empty_str.clone())),
9352 None,
9353 cx,
9354 );
9355 });
9356 let selections = this.selections.all::<usize>(cx);
9357 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9358 s.select(selections)
9359 });
9360 });
9361 }
9362
9363 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9364 if self.read_only(cx) {
9365 return;
9366 }
9367 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9368 let selections = self
9369 .selections
9370 .all::<usize>(cx)
9371 .into_iter()
9372 .map(|s| s.range());
9373
9374 self.transact(window, cx, |this, window, cx| {
9375 this.buffer.update(cx, |buffer, cx| {
9376 buffer.autoindent_ranges(selections, cx);
9377 });
9378 let selections = this.selections.all::<usize>(cx);
9379 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9380 s.select(selections)
9381 });
9382 });
9383 }
9384
9385 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9386 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9387 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9388 let selections = self.selections.all::<Point>(cx);
9389
9390 let mut new_cursors = Vec::new();
9391 let mut edit_ranges = Vec::new();
9392 let mut selections = selections.iter().peekable();
9393 while let Some(selection) = selections.next() {
9394 let mut rows = selection.spanned_rows(false, &display_map);
9395 let goal_display_column = selection.head().to_display_point(&display_map).column();
9396
9397 // Accumulate contiguous regions of rows that we want to delete.
9398 while let Some(next_selection) = selections.peek() {
9399 let next_rows = next_selection.spanned_rows(false, &display_map);
9400 if next_rows.start <= rows.end {
9401 rows.end = next_rows.end;
9402 selections.next().unwrap();
9403 } else {
9404 break;
9405 }
9406 }
9407
9408 let buffer = &display_map.buffer_snapshot;
9409 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9410 let edit_end;
9411 let cursor_buffer_row;
9412 if buffer.max_point().row >= rows.end.0 {
9413 // If there's a line after the range, delete the \n from the end of the row range
9414 // and position the cursor on the next line.
9415 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9416 cursor_buffer_row = rows.end;
9417 } else {
9418 // If there isn't a line after the range, delete the \n from the line before the
9419 // start of the row range and position the cursor there.
9420 edit_start = edit_start.saturating_sub(1);
9421 edit_end = buffer.len();
9422 cursor_buffer_row = rows.start.previous_row();
9423 }
9424
9425 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9426 *cursor.column_mut() =
9427 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9428
9429 new_cursors.push((
9430 selection.id,
9431 buffer.anchor_after(cursor.to_point(&display_map)),
9432 ));
9433 edit_ranges.push(edit_start..edit_end);
9434 }
9435
9436 self.transact(window, cx, |this, window, cx| {
9437 let buffer = this.buffer.update(cx, |buffer, cx| {
9438 let empty_str: Arc<str> = Arc::default();
9439 buffer.edit(
9440 edit_ranges
9441 .into_iter()
9442 .map(|range| (range, empty_str.clone())),
9443 None,
9444 cx,
9445 );
9446 buffer.snapshot(cx)
9447 });
9448 let new_selections = new_cursors
9449 .into_iter()
9450 .map(|(id, cursor)| {
9451 let cursor = cursor.to_point(&buffer);
9452 Selection {
9453 id,
9454 start: cursor,
9455 end: cursor,
9456 reversed: false,
9457 goal: SelectionGoal::None,
9458 }
9459 })
9460 .collect();
9461
9462 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9463 s.select(new_selections);
9464 });
9465 });
9466 }
9467
9468 pub fn join_lines_impl(
9469 &mut self,
9470 insert_whitespace: bool,
9471 window: &mut Window,
9472 cx: &mut Context<Self>,
9473 ) {
9474 if self.read_only(cx) {
9475 return;
9476 }
9477 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9478 for selection in self.selections.all::<Point>(cx) {
9479 let start = MultiBufferRow(selection.start.row);
9480 // Treat single line selections as if they include the next line. Otherwise this action
9481 // would do nothing for single line selections individual cursors.
9482 let end = if selection.start.row == selection.end.row {
9483 MultiBufferRow(selection.start.row + 1)
9484 } else {
9485 MultiBufferRow(selection.end.row)
9486 };
9487
9488 if let Some(last_row_range) = row_ranges.last_mut() {
9489 if start <= last_row_range.end {
9490 last_row_range.end = end;
9491 continue;
9492 }
9493 }
9494 row_ranges.push(start..end);
9495 }
9496
9497 let snapshot = self.buffer.read(cx).snapshot(cx);
9498 let mut cursor_positions = Vec::new();
9499 for row_range in &row_ranges {
9500 let anchor = snapshot.anchor_before(Point::new(
9501 row_range.end.previous_row().0,
9502 snapshot.line_len(row_range.end.previous_row()),
9503 ));
9504 cursor_positions.push(anchor..anchor);
9505 }
9506
9507 self.transact(window, cx, |this, window, cx| {
9508 for row_range in row_ranges.into_iter().rev() {
9509 for row in row_range.iter_rows().rev() {
9510 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9511 let next_line_row = row.next_row();
9512 let indent = snapshot.indent_size_for_line(next_line_row);
9513 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9514
9515 let replace =
9516 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9517 " "
9518 } else {
9519 ""
9520 };
9521
9522 this.buffer.update(cx, |buffer, cx| {
9523 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9524 });
9525 }
9526 }
9527
9528 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9529 s.select_anchor_ranges(cursor_positions)
9530 });
9531 });
9532 }
9533
9534 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9535 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9536 self.join_lines_impl(true, window, cx);
9537 }
9538
9539 pub fn sort_lines_case_sensitive(
9540 &mut self,
9541 _: &SortLinesCaseSensitive,
9542 window: &mut Window,
9543 cx: &mut Context<Self>,
9544 ) {
9545 self.manipulate_lines(window, cx, |lines| lines.sort())
9546 }
9547
9548 pub fn sort_lines_case_insensitive(
9549 &mut self,
9550 _: &SortLinesCaseInsensitive,
9551 window: &mut Window,
9552 cx: &mut Context<Self>,
9553 ) {
9554 self.manipulate_lines(window, cx, |lines| {
9555 lines.sort_by_key(|line| line.to_lowercase())
9556 })
9557 }
9558
9559 pub fn unique_lines_case_insensitive(
9560 &mut self,
9561 _: &UniqueLinesCaseInsensitive,
9562 window: &mut Window,
9563 cx: &mut Context<Self>,
9564 ) {
9565 self.manipulate_lines(window, cx, |lines| {
9566 let mut seen = HashSet::default();
9567 lines.retain(|line| seen.insert(line.to_lowercase()));
9568 })
9569 }
9570
9571 pub fn unique_lines_case_sensitive(
9572 &mut self,
9573 _: &UniqueLinesCaseSensitive,
9574 window: &mut Window,
9575 cx: &mut Context<Self>,
9576 ) {
9577 self.manipulate_lines(window, cx, |lines| {
9578 let mut seen = HashSet::default();
9579 lines.retain(|line| seen.insert(*line));
9580 })
9581 }
9582
9583 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9584 let Some(project) = self.project.clone() else {
9585 return;
9586 };
9587 self.reload(project, window, cx)
9588 .detach_and_notify_err(window, cx);
9589 }
9590
9591 pub fn restore_file(
9592 &mut self,
9593 _: &::git::RestoreFile,
9594 window: &mut Window,
9595 cx: &mut Context<Self>,
9596 ) {
9597 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9598 let mut buffer_ids = HashSet::default();
9599 let snapshot = self.buffer().read(cx).snapshot(cx);
9600 for selection in self.selections.all::<usize>(cx) {
9601 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9602 }
9603
9604 let buffer = self.buffer().read(cx);
9605 let ranges = buffer_ids
9606 .into_iter()
9607 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9608 .collect::<Vec<_>>();
9609
9610 self.restore_hunks_in_ranges(ranges, window, cx);
9611 }
9612
9613 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9614 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9615 let selections = self
9616 .selections
9617 .all(cx)
9618 .into_iter()
9619 .map(|s| s.range())
9620 .collect();
9621 self.restore_hunks_in_ranges(selections, window, cx);
9622 }
9623
9624 pub fn restore_hunks_in_ranges(
9625 &mut self,
9626 ranges: Vec<Range<Point>>,
9627 window: &mut Window,
9628 cx: &mut Context<Editor>,
9629 ) {
9630 let mut revert_changes = HashMap::default();
9631 let chunk_by = self
9632 .snapshot(window, cx)
9633 .hunks_for_ranges(ranges)
9634 .into_iter()
9635 .chunk_by(|hunk| hunk.buffer_id);
9636 for (buffer_id, hunks) in &chunk_by {
9637 let hunks = hunks.collect::<Vec<_>>();
9638 for hunk in &hunks {
9639 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9640 }
9641 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9642 }
9643 drop(chunk_by);
9644 if !revert_changes.is_empty() {
9645 self.transact(window, cx, |editor, window, cx| {
9646 editor.restore(revert_changes, window, cx);
9647 });
9648 }
9649 }
9650
9651 pub fn open_active_item_in_terminal(
9652 &mut self,
9653 _: &OpenInTerminal,
9654 window: &mut Window,
9655 cx: &mut Context<Self>,
9656 ) {
9657 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9658 let project_path = buffer.read(cx).project_path(cx)?;
9659 let project = self.project.as_ref()?.read(cx);
9660 let entry = project.entry_for_path(&project_path, cx)?;
9661 let parent = match &entry.canonical_path {
9662 Some(canonical_path) => canonical_path.to_path_buf(),
9663 None => project.absolute_path(&project_path, cx)?,
9664 }
9665 .parent()?
9666 .to_path_buf();
9667 Some(parent)
9668 }) {
9669 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9670 }
9671 }
9672
9673 fn set_breakpoint_context_menu(
9674 &mut self,
9675 display_row: DisplayRow,
9676 position: Option<Anchor>,
9677 clicked_point: gpui::Point<Pixels>,
9678 window: &mut Window,
9679 cx: &mut Context<Self>,
9680 ) {
9681 if !cx.has_flag::<DebuggerFeatureFlag>() {
9682 return;
9683 }
9684 let source = self
9685 .buffer
9686 .read(cx)
9687 .snapshot(cx)
9688 .anchor_before(Point::new(display_row.0, 0u32));
9689
9690 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9691
9692 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9693 self,
9694 source,
9695 clicked_point,
9696 context_menu,
9697 window,
9698 cx,
9699 );
9700 }
9701
9702 fn add_edit_breakpoint_block(
9703 &mut self,
9704 anchor: Anchor,
9705 breakpoint: &Breakpoint,
9706 edit_action: BreakpointPromptEditAction,
9707 window: &mut Window,
9708 cx: &mut Context<Self>,
9709 ) {
9710 let weak_editor = cx.weak_entity();
9711 let bp_prompt = cx.new(|cx| {
9712 BreakpointPromptEditor::new(
9713 weak_editor,
9714 anchor,
9715 breakpoint.clone(),
9716 edit_action,
9717 window,
9718 cx,
9719 )
9720 });
9721
9722 let height = bp_prompt.update(cx, |this, cx| {
9723 this.prompt
9724 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9725 });
9726 let cloned_prompt = bp_prompt.clone();
9727 let blocks = vec![BlockProperties {
9728 style: BlockStyle::Sticky,
9729 placement: BlockPlacement::Above(anchor),
9730 height: Some(height),
9731 render: Arc::new(move |cx| {
9732 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9733 cloned_prompt.clone().into_any_element()
9734 }),
9735 priority: 0,
9736 render_in_minimap: true,
9737 }];
9738
9739 let focus_handle = bp_prompt.focus_handle(cx);
9740 window.focus(&focus_handle);
9741
9742 let block_ids = self.insert_blocks(blocks, None, cx);
9743 bp_prompt.update(cx, |prompt, _| {
9744 prompt.add_block_ids(block_ids);
9745 });
9746 }
9747
9748 pub(crate) fn breakpoint_at_row(
9749 &self,
9750 row: u32,
9751 window: &mut Window,
9752 cx: &mut Context<Self>,
9753 ) -> Option<(Anchor, Breakpoint)> {
9754 let snapshot = self.snapshot(window, cx);
9755 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9756
9757 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9758 }
9759
9760 pub(crate) fn breakpoint_at_anchor(
9761 &self,
9762 breakpoint_position: Anchor,
9763 snapshot: &EditorSnapshot,
9764 cx: &mut Context<Self>,
9765 ) -> Option<(Anchor, Breakpoint)> {
9766 let project = self.project.clone()?;
9767
9768 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9769 snapshot
9770 .buffer_snapshot
9771 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9772 })?;
9773
9774 let enclosing_excerpt = breakpoint_position.excerpt_id;
9775 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
9776 let buffer_snapshot = buffer.read(cx).snapshot();
9777
9778 let row = buffer_snapshot
9779 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9780 .row;
9781
9782 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9783 let anchor_end = snapshot
9784 .buffer_snapshot
9785 .anchor_after(Point::new(row, line_len));
9786
9787 let bp = self
9788 .breakpoint_store
9789 .as_ref()?
9790 .read_with(cx, |breakpoint_store, cx| {
9791 breakpoint_store
9792 .breakpoints(
9793 &buffer,
9794 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9795 &buffer_snapshot,
9796 cx,
9797 )
9798 .next()
9799 .and_then(|(bp, _)| {
9800 let breakpoint_row = buffer_snapshot
9801 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9802 .row;
9803
9804 if breakpoint_row == row {
9805 snapshot
9806 .buffer_snapshot
9807 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9808 .map(|position| (position, bp.bp.clone()))
9809 } else {
9810 None
9811 }
9812 })
9813 });
9814 bp
9815 }
9816
9817 pub fn edit_log_breakpoint(
9818 &mut self,
9819 _: &EditLogBreakpoint,
9820 window: &mut Window,
9821 cx: &mut Context<Self>,
9822 ) {
9823 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9824 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9825 message: None,
9826 state: BreakpointState::Enabled,
9827 condition: None,
9828 hit_condition: None,
9829 });
9830
9831 self.add_edit_breakpoint_block(
9832 anchor,
9833 &breakpoint,
9834 BreakpointPromptEditAction::Log,
9835 window,
9836 cx,
9837 );
9838 }
9839 }
9840
9841 fn breakpoints_at_cursors(
9842 &self,
9843 window: &mut Window,
9844 cx: &mut Context<Self>,
9845 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9846 let snapshot = self.snapshot(window, cx);
9847 let cursors = self
9848 .selections
9849 .disjoint_anchors()
9850 .into_iter()
9851 .map(|selection| {
9852 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9853
9854 let breakpoint_position = self
9855 .breakpoint_at_row(cursor_position.row, window, cx)
9856 .map(|bp| bp.0)
9857 .unwrap_or_else(|| {
9858 snapshot
9859 .display_snapshot
9860 .buffer_snapshot
9861 .anchor_after(Point::new(cursor_position.row, 0))
9862 });
9863
9864 let breakpoint = self
9865 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9866 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9867
9868 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9869 })
9870 // 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.
9871 .collect::<HashMap<Anchor, _>>();
9872
9873 cursors.into_iter().collect()
9874 }
9875
9876 pub fn enable_breakpoint(
9877 &mut self,
9878 _: &crate::actions::EnableBreakpoint,
9879 window: &mut Window,
9880 cx: &mut Context<Self>,
9881 ) {
9882 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9883 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9884 continue;
9885 };
9886 self.edit_breakpoint_at_anchor(
9887 anchor,
9888 breakpoint,
9889 BreakpointEditAction::InvertState,
9890 cx,
9891 );
9892 }
9893 }
9894
9895 pub fn disable_breakpoint(
9896 &mut self,
9897 _: &crate::actions::DisableBreakpoint,
9898 window: &mut Window,
9899 cx: &mut Context<Self>,
9900 ) {
9901 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9902 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9903 continue;
9904 };
9905 self.edit_breakpoint_at_anchor(
9906 anchor,
9907 breakpoint,
9908 BreakpointEditAction::InvertState,
9909 cx,
9910 );
9911 }
9912 }
9913
9914 pub fn toggle_breakpoint(
9915 &mut self,
9916 _: &crate::actions::ToggleBreakpoint,
9917 window: &mut Window,
9918 cx: &mut Context<Self>,
9919 ) {
9920 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9921 if let Some(breakpoint) = breakpoint {
9922 self.edit_breakpoint_at_anchor(
9923 anchor,
9924 breakpoint,
9925 BreakpointEditAction::Toggle,
9926 cx,
9927 );
9928 } else {
9929 self.edit_breakpoint_at_anchor(
9930 anchor,
9931 Breakpoint::new_standard(),
9932 BreakpointEditAction::Toggle,
9933 cx,
9934 );
9935 }
9936 }
9937 }
9938
9939 pub fn edit_breakpoint_at_anchor(
9940 &mut self,
9941 breakpoint_position: Anchor,
9942 breakpoint: Breakpoint,
9943 edit_action: BreakpointEditAction,
9944 cx: &mut Context<Self>,
9945 ) {
9946 let Some(breakpoint_store) = &self.breakpoint_store else {
9947 return;
9948 };
9949
9950 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9951 if breakpoint_position == Anchor::min() {
9952 self.buffer()
9953 .read(cx)
9954 .excerpt_buffer_ids()
9955 .into_iter()
9956 .next()
9957 } else {
9958 None
9959 }
9960 }) else {
9961 return;
9962 };
9963
9964 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9965 return;
9966 };
9967
9968 breakpoint_store.update(cx, |breakpoint_store, cx| {
9969 breakpoint_store.toggle_breakpoint(
9970 buffer,
9971 BreakpointWithPosition {
9972 position: breakpoint_position.text_anchor,
9973 bp: breakpoint,
9974 },
9975 edit_action,
9976 cx,
9977 );
9978 });
9979
9980 cx.notify();
9981 }
9982
9983 #[cfg(any(test, feature = "test-support"))]
9984 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9985 self.breakpoint_store.clone()
9986 }
9987
9988 pub fn prepare_restore_change(
9989 &self,
9990 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9991 hunk: &MultiBufferDiffHunk,
9992 cx: &mut App,
9993 ) -> Option<()> {
9994 if hunk.is_created_file() {
9995 return None;
9996 }
9997 let buffer = self.buffer.read(cx);
9998 let diff = buffer.diff_for(hunk.buffer_id)?;
9999 let buffer = buffer.buffer(hunk.buffer_id)?;
10000 let buffer = buffer.read(cx);
10001 let original_text = diff
10002 .read(cx)
10003 .base_text()
10004 .as_rope()
10005 .slice(hunk.diff_base_byte_range.clone());
10006 let buffer_snapshot = buffer.snapshot();
10007 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10008 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10009 probe
10010 .0
10011 .start
10012 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10013 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10014 }) {
10015 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10016 Some(())
10017 } else {
10018 None
10019 }
10020 }
10021
10022 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10023 self.manipulate_lines(window, cx, |lines| lines.reverse())
10024 }
10025
10026 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10027 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10028 }
10029
10030 fn manipulate_lines<Fn>(
10031 &mut self,
10032 window: &mut Window,
10033 cx: &mut Context<Self>,
10034 mut callback: Fn,
10035 ) where
10036 Fn: FnMut(&mut Vec<&str>),
10037 {
10038 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10039
10040 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10041 let buffer = self.buffer.read(cx).snapshot(cx);
10042
10043 let mut edits = Vec::new();
10044
10045 let selections = self.selections.all::<Point>(cx);
10046 let mut selections = selections.iter().peekable();
10047 let mut contiguous_row_selections = Vec::new();
10048 let mut new_selections = Vec::new();
10049 let mut added_lines = 0;
10050 let mut removed_lines = 0;
10051
10052 while let Some(selection) = selections.next() {
10053 let (start_row, end_row) = consume_contiguous_rows(
10054 &mut contiguous_row_selections,
10055 selection,
10056 &display_map,
10057 &mut selections,
10058 );
10059
10060 let start_point = Point::new(start_row.0, 0);
10061 let end_point = Point::new(
10062 end_row.previous_row().0,
10063 buffer.line_len(end_row.previous_row()),
10064 );
10065 let text = buffer
10066 .text_for_range(start_point..end_point)
10067 .collect::<String>();
10068
10069 let mut lines = text.split('\n').collect_vec();
10070
10071 let lines_before = lines.len();
10072 callback(&mut lines);
10073 let lines_after = lines.len();
10074
10075 edits.push((start_point..end_point, lines.join("\n")));
10076
10077 // Selections must change based on added and removed line count
10078 let start_row =
10079 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10080 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10081 new_selections.push(Selection {
10082 id: selection.id,
10083 start: start_row,
10084 end: end_row,
10085 goal: SelectionGoal::None,
10086 reversed: selection.reversed,
10087 });
10088
10089 if lines_after > lines_before {
10090 added_lines += lines_after - lines_before;
10091 } else if lines_before > lines_after {
10092 removed_lines += lines_before - lines_after;
10093 }
10094 }
10095
10096 self.transact(window, cx, |this, window, cx| {
10097 let buffer = this.buffer.update(cx, |buffer, cx| {
10098 buffer.edit(edits, None, cx);
10099 buffer.snapshot(cx)
10100 });
10101
10102 // Recalculate offsets on newly edited buffer
10103 let new_selections = new_selections
10104 .iter()
10105 .map(|s| {
10106 let start_point = Point::new(s.start.0, 0);
10107 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10108 Selection {
10109 id: s.id,
10110 start: buffer.point_to_offset(start_point),
10111 end: buffer.point_to_offset(end_point),
10112 goal: s.goal,
10113 reversed: s.reversed,
10114 }
10115 })
10116 .collect();
10117
10118 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10119 s.select(new_selections);
10120 });
10121
10122 this.request_autoscroll(Autoscroll::fit(), cx);
10123 });
10124 }
10125
10126 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10127 self.manipulate_text(window, cx, |text| {
10128 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10129 if has_upper_case_characters {
10130 text.to_lowercase()
10131 } else {
10132 text.to_uppercase()
10133 }
10134 })
10135 }
10136
10137 pub fn convert_to_upper_case(
10138 &mut self,
10139 _: &ConvertToUpperCase,
10140 window: &mut Window,
10141 cx: &mut Context<Self>,
10142 ) {
10143 self.manipulate_text(window, cx, |text| text.to_uppercase())
10144 }
10145
10146 pub fn convert_to_lower_case(
10147 &mut self,
10148 _: &ConvertToLowerCase,
10149 window: &mut Window,
10150 cx: &mut Context<Self>,
10151 ) {
10152 self.manipulate_text(window, cx, |text| text.to_lowercase())
10153 }
10154
10155 pub fn convert_to_title_case(
10156 &mut self,
10157 _: &ConvertToTitleCase,
10158 window: &mut Window,
10159 cx: &mut Context<Self>,
10160 ) {
10161 self.manipulate_text(window, cx, |text| {
10162 text.split('\n')
10163 .map(|line| line.to_case(Case::Title))
10164 .join("\n")
10165 })
10166 }
10167
10168 pub fn convert_to_snake_case(
10169 &mut self,
10170 _: &ConvertToSnakeCase,
10171 window: &mut Window,
10172 cx: &mut Context<Self>,
10173 ) {
10174 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10175 }
10176
10177 pub fn convert_to_kebab_case(
10178 &mut self,
10179 _: &ConvertToKebabCase,
10180 window: &mut Window,
10181 cx: &mut Context<Self>,
10182 ) {
10183 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10184 }
10185
10186 pub fn convert_to_upper_camel_case(
10187 &mut self,
10188 _: &ConvertToUpperCamelCase,
10189 window: &mut Window,
10190 cx: &mut Context<Self>,
10191 ) {
10192 self.manipulate_text(window, cx, |text| {
10193 text.split('\n')
10194 .map(|line| line.to_case(Case::UpperCamel))
10195 .join("\n")
10196 })
10197 }
10198
10199 pub fn convert_to_lower_camel_case(
10200 &mut self,
10201 _: &ConvertToLowerCamelCase,
10202 window: &mut Window,
10203 cx: &mut Context<Self>,
10204 ) {
10205 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10206 }
10207
10208 pub fn convert_to_opposite_case(
10209 &mut self,
10210 _: &ConvertToOppositeCase,
10211 window: &mut Window,
10212 cx: &mut Context<Self>,
10213 ) {
10214 self.manipulate_text(window, cx, |text| {
10215 text.chars()
10216 .fold(String::with_capacity(text.len()), |mut t, c| {
10217 if c.is_uppercase() {
10218 t.extend(c.to_lowercase());
10219 } else {
10220 t.extend(c.to_uppercase());
10221 }
10222 t
10223 })
10224 })
10225 }
10226
10227 pub fn convert_to_rot13(
10228 &mut self,
10229 _: &ConvertToRot13,
10230 window: &mut Window,
10231 cx: &mut Context<Self>,
10232 ) {
10233 self.manipulate_text(window, cx, |text| {
10234 text.chars()
10235 .map(|c| match c {
10236 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10237 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10238 _ => c,
10239 })
10240 .collect()
10241 })
10242 }
10243
10244 pub fn convert_to_rot47(
10245 &mut self,
10246 _: &ConvertToRot47,
10247 window: &mut Window,
10248 cx: &mut Context<Self>,
10249 ) {
10250 self.manipulate_text(window, cx, |text| {
10251 text.chars()
10252 .map(|c| {
10253 let code_point = c as u32;
10254 if code_point >= 33 && code_point <= 126 {
10255 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10256 }
10257 c
10258 })
10259 .collect()
10260 })
10261 }
10262
10263 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10264 where
10265 Fn: FnMut(&str) -> String,
10266 {
10267 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10268 let buffer = self.buffer.read(cx).snapshot(cx);
10269
10270 let mut new_selections = Vec::new();
10271 let mut edits = Vec::new();
10272 let mut selection_adjustment = 0i32;
10273
10274 for selection in self.selections.all::<usize>(cx) {
10275 let selection_is_empty = selection.is_empty();
10276
10277 let (start, end) = if selection_is_empty {
10278 let word_range = movement::surrounding_word(
10279 &display_map,
10280 selection.start.to_display_point(&display_map),
10281 );
10282 let start = word_range.start.to_offset(&display_map, Bias::Left);
10283 let end = word_range.end.to_offset(&display_map, Bias::Left);
10284 (start, end)
10285 } else {
10286 (selection.start, selection.end)
10287 };
10288
10289 let text = buffer.text_for_range(start..end).collect::<String>();
10290 let old_length = text.len() as i32;
10291 let text = callback(&text);
10292
10293 new_selections.push(Selection {
10294 start: (start as i32 - selection_adjustment) as usize,
10295 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10296 goal: SelectionGoal::None,
10297 ..selection
10298 });
10299
10300 selection_adjustment += old_length - text.len() as i32;
10301
10302 edits.push((start..end, text));
10303 }
10304
10305 self.transact(window, cx, |this, window, cx| {
10306 this.buffer.update(cx, |buffer, cx| {
10307 buffer.edit(edits, None, cx);
10308 });
10309
10310 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10311 s.select(new_selections);
10312 });
10313
10314 this.request_autoscroll(Autoscroll::fit(), cx);
10315 });
10316 }
10317
10318 pub fn duplicate(
10319 &mut self,
10320 upwards: bool,
10321 whole_lines: bool,
10322 window: &mut Window,
10323 cx: &mut Context<Self>,
10324 ) {
10325 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10326
10327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10328 let buffer = &display_map.buffer_snapshot;
10329 let selections = self.selections.all::<Point>(cx);
10330
10331 let mut edits = Vec::new();
10332 let mut selections_iter = selections.iter().peekable();
10333 while let Some(selection) = selections_iter.next() {
10334 let mut rows = selection.spanned_rows(false, &display_map);
10335 // duplicate line-wise
10336 if whole_lines || selection.start == selection.end {
10337 // Avoid duplicating the same lines twice.
10338 while let Some(next_selection) = selections_iter.peek() {
10339 let next_rows = next_selection.spanned_rows(false, &display_map);
10340 if next_rows.start < rows.end {
10341 rows.end = next_rows.end;
10342 selections_iter.next().unwrap();
10343 } else {
10344 break;
10345 }
10346 }
10347
10348 // Copy the text from the selected row region and splice it either at the start
10349 // or end of the region.
10350 let start = Point::new(rows.start.0, 0);
10351 let end = Point::new(
10352 rows.end.previous_row().0,
10353 buffer.line_len(rows.end.previous_row()),
10354 );
10355 let text = buffer
10356 .text_for_range(start..end)
10357 .chain(Some("\n"))
10358 .collect::<String>();
10359 let insert_location = if upwards {
10360 Point::new(rows.end.0, 0)
10361 } else {
10362 start
10363 };
10364 edits.push((insert_location..insert_location, text));
10365 } else {
10366 // duplicate character-wise
10367 let start = selection.start;
10368 let end = selection.end;
10369 let text = buffer.text_for_range(start..end).collect::<String>();
10370 edits.push((selection.end..selection.end, text));
10371 }
10372 }
10373
10374 self.transact(window, cx, |this, _, cx| {
10375 this.buffer.update(cx, |buffer, cx| {
10376 buffer.edit(edits, None, cx);
10377 });
10378
10379 this.request_autoscroll(Autoscroll::fit(), cx);
10380 });
10381 }
10382
10383 pub fn duplicate_line_up(
10384 &mut self,
10385 _: &DuplicateLineUp,
10386 window: &mut Window,
10387 cx: &mut Context<Self>,
10388 ) {
10389 self.duplicate(true, true, window, cx);
10390 }
10391
10392 pub fn duplicate_line_down(
10393 &mut self,
10394 _: &DuplicateLineDown,
10395 window: &mut Window,
10396 cx: &mut Context<Self>,
10397 ) {
10398 self.duplicate(false, true, window, cx);
10399 }
10400
10401 pub fn duplicate_selection(
10402 &mut self,
10403 _: &DuplicateSelection,
10404 window: &mut Window,
10405 cx: &mut Context<Self>,
10406 ) {
10407 self.duplicate(false, false, window, cx);
10408 }
10409
10410 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10411 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10412
10413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10414 let buffer = self.buffer.read(cx).snapshot(cx);
10415
10416 let mut edits = Vec::new();
10417 let mut unfold_ranges = Vec::new();
10418 let mut refold_creases = Vec::new();
10419
10420 let selections = self.selections.all::<Point>(cx);
10421 let mut selections = selections.iter().peekable();
10422 let mut contiguous_row_selections = Vec::new();
10423 let mut new_selections = Vec::new();
10424
10425 while let Some(selection) = selections.next() {
10426 // Find all the selections that span a contiguous row range
10427 let (start_row, end_row) = consume_contiguous_rows(
10428 &mut contiguous_row_selections,
10429 selection,
10430 &display_map,
10431 &mut selections,
10432 );
10433
10434 // Move the text spanned by the row range to be before the line preceding the row range
10435 if start_row.0 > 0 {
10436 let range_to_move = Point::new(
10437 start_row.previous_row().0,
10438 buffer.line_len(start_row.previous_row()),
10439 )
10440 ..Point::new(
10441 end_row.previous_row().0,
10442 buffer.line_len(end_row.previous_row()),
10443 );
10444 let insertion_point = display_map
10445 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10446 .0;
10447
10448 // Don't move lines across excerpts
10449 if buffer
10450 .excerpt_containing(insertion_point..range_to_move.end)
10451 .is_some()
10452 {
10453 let text = buffer
10454 .text_for_range(range_to_move.clone())
10455 .flat_map(|s| s.chars())
10456 .skip(1)
10457 .chain(['\n'])
10458 .collect::<String>();
10459
10460 edits.push((
10461 buffer.anchor_after(range_to_move.start)
10462 ..buffer.anchor_before(range_to_move.end),
10463 String::new(),
10464 ));
10465 let insertion_anchor = buffer.anchor_after(insertion_point);
10466 edits.push((insertion_anchor..insertion_anchor, text));
10467
10468 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10469
10470 // Move selections up
10471 new_selections.extend(contiguous_row_selections.drain(..).map(
10472 |mut selection| {
10473 selection.start.row -= row_delta;
10474 selection.end.row -= row_delta;
10475 selection
10476 },
10477 ));
10478
10479 // Move folds up
10480 unfold_ranges.push(range_to_move.clone());
10481 for fold in display_map.folds_in_range(
10482 buffer.anchor_before(range_to_move.start)
10483 ..buffer.anchor_after(range_to_move.end),
10484 ) {
10485 let mut start = fold.range.start.to_point(&buffer);
10486 let mut end = fold.range.end.to_point(&buffer);
10487 start.row -= row_delta;
10488 end.row -= row_delta;
10489 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10490 }
10491 }
10492 }
10493
10494 // If we didn't move line(s), preserve the existing selections
10495 new_selections.append(&mut contiguous_row_selections);
10496 }
10497
10498 self.transact(window, cx, |this, window, cx| {
10499 this.unfold_ranges(&unfold_ranges, true, true, cx);
10500 this.buffer.update(cx, |buffer, cx| {
10501 for (range, text) in edits {
10502 buffer.edit([(range, text)], None, cx);
10503 }
10504 });
10505 this.fold_creases(refold_creases, true, window, cx);
10506 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10507 s.select(new_selections);
10508 })
10509 });
10510 }
10511
10512 pub fn move_line_down(
10513 &mut self,
10514 _: &MoveLineDown,
10515 window: &mut Window,
10516 cx: &mut Context<Self>,
10517 ) {
10518 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10519
10520 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10521 let buffer = self.buffer.read(cx).snapshot(cx);
10522
10523 let mut edits = Vec::new();
10524 let mut unfold_ranges = Vec::new();
10525 let mut refold_creases = Vec::new();
10526
10527 let selections = self.selections.all::<Point>(cx);
10528 let mut selections = selections.iter().peekable();
10529 let mut contiguous_row_selections = Vec::new();
10530 let mut new_selections = Vec::new();
10531
10532 while let Some(selection) = selections.next() {
10533 // Find all the selections that span a contiguous row range
10534 let (start_row, end_row) = consume_contiguous_rows(
10535 &mut contiguous_row_selections,
10536 selection,
10537 &display_map,
10538 &mut selections,
10539 );
10540
10541 // Move the text spanned by the row range to be after the last line of the row range
10542 if end_row.0 <= buffer.max_point().row {
10543 let range_to_move =
10544 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10545 let insertion_point = display_map
10546 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10547 .0;
10548
10549 // Don't move lines across excerpt boundaries
10550 if buffer
10551 .excerpt_containing(range_to_move.start..insertion_point)
10552 .is_some()
10553 {
10554 let mut text = String::from("\n");
10555 text.extend(buffer.text_for_range(range_to_move.clone()));
10556 text.pop(); // Drop trailing newline
10557 edits.push((
10558 buffer.anchor_after(range_to_move.start)
10559 ..buffer.anchor_before(range_to_move.end),
10560 String::new(),
10561 ));
10562 let insertion_anchor = buffer.anchor_after(insertion_point);
10563 edits.push((insertion_anchor..insertion_anchor, text));
10564
10565 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10566
10567 // Move selections down
10568 new_selections.extend(contiguous_row_selections.drain(..).map(
10569 |mut selection| {
10570 selection.start.row += row_delta;
10571 selection.end.row += row_delta;
10572 selection
10573 },
10574 ));
10575
10576 // Move folds down
10577 unfold_ranges.push(range_to_move.clone());
10578 for fold in display_map.folds_in_range(
10579 buffer.anchor_before(range_to_move.start)
10580 ..buffer.anchor_after(range_to_move.end),
10581 ) {
10582 let mut start = fold.range.start.to_point(&buffer);
10583 let mut end = fold.range.end.to_point(&buffer);
10584 start.row += row_delta;
10585 end.row += row_delta;
10586 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10587 }
10588 }
10589 }
10590
10591 // If we didn't move line(s), preserve the existing selections
10592 new_selections.append(&mut contiguous_row_selections);
10593 }
10594
10595 self.transact(window, cx, |this, window, cx| {
10596 this.unfold_ranges(&unfold_ranges, true, true, cx);
10597 this.buffer.update(cx, |buffer, cx| {
10598 for (range, text) in edits {
10599 buffer.edit([(range, text)], None, cx);
10600 }
10601 });
10602 this.fold_creases(refold_creases, true, window, cx);
10603 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10604 s.select(new_selections)
10605 });
10606 });
10607 }
10608
10609 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10610 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10611 let text_layout_details = &self.text_layout_details(window);
10612 self.transact(window, cx, |this, window, cx| {
10613 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10614 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10615 s.move_with(|display_map, selection| {
10616 if !selection.is_empty() {
10617 return;
10618 }
10619
10620 let mut head = selection.head();
10621 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10622 if head.column() == display_map.line_len(head.row()) {
10623 transpose_offset = display_map
10624 .buffer_snapshot
10625 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10626 }
10627
10628 if transpose_offset == 0 {
10629 return;
10630 }
10631
10632 *head.column_mut() += 1;
10633 head = display_map.clip_point(head, Bias::Right);
10634 let goal = SelectionGoal::HorizontalPosition(
10635 display_map
10636 .x_for_display_point(head, text_layout_details)
10637 .into(),
10638 );
10639 selection.collapse_to(head, goal);
10640
10641 let transpose_start = display_map
10642 .buffer_snapshot
10643 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10644 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10645 let transpose_end = display_map
10646 .buffer_snapshot
10647 .clip_offset(transpose_offset + 1, Bias::Right);
10648 if let Some(ch) =
10649 display_map.buffer_snapshot.chars_at(transpose_start).next()
10650 {
10651 edits.push((transpose_start..transpose_offset, String::new()));
10652 edits.push((transpose_end..transpose_end, ch.to_string()));
10653 }
10654 }
10655 });
10656 edits
10657 });
10658 this.buffer
10659 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10660 let selections = this.selections.all::<usize>(cx);
10661 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10662 s.select(selections);
10663 });
10664 });
10665 }
10666
10667 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10668 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10669 self.rewrap_impl(RewrapOptions::default(), cx)
10670 }
10671
10672 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10673 let buffer = self.buffer.read(cx).snapshot(cx);
10674 let selections = self.selections.all::<Point>(cx);
10675 let mut selections = selections.iter().peekable();
10676
10677 let mut edits = Vec::new();
10678 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10679
10680 while let Some(selection) = selections.next() {
10681 let mut start_row = selection.start.row;
10682 let mut end_row = selection.end.row;
10683
10684 // Skip selections that overlap with a range that has already been rewrapped.
10685 let selection_range = start_row..end_row;
10686 if rewrapped_row_ranges
10687 .iter()
10688 .any(|range| range.overlaps(&selection_range))
10689 {
10690 continue;
10691 }
10692
10693 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10694
10695 // Since not all lines in the selection may be at the same indent
10696 // level, choose the indent size that is the most common between all
10697 // of the lines.
10698 //
10699 // If there is a tie, we use the deepest indent.
10700 let (indent_size, indent_end) = {
10701 let mut indent_size_occurrences = HashMap::default();
10702 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10703
10704 for row in start_row..=end_row {
10705 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10706 rows_by_indent_size.entry(indent).or_default().push(row);
10707 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10708 }
10709
10710 let indent_size = indent_size_occurrences
10711 .into_iter()
10712 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10713 .map(|(indent, _)| indent)
10714 .unwrap_or_default();
10715 let row = rows_by_indent_size[&indent_size][0];
10716 let indent_end = Point::new(row, indent_size.len);
10717
10718 (indent_size, indent_end)
10719 };
10720
10721 let mut line_prefix = indent_size.chars().collect::<String>();
10722
10723 let mut inside_comment = false;
10724 if let Some(comment_prefix) =
10725 buffer
10726 .language_scope_at(selection.head())
10727 .and_then(|language| {
10728 language
10729 .line_comment_prefixes()
10730 .iter()
10731 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10732 .cloned()
10733 })
10734 {
10735 line_prefix.push_str(&comment_prefix);
10736 inside_comment = true;
10737 }
10738
10739 let language_settings = buffer.language_settings_at(selection.head(), cx);
10740 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10741 RewrapBehavior::InComments => inside_comment,
10742 RewrapBehavior::InSelections => !selection.is_empty(),
10743 RewrapBehavior::Anywhere => true,
10744 };
10745
10746 let should_rewrap = options.override_language_settings
10747 || allow_rewrap_based_on_language
10748 || self.hard_wrap.is_some();
10749 if !should_rewrap {
10750 continue;
10751 }
10752
10753 if selection.is_empty() {
10754 'expand_upwards: while start_row > 0 {
10755 let prev_row = start_row - 1;
10756 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10757 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10758 {
10759 start_row = prev_row;
10760 } else {
10761 break 'expand_upwards;
10762 }
10763 }
10764
10765 'expand_downwards: while end_row < buffer.max_point().row {
10766 let next_row = end_row + 1;
10767 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10768 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10769 {
10770 end_row = next_row;
10771 } else {
10772 break 'expand_downwards;
10773 }
10774 }
10775 }
10776
10777 let start = Point::new(start_row, 0);
10778 let start_offset = start.to_offset(&buffer);
10779 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10780 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10781 let Some(lines_without_prefixes) = selection_text
10782 .lines()
10783 .map(|line| {
10784 line.strip_prefix(&line_prefix)
10785 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10786 .with_context(|| {
10787 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10788 })
10789 })
10790 .collect::<Result<Vec<_>, _>>()
10791 .log_err()
10792 else {
10793 continue;
10794 };
10795
10796 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10797 buffer
10798 .language_settings_at(Point::new(start_row, 0), cx)
10799 .preferred_line_length as usize
10800 });
10801 let wrapped_text = wrap_with_prefix(
10802 line_prefix,
10803 lines_without_prefixes.join("\n"),
10804 wrap_column,
10805 tab_size,
10806 options.preserve_existing_whitespace,
10807 );
10808
10809 // TODO: should always use char-based diff while still supporting cursor behavior that
10810 // matches vim.
10811 let mut diff_options = DiffOptions::default();
10812 if options.override_language_settings {
10813 diff_options.max_word_diff_len = 0;
10814 diff_options.max_word_diff_line_count = 0;
10815 } else {
10816 diff_options.max_word_diff_len = usize::MAX;
10817 diff_options.max_word_diff_line_count = usize::MAX;
10818 }
10819
10820 for (old_range, new_text) in
10821 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10822 {
10823 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10824 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10825 edits.push((edit_start..edit_end, new_text));
10826 }
10827
10828 rewrapped_row_ranges.push(start_row..=end_row);
10829 }
10830
10831 self.buffer
10832 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10833 }
10834
10835 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10836 let mut text = String::new();
10837 let buffer = self.buffer.read(cx).snapshot(cx);
10838 let mut selections = self.selections.all::<Point>(cx);
10839 let mut clipboard_selections = Vec::with_capacity(selections.len());
10840 {
10841 let max_point = buffer.max_point();
10842 let mut is_first = true;
10843 for selection in &mut selections {
10844 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10845 if is_entire_line {
10846 selection.start = Point::new(selection.start.row, 0);
10847 if !selection.is_empty() && selection.end.column == 0 {
10848 selection.end = cmp::min(max_point, selection.end);
10849 } else {
10850 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10851 }
10852 selection.goal = SelectionGoal::None;
10853 }
10854 if is_first {
10855 is_first = false;
10856 } else {
10857 text += "\n";
10858 }
10859 let mut len = 0;
10860 for chunk in buffer.text_for_range(selection.start..selection.end) {
10861 text.push_str(chunk);
10862 len += chunk.len();
10863 }
10864 clipboard_selections.push(ClipboardSelection {
10865 len,
10866 is_entire_line,
10867 first_line_indent: buffer
10868 .indent_size_for_line(MultiBufferRow(selection.start.row))
10869 .len,
10870 });
10871 }
10872 }
10873
10874 self.transact(window, cx, |this, window, cx| {
10875 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10876 s.select(selections);
10877 });
10878 this.insert("", window, cx);
10879 });
10880 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10881 }
10882
10883 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10884 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10885 let item = self.cut_common(window, cx);
10886 cx.write_to_clipboard(item);
10887 }
10888
10889 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10890 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10891 self.change_selections(None, window, cx, |s| {
10892 s.move_with(|snapshot, sel| {
10893 if sel.is_empty() {
10894 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10895 }
10896 });
10897 });
10898 let item = self.cut_common(window, cx);
10899 cx.set_global(KillRing(item))
10900 }
10901
10902 pub fn kill_ring_yank(
10903 &mut self,
10904 _: &KillRingYank,
10905 window: &mut Window,
10906 cx: &mut Context<Self>,
10907 ) {
10908 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10909 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10910 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10911 (kill_ring.text().to_string(), kill_ring.metadata_json())
10912 } else {
10913 return;
10914 }
10915 } else {
10916 return;
10917 };
10918 self.do_paste(&text, metadata, false, window, cx);
10919 }
10920
10921 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10922 self.do_copy(true, cx);
10923 }
10924
10925 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10926 self.do_copy(false, cx);
10927 }
10928
10929 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10930 let selections = self.selections.all::<Point>(cx);
10931 let buffer = self.buffer.read(cx).read(cx);
10932 let mut text = String::new();
10933
10934 let mut clipboard_selections = Vec::with_capacity(selections.len());
10935 {
10936 let max_point = buffer.max_point();
10937 let mut is_first = true;
10938 for selection in &selections {
10939 let mut start = selection.start;
10940 let mut end = selection.end;
10941 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10942 if is_entire_line {
10943 start = Point::new(start.row, 0);
10944 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10945 }
10946
10947 let mut trimmed_selections = Vec::new();
10948 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10949 let row = MultiBufferRow(start.row);
10950 let first_indent = buffer.indent_size_for_line(row);
10951 if first_indent.len == 0 || start.column > first_indent.len {
10952 trimmed_selections.push(start..end);
10953 } else {
10954 trimmed_selections.push(
10955 Point::new(row.0, first_indent.len)
10956 ..Point::new(row.0, buffer.line_len(row)),
10957 );
10958 for row in start.row + 1..=end.row {
10959 let mut line_len = buffer.line_len(MultiBufferRow(row));
10960 if row == end.row {
10961 line_len = end.column;
10962 }
10963 if line_len == 0 {
10964 trimmed_selections
10965 .push(Point::new(row, 0)..Point::new(row, line_len));
10966 continue;
10967 }
10968 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10969 if row_indent_size.len >= first_indent.len {
10970 trimmed_selections.push(
10971 Point::new(row, first_indent.len)..Point::new(row, line_len),
10972 );
10973 } else {
10974 trimmed_selections.clear();
10975 trimmed_selections.push(start..end);
10976 break;
10977 }
10978 }
10979 }
10980 } else {
10981 trimmed_selections.push(start..end);
10982 }
10983
10984 for trimmed_range in trimmed_selections {
10985 if is_first {
10986 is_first = false;
10987 } else {
10988 text += "\n";
10989 }
10990 let mut len = 0;
10991 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10992 text.push_str(chunk);
10993 len += chunk.len();
10994 }
10995 clipboard_selections.push(ClipboardSelection {
10996 len,
10997 is_entire_line,
10998 first_line_indent: buffer
10999 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11000 .len,
11001 });
11002 }
11003 }
11004 }
11005
11006 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11007 text,
11008 clipboard_selections,
11009 ));
11010 }
11011
11012 pub fn do_paste(
11013 &mut self,
11014 text: &String,
11015 clipboard_selections: Option<Vec<ClipboardSelection>>,
11016 handle_entire_lines: bool,
11017 window: &mut Window,
11018 cx: &mut Context<Self>,
11019 ) {
11020 if self.read_only(cx) {
11021 return;
11022 }
11023
11024 let clipboard_text = Cow::Borrowed(text);
11025
11026 self.transact(window, cx, |this, window, cx| {
11027 if let Some(mut clipboard_selections) = clipboard_selections {
11028 let old_selections = this.selections.all::<usize>(cx);
11029 let all_selections_were_entire_line =
11030 clipboard_selections.iter().all(|s| s.is_entire_line);
11031 let first_selection_indent_column =
11032 clipboard_selections.first().map(|s| s.first_line_indent);
11033 if clipboard_selections.len() != old_selections.len() {
11034 clipboard_selections.drain(..);
11035 }
11036 let cursor_offset = this.selections.last::<usize>(cx).head();
11037 let mut auto_indent_on_paste = true;
11038
11039 this.buffer.update(cx, |buffer, cx| {
11040 let snapshot = buffer.read(cx);
11041 auto_indent_on_paste = snapshot
11042 .language_settings_at(cursor_offset, cx)
11043 .auto_indent_on_paste;
11044
11045 let mut start_offset = 0;
11046 let mut edits = Vec::new();
11047 let mut original_indent_columns = Vec::new();
11048 for (ix, selection) in old_selections.iter().enumerate() {
11049 let to_insert;
11050 let entire_line;
11051 let original_indent_column;
11052 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11053 let end_offset = start_offset + clipboard_selection.len;
11054 to_insert = &clipboard_text[start_offset..end_offset];
11055 entire_line = clipboard_selection.is_entire_line;
11056 start_offset = end_offset + 1;
11057 original_indent_column = Some(clipboard_selection.first_line_indent);
11058 } else {
11059 to_insert = clipboard_text.as_str();
11060 entire_line = all_selections_were_entire_line;
11061 original_indent_column = first_selection_indent_column
11062 }
11063
11064 // If the corresponding selection was empty when this slice of the
11065 // clipboard text was written, then the entire line containing the
11066 // selection was copied. If this selection is also currently empty,
11067 // then paste the line before the current line of the buffer.
11068 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11069 let column = selection.start.to_point(&snapshot).column as usize;
11070 let line_start = selection.start - column;
11071 line_start..line_start
11072 } else {
11073 selection.range()
11074 };
11075
11076 edits.push((range, to_insert));
11077 original_indent_columns.push(original_indent_column);
11078 }
11079 drop(snapshot);
11080
11081 buffer.edit(
11082 edits,
11083 if auto_indent_on_paste {
11084 Some(AutoindentMode::Block {
11085 original_indent_columns,
11086 })
11087 } else {
11088 None
11089 },
11090 cx,
11091 );
11092 });
11093
11094 let selections = this.selections.all::<usize>(cx);
11095 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11096 s.select(selections)
11097 });
11098 } else {
11099 this.insert(&clipboard_text, window, cx);
11100 }
11101 });
11102 }
11103
11104 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11105 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11106 if let Some(item) = cx.read_from_clipboard() {
11107 let entries = item.entries();
11108
11109 match entries.first() {
11110 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11111 // of all the pasted entries.
11112 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11113 .do_paste(
11114 clipboard_string.text(),
11115 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11116 true,
11117 window,
11118 cx,
11119 ),
11120 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11121 }
11122 }
11123 }
11124
11125 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11126 if self.read_only(cx) {
11127 return;
11128 }
11129
11130 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11131
11132 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11133 if let Some((selections, _)) =
11134 self.selection_history.transaction(transaction_id).cloned()
11135 {
11136 self.change_selections(None, window, cx, |s| {
11137 s.select_anchors(selections.to_vec());
11138 });
11139 } else {
11140 log::error!(
11141 "No entry in selection_history found for undo. \
11142 This may correspond to a bug where undo does not update the selection. \
11143 If this is occurring, please add details to \
11144 https://github.com/zed-industries/zed/issues/22692"
11145 );
11146 }
11147 self.request_autoscroll(Autoscroll::fit(), cx);
11148 self.unmark_text(window, cx);
11149 self.refresh_inline_completion(true, false, window, cx);
11150 cx.emit(EditorEvent::Edited { transaction_id });
11151 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11152 }
11153 }
11154
11155 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11156 if self.read_only(cx) {
11157 return;
11158 }
11159
11160 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11161
11162 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11163 if let Some((_, Some(selections))) =
11164 self.selection_history.transaction(transaction_id).cloned()
11165 {
11166 self.change_selections(None, window, cx, |s| {
11167 s.select_anchors(selections.to_vec());
11168 });
11169 } else {
11170 log::error!(
11171 "No entry in selection_history found for redo. \
11172 This may correspond to a bug where undo does not update the selection. \
11173 If this is occurring, please add details to \
11174 https://github.com/zed-industries/zed/issues/22692"
11175 );
11176 }
11177 self.request_autoscroll(Autoscroll::fit(), cx);
11178 self.unmark_text(window, cx);
11179 self.refresh_inline_completion(true, false, window, cx);
11180 cx.emit(EditorEvent::Edited { transaction_id });
11181 }
11182 }
11183
11184 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11185 self.buffer
11186 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11187 }
11188
11189 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11190 self.buffer
11191 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11192 }
11193
11194 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11195 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11196 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11197 s.move_with(|map, selection| {
11198 let cursor = if selection.is_empty() {
11199 movement::left(map, selection.start)
11200 } else {
11201 selection.start
11202 };
11203 selection.collapse_to(cursor, SelectionGoal::None);
11204 });
11205 })
11206 }
11207
11208 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11209 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11210 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11211 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11212 })
11213 }
11214
11215 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11216 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11217 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11218 s.move_with(|map, selection| {
11219 let cursor = if selection.is_empty() {
11220 movement::right(map, selection.end)
11221 } else {
11222 selection.end
11223 };
11224 selection.collapse_to(cursor, SelectionGoal::None)
11225 });
11226 })
11227 }
11228
11229 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11230 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11231 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11232 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11233 })
11234 }
11235
11236 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11237 if self.take_rename(true, window, cx).is_some() {
11238 return;
11239 }
11240
11241 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11242 cx.propagate();
11243 return;
11244 }
11245
11246 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11247
11248 let text_layout_details = &self.text_layout_details(window);
11249 let selection_count = self.selections.count();
11250 let first_selection = self.selections.first_anchor();
11251
11252 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11253 s.move_with(|map, selection| {
11254 if !selection.is_empty() {
11255 selection.goal = SelectionGoal::None;
11256 }
11257 let (cursor, goal) = movement::up(
11258 map,
11259 selection.start,
11260 selection.goal,
11261 false,
11262 text_layout_details,
11263 );
11264 selection.collapse_to(cursor, goal);
11265 });
11266 });
11267
11268 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11269 {
11270 cx.propagate();
11271 }
11272 }
11273
11274 pub fn move_up_by_lines(
11275 &mut self,
11276 action: &MoveUpByLines,
11277 window: &mut Window,
11278 cx: &mut Context<Self>,
11279 ) {
11280 if self.take_rename(true, window, cx).is_some() {
11281 return;
11282 }
11283
11284 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11285 cx.propagate();
11286 return;
11287 }
11288
11289 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11290
11291 let text_layout_details = &self.text_layout_details(window);
11292
11293 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11294 s.move_with(|map, selection| {
11295 if !selection.is_empty() {
11296 selection.goal = SelectionGoal::None;
11297 }
11298 let (cursor, goal) = movement::up_by_rows(
11299 map,
11300 selection.start,
11301 action.lines,
11302 selection.goal,
11303 false,
11304 text_layout_details,
11305 );
11306 selection.collapse_to(cursor, goal);
11307 });
11308 })
11309 }
11310
11311 pub fn move_down_by_lines(
11312 &mut self,
11313 action: &MoveDownByLines,
11314 window: &mut Window,
11315 cx: &mut Context<Self>,
11316 ) {
11317 if self.take_rename(true, window, cx).is_some() {
11318 return;
11319 }
11320
11321 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11322 cx.propagate();
11323 return;
11324 }
11325
11326 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11327
11328 let text_layout_details = &self.text_layout_details(window);
11329
11330 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11331 s.move_with(|map, selection| {
11332 if !selection.is_empty() {
11333 selection.goal = SelectionGoal::None;
11334 }
11335 let (cursor, goal) = movement::down_by_rows(
11336 map,
11337 selection.start,
11338 action.lines,
11339 selection.goal,
11340 false,
11341 text_layout_details,
11342 );
11343 selection.collapse_to(cursor, goal);
11344 });
11345 })
11346 }
11347
11348 pub fn select_down_by_lines(
11349 &mut self,
11350 action: &SelectDownByLines,
11351 window: &mut Window,
11352 cx: &mut Context<Self>,
11353 ) {
11354 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11355 let text_layout_details = &self.text_layout_details(window);
11356 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11357 s.move_heads_with(|map, head, goal| {
11358 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11359 })
11360 })
11361 }
11362
11363 pub fn select_up_by_lines(
11364 &mut self,
11365 action: &SelectUpByLines,
11366 window: &mut Window,
11367 cx: &mut Context<Self>,
11368 ) {
11369 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11370 let text_layout_details = &self.text_layout_details(window);
11371 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11372 s.move_heads_with(|map, head, goal| {
11373 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11374 })
11375 })
11376 }
11377
11378 pub fn select_page_up(
11379 &mut self,
11380 _: &SelectPageUp,
11381 window: &mut Window,
11382 cx: &mut Context<Self>,
11383 ) {
11384 let Some(row_count) = self.visible_row_count() else {
11385 return;
11386 };
11387
11388 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11389
11390 let text_layout_details = &self.text_layout_details(window);
11391
11392 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11393 s.move_heads_with(|map, head, goal| {
11394 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11395 })
11396 })
11397 }
11398
11399 pub fn move_page_up(
11400 &mut self,
11401 action: &MovePageUp,
11402 window: &mut Window,
11403 cx: &mut Context<Self>,
11404 ) {
11405 if self.take_rename(true, window, cx).is_some() {
11406 return;
11407 }
11408
11409 if self
11410 .context_menu
11411 .borrow_mut()
11412 .as_mut()
11413 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11414 .unwrap_or(false)
11415 {
11416 return;
11417 }
11418
11419 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11420 cx.propagate();
11421 return;
11422 }
11423
11424 let Some(row_count) = self.visible_row_count() else {
11425 return;
11426 };
11427
11428 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11429
11430 let autoscroll = if action.center_cursor {
11431 Autoscroll::center()
11432 } else {
11433 Autoscroll::fit()
11434 };
11435
11436 let text_layout_details = &self.text_layout_details(window);
11437
11438 self.change_selections(Some(autoscroll), window, cx, |s| {
11439 s.move_with(|map, selection| {
11440 if !selection.is_empty() {
11441 selection.goal = SelectionGoal::None;
11442 }
11443 let (cursor, goal) = movement::up_by_rows(
11444 map,
11445 selection.end,
11446 row_count,
11447 selection.goal,
11448 false,
11449 text_layout_details,
11450 );
11451 selection.collapse_to(cursor, goal);
11452 });
11453 });
11454 }
11455
11456 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11457 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11458 let text_layout_details = &self.text_layout_details(window);
11459 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11460 s.move_heads_with(|map, head, goal| {
11461 movement::up(map, head, goal, false, text_layout_details)
11462 })
11463 })
11464 }
11465
11466 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11467 self.take_rename(true, window, cx);
11468
11469 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11470 cx.propagate();
11471 return;
11472 }
11473
11474 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11475
11476 let text_layout_details = &self.text_layout_details(window);
11477 let selection_count = self.selections.count();
11478 let first_selection = self.selections.first_anchor();
11479
11480 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11481 s.move_with(|map, selection| {
11482 if !selection.is_empty() {
11483 selection.goal = SelectionGoal::None;
11484 }
11485 let (cursor, goal) = movement::down(
11486 map,
11487 selection.end,
11488 selection.goal,
11489 false,
11490 text_layout_details,
11491 );
11492 selection.collapse_to(cursor, goal);
11493 });
11494 });
11495
11496 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11497 {
11498 cx.propagate();
11499 }
11500 }
11501
11502 pub fn select_page_down(
11503 &mut self,
11504 _: &SelectPageDown,
11505 window: &mut Window,
11506 cx: &mut Context<Self>,
11507 ) {
11508 let Some(row_count) = self.visible_row_count() else {
11509 return;
11510 };
11511
11512 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11513
11514 let text_layout_details = &self.text_layout_details(window);
11515
11516 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11517 s.move_heads_with(|map, head, goal| {
11518 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11519 })
11520 })
11521 }
11522
11523 pub fn move_page_down(
11524 &mut self,
11525 action: &MovePageDown,
11526 window: &mut Window,
11527 cx: &mut Context<Self>,
11528 ) {
11529 if self.take_rename(true, window, cx).is_some() {
11530 return;
11531 }
11532
11533 if self
11534 .context_menu
11535 .borrow_mut()
11536 .as_mut()
11537 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11538 .unwrap_or(false)
11539 {
11540 return;
11541 }
11542
11543 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11544 cx.propagate();
11545 return;
11546 }
11547
11548 let Some(row_count) = self.visible_row_count() else {
11549 return;
11550 };
11551
11552 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11553
11554 let autoscroll = if action.center_cursor {
11555 Autoscroll::center()
11556 } else {
11557 Autoscroll::fit()
11558 };
11559
11560 let text_layout_details = &self.text_layout_details(window);
11561 self.change_selections(Some(autoscroll), window, cx, |s| {
11562 s.move_with(|map, selection| {
11563 if !selection.is_empty() {
11564 selection.goal = SelectionGoal::None;
11565 }
11566 let (cursor, goal) = movement::down_by_rows(
11567 map,
11568 selection.end,
11569 row_count,
11570 selection.goal,
11571 false,
11572 text_layout_details,
11573 );
11574 selection.collapse_to(cursor, goal);
11575 });
11576 });
11577 }
11578
11579 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11580 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11581 let text_layout_details = &self.text_layout_details(window);
11582 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11583 s.move_heads_with(|map, head, goal| {
11584 movement::down(map, head, goal, false, text_layout_details)
11585 })
11586 });
11587 }
11588
11589 pub fn context_menu_first(
11590 &mut self,
11591 _: &ContextMenuFirst,
11592 window: &mut Window,
11593 cx: &mut Context<Self>,
11594 ) {
11595 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11596 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11597 }
11598 }
11599
11600 pub fn context_menu_prev(
11601 &mut self,
11602 _: &ContextMenuPrevious,
11603 window: &mut Window,
11604 cx: &mut Context<Self>,
11605 ) {
11606 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11607 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11608 }
11609 }
11610
11611 pub fn context_menu_next(
11612 &mut self,
11613 _: &ContextMenuNext,
11614 window: &mut Window,
11615 cx: &mut Context<Self>,
11616 ) {
11617 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11618 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11619 }
11620 }
11621
11622 pub fn context_menu_last(
11623 &mut self,
11624 _: &ContextMenuLast,
11625 window: &mut Window,
11626 cx: &mut Context<Self>,
11627 ) {
11628 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11629 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11630 }
11631 }
11632
11633 pub fn move_to_previous_word_start(
11634 &mut self,
11635 _: &MoveToPreviousWordStart,
11636 window: &mut Window,
11637 cx: &mut Context<Self>,
11638 ) {
11639 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11640 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11641 s.move_cursors_with(|map, head, _| {
11642 (
11643 movement::previous_word_start(map, head),
11644 SelectionGoal::None,
11645 )
11646 });
11647 })
11648 }
11649
11650 pub fn move_to_previous_subword_start(
11651 &mut self,
11652 _: &MoveToPreviousSubwordStart,
11653 window: &mut Window,
11654 cx: &mut Context<Self>,
11655 ) {
11656 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11657 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11658 s.move_cursors_with(|map, head, _| {
11659 (
11660 movement::previous_subword_start(map, head),
11661 SelectionGoal::None,
11662 )
11663 });
11664 })
11665 }
11666
11667 pub fn select_to_previous_word_start(
11668 &mut self,
11669 _: &SelectToPreviousWordStart,
11670 window: &mut Window,
11671 cx: &mut Context<Self>,
11672 ) {
11673 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11674 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11675 s.move_heads_with(|map, head, _| {
11676 (
11677 movement::previous_word_start(map, head),
11678 SelectionGoal::None,
11679 )
11680 });
11681 })
11682 }
11683
11684 pub fn select_to_previous_subword_start(
11685 &mut self,
11686 _: &SelectToPreviousSubwordStart,
11687 window: &mut Window,
11688 cx: &mut Context<Self>,
11689 ) {
11690 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11691 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11692 s.move_heads_with(|map, head, _| {
11693 (
11694 movement::previous_subword_start(map, head),
11695 SelectionGoal::None,
11696 )
11697 });
11698 })
11699 }
11700
11701 pub fn delete_to_previous_word_start(
11702 &mut self,
11703 action: &DeleteToPreviousWordStart,
11704 window: &mut Window,
11705 cx: &mut Context<Self>,
11706 ) {
11707 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11708 self.transact(window, cx, |this, window, cx| {
11709 this.select_autoclose_pair(window, cx);
11710 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11711 s.move_with(|map, selection| {
11712 if selection.is_empty() {
11713 let cursor = if action.ignore_newlines {
11714 movement::previous_word_start(map, selection.head())
11715 } else {
11716 movement::previous_word_start_or_newline(map, selection.head())
11717 };
11718 selection.set_head(cursor, SelectionGoal::None);
11719 }
11720 });
11721 });
11722 this.insert("", window, cx);
11723 });
11724 }
11725
11726 pub fn delete_to_previous_subword_start(
11727 &mut self,
11728 _: &DeleteToPreviousSubwordStart,
11729 window: &mut Window,
11730 cx: &mut Context<Self>,
11731 ) {
11732 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11733 self.transact(window, cx, |this, window, cx| {
11734 this.select_autoclose_pair(window, cx);
11735 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11736 s.move_with(|map, selection| {
11737 if selection.is_empty() {
11738 let cursor = movement::previous_subword_start(map, selection.head());
11739 selection.set_head(cursor, SelectionGoal::None);
11740 }
11741 });
11742 });
11743 this.insert("", window, cx);
11744 });
11745 }
11746
11747 pub fn move_to_next_word_end(
11748 &mut self,
11749 _: &MoveToNextWordEnd,
11750 window: &mut Window,
11751 cx: &mut Context<Self>,
11752 ) {
11753 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11754 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11755 s.move_cursors_with(|map, head, _| {
11756 (movement::next_word_end(map, head), SelectionGoal::None)
11757 });
11758 })
11759 }
11760
11761 pub fn move_to_next_subword_end(
11762 &mut self,
11763 _: &MoveToNextSubwordEnd,
11764 window: &mut Window,
11765 cx: &mut Context<Self>,
11766 ) {
11767 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11768 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11769 s.move_cursors_with(|map, head, _| {
11770 (movement::next_subword_end(map, head), SelectionGoal::None)
11771 });
11772 })
11773 }
11774
11775 pub fn select_to_next_word_end(
11776 &mut self,
11777 _: &SelectToNextWordEnd,
11778 window: &mut Window,
11779 cx: &mut Context<Self>,
11780 ) {
11781 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11782 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11783 s.move_heads_with(|map, head, _| {
11784 (movement::next_word_end(map, head), SelectionGoal::None)
11785 });
11786 })
11787 }
11788
11789 pub fn select_to_next_subword_end(
11790 &mut self,
11791 _: &SelectToNextSubwordEnd,
11792 window: &mut Window,
11793 cx: &mut Context<Self>,
11794 ) {
11795 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11796 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11797 s.move_heads_with(|map, head, _| {
11798 (movement::next_subword_end(map, head), SelectionGoal::None)
11799 });
11800 })
11801 }
11802
11803 pub fn delete_to_next_word_end(
11804 &mut self,
11805 action: &DeleteToNextWordEnd,
11806 window: &mut Window,
11807 cx: &mut Context<Self>,
11808 ) {
11809 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11810 self.transact(window, cx, |this, window, cx| {
11811 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11812 s.move_with(|map, selection| {
11813 if selection.is_empty() {
11814 let cursor = if action.ignore_newlines {
11815 movement::next_word_end(map, selection.head())
11816 } else {
11817 movement::next_word_end_or_newline(map, selection.head())
11818 };
11819 selection.set_head(cursor, SelectionGoal::None);
11820 }
11821 });
11822 });
11823 this.insert("", window, cx);
11824 });
11825 }
11826
11827 pub fn delete_to_next_subword_end(
11828 &mut self,
11829 _: &DeleteToNextSubwordEnd,
11830 window: &mut Window,
11831 cx: &mut Context<Self>,
11832 ) {
11833 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11834 self.transact(window, cx, |this, window, cx| {
11835 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11836 s.move_with(|map, selection| {
11837 if selection.is_empty() {
11838 let cursor = movement::next_subword_end(map, selection.head());
11839 selection.set_head(cursor, SelectionGoal::None);
11840 }
11841 });
11842 });
11843 this.insert("", window, cx);
11844 });
11845 }
11846
11847 pub fn move_to_beginning_of_line(
11848 &mut self,
11849 action: &MoveToBeginningOfLine,
11850 window: &mut Window,
11851 cx: &mut Context<Self>,
11852 ) {
11853 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11854 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11855 s.move_cursors_with(|map, head, _| {
11856 (
11857 movement::indented_line_beginning(
11858 map,
11859 head,
11860 action.stop_at_soft_wraps,
11861 action.stop_at_indent,
11862 ),
11863 SelectionGoal::None,
11864 )
11865 });
11866 })
11867 }
11868
11869 pub fn select_to_beginning_of_line(
11870 &mut self,
11871 action: &SelectToBeginningOfLine,
11872 window: &mut Window,
11873 cx: &mut Context<Self>,
11874 ) {
11875 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11876 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11877 s.move_heads_with(|map, head, _| {
11878 (
11879 movement::indented_line_beginning(
11880 map,
11881 head,
11882 action.stop_at_soft_wraps,
11883 action.stop_at_indent,
11884 ),
11885 SelectionGoal::None,
11886 )
11887 });
11888 });
11889 }
11890
11891 pub fn delete_to_beginning_of_line(
11892 &mut self,
11893 action: &DeleteToBeginningOfLine,
11894 window: &mut Window,
11895 cx: &mut Context<Self>,
11896 ) {
11897 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11898 self.transact(window, cx, |this, window, cx| {
11899 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11900 s.move_with(|_, selection| {
11901 selection.reversed = true;
11902 });
11903 });
11904
11905 this.select_to_beginning_of_line(
11906 &SelectToBeginningOfLine {
11907 stop_at_soft_wraps: false,
11908 stop_at_indent: action.stop_at_indent,
11909 },
11910 window,
11911 cx,
11912 );
11913 this.backspace(&Backspace, window, cx);
11914 });
11915 }
11916
11917 pub fn move_to_end_of_line(
11918 &mut self,
11919 action: &MoveToEndOfLine,
11920 window: &mut Window,
11921 cx: &mut Context<Self>,
11922 ) {
11923 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11924 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11925 s.move_cursors_with(|map, head, _| {
11926 (
11927 movement::line_end(map, head, action.stop_at_soft_wraps),
11928 SelectionGoal::None,
11929 )
11930 });
11931 })
11932 }
11933
11934 pub fn select_to_end_of_line(
11935 &mut self,
11936 action: &SelectToEndOfLine,
11937 window: &mut Window,
11938 cx: &mut Context<Self>,
11939 ) {
11940 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11941 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11942 s.move_heads_with(|map, head, _| {
11943 (
11944 movement::line_end(map, head, action.stop_at_soft_wraps),
11945 SelectionGoal::None,
11946 )
11947 });
11948 })
11949 }
11950
11951 pub fn delete_to_end_of_line(
11952 &mut self,
11953 _: &DeleteToEndOfLine,
11954 window: &mut Window,
11955 cx: &mut Context<Self>,
11956 ) {
11957 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11958 self.transact(window, cx, |this, window, cx| {
11959 this.select_to_end_of_line(
11960 &SelectToEndOfLine {
11961 stop_at_soft_wraps: false,
11962 },
11963 window,
11964 cx,
11965 );
11966 this.delete(&Delete, window, cx);
11967 });
11968 }
11969
11970 pub fn cut_to_end_of_line(
11971 &mut self,
11972 _: &CutToEndOfLine,
11973 window: &mut Window,
11974 cx: &mut Context<Self>,
11975 ) {
11976 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11977 self.transact(window, cx, |this, window, cx| {
11978 this.select_to_end_of_line(
11979 &SelectToEndOfLine {
11980 stop_at_soft_wraps: false,
11981 },
11982 window,
11983 cx,
11984 );
11985 this.cut(&Cut, window, cx);
11986 });
11987 }
11988
11989 pub fn move_to_start_of_paragraph(
11990 &mut self,
11991 _: &MoveToStartOfParagraph,
11992 window: &mut Window,
11993 cx: &mut Context<Self>,
11994 ) {
11995 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11996 cx.propagate();
11997 return;
11998 }
11999 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12000 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12001 s.move_with(|map, selection| {
12002 selection.collapse_to(
12003 movement::start_of_paragraph(map, selection.head(), 1),
12004 SelectionGoal::None,
12005 )
12006 });
12007 })
12008 }
12009
12010 pub fn move_to_end_of_paragraph(
12011 &mut self,
12012 _: &MoveToEndOfParagraph,
12013 window: &mut Window,
12014 cx: &mut Context<Self>,
12015 ) {
12016 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12017 cx.propagate();
12018 return;
12019 }
12020 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12021 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12022 s.move_with(|map, selection| {
12023 selection.collapse_to(
12024 movement::end_of_paragraph(map, selection.head(), 1),
12025 SelectionGoal::None,
12026 )
12027 });
12028 })
12029 }
12030
12031 pub fn select_to_start_of_paragraph(
12032 &mut self,
12033 _: &SelectToStartOfParagraph,
12034 window: &mut Window,
12035 cx: &mut Context<Self>,
12036 ) {
12037 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12038 cx.propagate();
12039 return;
12040 }
12041 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12042 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12043 s.move_heads_with(|map, head, _| {
12044 (
12045 movement::start_of_paragraph(map, head, 1),
12046 SelectionGoal::None,
12047 )
12048 });
12049 })
12050 }
12051
12052 pub fn select_to_end_of_paragraph(
12053 &mut self,
12054 _: &SelectToEndOfParagraph,
12055 window: &mut Window,
12056 cx: &mut Context<Self>,
12057 ) {
12058 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12059 cx.propagate();
12060 return;
12061 }
12062 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12063 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12064 s.move_heads_with(|map, head, _| {
12065 (
12066 movement::end_of_paragraph(map, head, 1),
12067 SelectionGoal::None,
12068 )
12069 });
12070 })
12071 }
12072
12073 pub fn move_to_start_of_excerpt(
12074 &mut self,
12075 _: &MoveToStartOfExcerpt,
12076 window: &mut Window,
12077 cx: &mut Context<Self>,
12078 ) {
12079 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12080 cx.propagate();
12081 return;
12082 }
12083 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12084 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12085 s.move_with(|map, selection| {
12086 selection.collapse_to(
12087 movement::start_of_excerpt(
12088 map,
12089 selection.head(),
12090 workspace::searchable::Direction::Prev,
12091 ),
12092 SelectionGoal::None,
12093 )
12094 });
12095 })
12096 }
12097
12098 pub fn move_to_start_of_next_excerpt(
12099 &mut self,
12100 _: &MoveToStartOfNextExcerpt,
12101 window: &mut Window,
12102 cx: &mut Context<Self>,
12103 ) {
12104 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12105 cx.propagate();
12106 return;
12107 }
12108
12109 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12110 s.move_with(|map, selection| {
12111 selection.collapse_to(
12112 movement::start_of_excerpt(
12113 map,
12114 selection.head(),
12115 workspace::searchable::Direction::Next,
12116 ),
12117 SelectionGoal::None,
12118 )
12119 });
12120 })
12121 }
12122
12123 pub fn move_to_end_of_excerpt(
12124 &mut self,
12125 _: &MoveToEndOfExcerpt,
12126 window: &mut Window,
12127 cx: &mut Context<Self>,
12128 ) {
12129 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12130 cx.propagate();
12131 return;
12132 }
12133 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12134 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12135 s.move_with(|map, selection| {
12136 selection.collapse_to(
12137 movement::end_of_excerpt(
12138 map,
12139 selection.head(),
12140 workspace::searchable::Direction::Next,
12141 ),
12142 SelectionGoal::None,
12143 )
12144 });
12145 })
12146 }
12147
12148 pub fn move_to_end_of_previous_excerpt(
12149 &mut self,
12150 _: &MoveToEndOfPreviousExcerpt,
12151 window: &mut Window,
12152 cx: &mut Context<Self>,
12153 ) {
12154 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12155 cx.propagate();
12156 return;
12157 }
12158 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12159 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12160 s.move_with(|map, selection| {
12161 selection.collapse_to(
12162 movement::end_of_excerpt(
12163 map,
12164 selection.head(),
12165 workspace::searchable::Direction::Prev,
12166 ),
12167 SelectionGoal::None,
12168 )
12169 });
12170 })
12171 }
12172
12173 pub fn select_to_start_of_excerpt(
12174 &mut self,
12175 _: &SelectToStartOfExcerpt,
12176 window: &mut Window,
12177 cx: &mut Context<Self>,
12178 ) {
12179 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12180 cx.propagate();
12181 return;
12182 }
12183 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12184 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12185 s.move_heads_with(|map, head, _| {
12186 (
12187 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12188 SelectionGoal::None,
12189 )
12190 });
12191 })
12192 }
12193
12194 pub fn select_to_start_of_next_excerpt(
12195 &mut self,
12196 _: &SelectToStartOfNextExcerpt,
12197 window: &mut Window,
12198 cx: &mut Context<Self>,
12199 ) {
12200 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12201 cx.propagate();
12202 return;
12203 }
12204 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12205 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12206 s.move_heads_with(|map, head, _| {
12207 (
12208 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12209 SelectionGoal::None,
12210 )
12211 });
12212 })
12213 }
12214
12215 pub fn select_to_end_of_excerpt(
12216 &mut self,
12217 _: &SelectToEndOfExcerpt,
12218 window: &mut Window,
12219 cx: &mut Context<Self>,
12220 ) {
12221 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12222 cx.propagate();
12223 return;
12224 }
12225 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12226 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12227 s.move_heads_with(|map, head, _| {
12228 (
12229 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12230 SelectionGoal::None,
12231 )
12232 });
12233 })
12234 }
12235
12236 pub fn select_to_end_of_previous_excerpt(
12237 &mut self,
12238 _: &SelectToEndOfPreviousExcerpt,
12239 window: &mut Window,
12240 cx: &mut Context<Self>,
12241 ) {
12242 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12243 cx.propagate();
12244 return;
12245 }
12246 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12247 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12248 s.move_heads_with(|map, head, _| {
12249 (
12250 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12251 SelectionGoal::None,
12252 )
12253 });
12254 })
12255 }
12256
12257 pub fn move_to_beginning(
12258 &mut self,
12259 _: &MoveToBeginning,
12260 window: &mut Window,
12261 cx: &mut Context<Self>,
12262 ) {
12263 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12264 cx.propagate();
12265 return;
12266 }
12267 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12268 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12269 s.select_ranges(vec![0..0]);
12270 });
12271 }
12272
12273 pub fn select_to_beginning(
12274 &mut self,
12275 _: &SelectToBeginning,
12276 window: &mut Window,
12277 cx: &mut Context<Self>,
12278 ) {
12279 let mut selection = self.selections.last::<Point>(cx);
12280 selection.set_head(Point::zero(), SelectionGoal::None);
12281 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12282 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12283 s.select(vec![selection]);
12284 });
12285 }
12286
12287 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12288 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12289 cx.propagate();
12290 return;
12291 }
12292 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12293 let cursor = self.buffer.read(cx).read(cx).len();
12294 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12295 s.select_ranges(vec![cursor..cursor])
12296 });
12297 }
12298
12299 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12300 self.nav_history = nav_history;
12301 }
12302
12303 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12304 self.nav_history.as_ref()
12305 }
12306
12307 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12308 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12309 }
12310
12311 fn push_to_nav_history(
12312 &mut self,
12313 cursor_anchor: Anchor,
12314 new_position: Option<Point>,
12315 is_deactivate: bool,
12316 cx: &mut Context<Self>,
12317 ) {
12318 if let Some(nav_history) = self.nav_history.as_mut() {
12319 let buffer = self.buffer.read(cx).read(cx);
12320 let cursor_position = cursor_anchor.to_point(&buffer);
12321 let scroll_state = self.scroll_manager.anchor();
12322 let scroll_top_row = scroll_state.top_row(&buffer);
12323 drop(buffer);
12324
12325 if let Some(new_position) = new_position {
12326 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12327 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12328 return;
12329 }
12330 }
12331
12332 nav_history.push(
12333 Some(NavigationData {
12334 cursor_anchor,
12335 cursor_position,
12336 scroll_anchor: scroll_state,
12337 scroll_top_row,
12338 }),
12339 cx,
12340 );
12341 cx.emit(EditorEvent::PushedToNavHistory {
12342 anchor: cursor_anchor,
12343 is_deactivate,
12344 })
12345 }
12346 }
12347
12348 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12349 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12350 let buffer = self.buffer.read(cx).snapshot(cx);
12351 let mut selection = self.selections.first::<usize>(cx);
12352 selection.set_head(buffer.len(), SelectionGoal::None);
12353 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12354 s.select(vec![selection]);
12355 });
12356 }
12357
12358 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12359 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12360 let end = self.buffer.read(cx).read(cx).len();
12361 self.change_selections(None, window, cx, |s| {
12362 s.select_ranges(vec![0..end]);
12363 });
12364 }
12365
12366 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12367 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12368 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12369 let mut selections = self.selections.all::<Point>(cx);
12370 let max_point = display_map.buffer_snapshot.max_point();
12371 for selection in &mut selections {
12372 let rows = selection.spanned_rows(true, &display_map);
12373 selection.start = Point::new(rows.start.0, 0);
12374 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12375 selection.reversed = false;
12376 }
12377 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12378 s.select(selections);
12379 });
12380 }
12381
12382 pub fn split_selection_into_lines(
12383 &mut self,
12384 _: &SplitSelectionIntoLines,
12385 window: &mut Window,
12386 cx: &mut Context<Self>,
12387 ) {
12388 let selections = self
12389 .selections
12390 .all::<Point>(cx)
12391 .into_iter()
12392 .map(|selection| selection.start..selection.end)
12393 .collect::<Vec<_>>();
12394 self.unfold_ranges(&selections, true, true, cx);
12395
12396 let mut new_selection_ranges = Vec::new();
12397 {
12398 let buffer = self.buffer.read(cx).read(cx);
12399 for selection in selections {
12400 for row in selection.start.row..selection.end.row {
12401 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12402 new_selection_ranges.push(cursor..cursor);
12403 }
12404
12405 let is_multiline_selection = selection.start.row != selection.end.row;
12406 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12407 // so this action feels more ergonomic when paired with other selection operations
12408 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12409 if !should_skip_last {
12410 new_selection_ranges.push(selection.end..selection.end);
12411 }
12412 }
12413 }
12414 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12415 s.select_ranges(new_selection_ranges);
12416 });
12417 }
12418
12419 pub fn add_selection_above(
12420 &mut self,
12421 _: &AddSelectionAbove,
12422 window: &mut Window,
12423 cx: &mut Context<Self>,
12424 ) {
12425 self.add_selection(true, window, cx);
12426 }
12427
12428 pub fn add_selection_below(
12429 &mut self,
12430 _: &AddSelectionBelow,
12431 window: &mut Window,
12432 cx: &mut Context<Self>,
12433 ) {
12434 self.add_selection(false, window, cx);
12435 }
12436
12437 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12438 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12439
12440 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12441 let mut selections = self.selections.all::<Point>(cx);
12442 let text_layout_details = self.text_layout_details(window);
12443 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12444 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12445 let range = oldest_selection.display_range(&display_map).sorted();
12446
12447 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12448 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12449 let positions = start_x.min(end_x)..start_x.max(end_x);
12450
12451 selections.clear();
12452 let mut stack = Vec::new();
12453 for row in range.start.row().0..=range.end.row().0 {
12454 if let Some(selection) = self.selections.build_columnar_selection(
12455 &display_map,
12456 DisplayRow(row),
12457 &positions,
12458 oldest_selection.reversed,
12459 &text_layout_details,
12460 ) {
12461 stack.push(selection.id);
12462 selections.push(selection);
12463 }
12464 }
12465
12466 if above {
12467 stack.reverse();
12468 }
12469
12470 AddSelectionsState { above, stack }
12471 });
12472
12473 let last_added_selection = *state.stack.last().unwrap();
12474 let mut new_selections = Vec::new();
12475 if above == state.above {
12476 let end_row = if above {
12477 DisplayRow(0)
12478 } else {
12479 display_map.max_point().row()
12480 };
12481
12482 'outer: for selection in selections {
12483 if selection.id == last_added_selection {
12484 let range = selection.display_range(&display_map).sorted();
12485 debug_assert_eq!(range.start.row(), range.end.row());
12486 let mut row = range.start.row();
12487 let positions =
12488 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12489 px(start)..px(end)
12490 } else {
12491 let start_x =
12492 display_map.x_for_display_point(range.start, &text_layout_details);
12493 let end_x =
12494 display_map.x_for_display_point(range.end, &text_layout_details);
12495 start_x.min(end_x)..start_x.max(end_x)
12496 };
12497
12498 while row != end_row {
12499 if above {
12500 row.0 -= 1;
12501 } else {
12502 row.0 += 1;
12503 }
12504
12505 if let Some(new_selection) = self.selections.build_columnar_selection(
12506 &display_map,
12507 row,
12508 &positions,
12509 selection.reversed,
12510 &text_layout_details,
12511 ) {
12512 state.stack.push(new_selection.id);
12513 if above {
12514 new_selections.push(new_selection);
12515 new_selections.push(selection);
12516 } else {
12517 new_selections.push(selection);
12518 new_selections.push(new_selection);
12519 }
12520
12521 continue 'outer;
12522 }
12523 }
12524 }
12525
12526 new_selections.push(selection);
12527 }
12528 } else {
12529 new_selections = selections;
12530 new_selections.retain(|s| s.id != last_added_selection);
12531 state.stack.pop();
12532 }
12533
12534 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12535 s.select(new_selections);
12536 });
12537 if state.stack.len() > 1 {
12538 self.add_selections_state = Some(state);
12539 }
12540 }
12541
12542 fn select_match_ranges(
12543 &mut self,
12544 range: Range<usize>,
12545 reversed: bool,
12546 replace_newest: bool,
12547 auto_scroll: Option<Autoscroll>,
12548 window: &mut Window,
12549 cx: &mut Context<Editor>,
12550 ) {
12551 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12552 self.change_selections(auto_scroll, window, cx, |s| {
12553 if replace_newest {
12554 s.delete(s.newest_anchor().id);
12555 }
12556 if reversed {
12557 s.insert_range(range.end..range.start);
12558 } else {
12559 s.insert_range(range);
12560 }
12561 });
12562 }
12563
12564 pub fn select_next_match_internal(
12565 &mut self,
12566 display_map: &DisplaySnapshot,
12567 replace_newest: bool,
12568 autoscroll: Option<Autoscroll>,
12569 window: &mut Window,
12570 cx: &mut Context<Self>,
12571 ) -> Result<()> {
12572 let buffer = &display_map.buffer_snapshot;
12573 let mut selections = self.selections.all::<usize>(cx);
12574 if let Some(mut select_next_state) = self.select_next_state.take() {
12575 let query = &select_next_state.query;
12576 if !select_next_state.done {
12577 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12578 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12579 let mut next_selected_range = None;
12580
12581 let bytes_after_last_selection =
12582 buffer.bytes_in_range(last_selection.end..buffer.len());
12583 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12584 let query_matches = query
12585 .stream_find_iter(bytes_after_last_selection)
12586 .map(|result| (last_selection.end, result))
12587 .chain(
12588 query
12589 .stream_find_iter(bytes_before_first_selection)
12590 .map(|result| (0, result)),
12591 );
12592
12593 for (start_offset, query_match) in query_matches {
12594 let query_match = query_match.unwrap(); // can only fail due to I/O
12595 let offset_range =
12596 start_offset + query_match.start()..start_offset + query_match.end();
12597 let display_range = offset_range.start.to_display_point(display_map)
12598 ..offset_range.end.to_display_point(display_map);
12599
12600 if !select_next_state.wordwise
12601 || (!movement::is_inside_word(display_map, display_range.start)
12602 && !movement::is_inside_word(display_map, display_range.end))
12603 {
12604 // TODO: This is n^2, because we might check all the selections
12605 if !selections
12606 .iter()
12607 .any(|selection| selection.range().overlaps(&offset_range))
12608 {
12609 next_selected_range = Some(offset_range);
12610 break;
12611 }
12612 }
12613 }
12614
12615 if let Some(next_selected_range) = next_selected_range {
12616 self.select_match_ranges(
12617 next_selected_range,
12618 last_selection.reversed,
12619 replace_newest,
12620 autoscroll,
12621 window,
12622 cx,
12623 );
12624 } else {
12625 select_next_state.done = true;
12626 }
12627 }
12628
12629 self.select_next_state = Some(select_next_state);
12630 } else {
12631 let mut only_carets = true;
12632 let mut same_text_selected = true;
12633 let mut selected_text = None;
12634
12635 let mut selections_iter = selections.iter().peekable();
12636 while let Some(selection) = selections_iter.next() {
12637 if selection.start != selection.end {
12638 only_carets = false;
12639 }
12640
12641 if same_text_selected {
12642 if selected_text.is_none() {
12643 selected_text =
12644 Some(buffer.text_for_range(selection.range()).collect::<String>());
12645 }
12646
12647 if let Some(next_selection) = selections_iter.peek() {
12648 if next_selection.range().len() == selection.range().len() {
12649 let next_selected_text = buffer
12650 .text_for_range(next_selection.range())
12651 .collect::<String>();
12652 if Some(next_selected_text) != selected_text {
12653 same_text_selected = false;
12654 selected_text = None;
12655 }
12656 } else {
12657 same_text_selected = false;
12658 selected_text = None;
12659 }
12660 }
12661 }
12662 }
12663
12664 if only_carets {
12665 for selection in &mut selections {
12666 let word_range = movement::surrounding_word(
12667 display_map,
12668 selection.start.to_display_point(display_map),
12669 );
12670 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12671 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12672 selection.goal = SelectionGoal::None;
12673 selection.reversed = false;
12674 self.select_match_ranges(
12675 selection.start..selection.end,
12676 selection.reversed,
12677 replace_newest,
12678 autoscroll,
12679 window,
12680 cx,
12681 );
12682 }
12683
12684 if selections.len() == 1 {
12685 let selection = selections
12686 .last()
12687 .expect("ensured that there's only one selection");
12688 let query = buffer
12689 .text_for_range(selection.start..selection.end)
12690 .collect::<String>();
12691 let is_empty = query.is_empty();
12692 let select_state = SelectNextState {
12693 query: AhoCorasick::new(&[query])?,
12694 wordwise: true,
12695 done: is_empty,
12696 };
12697 self.select_next_state = Some(select_state);
12698 } else {
12699 self.select_next_state = None;
12700 }
12701 } else if let Some(selected_text) = selected_text {
12702 self.select_next_state = Some(SelectNextState {
12703 query: AhoCorasick::new(&[selected_text])?,
12704 wordwise: false,
12705 done: false,
12706 });
12707 self.select_next_match_internal(
12708 display_map,
12709 replace_newest,
12710 autoscroll,
12711 window,
12712 cx,
12713 )?;
12714 }
12715 }
12716 Ok(())
12717 }
12718
12719 pub fn select_all_matches(
12720 &mut self,
12721 _action: &SelectAllMatches,
12722 window: &mut Window,
12723 cx: &mut Context<Self>,
12724 ) -> Result<()> {
12725 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12726
12727 self.push_to_selection_history();
12728 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12729
12730 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12731 let Some(select_next_state) = self.select_next_state.as_mut() else {
12732 return Ok(());
12733 };
12734 if select_next_state.done {
12735 return Ok(());
12736 }
12737
12738 let mut new_selections = Vec::new();
12739
12740 let reversed = self.selections.oldest::<usize>(cx).reversed;
12741 let buffer = &display_map.buffer_snapshot;
12742 let query_matches = select_next_state
12743 .query
12744 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12745
12746 for query_match in query_matches.into_iter() {
12747 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12748 let offset_range = if reversed {
12749 query_match.end()..query_match.start()
12750 } else {
12751 query_match.start()..query_match.end()
12752 };
12753 let display_range = offset_range.start.to_display_point(&display_map)
12754 ..offset_range.end.to_display_point(&display_map);
12755
12756 if !select_next_state.wordwise
12757 || (!movement::is_inside_word(&display_map, display_range.start)
12758 && !movement::is_inside_word(&display_map, display_range.end))
12759 {
12760 new_selections.push(offset_range.start..offset_range.end);
12761 }
12762 }
12763
12764 select_next_state.done = true;
12765 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12766 self.change_selections(None, window, cx, |selections| {
12767 selections.select_ranges(new_selections)
12768 });
12769
12770 Ok(())
12771 }
12772
12773 pub fn select_next(
12774 &mut self,
12775 action: &SelectNext,
12776 window: &mut Window,
12777 cx: &mut Context<Self>,
12778 ) -> Result<()> {
12779 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12780 self.push_to_selection_history();
12781 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12782 self.select_next_match_internal(
12783 &display_map,
12784 action.replace_newest,
12785 Some(Autoscroll::newest()),
12786 window,
12787 cx,
12788 )?;
12789 Ok(())
12790 }
12791
12792 pub fn select_previous(
12793 &mut self,
12794 action: &SelectPrevious,
12795 window: &mut Window,
12796 cx: &mut Context<Self>,
12797 ) -> Result<()> {
12798 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12799 self.push_to_selection_history();
12800 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12801 let buffer = &display_map.buffer_snapshot;
12802 let mut selections = self.selections.all::<usize>(cx);
12803 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12804 let query = &select_prev_state.query;
12805 if !select_prev_state.done {
12806 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12807 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12808 let mut next_selected_range = None;
12809 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12810 let bytes_before_last_selection =
12811 buffer.reversed_bytes_in_range(0..last_selection.start);
12812 let bytes_after_first_selection =
12813 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12814 let query_matches = query
12815 .stream_find_iter(bytes_before_last_selection)
12816 .map(|result| (last_selection.start, result))
12817 .chain(
12818 query
12819 .stream_find_iter(bytes_after_first_selection)
12820 .map(|result| (buffer.len(), result)),
12821 );
12822 for (end_offset, query_match) in query_matches {
12823 let query_match = query_match.unwrap(); // can only fail due to I/O
12824 let offset_range =
12825 end_offset - query_match.end()..end_offset - query_match.start();
12826 let display_range = offset_range.start.to_display_point(&display_map)
12827 ..offset_range.end.to_display_point(&display_map);
12828
12829 if !select_prev_state.wordwise
12830 || (!movement::is_inside_word(&display_map, display_range.start)
12831 && !movement::is_inside_word(&display_map, display_range.end))
12832 {
12833 next_selected_range = Some(offset_range);
12834 break;
12835 }
12836 }
12837
12838 if let Some(next_selected_range) = next_selected_range {
12839 self.select_match_ranges(
12840 next_selected_range,
12841 last_selection.reversed,
12842 action.replace_newest,
12843 Some(Autoscroll::newest()),
12844 window,
12845 cx,
12846 );
12847 } else {
12848 select_prev_state.done = true;
12849 }
12850 }
12851
12852 self.select_prev_state = Some(select_prev_state);
12853 } else {
12854 let mut only_carets = true;
12855 let mut same_text_selected = true;
12856 let mut selected_text = None;
12857
12858 let mut selections_iter = selections.iter().peekable();
12859 while let Some(selection) = selections_iter.next() {
12860 if selection.start != selection.end {
12861 only_carets = false;
12862 }
12863
12864 if same_text_selected {
12865 if selected_text.is_none() {
12866 selected_text =
12867 Some(buffer.text_for_range(selection.range()).collect::<String>());
12868 }
12869
12870 if let Some(next_selection) = selections_iter.peek() {
12871 if next_selection.range().len() == selection.range().len() {
12872 let next_selected_text = buffer
12873 .text_for_range(next_selection.range())
12874 .collect::<String>();
12875 if Some(next_selected_text) != selected_text {
12876 same_text_selected = false;
12877 selected_text = None;
12878 }
12879 } else {
12880 same_text_selected = false;
12881 selected_text = None;
12882 }
12883 }
12884 }
12885 }
12886
12887 if only_carets {
12888 for selection in &mut selections {
12889 let word_range = movement::surrounding_word(
12890 &display_map,
12891 selection.start.to_display_point(&display_map),
12892 );
12893 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12894 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12895 selection.goal = SelectionGoal::None;
12896 selection.reversed = false;
12897 self.select_match_ranges(
12898 selection.start..selection.end,
12899 selection.reversed,
12900 action.replace_newest,
12901 Some(Autoscroll::newest()),
12902 window,
12903 cx,
12904 );
12905 }
12906 if selections.len() == 1 {
12907 let selection = selections
12908 .last()
12909 .expect("ensured that there's only one selection");
12910 let query = buffer
12911 .text_for_range(selection.start..selection.end)
12912 .collect::<String>();
12913 let is_empty = query.is_empty();
12914 let select_state = SelectNextState {
12915 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12916 wordwise: true,
12917 done: is_empty,
12918 };
12919 self.select_prev_state = Some(select_state);
12920 } else {
12921 self.select_prev_state = None;
12922 }
12923 } else if let Some(selected_text) = selected_text {
12924 self.select_prev_state = Some(SelectNextState {
12925 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12926 wordwise: false,
12927 done: false,
12928 });
12929 self.select_previous(action, window, cx)?;
12930 }
12931 }
12932 Ok(())
12933 }
12934
12935 pub fn find_next_match(
12936 &mut self,
12937 _: &FindNextMatch,
12938 window: &mut Window,
12939 cx: &mut Context<Self>,
12940 ) -> Result<()> {
12941 let selections = self.selections.disjoint_anchors();
12942 match selections.first() {
12943 Some(first) if selections.len() >= 2 => {
12944 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12945 s.select_ranges([first.range()]);
12946 });
12947 }
12948 _ => self.select_next(
12949 &SelectNext {
12950 replace_newest: true,
12951 },
12952 window,
12953 cx,
12954 )?,
12955 }
12956 Ok(())
12957 }
12958
12959 pub fn find_previous_match(
12960 &mut self,
12961 _: &FindPreviousMatch,
12962 window: &mut Window,
12963 cx: &mut Context<Self>,
12964 ) -> Result<()> {
12965 let selections = self.selections.disjoint_anchors();
12966 match selections.last() {
12967 Some(last) if selections.len() >= 2 => {
12968 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12969 s.select_ranges([last.range()]);
12970 });
12971 }
12972 _ => self.select_previous(
12973 &SelectPrevious {
12974 replace_newest: true,
12975 },
12976 window,
12977 cx,
12978 )?,
12979 }
12980 Ok(())
12981 }
12982
12983 pub fn toggle_comments(
12984 &mut self,
12985 action: &ToggleComments,
12986 window: &mut Window,
12987 cx: &mut Context<Self>,
12988 ) {
12989 if self.read_only(cx) {
12990 return;
12991 }
12992 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12993 let text_layout_details = &self.text_layout_details(window);
12994 self.transact(window, cx, |this, window, cx| {
12995 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12996 let mut edits = Vec::new();
12997 let mut selection_edit_ranges = Vec::new();
12998 let mut last_toggled_row = None;
12999 let snapshot = this.buffer.read(cx).read(cx);
13000 let empty_str: Arc<str> = Arc::default();
13001 let mut suffixes_inserted = Vec::new();
13002 let ignore_indent = action.ignore_indent;
13003
13004 fn comment_prefix_range(
13005 snapshot: &MultiBufferSnapshot,
13006 row: MultiBufferRow,
13007 comment_prefix: &str,
13008 comment_prefix_whitespace: &str,
13009 ignore_indent: bool,
13010 ) -> Range<Point> {
13011 let indent_size = if ignore_indent {
13012 0
13013 } else {
13014 snapshot.indent_size_for_line(row).len
13015 };
13016
13017 let start = Point::new(row.0, indent_size);
13018
13019 let mut line_bytes = snapshot
13020 .bytes_in_range(start..snapshot.max_point())
13021 .flatten()
13022 .copied();
13023
13024 // If this line currently begins with the line comment prefix, then record
13025 // the range containing the prefix.
13026 if line_bytes
13027 .by_ref()
13028 .take(comment_prefix.len())
13029 .eq(comment_prefix.bytes())
13030 {
13031 // Include any whitespace that matches the comment prefix.
13032 let matching_whitespace_len = line_bytes
13033 .zip(comment_prefix_whitespace.bytes())
13034 .take_while(|(a, b)| a == b)
13035 .count() as u32;
13036 let end = Point::new(
13037 start.row,
13038 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13039 );
13040 start..end
13041 } else {
13042 start..start
13043 }
13044 }
13045
13046 fn comment_suffix_range(
13047 snapshot: &MultiBufferSnapshot,
13048 row: MultiBufferRow,
13049 comment_suffix: &str,
13050 comment_suffix_has_leading_space: bool,
13051 ) -> Range<Point> {
13052 let end = Point::new(row.0, snapshot.line_len(row));
13053 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13054
13055 let mut line_end_bytes = snapshot
13056 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13057 .flatten()
13058 .copied();
13059
13060 let leading_space_len = if suffix_start_column > 0
13061 && line_end_bytes.next() == Some(b' ')
13062 && comment_suffix_has_leading_space
13063 {
13064 1
13065 } else {
13066 0
13067 };
13068
13069 // If this line currently begins with the line comment prefix, then record
13070 // the range containing the prefix.
13071 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13072 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13073 start..end
13074 } else {
13075 end..end
13076 }
13077 }
13078
13079 // TODO: Handle selections that cross excerpts
13080 for selection in &mut selections {
13081 let start_column = snapshot
13082 .indent_size_for_line(MultiBufferRow(selection.start.row))
13083 .len;
13084 let language = if let Some(language) =
13085 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13086 {
13087 language
13088 } else {
13089 continue;
13090 };
13091
13092 selection_edit_ranges.clear();
13093
13094 // If multiple selections contain a given row, avoid processing that
13095 // row more than once.
13096 let mut start_row = MultiBufferRow(selection.start.row);
13097 if last_toggled_row == Some(start_row) {
13098 start_row = start_row.next_row();
13099 }
13100 let end_row =
13101 if selection.end.row > selection.start.row && selection.end.column == 0 {
13102 MultiBufferRow(selection.end.row - 1)
13103 } else {
13104 MultiBufferRow(selection.end.row)
13105 };
13106 last_toggled_row = Some(end_row);
13107
13108 if start_row > end_row {
13109 continue;
13110 }
13111
13112 // If the language has line comments, toggle those.
13113 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13114
13115 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13116 if ignore_indent {
13117 full_comment_prefixes = full_comment_prefixes
13118 .into_iter()
13119 .map(|s| Arc::from(s.trim_end()))
13120 .collect();
13121 }
13122
13123 if !full_comment_prefixes.is_empty() {
13124 let first_prefix = full_comment_prefixes
13125 .first()
13126 .expect("prefixes is non-empty");
13127 let prefix_trimmed_lengths = full_comment_prefixes
13128 .iter()
13129 .map(|p| p.trim_end_matches(' ').len())
13130 .collect::<SmallVec<[usize; 4]>>();
13131
13132 let mut all_selection_lines_are_comments = true;
13133
13134 for row in start_row.0..=end_row.0 {
13135 let row = MultiBufferRow(row);
13136 if start_row < end_row && snapshot.is_line_blank(row) {
13137 continue;
13138 }
13139
13140 let prefix_range = full_comment_prefixes
13141 .iter()
13142 .zip(prefix_trimmed_lengths.iter().copied())
13143 .map(|(prefix, trimmed_prefix_len)| {
13144 comment_prefix_range(
13145 snapshot.deref(),
13146 row,
13147 &prefix[..trimmed_prefix_len],
13148 &prefix[trimmed_prefix_len..],
13149 ignore_indent,
13150 )
13151 })
13152 .max_by_key(|range| range.end.column - range.start.column)
13153 .expect("prefixes is non-empty");
13154
13155 if prefix_range.is_empty() {
13156 all_selection_lines_are_comments = false;
13157 }
13158
13159 selection_edit_ranges.push(prefix_range);
13160 }
13161
13162 if all_selection_lines_are_comments {
13163 edits.extend(
13164 selection_edit_ranges
13165 .iter()
13166 .cloned()
13167 .map(|range| (range, empty_str.clone())),
13168 );
13169 } else {
13170 let min_column = selection_edit_ranges
13171 .iter()
13172 .map(|range| range.start.column)
13173 .min()
13174 .unwrap_or(0);
13175 edits.extend(selection_edit_ranges.iter().map(|range| {
13176 let position = Point::new(range.start.row, min_column);
13177 (position..position, first_prefix.clone())
13178 }));
13179 }
13180 } else if let Some((full_comment_prefix, comment_suffix)) =
13181 language.block_comment_delimiters()
13182 {
13183 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13184 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13185 let prefix_range = comment_prefix_range(
13186 snapshot.deref(),
13187 start_row,
13188 comment_prefix,
13189 comment_prefix_whitespace,
13190 ignore_indent,
13191 );
13192 let suffix_range = comment_suffix_range(
13193 snapshot.deref(),
13194 end_row,
13195 comment_suffix.trim_start_matches(' '),
13196 comment_suffix.starts_with(' '),
13197 );
13198
13199 if prefix_range.is_empty() || suffix_range.is_empty() {
13200 edits.push((
13201 prefix_range.start..prefix_range.start,
13202 full_comment_prefix.clone(),
13203 ));
13204 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13205 suffixes_inserted.push((end_row, comment_suffix.len()));
13206 } else {
13207 edits.push((prefix_range, empty_str.clone()));
13208 edits.push((suffix_range, empty_str.clone()));
13209 }
13210 } else {
13211 continue;
13212 }
13213 }
13214
13215 drop(snapshot);
13216 this.buffer.update(cx, |buffer, cx| {
13217 buffer.edit(edits, None, cx);
13218 });
13219
13220 // Adjust selections so that they end before any comment suffixes that
13221 // were inserted.
13222 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13223 let mut selections = this.selections.all::<Point>(cx);
13224 let snapshot = this.buffer.read(cx).read(cx);
13225 for selection in &mut selections {
13226 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13227 match row.cmp(&MultiBufferRow(selection.end.row)) {
13228 Ordering::Less => {
13229 suffixes_inserted.next();
13230 continue;
13231 }
13232 Ordering::Greater => break,
13233 Ordering::Equal => {
13234 if selection.end.column == snapshot.line_len(row) {
13235 if selection.is_empty() {
13236 selection.start.column -= suffix_len as u32;
13237 }
13238 selection.end.column -= suffix_len as u32;
13239 }
13240 break;
13241 }
13242 }
13243 }
13244 }
13245
13246 drop(snapshot);
13247 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13248 s.select(selections)
13249 });
13250
13251 let selections = this.selections.all::<Point>(cx);
13252 let selections_on_single_row = selections.windows(2).all(|selections| {
13253 selections[0].start.row == selections[1].start.row
13254 && selections[0].end.row == selections[1].end.row
13255 && selections[0].start.row == selections[0].end.row
13256 });
13257 let selections_selecting = selections
13258 .iter()
13259 .any(|selection| selection.start != selection.end);
13260 let advance_downwards = action.advance_downwards
13261 && selections_on_single_row
13262 && !selections_selecting
13263 && !matches!(this.mode, EditorMode::SingleLine { .. });
13264
13265 if advance_downwards {
13266 let snapshot = this.buffer.read(cx).snapshot(cx);
13267
13268 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13269 s.move_cursors_with(|display_snapshot, display_point, _| {
13270 let mut point = display_point.to_point(display_snapshot);
13271 point.row += 1;
13272 point = snapshot.clip_point(point, Bias::Left);
13273 let display_point = point.to_display_point(display_snapshot);
13274 let goal = SelectionGoal::HorizontalPosition(
13275 display_snapshot
13276 .x_for_display_point(display_point, text_layout_details)
13277 .into(),
13278 );
13279 (display_point, goal)
13280 })
13281 });
13282 }
13283 });
13284 }
13285
13286 pub fn select_enclosing_symbol(
13287 &mut self,
13288 _: &SelectEnclosingSymbol,
13289 window: &mut Window,
13290 cx: &mut Context<Self>,
13291 ) {
13292 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13293
13294 let buffer = self.buffer.read(cx).snapshot(cx);
13295 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13296
13297 fn update_selection(
13298 selection: &Selection<usize>,
13299 buffer_snap: &MultiBufferSnapshot,
13300 ) -> Option<Selection<usize>> {
13301 let cursor = selection.head();
13302 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13303 for symbol in symbols.iter().rev() {
13304 let start = symbol.range.start.to_offset(buffer_snap);
13305 let end = symbol.range.end.to_offset(buffer_snap);
13306 let new_range = start..end;
13307 if start < selection.start || end > selection.end {
13308 return Some(Selection {
13309 id: selection.id,
13310 start: new_range.start,
13311 end: new_range.end,
13312 goal: SelectionGoal::None,
13313 reversed: selection.reversed,
13314 });
13315 }
13316 }
13317 None
13318 }
13319
13320 let mut selected_larger_symbol = false;
13321 let new_selections = old_selections
13322 .iter()
13323 .map(|selection| match update_selection(selection, &buffer) {
13324 Some(new_selection) => {
13325 if new_selection.range() != selection.range() {
13326 selected_larger_symbol = true;
13327 }
13328 new_selection
13329 }
13330 None => selection.clone(),
13331 })
13332 .collect::<Vec<_>>();
13333
13334 if selected_larger_symbol {
13335 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13336 s.select(new_selections);
13337 });
13338 }
13339 }
13340
13341 pub fn select_larger_syntax_node(
13342 &mut self,
13343 _: &SelectLargerSyntaxNode,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) {
13347 let Some(visible_row_count) = self.visible_row_count() else {
13348 return;
13349 };
13350 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13351 if old_selections.is_empty() {
13352 return;
13353 }
13354
13355 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13356
13357 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13358 let buffer = self.buffer.read(cx).snapshot(cx);
13359
13360 let mut selected_larger_node = false;
13361 let mut new_selections = old_selections
13362 .iter()
13363 .map(|selection| {
13364 let old_range = selection.start..selection.end;
13365
13366 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13367 // manually select word at selection
13368 if ["string_content", "inline"].contains(&node.kind()) {
13369 let word_range = {
13370 let display_point = buffer
13371 .offset_to_point(old_range.start)
13372 .to_display_point(&display_map);
13373 let Range { start, end } =
13374 movement::surrounding_word(&display_map, display_point);
13375 start.to_point(&display_map).to_offset(&buffer)
13376 ..end.to_point(&display_map).to_offset(&buffer)
13377 };
13378 // ignore if word is already selected
13379 if !word_range.is_empty() && old_range != word_range {
13380 let last_word_range = {
13381 let display_point = buffer
13382 .offset_to_point(old_range.end)
13383 .to_display_point(&display_map);
13384 let Range { start, end } =
13385 movement::surrounding_word(&display_map, display_point);
13386 start.to_point(&display_map).to_offset(&buffer)
13387 ..end.to_point(&display_map).to_offset(&buffer)
13388 };
13389 // only select word if start and end point belongs to same word
13390 if word_range == last_word_range {
13391 selected_larger_node = true;
13392 return Selection {
13393 id: selection.id,
13394 start: word_range.start,
13395 end: word_range.end,
13396 goal: SelectionGoal::None,
13397 reversed: selection.reversed,
13398 };
13399 }
13400 }
13401 }
13402 }
13403
13404 let mut new_range = old_range.clone();
13405 while let Some((_node, containing_range)) =
13406 buffer.syntax_ancestor(new_range.clone())
13407 {
13408 new_range = match containing_range {
13409 MultiOrSingleBufferOffsetRange::Single(_) => break,
13410 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13411 };
13412 if !display_map.intersects_fold(new_range.start)
13413 && !display_map.intersects_fold(new_range.end)
13414 {
13415 break;
13416 }
13417 }
13418
13419 selected_larger_node |= new_range != old_range;
13420 Selection {
13421 id: selection.id,
13422 start: new_range.start,
13423 end: new_range.end,
13424 goal: SelectionGoal::None,
13425 reversed: selection.reversed,
13426 }
13427 })
13428 .collect::<Vec<_>>();
13429
13430 if !selected_larger_node {
13431 return; // don't put this call in the history
13432 }
13433
13434 // scroll based on transformation done to the last selection created by the user
13435 let (last_old, last_new) = old_selections
13436 .last()
13437 .zip(new_selections.last().cloned())
13438 .expect("old_selections isn't empty");
13439
13440 // revert selection
13441 let is_selection_reversed = {
13442 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13443 new_selections.last_mut().expect("checked above").reversed =
13444 should_newest_selection_be_reversed;
13445 should_newest_selection_be_reversed
13446 };
13447
13448 if selected_larger_node {
13449 self.select_syntax_node_history.disable_clearing = true;
13450 self.change_selections(None, window, cx, |s| {
13451 s.select(new_selections.clone());
13452 });
13453 self.select_syntax_node_history.disable_clearing = false;
13454 }
13455
13456 let start_row = last_new.start.to_display_point(&display_map).row().0;
13457 let end_row = last_new.end.to_display_point(&display_map).row().0;
13458 let selection_height = end_row - start_row + 1;
13459 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13460
13461 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13462 let scroll_behavior = if fits_on_the_screen {
13463 self.request_autoscroll(Autoscroll::fit(), cx);
13464 SelectSyntaxNodeScrollBehavior::FitSelection
13465 } else if is_selection_reversed {
13466 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13467 SelectSyntaxNodeScrollBehavior::CursorTop
13468 } else {
13469 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13470 SelectSyntaxNodeScrollBehavior::CursorBottom
13471 };
13472
13473 self.select_syntax_node_history.push((
13474 old_selections,
13475 scroll_behavior,
13476 is_selection_reversed,
13477 ));
13478 }
13479
13480 pub fn select_smaller_syntax_node(
13481 &mut self,
13482 _: &SelectSmallerSyntaxNode,
13483 window: &mut Window,
13484 cx: &mut Context<Self>,
13485 ) {
13486 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13487
13488 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13489 self.select_syntax_node_history.pop()
13490 {
13491 if let Some(selection) = selections.last_mut() {
13492 selection.reversed = is_selection_reversed;
13493 }
13494
13495 self.select_syntax_node_history.disable_clearing = true;
13496 self.change_selections(None, window, cx, |s| {
13497 s.select(selections.to_vec());
13498 });
13499 self.select_syntax_node_history.disable_clearing = false;
13500
13501 match scroll_behavior {
13502 SelectSyntaxNodeScrollBehavior::CursorTop => {
13503 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13504 }
13505 SelectSyntaxNodeScrollBehavior::FitSelection => {
13506 self.request_autoscroll(Autoscroll::fit(), cx);
13507 }
13508 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13509 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13510 }
13511 }
13512 }
13513 }
13514
13515 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13516 if !EditorSettings::get_global(cx).gutter.runnables {
13517 self.clear_tasks();
13518 return Task::ready(());
13519 }
13520 let project = self.project.as_ref().map(Entity::downgrade);
13521 let task_sources = self.lsp_task_sources(cx);
13522 cx.spawn_in(window, async move |editor, cx| {
13523 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13524 let Some(project) = project.and_then(|p| p.upgrade()) else {
13525 return;
13526 };
13527 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13528 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13529 }) else {
13530 return;
13531 };
13532
13533 let hide_runnables = project
13534 .update(cx, |project, cx| {
13535 // Do not display any test indicators in non-dev server remote projects.
13536 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13537 })
13538 .unwrap_or(true);
13539 if hide_runnables {
13540 return;
13541 }
13542 let new_rows =
13543 cx.background_spawn({
13544 let snapshot = display_snapshot.clone();
13545 async move {
13546 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13547 }
13548 })
13549 .await;
13550 let Ok(lsp_tasks) =
13551 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13552 else {
13553 return;
13554 };
13555 let lsp_tasks = lsp_tasks.await;
13556
13557 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13558 lsp_tasks
13559 .into_iter()
13560 .flat_map(|(kind, tasks)| {
13561 tasks.into_iter().filter_map(move |(location, task)| {
13562 Some((kind.clone(), location?, task))
13563 })
13564 })
13565 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13566 let buffer = location.target.buffer;
13567 let buffer_snapshot = buffer.read(cx).snapshot();
13568 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13569 |(excerpt_id, snapshot, _)| {
13570 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13571 display_snapshot
13572 .buffer_snapshot
13573 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13574 } else {
13575 None
13576 }
13577 },
13578 );
13579 if let Some(offset) = offset {
13580 let task_buffer_range =
13581 location.target.range.to_point(&buffer_snapshot);
13582 let context_buffer_range =
13583 task_buffer_range.to_offset(&buffer_snapshot);
13584 let context_range = BufferOffset(context_buffer_range.start)
13585 ..BufferOffset(context_buffer_range.end);
13586
13587 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13588 .or_insert_with(|| RunnableTasks {
13589 templates: Vec::new(),
13590 offset,
13591 column: task_buffer_range.start.column,
13592 extra_variables: HashMap::default(),
13593 context_range,
13594 })
13595 .templates
13596 .push((kind, task.original_task().clone()));
13597 }
13598
13599 acc
13600 })
13601 }) else {
13602 return;
13603 };
13604
13605 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13606 editor
13607 .update(cx, |editor, _| {
13608 editor.clear_tasks();
13609 for (key, mut value) in rows {
13610 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13611 value.templates.extend(lsp_tasks.templates);
13612 }
13613
13614 editor.insert_tasks(key, value);
13615 }
13616 for (key, value) in lsp_tasks_by_rows {
13617 editor.insert_tasks(key, value);
13618 }
13619 })
13620 .ok();
13621 })
13622 }
13623 fn fetch_runnable_ranges(
13624 snapshot: &DisplaySnapshot,
13625 range: Range<Anchor>,
13626 ) -> Vec<language::RunnableRange> {
13627 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13628 }
13629
13630 fn runnable_rows(
13631 project: Entity<Project>,
13632 snapshot: DisplaySnapshot,
13633 runnable_ranges: Vec<RunnableRange>,
13634 mut cx: AsyncWindowContext,
13635 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13636 runnable_ranges
13637 .into_iter()
13638 .filter_map(|mut runnable| {
13639 let tasks = cx
13640 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13641 .ok()?;
13642 if tasks.is_empty() {
13643 return None;
13644 }
13645
13646 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13647
13648 let row = snapshot
13649 .buffer_snapshot
13650 .buffer_line_for_row(MultiBufferRow(point.row))?
13651 .1
13652 .start
13653 .row;
13654
13655 let context_range =
13656 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13657 Some((
13658 (runnable.buffer_id, row),
13659 RunnableTasks {
13660 templates: tasks,
13661 offset: snapshot
13662 .buffer_snapshot
13663 .anchor_before(runnable.run_range.start),
13664 context_range,
13665 column: point.column,
13666 extra_variables: runnable.extra_captures,
13667 },
13668 ))
13669 })
13670 .collect()
13671 }
13672
13673 fn templates_with_tags(
13674 project: &Entity<Project>,
13675 runnable: &mut Runnable,
13676 cx: &mut App,
13677 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13678 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13679 let (worktree_id, file) = project
13680 .buffer_for_id(runnable.buffer, cx)
13681 .and_then(|buffer| buffer.read(cx).file())
13682 .map(|file| (file.worktree_id(cx), file.clone()))
13683 .unzip();
13684
13685 (
13686 project.task_store().read(cx).task_inventory().cloned(),
13687 worktree_id,
13688 file,
13689 )
13690 });
13691
13692 let mut templates_with_tags = mem::take(&mut runnable.tags)
13693 .into_iter()
13694 .flat_map(|RunnableTag(tag)| {
13695 inventory
13696 .as_ref()
13697 .into_iter()
13698 .flat_map(|inventory| {
13699 inventory.read(cx).list_tasks(
13700 file.clone(),
13701 Some(runnable.language.clone()),
13702 worktree_id,
13703 cx,
13704 )
13705 })
13706 .filter(move |(_, template)| {
13707 template.tags.iter().any(|source_tag| source_tag == &tag)
13708 })
13709 })
13710 .sorted_by_key(|(kind, _)| kind.to_owned())
13711 .collect::<Vec<_>>();
13712 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13713 // Strongest source wins; if we have worktree tag binding, prefer that to
13714 // global and language bindings;
13715 // if we have a global binding, prefer that to language binding.
13716 let first_mismatch = templates_with_tags
13717 .iter()
13718 .position(|(tag_source, _)| tag_source != leading_tag_source);
13719 if let Some(index) = first_mismatch {
13720 templates_with_tags.truncate(index);
13721 }
13722 }
13723
13724 templates_with_tags
13725 }
13726
13727 pub fn move_to_enclosing_bracket(
13728 &mut self,
13729 _: &MoveToEnclosingBracket,
13730 window: &mut Window,
13731 cx: &mut Context<Self>,
13732 ) {
13733 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13734 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13735 s.move_offsets_with(|snapshot, selection| {
13736 let Some(enclosing_bracket_ranges) =
13737 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13738 else {
13739 return;
13740 };
13741
13742 let mut best_length = usize::MAX;
13743 let mut best_inside = false;
13744 let mut best_in_bracket_range = false;
13745 let mut best_destination = None;
13746 for (open, close) in enclosing_bracket_ranges {
13747 let close = close.to_inclusive();
13748 let length = close.end() - open.start;
13749 let inside = selection.start >= open.end && selection.end <= *close.start();
13750 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13751 || close.contains(&selection.head());
13752
13753 // If best is next to a bracket and current isn't, skip
13754 if !in_bracket_range && best_in_bracket_range {
13755 continue;
13756 }
13757
13758 // Prefer smaller lengths unless best is inside and current isn't
13759 if length > best_length && (best_inside || !inside) {
13760 continue;
13761 }
13762
13763 best_length = length;
13764 best_inside = inside;
13765 best_in_bracket_range = in_bracket_range;
13766 best_destination = Some(
13767 if close.contains(&selection.start) && close.contains(&selection.end) {
13768 if inside { open.end } else { open.start }
13769 } else if inside {
13770 *close.start()
13771 } else {
13772 *close.end()
13773 },
13774 );
13775 }
13776
13777 if let Some(destination) = best_destination {
13778 selection.collapse_to(destination, SelectionGoal::None);
13779 }
13780 })
13781 });
13782 }
13783
13784 pub fn undo_selection(
13785 &mut self,
13786 _: &UndoSelection,
13787 window: &mut Window,
13788 cx: &mut Context<Self>,
13789 ) {
13790 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13791 self.end_selection(window, cx);
13792 self.selection_history.mode = SelectionHistoryMode::Undoing;
13793 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13794 self.change_selections(None, window, cx, |s| {
13795 s.select_anchors(entry.selections.to_vec())
13796 });
13797 self.select_next_state = entry.select_next_state;
13798 self.select_prev_state = entry.select_prev_state;
13799 self.add_selections_state = entry.add_selections_state;
13800 self.request_autoscroll(Autoscroll::newest(), cx);
13801 }
13802 self.selection_history.mode = SelectionHistoryMode::Normal;
13803 }
13804
13805 pub fn redo_selection(
13806 &mut self,
13807 _: &RedoSelection,
13808 window: &mut Window,
13809 cx: &mut Context<Self>,
13810 ) {
13811 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13812 self.end_selection(window, cx);
13813 self.selection_history.mode = SelectionHistoryMode::Redoing;
13814 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13815 self.change_selections(None, window, cx, |s| {
13816 s.select_anchors(entry.selections.to_vec())
13817 });
13818 self.select_next_state = entry.select_next_state;
13819 self.select_prev_state = entry.select_prev_state;
13820 self.add_selections_state = entry.add_selections_state;
13821 self.request_autoscroll(Autoscroll::newest(), cx);
13822 }
13823 self.selection_history.mode = SelectionHistoryMode::Normal;
13824 }
13825
13826 pub fn expand_excerpts(
13827 &mut self,
13828 action: &ExpandExcerpts,
13829 _: &mut Window,
13830 cx: &mut Context<Self>,
13831 ) {
13832 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13833 }
13834
13835 pub fn expand_excerpts_down(
13836 &mut self,
13837 action: &ExpandExcerptsDown,
13838 _: &mut Window,
13839 cx: &mut Context<Self>,
13840 ) {
13841 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13842 }
13843
13844 pub fn expand_excerpts_up(
13845 &mut self,
13846 action: &ExpandExcerptsUp,
13847 _: &mut Window,
13848 cx: &mut Context<Self>,
13849 ) {
13850 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13851 }
13852
13853 pub fn expand_excerpts_for_direction(
13854 &mut self,
13855 lines: u32,
13856 direction: ExpandExcerptDirection,
13857
13858 cx: &mut Context<Self>,
13859 ) {
13860 let selections = self.selections.disjoint_anchors();
13861
13862 let lines = if lines == 0 {
13863 EditorSettings::get_global(cx).expand_excerpt_lines
13864 } else {
13865 lines
13866 };
13867
13868 self.buffer.update(cx, |buffer, cx| {
13869 let snapshot = buffer.snapshot(cx);
13870 let mut excerpt_ids = selections
13871 .iter()
13872 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13873 .collect::<Vec<_>>();
13874 excerpt_ids.sort();
13875 excerpt_ids.dedup();
13876 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13877 })
13878 }
13879
13880 pub fn expand_excerpt(
13881 &mut self,
13882 excerpt: ExcerptId,
13883 direction: ExpandExcerptDirection,
13884 window: &mut Window,
13885 cx: &mut Context<Self>,
13886 ) {
13887 let current_scroll_position = self.scroll_position(cx);
13888 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13889 let mut should_scroll_up = false;
13890
13891 if direction == ExpandExcerptDirection::Down {
13892 let multi_buffer = self.buffer.read(cx);
13893 let snapshot = multi_buffer.snapshot(cx);
13894 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13895 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13896 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13897 let buffer_snapshot = buffer.read(cx).snapshot();
13898 let excerpt_end_row =
13899 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13900 let last_row = buffer_snapshot.max_point().row;
13901 let lines_below = last_row.saturating_sub(excerpt_end_row);
13902 should_scroll_up = lines_below >= lines_to_expand;
13903 }
13904 }
13905 }
13906 }
13907
13908 self.buffer.update(cx, |buffer, cx| {
13909 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13910 });
13911
13912 if should_scroll_up {
13913 let new_scroll_position =
13914 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13915 self.set_scroll_position(new_scroll_position, window, cx);
13916 }
13917 }
13918
13919 pub fn go_to_singleton_buffer_point(
13920 &mut self,
13921 point: Point,
13922 window: &mut Window,
13923 cx: &mut Context<Self>,
13924 ) {
13925 self.go_to_singleton_buffer_range(point..point, window, cx);
13926 }
13927
13928 pub fn go_to_singleton_buffer_range(
13929 &mut self,
13930 range: Range<Point>,
13931 window: &mut Window,
13932 cx: &mut Context<Self>,
13933 ) {
13934 let multibuffer = self.buffer().read(cx);
13935 let Some(buffer) = multibuffer.as_singleton() else {
13936 return;
13937 };
13938 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13939 return;
13940 };
13941 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13942 return;
13943 };
13944 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13945 s.select_anchor_ranges([start..end])
13946 });
13947 }
13948
13949 pub fn go_to_diagnostic(
13950 &mut self,
13951 _: &GoToDiagnostic,
13952 window: &mut Window,
13953 cx: &mut Context<Self>,
13954 ) {
13955 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13956 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13957 }
13958
13959 pub fn go_to_prev_diagnostic(
13960 &mut self,
13961 _: &GoToPreviousDiagnostic,
13962 window: &mut Window,
13963 cx: &mut Context<Self>,
13964 ) {
13965 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13966 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13967 }
13968
13969 pub fn go_to_diagnostic_impl(
13970 &mut self,
13971 direction: Direction,
13972 window: &mut Window,
13973 cx: &mut Context<Self>,
13974 ) {
13975 let buffer = self.buffer.read(cx).snapshot(cx);
13976 let selection = self.selections.newest::<usize>(cx);
13977
13978 let mut active_group_id = None;
13979 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13980 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13981 active_group_id = Some(active_group.group_id);
13982 }
13983 }
13984
13985 fn filtered(
13986 snapshot: EditorSnapshot,
13987 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13988 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13989 diagnostics
13990 .filter(|entry| entry.range.start != entry.range.end)
13991 .filter(|entry| !entry.diagnostic.is_unnecessary)
13992 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13993 }
13994
13995 let snapshot = self.snapshot(window, cx);
13996 let before = filtered(
13997 snapshot.clone(),
13998 buffer
13999 .diagnostics_in_range(0..selection.start)
14000 .filter(|entry| entry.range.start <= selection.start),
14001 );
14002 let after = filtered(
14003 snapshot,
14004 buffer
14005 .diagnostics_in_range(selection.start..buffer.len())
14006 .filter(|entry| entry.range.start >= selection.start),
14007 );
14008
14009 let mut found: Option<DiagnosticEntry<usize>> = None;
14010 if direction == Direction::Prev {
14011 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14012 {
14013 for diagnostic in prev_diagnostics.into_iter().rev() {
14014 if diagnostic.range.start != selection.start
14015 || active_group_id
14016 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14017 {
14018 found = Some(diagnostic);
14019 break 'outer;
14020 }
14021 }
14022 }
14023 } else {
14024 for diagnostic in after.chain(before) {
14025 if diagnostic.range.start != selection.start
14026 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14027 {
14028 found = Some(diagnostic);
14029 break;
14030 }
14031 }
14032 }
14033 let Some(next_diagnostic) = found else {
14034 return;
14035 };
14036
14037 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14038 return;
14039 };
14040 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14041 s.select_ranges(vec![
14042 next_diagnostic.range.start..next_diagnostic.range.start,
14043 ])
14044 });
14045 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14046 self.refresh_inline_completion(false, true, window, cx);
14047 }
14048
14049 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14050 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14051 let snapshot = self.snapshot(window, cx);
14052 let selection = self.selections.newest::<Point>(cx);
14053 self.go_to_hunk_before_or_after_position(
14054 &snapshot,
14055 selection.head(),
14056 Direction::Next,
14057 window,
14058 cx,
14059 );
14060 }
14061
14062 pub fn go_to_hunk_before_or_after_position(
14063 &mut self,
14064 snapshot: &EditorSnapshot,
14065 position: Point,
14066 direction: Direction,
14067 window: &mut Window,
14068 cx: &mut Context<Editor>,
14069 ) {
14070 let row = if direction == Direction::Next {
14071 self.hunk_after_position(snapshot, position)
14072 .map(|hunk| hunk.row_range.start)
14073 } else {
14074 self.hunk_before_position(snapshot, position)
14075 };
14076
14077 if let Some(row) = row {
14078 let destination = Point::new(row.0, 0);
14079 let autoscroll = Autoscroll::center();
14080
14081 self.unfold_ranges(&[destination..destination], false, false, cx);
14082 self.change_selections(Some(autoscroll), window, cx, |s| {
14083 s.select_ranges([destination..destination]);
14084 });
14085 }
14086 }
14087
14088 fn hunk_after_position(
14089 &mut self,
14090 snapshot: &EditorSnapshot,
14091 position: Point,
14092 ) -> Option<MultiBufferDiffHunk> {
14093 snapshot
14094 .buffer_snapshot
14095 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14096 .find(|hunk| hunk.row_range.start.0 > position.row)
14097 .or_else(|| {
14098 snapshot
14099 .buffer_snapshot
14100 .diff_hunks_in_range(Point::zero()..position)
14101 .find(|hunk| hunk.row_range.end.0 < position.row)
14102 })
14103 }
14104
14105 fn go_to_prev_hunk(
14106 &mut self,
14107 _: &GoToPreviousHunk,
14108 window: &mut Window,
14109 cx: &mut Context<Self>,
14110 ) {
14111 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14112 let snapshot = self.snapshot(window, cx);
14113 let selection = self.selections.newest::<Point>(cx);
14114 self.go_to_hunk_before_or_after_position(
14115 &snapshot,
14116 selection.head(),
14117 Direction::Prev,
14118 window,
14119 cx,
14120 );
14121 }
14122
14123 fn hunk_before_position(
14124 &mut self,
14125 snapshot: &EditorSnapshot,
14126 position: Point,
14127 ) -> Option<MultiBufferRow> {
14128 snapshot
14129 .buffer_snapshot
14130 .diff_hunk_before(position)
14131 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14132 }
14133
14134 fn go_to_next_change(
14135 &mut self,
14136 _: &GoToNextChange,
14137 window: &mut Window,
14138 cx: &mut Context<Self>,
14139 ) {
14140 if let Some(selections) = self
14141 .change_list
14142 .next_change(1, Direction::Next)
14143 .map(|s| s.to_vec())
14144 {
14145 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14146 let map = s.display_map();
14147 s.select_display_ranges(selections.iter().map(|a| {
14148 let point = a.to_display_point(&map);
14149 point..point
14150 }))
14151 })
14152 }
14153 }
14154
14155 fn go_to_previous_change(
14156 &mut self,
14157 _: &GoToPreviousChange,
14158 window: &mut Window,
14159 cx: &mut Context<Self>,
14160 ) {
14161 if let Some(selections) = self
14162 .change_list
14163 .next_change(1, Direction::Prev)
14164 .map(|s| s.to_vec())
14165 {
14166 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14167 let map = s.display_map();
14168 s.select_display_ranges(selections.iter().map(|a| {
14169 let point = a.to_display_point(&map);
14170 point..point
14171 }))
14172 })
14173 }
14174 }
14175
14176 fn go_to_line<T: 'static>(
14177 &mut self,
14178 position: Anchor,
14179 highlight_color: Option<Hsla>,
14180 window: &mut Window,
14181 cx: &mut Context<Self>,
14182 ) {
14183 let snapshot = self.snapshot(window, cx).display_snapshot;
14184 let position = position.to_point(&snapshot.buffer_snapshot);
14185 let start = snapshot
14186 .buffer_snapshot
14187 .clip_point(Point::new(position.row, 0), Bias::Left);
14188 let end = start + Point::new(1, 0);
14189 let start = snapshot.buffer_snapshot.anchor_before(start);
14190 let end = snapshot.buffer_snapshot.anchor_before(end);
14191
14192 self.highlight_rows::<T>(
14193 start..end,
14194 highlight_color
14195 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14196 Default::default(),
14197 cx,
14198 );
14199
14200 if self.buffer.read(cx).is_singleton() {
14201 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14202 }
14203 }
14204
14205 pub fn go_to_definition(
14206 &mut self,
14207 _: &GoToDefinition,
14208 window: &mut Window,
14209 cx: &mut Context<Self>,
14210 ) -> Task<Result<Navigated>> {
14211 let definition =
14212 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14213 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14214 cx.spawn_in(window, async move |editor, cx| {
14215 if definition.await? == Navigated::Yes {
14216 return Ok(Navigated::Yes);
14217 }
14218 match fallback_strategy {
14219 GoToDefinitionFallback::None => Ok(Navigated::No),
14220 GoToDefinitionFallback::FindAllReferences => {
14221 match editor.update_in(cx, |editor, window, cx| {
14222 editor.find_all_references(&FindAllReferences, window, cx)
14223 })? {
14224 Some(references) => references.await,
14225 None => Ok(Navigated::No),
14226 }
14227 }
14228 }
14229 })
14230 }
14231
14232 pub fn go_to_declaration(
14233 &mut self,
14234 _: &GoToDeclaration,
14235 window: &mut Window,
14236 cx: &mut Context<Self>,
14237 ) -> Task<Result<Navigated>> {
14238 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14239 }
14240
14241 pub fn go_to_declaration_split(
14242 &mut self,
14243 _: &GoToDeclaration,
14244 window: &mut Window,
14245 cx: &mut Context<Self>,
14246 ) -> Task<Result<Navigated>> {
14247 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14248 }
14249
14250 pub fn go_to_implementation(
14251 &mut self,
14252 _: &GoToImplementation,
14253 window: &mut Window,
14254 cx: &mut Context<Self>,
14255 ) -> Task<Result<Navigated>> {
14256 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14257 }
14258
14259 pub fn go_to_implementation_split(
14260 &mut self,
14261 _: &GoToImplementationSplit,
14262 window: &mut Window,
14263 cx: &mut Context<Self>,
14264 ) -> Task<Result<Navigated>> {
14265 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14266 }
14267
14268 pub fn go_to_type_definition(
14269 &mut self,
14270 _: &GoToTypeDefinition,
14271 window: &mut Window,
14272 cx: &mut Context<Self>,
14273 ) -> Task<Result<Navigated>> {
14274 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14275 }
14276
14277 pub fn go_to_definition_split(
14278 &mut self,
14279 _: &GoToDefinitionSplit,
14280 window: &mut Window,
14281 cx: &mut Context<Self>,
14282 ) -> Task<Result<Navigated>> {
14283 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14284 }
14285
14286 pub fn go_to_type_definition_split(
14287 &mut self,
14288 _: &GoToTypeDefinitionSplit,
14289 window: &mut Window,
14290 cx: &mut Context<Self>,
14291 ) -> Task<Result<Navigated>> {
14292 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14293 }
14294
14295 fn go_to_definition_of_kind(
14296 &mut self,
14297 kind: GotoDefinitionKind,
14298 split: bool,
14299 window: &mut Window,
14300 cx: &mut Context<Self>,
14301 ) -> Task<Result<Navigated>> {
14302 let Some(provider) = self.semantics_provider.clone() else {
14303 return Task::ready(Ok(Navigated::No));
14304 };
14305 let head = self.selections.newest::<usize>(cx).head();
14306 let buffer = self.buffer.read(cx);
14307 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14308 text_anchor
14309 } else {
14310 return Task::ready(Ok(Navigated::No));
14311 };
14312
14313 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14314 return Task::ready(Ok(Navigated::No));
14315 };
14316
14317 cx.spawn_in(window, async move |editor, cx| {
14318 let definitions = definitions.await?;
14319 let navigated = editor
14320 .update_in(cx, |editor, window, cx| {
14321 editor.navigate_to_hover_links(
14322 Some(kind),
14323 definitions
14324 .into_iter()
14325 .filter(|location| {
14326 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14327 })
14328 .map(HoverLink::Text)
14329 .collect::<Vec<_>>(),
14330 split,
14331 window,
14332 cx,
14333 )
14334 })?
14335 .await?;
14336 anyhow::Ok(navigated)
14337 })
14338 }
14339
14340 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14341 let selection = self.selections.newest_anchor();
14342 let head = selection.head();
14343 let tail = selection.tail();
14344
14345 let Some((buffer, start_position)) =
14346 self.buffer.read(cx).text_anchor_for_position(head, cx)
14347 else {
14348 return;
14349 };
14350
14351 let end_position = if head != tail {
14352 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14353 return;
14354 };
14355 Some(pos)
14356 } else {
14357 None
14358 };
14359
14360 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14361 let url = if let Some(end_pos) = end_position {
14362 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14363 } else {
14364 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14365 };
14366
14367 if let Some(url) = url {
14368 editor.update(cx, |_, cx| {
14369 cx.open_url(&url);
14370 })
14371 } else {
14372 Ok(())
14373 }
14374 });
14375
14376 url_finder.detach();
14377 }
14378
14379 pub fn open_selected_filename(
14380 &mut self,
14381 _: &OpenSelectedFilename,
14382 window: &mut Window,
14383 cx: &mut Context<Self>,
14384 ) {
14385 let Some(workspace) = self.workspace() else {
14386 return;
14387 };
14388
14389 let position = self.selections.newest_anchor().head();
14390
14391 let Some((buffer, buffer_position)) =
14392 self.buffer.read(cx).text_anchor_for_position(position, cx)
14393 else {
14394 return;
14395 };
14396
14397 let project = self.project.clone();
14398
14399 cx.spawn_in(window, async move |_, cx| {
14400 let result = find_file(&buffer, project, buffer_position, cx).await;
14401
14402 if let Some((_, path)) = result {
14403 workspace
14404 .update_in(cx, |workspace, window, cx| {
14405 workspace.open_resolved_path(path, window, cx)
14406 })?
14407 .await?;
14408 }
14409 anyhow::Ok(())
14410 })
14411 .detach();
14412 }
14413
14414 pub(crate) fn navigate_to_hover_links(
14415 &mut self,
14416 kind: Option<GotoDefinitionKind>,
14417 mut definitions: Vec<HoverLink>,
14418 split: bool,
14419 window: &mut Window,
14420 cx: &mut Context<Editor>,
14421 ) -> Task<Result<Navigated>> {
14422 // If there is one definition, just open it directly
14423 if definitions.len() == 1 {
14424 let definition = definitions.pop().unwrap();
14425
14426 enum TargetTaskResult {
14427 Location(Option<Location>),
14428 AlreadyNavigated,
14429 }
14430
14431 let target_task = match definition {
14432 HoverLink::Text(link) => {
14433 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14434 }
14435 HoverLink::InlayHint(lsp_location, server_id) => {
14436 let computation =
14437 self.compute_target_location(lsp_location, server_id, window, cx);
14438 cx.background_spawn(async move {
14439 let location = computation.await?;
14440 Ok(TargetTaskResult::Location(location))
14441 })
14442 }
14443 HoverLink::Url(url) => {
14444 cx.open_url(&url);
14445 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14446 }
14447 HoverLink::File(path) => {
14448 if let Some(workspace) = self.workspace() {
14449 cx.spawn_in(window, async move |_, cx| {
14450 workspace
14451 .update_in(cx, |workspace, window, cx| {
14452 workspace.open_resolved_path(path, window, cx)
14453 })?
14454 .await
14455 .map(|_| TargetTaskResult::AlreadyNavigated)
14456 })
14457 } else {
14458 Task::ready(Ok(TargetTaskResult::Location(None)))
14459 }
14460 }
14461 };
14462 cx.spawn_in(window, async move |editor, cx| {
14463 let target = match target_task.await.context("target resolution task")? {
14464 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14465 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14466 TargetTaskResult::Location(Some(target)) => target,
14467 };
14468
14469 editor.update_in(cx, |editor, window, cx| {
14470 let Some(workspace) = editor.workspace() else {
14471 return Navigated::No;
14472 };
14473 let pane = workspace.read(cx).active_pane().clone();
14474
14475 let range = target.range.to_point(target.buffer.read(cx));
14476 let range = editor.range_for_match(&range);
14477 let range = collapse_multiline_range(range);
14478
14479 if !split
14480 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14481 {
14482 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14483 } else {
14484 window.defer(cx, move |window, cx| {
14485 let target_editor: Entity<Self> =
14486 workspace.update(cx, |workspace, cx| {
14487 let pane = if split {
14488 workspace.adjacent_pane(window, cx)
14489 } else {
14490 workspace.active_pane().clone()
14491 };
14492
14493 workspace.open_project_item(
14494 pane,
14495 target.buffer.clone(),
14496 true,
14497 true,
14498 window,
14499 cx,
14500 )
14501 });
14502 target_editor.update(cx, |target_editor, cx| {
14503 // When selecting a definition in a different buffer, disable the nav history
14504 // to avoid creating a history entry at the previous cursor location.
14505 pane.update(cx, |pane, _| pane.disable_history());
14506 target_editor.go_to_singleton_buffer_range(range, window, cx);
14507 pane.update(cx, |pane, _| pane.enable_history());
14508 });
14509 });
14510 }
14511 Navigated::Yes
14512 })
14513 })
14514 } else if !definitions.is_empty() {
14515 cx.spawn_in(window, async move |editor, cx| {
14516 let (title, location_tasks, workspace) = editor
14517 .update_in(cx, |editor, window, cx| {
14518 let tab_kind = match kind {
14519 Some(GotoDefinitionKind::Implementation) => "Implementations",
14520 _ => "Definitions",
14521 };
14522 let title = definitions
14523 .iter()
14524 .find_map(|definition| match definition {
14525 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14526 let buffer = origin.buffer.read(cx);
14527 format!(
14528 "{} for {}",
14529 tab_kind,
14530 buffer
14531 .text_for_range(origin.range.clone())
14532 .collect::<String>()
14533 )
14534 }),
14535 HoverLink::InlayHint(_, _) => None,
14536 HoverLink::Url(_) => None,
14537 HoverLink::File(_) => None,
14538 })
14539 .unwrap_or(tab_kind.to_string());
14540 let location_tasks = definitions
14541 .into_iter()
14542 .map(|definition| match definition {
14543 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14544 HoverLink::InlayHint(lsp_location, server_id) => editor
14545 .compute_target_location(lsp_location, server_id, window, cx),
14546 HoverLink::Url(_) => Task::ready(Ok(None)),
14547 HoverLink::File(_) => Task::ready(Ok(None)),
14548 })
14549 .collect::<Vec<_>>();
14550 (title, location_tasks, editor.workspace().clone())
14551 })
14552 .context("location tasks preparation")?;
14553
14554 let locations = future::join_all(location_tasks)
14555 .await
14556 .into_iter()
14557 .filter_map(|location| location.transpose())
14558 .collect::<Result<_>>()
14559 .context("location tasks")?;
14560
14561 let Some(workspace) = workspace else {
14562 return Ok(Navigated::No);
14563 };
14564 let opened = workspace
14565 .update_in(cx, |workspace, window, cx| {
14566 Self::open_locations_in_multibuffer(
14567 workspace,
14568 locations,
14569 title,
14570 split,
14571 MultibufferSelectionMode::First,
14572 window,
14573 cx,
14574 )
14575 })
14576 .ok();
14577
14578 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14579 })
14580 } else {
14581 Task::ready(Ok(Navigated::No))
14582 }
14583 }
14584
14585 fn compute_target_location(
14586 &self,
14587 lsp_location: lsp::Location,
14588 server_id: LanguageServerId,
14589 window: &mut Window,
14590 cx: &mut Context<Self>,
14591 ) -> Task<anyhow::Result<Option<Location>>> {
14592 let Some(project) = self.project.clone() else {
14593 return Task::ready(Ok(None));
14594 };
14595
14596 cx.spawn_in(window, async move |editor, cx| {
14597 let location_task = editor.update(cx, |_, cx| {
14598 project.update(cx, |project, cx| {
14599 let language_server_name = project
14600 .language_server_statuses(cx)
14601 .find(|(id, _)| server_id == *id)
14602 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14603 language_server_name.map(|language_server_name| {
14604 project.open_local_buffer_via_lsp(
14605 lsp_location.uri.clone(),
14606 server_id,
14607 language_server_name,
14608 cx,
14609 )
14610 })
14611 })
14612 })?;
14613 let location = match location_task {
14614 Some(task) => Some({
14615 let target_buffer_handle = task.await.context("open local buffer")?;
14616 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
14617 let target_start = target_buffer
14618 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14619 let target_end = target_buffer
14620 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14621 target_buffer.anchor_after(target_start)
14622 ..target_buffer.anchor_before(target_end)
14623 })?;
14624 Location {
14625 buffer: target_buffer_handle,
14626 range,
14627 }
14628 }),
14629 None => None,
14630 };
14631 Ok(location)
14632 })
14633 }
14634
14635 pub fn find_all_references(
14636 &mut self,
14637 _: &FindAllReferences,
14638 window: &mut Window,
14639 cx: &mut Context<Self>,
14640 ) -> Option<Task<Result<Navigated>>> {
14641 let selection = self.selections.newest::<usize>(cx);
14642 let multi_buffer = self.buffer.read(cx);
14643 let head = selection.head();
14644
14645 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14646 let head_anchor = multi_buffer_snapshot.anchor_at(
14647 head,
14648 if head < selection.tail() {
14649 Bias::Right
14650 } else {
14651 Bias::Left
14652 },
14653 );
14654
14655 match self
14656 .find_all_references_task_sources
14657 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14658 {
14659 Ok(_) => {
14660 log::info!(
14661 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14662 );
14663 return None;
14664 }
14665 Err(i) => {
14666 self.find_all_references_task_sources.insert(i, head_anchor);
14667 }
14668 }
14669
14670 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14671 let workspace = self.workspace()?;
14672 let project = workspace.read(cx).project().clone();
14673 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14674 Some(cx.spawn_in(window, async move |editor, cx| {
14675 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14676 if let Ok(i) = editor
14677 .find_all_references_task_sources
14678 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14679 {
14680 editor.find_all_references_task_sources.remove(i);
14681 }
14682 });
14683
14684 let locations = references.await?;
14685 if locations.is_empty() {
14686 return anyhow::Ok(Navigated::No);
14687 }
14688
14689 workspace.update_in(cx, |workspace, window, cx| {
14690 let title = locations
14691 .first()
14692 .as_ref()
14693 .map(|location| {
14694 let buffer = location.buffer.read(cx);
14695 format!(
14696 "References to `{}`",
14697 buffer
14698 .text_for_range(location.range.clone())
14699 .collect::<String>()
14700 )
14701 })
14702 .unwrap();
14703 Self::open_locations_in_multibuffer(
14704 workspace,
14705 locations,
14706 title,
14707 false,
14708 MultibufferSelectionMode::First,
14709 window,
14710 cx,
14711 );
14712 Navigated::Yes
14713 })
14714 }))
14715 }
14716
14717 /// Opens a multibuffer with the given project locations in it
14718 pub fn open_locations_in_multibuffer(
14719 workspace: &mut Workspace,
14720 mut locations: Vec<Location>,
14721 title: String,
14722 split: bool,
14723 multibuffer_selection_mode: MultibufferSelectionMode,
14724 window: &mut Window,
14725 cx: &mut Context<Workspace>,
14726 ) {
14727 // If there are multiple definitions, open them in a multibuffer
14728 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14729 let mut locations = locations.into_iter().peekable();
14730 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14731 let capability = workspace.project().read(cx).capability();
14732
14733 let excerpt_buffer = cx.new(|cx| {
14734 let mut multibuffer = MultiBuffer::new(capability);
14735 while let Some(location) = locations.next() {
14736 let buffer = location.buffer.read(cx);
14737 let mut ranges_for_buffer = Vec::new();
14738 let range = location.range.to_point(buffer);
14739 ranges_for_buffer.push(range.clone());
14740
14741 while let Some(next_location) = locations.peek() {
14742 if next_location.buffer == location.buffer {
14743 ranges_for_buffer.push(next_location.range.to_point(buffer));
14744 locations.next();
14745 } else {
14746 break;
14747 }
14748 }
14749
14750 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14751 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14752 PathKey::for_buffer(&location.buffer, cx),
14753 location.buffer.clone(),
14754 ranges_for_buffer,
14755 DEFAULT_MULTIBUFFER_CONTEXT,
14756 cx,
14757 );
14758 ranges.extend(new_ranges)
14759 }
14760
14761 multibuffer.with_title(title)
14762 });
14763
14764 let editor = cx.new(|cx| {
14765 Editor::for_multibuffer(
14766 excerpt_buffer,
14767 Some(workspace.project().clone()),
14768 window,
14769 cx,
14770 )
14771 });
14772 editor.update(cx, |editor, cx| {
14773 match multibuffer_selection_mode {
14774 MultibufferSelectionMode::First => {
14775 if let Some(first_range) = ranges.first() {
14776 editor.change_selections(None, window, cx, |selections| {
14777 selections.clear_disjoint();
14778 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14779 });
14780 }
14781 editor.highlight_background::<Self>(
14782 &ranges,
14783 |theme| theme.editor_highlighted_line_background,
14784 cx,
14785 );
14786 }
14787 MultibufferSelectionMode::All => {
14788 editor.change_selections(None, window, cx, |selections| {
14789 selections.clear_disjoint();
14790 selections.select_anchor_ranges(ranges);
14791 });
14792 }
14793 }
14794 editor.register_buffers_with_language_servers(cx);
14795 });
14796
14797 let item = Box::new(editor);
14798 let item_id = item.item_id();
14799
14800 if split {
14801 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14802 } else {
14803 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14804 let (preview_item_id, preview_item_idx) =
14805 workspace.active_pane().read_with(cx, |pane, _| {
14806 (pane.preview_item_id(), pane.preview_item_idx())
14807 });
14808
14809 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14810
14811 if let Some(preview_item_id) = preview_item_id {
14812 workspace.active_pane().update(cx, |pane, cx| {
14813 pane.remove_item(preview_item_id, false, false, window, cx);
14814 });
14815 }
14816 } else {
14817 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14818 }
14819 }
14820 workspace.active_pane().update(cx, |pane, cx| {
14821 pane.set_preview_item_id(Some(item_id), cx);
14822 });
14823 }
14824
14825 pub fn rename(
14826 &mut self,
14827 _: &Rename,
14828 window: &mut Window,
14829 cx: &mut Context<Self>,
14830 ) -> Option<Task<Result<()>>> {
14831 use language::ToOffset as _;
14832
14833 let provider = self.semantics_provider.clone()?;
14834 let selection = self.selections.newest_anchor().clone();
14835 let (cursor_buffer, cursor_buffer_position) = self
14836 .buffer
14837 .read(cx)
14838 .text_anchor_for_position(selection.head(), cx)?;
14839 let (tail_buffer, cursor_buffer_position_end) = self
14840 .buffer
14841 .read(cx)
14842 .text_anchor_for_position(selection.tail(), cx)?;
14843 if tail_buffer != cursor_buffer {
14844 return None;
14845 }
14846
14847 let snapshot = cursor_buffer.read(cx).snapshot();
14848 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14849 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14850 let prepare_rename = provider
14851 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14852 .unwrap_or_else(|| Task::ready(Ok(None)));
14853 drop(snapshot);
14854
14855 Some(cx.spawn_in(window, async move |this, cx| {
14856 let rename_range = if let Some(range) = prepare_rename.await? {
14857 Some(range)
14858 } else {
14859 this.update(cx, |this, cx| {
14860 let buffer = this.buffer.read(cx).snapshot(cx);
14861 let mut buffer_highlights = this
14862 .document_highlights_for_position(selection.head(), &buffer)
14863 .filter(|highlight| {
14864 highlight.start.excerpt_id == selection.head().excerpt_id
14865 && highlight.end.excerpt_id == selection.head().excerpt_id
14866 });
14867 buffer_highlights
14868 .next()
14869 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14870 })?
14871 };
14872 if let Some(rename_range) = rename_range {
14873 this.update_in(cx, |this, window, cx| {
14874 let snapshot = cursor_buffer.read(cx).snapshot();
14875 let rename_buffer_range = rename_range.to_offset(&snapshot);
14876 let cursor_offset_in_rename_range =
14877 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14878 let cursor_offset_in_rename_range_end =
14879 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14880
14881 this.take_rename(false, window, cx);
14882 let buffer = this.buffer.read(cx).read(cx);
14883 let cursor_offset = selection.head().to_offset(&buffer);
14884 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14885 let rename_end = rename_start + rename_buffer_range.len();
14886 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14887 let mut old_highlight_id = None;
14888 let old_name: Arc<str> = buffer
14889 .chunks(rename_start..rename_end, true)
14890 .map(|chunk| {
14891 if old_highlight_id.is_none() {
14892 old_highlight_id = chunk.syntax_highlight_id;
14893 }
14894 chunk.text
14895 })
14896 .collect::<String>()
14897 .into();
14898
14899 drop(buffer);
14900
14901 // Position the selection in the rename editor so that it matches the current selection.
14902 this.show_local_selections = false;
14903 let rename_editor = cx.new(|cx| {
14904 let mut editor = Editor::single_line(window, cx);
14905 editor.buffer.update(cx, |buffer, cx| {
14906 buffer.edit([(0..0, old_name.clone())], None, cx)
14907 });
14908 let rename_selection_range = match cursor_offset_in_rename_range
14909 .cmp(&cursor_offset_in_rename_range_end)
14910 {
14911 Ordering::Equal => {
14912 editor.select_all(&SelectAll, window, cx);
14913 return editor;
14914 }
14915 Ordering::Less => {
14916 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14917 }
14918 Ordering::Greater => {
14919 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14920 }
14921 };
14922 if rename_selection_range.end > old_name.len() {
14923 editor.select_all(&SelectAll, window, cx);
14924 } else {
14925 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14926 s.select_ranges([rename_selection_range]);
14927 });
14928 }
14929 editor
14930 });
14931 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14932 if e == &EditorEvent::Focused {
14933 cx.emit(EditorEvent::FocusedIn)
14934 }
14935 })
14936 .detach();
14937
14938 let write_highlights =
14939 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14940 let read_highlights =
14941 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14942 let ranges = write_highlights
14943 .iter()
14944 .flat_map(|(_, ranges)| ranges.iter())
14945 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14946 .cloned()
14947 .collect();
14948
14949 this.highlight_text::<Rename>(
14950 ranges,
14951 HighlightStyle {
14952 fade_out: Some(0.6),
14953 ..Default::default()
14954 },
14955 cx,
14956 );
14957 let rename_focus_handle = rename_editor.focus_handle(cx);
14958 window.focus(&rename_focus_handle);
14959 let block_id = this.insert_blocks(
14960 [BlockProperties {
14961 style: BlockStyle::Flex,
14962 placement: BlockPlacement::Below(range.start),
14963 height: Some(1),
14964 render: Arc::new({
14965 let rename_editor = rename_editor.clone();
14966 move |cx: &mut BlockContext| {
14967 let mut text_style = cx.editor_style.text.clone();
14968 if let Some(highlight_style) = old_highlight_id
14969 .and_then(|h| h.style(&cx.editor_style.syntax))
14970 {
14971 text_style = text_style.highlight(highlight_style);
14972 }
14973 div()
14974 .block_mouse_down()
14975 .pl(cx.anchor_x)
14976 .child(EditorElement::new(
14977 &rename_editor,
14978 EditorStyle {
14979 background: cx.theme().system().transparent,
14980 local_player: cx.editor_style.local_player,
14981 text: text_style,
14982 scrollbar_width: cx.editor_style.scrollbar_width,
14983 syntax: cx.editor_style.syntax.clone(),
14984 status: cx.editor_style.status.clone(),
14985 inlay_hints_style: HighlightStyle {
14986 font_weight: Some(FontWeight::BOLD),
14987 ..make_inlay_hints_style(cx.app)
14988 },
14989 inline_completion_styles: make_suggestion_styles(
14990 cx.app,
14991 ),
14992 ..EditorStyle::default()
14993 },
14994 ))
14995 .into_any_element()
14996 }
14997 }),
14998 priority: 0,
14999 render_in_minimap: true,
15000 }],
15001 Some(Autoscroll::fit()),
15002 cx,
15003 )[0];
15004 this.pending_rename = Some(RenameState {
15005 range,
15006 old_name,
15007 editor: rename_editor,
15008 block_id,
15009 });
15010 })?;
15011 }
15012
15013 Ok(())
15014 }))
15015 }
15016
15017 pub fn confirm_rename(
15018 &mut self,
15019 _: &ConfirmRename,
15020 window: &mut Window,
15021 cx: &mut Context<Self>,
15022 ) -> Option<Task<Result<()>>> {
15023 let rename = self.take_rename(false, window, cx)?;
15024 let workspace = self.workspace()?.downgrade();
15025 let (buffer, start) = self
15026 .buffer
15027 .read(cx)
15028 .text_anchor_for_position(rename.range.start, cx)?;
15029 let (end_buffer, _) = self
15030 .buffer
15031 .read(cx)
15032 .text_anchor_for_position(rename.range.end, cx)?;
15033 if buffer != end_buffer {
15034 return None;
15035 }
15036
15037 let old_name = rename.old_name;
15038 let new_name = rename.editor.read(cx).text(cx);
15039
15040 let rename = self.semantics_provider.as_ref()?.perform_rename(
15041 &buffer,
15042 start,
15043 new_name.clone(),
15044 cx,
15045 )?;
15046
15047 Some(cx.spawn_in(window, async move |editor, cx| {
15048 let project_transaction = rename.await?;
15049 Self::open_project_transaction(
15050 &editor,
15051 workspace,
15052 project_transaction,
15053 format!("Rename: {} → {}", old_name, new_name),
15054 cx,
15055 )
15056 .await?;
15057
15058 editor.update(cx, |editor, cx| {
15059 editor.refresh_document_highlights(cx);
15060 })?;
15061 Ok(())
15062 }))
15063 }
15064
15065 fn take_rename(
15066 &mut self,
15067 moving_cursor: bool,
15068 window: &mut Window,
15069 cx: &mut Context<Self>,
15070 ) -> Option<RenameState> {
15071 let rename = self.pending_rename.take()?;
15072 if rename.editor.focus_handle(cx).is_focused(window) {
15073 window.focus(&self.focus_handle);
15074 }
15075
15076 self.remove_blocks(
15077 [rename.block_id].into_iter().collect(),
15078 Some(Autoscroll::fit()),
15079 cx,
15080 );
15081 self.clear_highlights::<Rename>(cx);
15082 self.show_local_selections = true;
15083
15084 if moving_cursor {
15085 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15086 editor.selections.newest::<usize>(cx).head()
15087 });
15088
15089 // Update the selection to match the position of the selection inside
15090 // the rename editor.
15091 let snapshot = self.buffer.read(cx).read(cx);
15092 let rename_range = rename.range.to_offset(&snapshot);
15093 let cursor_in_editor = snapshot
15094 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15095 .min(rename_range.end);
15096 drop(snapshot);
15097
15098 self.change_selections(None, window, cx, |s| {
15099 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15100 });
15101 } else {
15102 self.refresh_document_highlights(cx);
15103 }
15104
15105 Some(rename)
15106 }
15107
15108 pub fn pending_rename(&self) -> Option<&RenameState> {
15109 self.pending_rename.as_ref()
15110 }
15111
15112 fn format(
15113 &mut self,
15114 _: &Format,
15115 window: &mut Window,
15116 cx: &mut Context<Self>,
15117 ) -> Option<Task<Result<()>>> {
15118 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15119
15120 let project = match &self.project {
15121 Some(project) => project.clone(),
15122 None => return None,
15123 };
15124
15125 Some(self.perform_format(
15126 project,
15127 FormatTrigger::Manual,
15128 FormatTarget::Buffers,
15129 window,
15130 cx,
15131 ))
15132 }
15133
15134 fn format_selections(
15135 &mut self,
15136 _: &FormatSelections,
15137 window: &mut Window,
15138 cx: &mut Context<Self>,
15139 ) -> Option<Task<Result<()>>> {
15140 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15141
15142 let project = match &self.project {
15143 Some(project) => project.clone(),
15144 None => return None,
15145 };
15146
15147 let ranges = self
15148 .selections
15149 .all_adjusted(cx)
15150 .into_iter()
15151 .map(|selection| selection.range())
15152 .collect_vec();
15153
15154 Some(self.perform_format(
15155 project,
15156 FormatTrigger::Manual,
15157 FormatTarget::Ranges(ranges),
15158 window,
15159 cx,
15160 ))
15161 }
15162
15163 fn perform_format(
15164 &mut self,
15165 project: Entity<Project>,
15166 trigger: FormatTrigger,
15167 target: FormatTarget,
15168 window: &mut Window,
15169 cx: &mut Context<Self>,
15170 ) -> Task<Result<()>> {
15171 let buffer = self.buffer.clone();
15172 let (buffers, target) = match target {
15173 FormatTarget::Buffers => {
15174 let mut buffers = buffer.read(cx).all_buffers();
15175 if trigger == FormatTrigger::Save {
15176 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15177 }
15178 (buffers, LspFormatTarget::Buffers)
15179 }
15180 FormatTarget::Ranges(selection_ranges) => {
15181 let multi_buffer = buffer.read(cx);
15182 let snapshot = multi_buffer.read(cx);
15183 let mut buffers = HashSet::default();
15184 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15185 BTreeMap::new();
15186 for selection_range in selection_ranges {
15187 for (buffer, buffer_range, _) in
15188 snapshot.range_to_buffer_ranges(selection_range)
15189 {
15190 let buffer_id = buffer.remote_id();
15191 let start = buffer.anchor_before(buffer_range.start);
15192 let end = buffer.anchor_after(buffer_range.end);
15193 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15194 buffer_id_to_ranges
15195 .entry(buffer_id)
15196 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15197 .or_insert_with(|| vec![start..end]);
15198 }
15199 }
15200 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15201 }
15202 };
15203
15204 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15205 let selections_prev = transaction_id_prev
15206 .and_then(|transaction_id_prev| {
15207 // default to selections as they were after the last edit, if we have them,
15208 // instead of how they are now.
15209 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15210 // will take you back to where you made the last edit, instead of staying where you scrolled
15211 self.selection_history
15212 .transaction(transaction_id_prev)
15213 .map(|t| t.0.clone())
15214 })
15215 .unwrap_or_else(|| {
15216 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15217 self.selections.disjoint_anchors()
15218 });
15219
15220 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15221 let format = project.update(cx, |project, cx| {
15222 project.format(buffers, target, true, trigger, cx)
15223 });
15224
15225 cx.spawn_in(window, async move |editor, cx| {
15226 let transaction = futures::select_biased! {
15227 transaction = format.log_err().fuse() => transaction,
15228 () = timeout => {
15229 log::warn!("timed out waiting for formatting");
15230 None
15231 }
15232 };
15233
15234 buffer
15235 .update(cx, |buffer, cx| {
15236 if let Some(transaction) = transaction {
15237 if !buffer.is_singleton() {
15238 buffer.push_transaction(&transaction.0, cx);
15239 }
15240 }
15241 cx.notify();
15242 })
15243 .ok();
15244
15245 if let Some(transaction_id_now) =
15246 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15247 {
15248 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15249 if has_new_transaction {
15250 _ = editor.update(cx, |editor, _| {
15251 editor
15252 .selection_history
15253 .insert_transaction(transaction_id_now, selections_prev);
15254 });
15255 }
15256 }
15257
15258 Ok(())
15259 })
15260 }
15261
15262 fn organize_imports(
15263 &mut self,
15264 _: &OrganizeImports,
15265 window: &mut Window,
15266 cx: &mut Context<Self>,
15267 ) -> Option<Task<Result<()>>> {
15268 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15269 let project = match &self.project {
15270 Some(project) => project.clone(),
15271 None => return None,
15272 };
15273 Some(self.perform_code_action_kind(
15274 project,
15275 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15276 window,
15277 cx,
15278 ))
15279 }
15280
15281 fn perform_code_action_kind(
15282 &mut self,
15283 project: Entity<Project>,
15284 kind: CodeActionKind,
15285 window: &mut Window,
15286 cx: &mut Context<Self>,
15287 ) -> Task<Result<()>> {
15288 let buffer = self.buffer.clone();
15289 let buffers = buffer.read(cx).all_buffers();
15290 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15291 let apply_action = project.update(cx, |project, cx| {
15292 project.apply_code_action_kind(buffers, kind, true, cx)
15293 });
15294 cx.spawn_in(window, async move |_, cx| {
15295 let transaction = futures::select_biased! {
15296 () = timeout => {
15297 log::warn!("timed out waiting for executing code action");
15298 None
15299 }
15300 transaction = apply_action.log_err().fuse() => transaction,
15301 };
15302 buffer
15303 .update(cx, |buffer, cx| {
15304 // check if we need this
15305 if let Some(transaction) = transaction {
15306 if !buffer.is_singleton() {
15307 buffer.push_transaction(&transaction.0, cx);
15308 }
15309 }
15310 cx.notify();
15311 })
15312 .ok();
15313 Ok(())
15314 })
15315 }
15316
15317 fn restart_language_server(
15318 &mut self,
15319 _: &RestartLanguageServer,
15320 _: &mut Window,
15321 cx: &mut Context<Self>,
15322 ) {
15323 if let Some(project) = self.project.clone() {
15324 self.buffer.update(cx, |multi_buffer, cx| {
15325 project.update(cx, |project, cx| {
15326 project.restart_language_servers_for_buffers(
15327 multi_buffer.all_buffers().into_iter().collect(),
15328 cx,
15329 );
15330 });
15331 })
15332 }
15333 }
15334
15335 fn stop_language_server(
15336 &mut self,
15337 _: &StopLanguageServer,
15338 _: &mut Window,
15339 cx: &mut Context<Self>,
15340 ) {
15341 if let Some(project) = self.project.clone() {
15342 self.buffer.update(cx, |multi_buffer, cx| {
15343 project.update(cx, |project, cx| {
15344 project.stop_language_servers_for_buffers(
15345 multi_buffer.all_buffers().into_iter().collect(),
15346 cx,
15347 );
15348 cx.emit(project::Event::RefreshInlayHints);
15349 });
15350 });
15351 }
15352 }
15353
15354 fn cancel_language_server_work(
15355 workspace: &mut Workspace,
15356 _: &actions::CancelLanguageServerWork,
15357 _: &mut Window,
15358 cx: &mut Context<Workspace>,
15359 ) {
15360 let project = workspace.project();
15361 let buffers = workspace
15362 .active_item(cx)
15363 .and_then(|item| item.act_as::<Editor>(cx))
15364 .map_or(HashSet::default(), |editor| {
15365 editor.read(cx).buffer.read(cx).all_buffers()
15366 });
15367 project.update(cx, |project, cx| {
15368 project.cancel_language_server_work_for_buffers(buffers, cx);
15369 });
15370 }
15371
15372 fn show_character_palette(
15373 &mut self,
15374 _: &ShowCharacterPalette,
15375 window: &mut Window,
15376 _: &mut Context<Self>,
15377 ) {
15378 window.show_character_palette();
15379 }
15380
15381 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15382 if self.mode.is_minimap() {
15383 return;
15384 }
15385
15386 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15387 let buffer = self.buffer.read(cx).snapshot(cx);
15388 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15389 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15390 let is_valid = buffer
15391 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15392 .any(|entry| {
15393 entry.diagnostic.is_primary
15394 && !entry.range.is_empty()
15395 && entry.range.start == primary_range_start
15396 && entry.diagnostic.message == active_diagnostics.active_message
15397 });
15398
15399 if !is_valid {
15400 self.dismiss_diagnostics(cx);
15401 }
15402 }
15403 }
15404
15405 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15406 match &self.active_diagnostics {
15407 ActiveDiagnostic::Group(group) => Some(group),
15408 _ => None,
15409 }
15410 }
15411
15412 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15413 self.dismiss_diagnostics(cx);
15414 self.active_diagnostics = ActiveDiagnostic::All;
15415 }
15416
15417 fn activate_diagnostics(
15418 &mut self,
15419 buffer_id: BufferId,
15420 diagnostic: DiagnosticEntry<usize>,
15421 window: &mut Window,
15422 cx: &mut Context<Self>,
15423 ) {
15424 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15425 return;
15426 }
15427 self.dismiss_diagnostics(cx);
15428 let snapshot = self.snapshot(window, cx);
15429 let buffer = self.buffer.read(cx).snapshot(cx);
15430 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15431 return;
15432 };
15433
15434 let diagnostic_group = buffer
15435 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15436 .collect::<Vec<_>>();
15437
15438 let blocks =
15439 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15440
15441 let blocks = self.display_map.update(cx, |display_map, cx| {
15442 display_map.insert_blocks(blocks, cx).into_iter().collect()
15443 });
15444 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15445 active_range: buffer.anchor_before(diagnostic.range.start)
15446 ..buffer.anchor_after(diagnostic.range.end),
15447 active_message: diagnostic.diagnostic.message.clone(),
15448 group_id: diagnostic.diagnostic.group_id,
15449 blocks,
15450 });
15451 cx.notify();
15452 }
15453
15454 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15455 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15456 return;
15457 };
15458
15459 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15460 if let ActiveDiagnostic::Group(group) = prev {
15461 self.display_map.update(cx, |display_map, cx| {
15462 display_map.remove_blocks(group.blocks, cx);
15463 });
15464 cx.notify();
15465 }
15466 }
15467
15468 /// Disable inline diagnostics rendering for this editor.
15469 pub fn disable_inline_diagnostics(&mut self) {
15470 self.inline_diagnostics_enabled = false;
15471 self.inline_diagnostics_update = Task::ready(());
15472 self.inline_diagnostics.clear();
15473 }
15474
15475 pub fn diagnostics_enabled(&self) -> bool {
15476 self.mode.is_full()
15477 }
15478
15479 pub fn inline_diagnostics_enabled(&self) -> bool {
15480 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15481 }
15482
15483 pub fn show_inline_diagnostics(&self) -> bool {
15484 self.show_inline_diagnostics
15485 }
15486
15487 pub fn toggle_inline_diagnostics(
15488 &mut self,
15489 _: &ToggleInlineDiagnostics,
15490 window: &mut Window,
15491 cx: &mut Context<Editor>,
15492 ) {
15493 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15494 self.refresh_inline_diagnostics(false, window, cx);
15495 }
15496
15497 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15498 self.diagnostics_max_severity = severity;
15499 self.display_map.update(cx, |display_map, _| {
15500 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15501 });
15502 }
15503
15504 pub fn toggle_diagnostics(
15505 &mut self,
15506 _: &ToggleDiagnostics,
15507 window: &mut Window,
15508 cx: &mut Context<Editor>,
15509 ) {
15510 if !self.diagnostics_enabled() {
15511 return;
15512 }
15513
15514 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15515 EditorSettings::get_global(cx)
15516 .diagnostics_max_severity
15517 .filter(|severity| severity != &DiagnosticSeverity::Off)
15518 .unwrap_or(DiagnosticSeverity::Hint)
15519 } else {
15520 DiagnosticSeverity::Off
15521 };
15522 self.set_max_diagnostics_severity(new_severity, cx);
15523 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15524 self.active_diagnostics = ActiveDiagnostic::None;
15525 self.inline_diagnostics_update = Task::ready(());
15526 self.inline_diagnostics.clear();
15527 } else {
15528 self.refresh_inline_diagnostics(false, window, cx);
15529 }
15530
15531 cx.notify();
15532 }
15533
15534 pub fn toggle_minimap(
15535 &mut self,
15536 _: &ToggleMinimap,
15537 window: &mut Window,
15538 cx: &mut Context<Editor>,
15539 ) {
15540 if self.supports_minimap(cx) {
15541 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15542 }
15543 }
15544
15545 fn refresh_inline_diagnostics(
15546 &mut self,
15547 debounce: bool,
15548 window: &mut Window,
15549 cx: &mut Context<Self>,
15550 ) {
15551 let max_severity = ProjectSettings::get_global(cx)
15552 .diagnostics
15553 .inline
15554 .max_severity
15555 .unwrap_or(self.diagnostics_max_severity);
15556
15557 if !self.inline_diagnostics_enabled()
15558 || !self.show_inline_diagnostics
15559 || max_severity == DiagnosticSeverity::Off
15560 {
15561 self.inline_diagnostics_update = Task::ready(());
15562 self.inline_diagnostics.clear();
15563 return;
15564 }
15565
15566 let debounce_ms = ProjectSettings::get_global(cx)
15567 .diagnostics
15568 .inline
15569 .update_debounce_ms;
15570 let debounce = if debounce && debounce_ms > 0 {
15571 Some(Duration::from_millis(debounce_ms))
15572 } else {
15573 None
15574 };
15575 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15576 let editor = editor.upgrade().unwrap();
15577
15578 if let Some(debounce) = debounce {
15579 cx.background_executor().timer(debounce).await;
15580 }
15581 let Some(snapshot) = editor
15582 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15583 .ok()
15584 else {
15585 return;
15586 };
15587
15588 let new_inline_diagnostics = cx
15589 .background_spawn(async move {
15590 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15591 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15592 let message = diagnostic_entry
15593 .diagnostic
15594 .message
15595 .split_once('\n')
15596 .map(|(line, _)| line)
15597 .map(SharedString::new)
15598 .unwrap_or_else(|| {
15599 SharedString::from(diagnostic_entry.diagnostic.message)
15600 });
15601 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15602 let (Ok(i) | Err(i)) = inline_diagnostics
15603 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15604 inline_diagnostics.insert(
15605 i,
15606 (
15607 start_anchor,
15608 InlineDiagnostic {
15609 message,
15610 group_id: diagnostic_entry.diagnostic.group_id,
15611 start: diagnostic_entry.range.start.to_point(&snapshot),
15612 is_primary: diagnostic_entry.diagnostic.is_primary,
15613 severity: diagnostic_entry.diagnostic.severity,
15614 },
15615 ),
15616 );
15617 }
15618 inline_diagnostics
15619 })
15620 .await;
15621
15622 editor
15623 .update(cx, |editor, cx| {
15624 editor.inline_diagnostics = new_inline_diagnostics;
15625 cx.notify();
15626 })
15627 .ok();
15628 });
15629 }
15630
15631 pub fn set_selections_from_remote(
15632 &mut self,
15633 selections: Vec<Selection<Anchor>>,
15634 pending_selection: Option<Selection<Anchor>>,
15635 window: &mut Window,
15636 cx: &mut Context<Self>,
15637 ) {
15638 let old_cursor_position = self.selections.newest_anchor().head();
15639 self.selections.change_with(cx, |s| {
15640 s.select_anchors(selections);
15641 if let Some(pending_selection) = pending_selection {
15642 s.set_pending(pending_selection, SelectMode::Character);
15643 } else {
15644 s.clear_pending();
15645 }
15646 });
15647 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15648 }
15649
15650 fn push_to_selection_history(&mut self) {
15651 self.selection_history.push(SelectionHistoryEntry {
15652 selections: self.selections.disjoint_anchors(),
15653 select_next_state: self.select_next_state.clone(),
15654 select_prev_state: self.select_prev_state.clone(),
15655 add_selections_state: self.add_selections_state.clone(),
15656 });
15657 }
15658
15659 pub fn transact(
15660 &mut self,
15661 window: &mut Window,
15662 cx: &mut Context<Self>,
15663 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15664 ) -> Option<TransactionId> {
15665 self.start_transaction_at(Instant::now(), window, cx);
15666 update(self, window, cx);
15667 self.end_transaction_at(Instant::now(), cx)
15668 }
15669
15670 pub fn start_transaction_at(
15671 &mut self,
15672 now: Instant,
15673 window: &mut Window,
15674 cx: &mut Context<Self>,
15675 ) {
15676 self.end_selection(window, cx);
15677 if let Some(tx_id) = self
15678 .buffer
15679 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15680 {
15681 self.selection_history
15682 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15683 cx.emit(EditorEvent::TransactionBegun {
15684 transaction_id: tx_id,
15685 })
15686 }
15687 }
15688
15689 pub fn end_transaction_at(
15690 &mut self,
15691 now: Instant,
15692 cx: &mut Context<Self>,
15693 ) -> Option<TransactionId> {
15694 if let Some(transaction_id) = self
15695 .buffer
15696 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15697 {
15698 if let Some((_, end_selections)) =
15699 self.selection_history.transaction_mut(transaction_id)
15700 {
15701 *end_selections = Some(self.selections.disjoint_anchors());
15702 } else {
15703 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15704 }
15705
15706 cx.emit(EditorEvent::Edited { transaction_id });
15707 Some(transaction_id)
15708 } else {
15709 None
15710 }
15711 }
15712
15713 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15714 if self.selection_mark_mode {
15715 self.change_selections(None, window, cx, |s| {
15716 s.move_with(|_, sel| {
15717 sel.collapse_to(sel.head(), SelectionGoal::None);
15718 });
15719 })
15720 }
15721 self.selection_mark_mode = true;
15722 cx.notify();
15723 }
15724
15725 pub fn swap_selection_ends(
15726 &mut self,
15727 _: &actions::SwapSelectionEnds,
15728 window: &mut Window,
15729 cx: &mut Context<Self>,
15730 ) {
15731 self.change_selections(None, window, cx, |s| {
15732 s.move_with(|_, sel| {
15733 if sel.start != sel.end {
15734 sel.reversed = !sel.reversed
15735 }
15736 });
15737 });
15738 self.request_autoscroll(Autoscroll::newest(), cx);
15739 cx.notify();
15740 }
15741
15742 pub fn toggle_fold(
15743 &mut self,
15744 _: &actions::ToggleFold,
15745 window: &mut Window,
15746 cx: &mut Context<Self>,
15747 ) {
15748 if self.is_singleton(cx) {
15749 let selection = self.selections.newest::<Point>(cx);
15750
15751 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15752 let range = if selection.is_empty() {
15753 let point = selection.head().to_display_point(&display_map);
15754 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15755 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15756 .to_point(&display_map);
15757 start..end
15758 } else {
15759 selection.range()
15760 };
15761 if display_map.folds_in_range(range).next().is_some() {
15762 self.unfold_lines(&Default::default(), window, cx)
15763 } else {
15764 self.fold(&Default::default(), window, cx)
15765 }
15766 } else {
15767 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15768 let buffer_ids: HashSet<_> = self
15769 .selections
15770 .disjoint_anchor_ranges()
15771 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15772 .collect();
15773
15774 let should_unfold = buffer_ids
15775 .iter()
15776 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15777
15778 for buffer_id in buffer_ids {
15779 if should_unfold {
15780 self.unfold_buffer(buffer_id, cx);
15781 } else {
15782 self.fold_buffer(buffer_id, cx);
15783 }
15784 }
15785 }
15786 }
15787
15788 pub fn toggle_fold_recursive(
15789 &mut self,
15790 _: &actions::ToggleFoldRecursive,
15791 window: &mut Window,
15792 cx: &mut Context<Self>,
15793 ) {
15794 let selection = self.selections.newest::<Point>(cx);
15795
15796 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15797 let range = if selection.is_empty() {
15798 let point = selection.head().to_display_point(&display_map);
15799 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15800 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15801 .to_point(&display_map);
15802 start..end
15803 } else {
15804 selection.range()
15805 };
15806 if display_map.folds_in_range(range).next().is_some() {
15807 self.unfold_recursive(&Default::default(), window, cx)
15808 } else {
15809 self.fold_recursive(&Default::default(), window, cx)
15810 }
15811 }
15812
15813 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15814 if self.is_singleton(cx) {
15815 let mut to_fold = Vec::new();
15816 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15817 let selections = self.selections.all_adjusted(cx);
15818
15819 for selection in selections {
15820 let range = selection.range().sorted();
15821 let buffer_start_row = range.start.row;
15822
15823 if range.start.row != range.end.row {
15824 let mut found = false;
15825 let mut row = range.start.row;
15826 while row <= range.end.row {
15827 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15828 {
15829 found = true;
15830 row = crease.range().end.row + 1;
15831 to_fold.push(crease);
15832 } else {
15833 row += 1
15834 }
15835 }
15836 if found {
15837 continue;
15838 }
15839 }
15840
15841 for row in (0..=range.start.row).rev() {
15842 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15843 if crease.range().end.row >= buffer_start_row {
15844 to_fold.push(crease);
15845 if row <= range.start.row {
15846 break;
15847 }
15848 }
15849 }
15850 }
15851 }
15852
15853 self.fold_creases(to_fold, true, window, cx);
15854 } else {
15855 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15856 let buffer_ids = self
15857 .selections
15858 .disjoint_anchor_ranges()
15859 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15860 .collect::<HashSet<_>>();
15861 for buffer_id in buffer_ids {
15862 self.fold_buffer(buffer_id, cx);
15863 }
15864 }
15865 }
15866
15867 fn fold_at_level(
15868 &mut self,
15869 fold_at: &FoldAtLevel,
15870 window: &mut Window,
15871 cx: &mut Context<Self>,
15872 ) {
15873 if !self.buffer.read(cx).is_singleton() {
15874 return;
15875 }
15876
15877 let fold_at_level = fold_at.0;
15878 let snapshot = self.buffer.read(cx).snapshot(cx);
15879 let mut to_fold = Vec::new();
15880 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15881
15882 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15883 while start_row < end_row {
15884 match self
15885 .snapshot(window, cx)
15886 .crease_for_buffer_row(MultiBufferRow(start_row))
15887 {
15888 Some(crease) => {
15889 let nested_start_row = crease.range().start.row + 1;
15890 let nested_end_row = crease.range().end.row;
15891
15892 if current_level < fold_at_level {
15893 stack.push((nested_start_row, nested_end_row, current_level + 1));
15894 } else if current_level == fold_at_level {
15895 to_fold.push(crease);
15896 }
15897
15898 start_row = nested_end_row + 1;
15899 }
15900 None => start_row += 1,
15901 }
15902 }
15903 }
15904
15905 self.fold_creases(to_fold, true, window, cx);
15906 }
15907
15908 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15909 if self.buffer.read(cx).is_singleton() {
15910 let mut fold_ranges = Vec::new();
15911 let snapshot = self.buffer.read(cx).snapshot(cx);
15912
15913 for row in 0..snapshot.max_row().0 {
15914 if let Some(foldable_range) = self
15915 .snapshot(window, cx)
15916 .crease_for_buffer_row(MultiBufferRow(row))
15917 {
15918 fold_ranges.push(foldable_range);
15919 }
15920 }
15921
15922 self.fold_creases(fold_ranges, true, window, cx);
15923 } else {
15924 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15925 editor
15926 .update_in(cx, |editor, _, cx| {
15927 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15928 editor.fold_buffer(buffer_id, cx);
15929 }
15930 })
15931 .ok();
15932 });
15933 }
15934 }
15935
15936 pub fn fold_function_bodies(
15937 &mut self,
15938 _: &actions::FoldFunctionBodies,
15939 window: &mut Window,
15940 cx: &mut Context<Self>,
15941 ) {
15942 let snapshot = self.buffer.read(cx).snapshot(cx);
15943
15944 let ranges = snapshot
15945 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15946 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15947 .collect::<Vec<_>>();
15948
15949 let creases = ranges
15950 .into_iter()
15951 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15952 .collect();
15953
15954 self.fold_creases(creases, true, window, cx);
15955 }
15956
15957 pub fn fold_recursive(
15958 &mut self,
15959 _: &actions::FoldRecursive,
15960 window: &mut Window,
15961 cx: &mut Context<Self>,
15962 ) {
15963 let mut to_fold = Vec::new();
15964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15965 let selections = self.selections.all_adjusted(cx);
15966
15967 for selection in selections {
15968 let range = selection.range().sorted();
15969 let buffer_start_row = range.start.row;
15970
15971 if range.start.row != range.end.row {
15972 let mut found = false;
15973 for row in range.start.row..=range.end.row {
15974 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15975 found = true;
15976 to_fold.push(crease);
15977 }
15978 }
15979 if found {
15980 continue;
15981 }
15982 }
15983
15984 for row in (0..=range.start.row).rev() {
15985 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15986 if crease.range().end.row >= buffer_start_row {
15987 to_fold.push(crease);
15988 } else {
15989 break;
15990 }
15991 }
15992 }
15993 }
15994
15995 self.fold_creases(to_fold, true, window, cx);
15996 }
15997
15998 pub fn fold_at(
15999 &mut self,
16000 buffer_row: MultiBufferRow,
16001 window: &mut Window,
16002 cx: &mut Context<Self>,
16003 ) {
16004 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16005
16006 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16007 let autoscroll = self
16008 .selections
16009 .all::<Point>(cx)
16010 .iter()
16011 .any(|selection| crease.range().overlaps(&selection.range()));
16012
16013 self.fold_creases(vec![crease], autoscroll, window, cx);
16014 }
16015 }
16016
16017 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16018 if self.is_singleton(cx) {
16019 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16020 let buffer = &display_map.buffer_snapshot;
16021 let selections = self.selections.all::<Point>(cx);
16022 let ranges = selections
16023 .iter()
16024 .map(|s| {
16025 let range = s.display_range(&display_map).sorted();
16026 let mut start = range.start.to_point(&display_map);
16027 let mut end = range.end.to_point(&display_map);
16028 start.column = 0;
16029 end.column = buffer.line_len(MultiBufferRow(end.row));
16030 start..end
16031 })
16032 .collect::<Vec<_>>();
16033
16034 self.unfold_ranges(&ranges, true, true, cx);
16035 } else {
16036 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16037 let buffer_ids = self
16038 .selections
16039 .disjoint_anchor_ranges()
16040 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16041 .collect::<HashSet<_>>();
16042 for buffer_id in buffer_ids {
16043 self.unfold_buffer(buffer_id, cx);
16044 }
16045 }
16046 }
16047
16048 pub fn unfold_recursive(
16049 &mut self,
16050 _: &UnfoldRecursive,
16051 _window: &mut Window,
16052 cx: &mut Context<Self>,
16053 ) {
16054 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16055 let selections = self.selections.all::<Point>(cx);
16056 let ranges = selections
16057 .iter()
16058 .map(|s| {
16059 let mut range = s.display_range(&display_map).sorted();
16060 *range.start.column_mut() = 0;
16061 *range.end.column_mut() = display_map.line_len(range.end.row());
16062 let start = range.start.to_point(&display_map);
16063 let end = range.end.to_point(&display_map);
16064 start..end
16065 })
16066 .collect::<Vec<_>>();
16067
16068 self.unfold_ranges(&ranges, true, true, cx);
16069 }
16070
16071 pub fn unfold_at(
16072 &mut self,
16073 buffer_row: MultiBufferRow,
16074 _window: &mut Window,
16075 cx: &mut Context<Self>,
16076 ) {
16077 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16078
16079 let intersection_range = Point::new(buffer_row.0, 0)
16080 ..Point::new(
16081 buffer_row.0,
16082 display_map.buffer_snapshot.line_len(buffer_row),
16083 );
16084
16085 let autoscroll = self
16086 .selections
16087 .all::<Point>(cx)
16088 .iter()
16089 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16090
16091 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16092 }
16093
16094 pub fn unfold_all(
16095 &mut self,
16096 _: &actions::UnfoldAll,
16097 _window: &mut Window,
16098 cx: &mut Context<Self>,
16099 ) {
16100 if self.buffer.read(cx).is_singleton() {
16101 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16102 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16103 } else {
16104 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16105 editor
16106 .update(cx, |editor, cx| {
16107 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16108 editor.unfold_buffer(buffer_id, cx);
16109 }
16110 })
16111 .ok();
16112 });
16113 }
16114 }
16115
16116 pub fn fold_selected_ranges(
16117 &mut self,
16118 _: &FoldSelectedRanges,
16119 window: &mut Window,
16120 cx: &mut Context<Self>,
16121 ) {
16122 let selections = self.selections.all_adjusted(cx);
16123 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16124 let ranges = selections
16125 .into_iter()
16126 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16127 .collect::<Vec<_>>();
16128 self.fold_creases(ranges, true, window, cx);
16129 }
16130
16131 pub fn fold_ranges<T: ToOffset + Clone>(
16132 &mut self,
16133 ranges: Vec<Range<T>>,
16134 auto_scroll: bool,
16135 window: &mut Window,
16136 cx: &mut Context<Self>,
16137 ) {
16138 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16139 let ranges = ranges
16140 .into_iter()
16141 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16142 .collect::<Vec<_>>();
16143 self.fold_creases(ranges, auto_scroll, window, cx);
16144 }
16145
16146 pub fn fold_creases<T: ToOffset + Clone>(
16147 &mut self,
16148 creases: Vec<Crease<T>>,
16149 auto_scroll: bool,
16150 _window: &mut Window,
16151 cx: &mut Context<Self>,
16152 ) {
16153 if creases.is_empty() {
16154 return;
16155 }
16156
16157 let mut buffers_affected = HashSet::default();
16158 let multi_buffer = self.buffer().read(cx);
16159 for crease in &creases {
16160 if let Some((_, buffer, _)) =
16161 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16162 {
16163 buffers_affected.insert(buffer.read(cx).remote_id());
16164 };
16165 }
16166
16167 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16168
16169 if auto_scroll {
16170 self.request_autoscroll(Autoscroll::fit(), cx);
16171 }
16172
16173 cx.notify();
16174
16175 self.scrollbar_marker_state.dirty = true;
16176 self.folds_did_change(cx);
16177 }
16178
16179 /// Removes any folds whose ranges intersect any of the given ranges.
16180 pub fn unfold_ranges<T: ToOffset + Clone>(
16181 &mut self,
16182 ranges: &[Range<T>],
16183 inclusive: bool,
16184 auto_scroll: bool,
16185 cx: &mut Context<Self>,
16186 ) {
16187 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16188 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16189 });
16190 self.folds_did_change(cx);
16191 }
16192
16193 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16194 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16195 return;
16196 }
16197 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16198 self.display_map.update(cx, |display_map, cx| {
16199 display_map.fold_buffers([buffer_id], cx)
16200 });
16201 cx.emit(EditorEvent::BufferFoldToggled {
16202 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16203 folded: true,
16204 });
16205 cx.notify();
16206 }
16207
16208 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16209 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16210 return;
16211 }
16212 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16213 self.display_map.update(cx, |display_map, cx| {
16214 display_map.unfold_buffers([buffer_id], cx);
16215 });
16216 cx.emit(EditorEvent::BufferFoldToggled {
16217 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16218 folded: false,
16219 });
16220 cx.notify();
16221 }
16222
16223 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16224 self.display_map.read(cx).is_buffer_folded(buffer)
16225 }
16226
16227 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16228 self.display_map.read(cx).folded_buffers()
16229 }
16230
16231 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16232 self.display_map.update(cx, |display_map, cx| {
16233 display_map.disable_header_for_buffer(buffer_id, cx);
16234 });
16235 cx.notify();
16236 }
16237
16238 /// Removes any folds with the given ranges.
16239 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16240 &mut self,
16241 ranges: &[Range<T>],
16242 type_id: TypeId,
16243 auto_scroll: bool,
16244 cx: &mut Context<Self>,
16245 ) {
16246 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16247 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16248 });
16249 self.folds_did_change(cx);
16250 }
16251
16252 fn remove_folds_with<T: ToOffset + Clone>(
16253 &mut self,
16254 ranges: &[Range<T>],
16255 auto_scroll: bool,
16256 cx: &mut Context<Self>,
16257 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16258 ) {
16259 if ranges.is_empty() {
16260 return;
16261 }
16262
16263 let mut buffers_affected = HashSet::default();
16264 let multi_buffer = self.buffer().read(cx);
16265 for range in ranges {
16266 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16267 buffers_affected.insert(buffer.read(cx).remote_id());
16268 };
16269 }
16270
16271 self.display_map.update(cx, update);
16272
16273 if auto_scroll {
16274 self.request_autoscroll(Autoscroll::fit(), cx);
16275 }
16276
16277 cx.notify();
16278 self.scrollbar_marker_state.dirty = true;
16279 self.active_indent_guides_state.dirty = true;
16280 }
16281
16282 pub fn update_fold_widths(
16283 &mut self,
16284 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16285 cx: &mut Context<Self>,
16286 ) -> bool {
16287 self.display_map
16288 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16289 }
16290
16291 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16292 self.display_map.read(cx).fold_placeholder.clone()
16293 }
16294
16295 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16296 self.buffer.update(cx, |buffer, cx| {
16297 buffer.set_all_diff_hunks_expanded(cx);
16298 });
16299 }
16300
16301 pub fn expand_all_diff_hunks(
16302 &mut self,
16303 _: &ExpandAllDiffHunks,
16304 _window: &mut Window,
16305 cx: &mut Context<Self>,
16306 ) {
16307 self.buffer.update(cx, |buffer, cx| {
16308 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16309 });
16310 }
16311
16312 pub fn toggle_selected_diff_hunks(
16313 &mut self,
16314 _: &ToggleSelectedDiffHunks,
16315 _window: &mut Window,
16316 cx: &mut Context<Self>,
16317 ) {
16318 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16319 self.toggle_diff_hunks_in_ranges(ranges, cx);
16320 }
16321
16322 pub fn diff_hunks_in_ranges<'a>(
16323 &'a self,
16324 ranges: &'a [Range<Anchor>],
16325 buffer: &'a MultiBufferSnapshot,
16326 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16327 ranges.iter().flat_map(move |range| {
16328 let end_excerpt_id = range.end.excerpt_id;
16329 let range = range.to_point(buffer);
16330 let mut peek_end = range.end;
16331 if range.end.row < buffer.max_row().0 {
16332 peek_end = Point::new(range.end.row + 1, 0);
16333 }
16334 buffer
16335 .diff_hunks_in_range(range.start..peek_end)
16336 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16337 })
16338 }
16339
16340 pub fn has_stageable_diff_hunks_in_ranges(
16341 &self,
16342 ranges: &[Range<Anchor>],
16343 snapshot: &MultiBufferSnapshot,
16344 ) -> bool {
16345 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16346 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16347 }
16348
16349 pub fn toggle_staged_selected_diff_hunks(
16350 &mut self,
16351 _: &::git::ToggleStaged,
16352 _: &mut Window,
16353 cx: &mut Context<Self>,
16354 ) {
16355 let snapshot = self.buffer.read(cx).snapshot(cx);
16356 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16357 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16358 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16359 }
16360
16361 pub fn set_render_diff_hunk_controls(
16362 &mut self,
16363 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16364 cx: &mut Context<Self>,
16365 ) {
16366 self.render_diff_hunk_controls = render_diff_hunk_controls;
16367 cx.notify();
16368 }
16369
16370 pub fn stage_and_next(
16371 &mut self,
16372 _: &::git::StageAndNext,
16373 window: &mut Window,
16374 cx: &mut Context<Self>,
16375 ) {
16376 self.do_stage_or_unstage_and_next(true, window, cx);
16377 }
16378
16379 pub fn unstage_and_next(
16380 &mut self,
16381 _: &::git::UnstageAndNext,
16382 window: &mut Window,
16383 cx: &mut Context<Self>,
16384 ) {
16385 self.do_stage_or_unstage_and_next(false, window, cx);
16386 }
16387
16388 pub fn stage_or_unstage_diff_hunks(
16389 &mut self,
16390 stage: bool,
16391 ranges: Vec<Range<Anchor>>,
16392 cx: &mut Context<Self>,
16393 ) {
16394 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16395 cx.spawn(async move |this, cx| {
16396 task.await?;
16397 this.update(cx, |this, cx| {
16398 let snapshot = this.buffer.read(cx).snapshot(cx);
16399 let chunk_by = this
16400 .diff_hunks_in_ranges(&ranges, &snapshot)
16401 .chunk_by(|hunk| hunk.buffer_id);
16402 for (buffer_id, hunks) in &chunk_by {
16403 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16404 }
16405 })
16406 })
16407 .detach_and_log_err(cx);
16408 }
16409
16410 fn save_buffers_for_ranges_if_needed(
16411 &mut self,
16412 ranges: &[Range<Anchor>],
16413 cx: &mut Context<Editor>,
16414 ) -> Task<Result<()>> {
16415 let multibuffer = self.buffer.read(cx);
16416 let snapshot = multibuffer.read(cx);
16417 let buffer_ids: HashSet<_> = ranges
16418 .iter()
16419 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16420 .collect();
16421 drop(snapshot);
16422
16423 let mut buffers = HashSet::default();
16424 for buffer_id in buffer_ids {
16425 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16426 let buffer = buffer_entity.read(cx);
16427 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16428 {
16429 buffers.insert(buffer_entity);
16430 }
16431 }
16432 }
16433
16434 if let Some(project) = &self.project {
16435 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16436 } else {
16437 Task::ready(Ok(()))
16438 }
16439 }
16440
16441 fn do_stage_or_unstage_and_next(
16442 &mut self,
16443 stage: bool,
16444 window: &mut Window,
16445 cx: &mut Context<Self>,
16446 ) {
16447 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16448
16449 if ranges.iter().any(|range| range.start != range.end) {
16450 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16451 return;
16452 }
16453
16454 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16455 let snapshot = self.snapshot(window, cx);
16456 let position = self.selections.newest::<Point>(cx).head();
16457 let mut row = snapshot
16458 .buffer_snapshot
16459 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16460 .find(|hunk| hunk.row_range.start.0 > position.row)
16461 .map(|hunk| hunk.row_range.start);
16462
16463 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16464 // Outside of the project diff editor, wrap around to the beginning.
16465 if !all_diff_hunks_expanded {
16466 row = row.or_else(|| {
16467 snapshot
16468 .buffer_snapshot
16469 .diff_hunks_in_range(Point::zero()..position)
16470 .find(|hunk| hunk.row_range.end.0 < position.row)
16471 .map(|hunk| hunk.row_range.start)
16472 });
16473 }
16474
16475 if let Some(row) = row {
16476 let destination = Point::new(row.0, 0);
16477 let autoscroll = Autoscroll::center();
16478
16479 self.unfold_ranges(&[destination..destination], false, false, cx);
16480 self.change_selections(Some(autoscroll), window, cx, |s| {
16481 s.select_ranges([destination..destination]);
16482 });
16483 }
16484 }
16485
16486 fn do_stage_or_unstage(
16487 &self,
16488 stage: bool,
16489 buffer_id: BufferId,
16490 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16491 cx: &mut App,
16492 ) -> Option<()> {
16493 let project = self.project.as_ref()?;
16494 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16495 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16496 let buffer_snapshot = buffer.read(cx).snapshot();
16497 let file_exists = buffer_snapshot
16498 .file()
16499 .is_some_and(|file| file.disk_state().exists());
16500 diff.update(cx, |diff, cx| {
16501 diff.stage_or_unstage_hunks(
16502 stage,
16503 &hunks
16504 .map(|hunk| buffer_diff::DiffHunk {
16505 buffer_range: hunk.buffer_range,
16506 diff_base_byte_range: hunk.diff_base_byte_range,
16507 secondary_status: hunk.secondary_status,
16508 range: Point::zero()..Point::zero(), // unused
16509 })
16510 .collect::<Vec<_>>(),
16511 &buffer_snapshot,
16512 file_exists,
16513 cx,
16514 )
16515 });
16516 None
16517 }
16518
16519 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16520 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16521 self.buffer
16522 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16523 }
16524
16525 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16526 self.buffer.update(cx, |buffer, cx| {
16527 let ranges = vec![Anchor::min()..Anchor::max()];
16528 if !buffer.all_diff_hunks_expanded()
16529 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16530 {
16531 buffer.collapse_diff_hunks(ranges, cx);
16532 true
16533 } else {
16534 false
16535 }
16536 })
16537 }
16538
16539 fn toggle_diff_hunks_in_ranges(
16540 &mut self,
16541 ranges: Vec<Range<Anchor>>,
16542 cx: &mut Context<Editor>,
16543 ) {
16544 self.buffer.update(cx, |buffer, cx| {
16545 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16546 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16547 })
16548 }
16549
16550 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16551 self.buffer.update(cx, |buffer, cx| {
16552 let snapshot = buffer.snapshot(cx);
16553 let excerpt_id = range.end.excerpt_id;
16554 let point_range = range.to_point(&snapshot);
16555 let expand = !buffer.single_hunk_is_expanded(range, cx);
16556 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16557 })
16558 }
16559
16560 pub(crate) fn apply_all_diff_hunks(
16561 &mut self,
16562 _: &ApplyAllDiffHunks,
16563 window: &mut Window,
16564 cx: &mut Context<Self>,
16565 ) {
16566 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16567
16568 let buffers = self.buffer.read(cx).all_buffers();
16569 for branch_buffer in buffers {
16570 branch_buffer.update(cx, |branch_buffer, cx| {
16571 branch_buffer.merge_into_base(Vec::new(), cx);
16572 });
16573 }
16574
16575 if let Some(project) = self.project.clone() {
16576 self.save(true, project, window, cx).detach_and_log_err(cx);
16577 }
16578 }
16579
16580 pub(crate) fn apply_selected_diff_hunks(
16581 &mut self,
16582 _: &ApplyDiffHunk,
16583 window: &mut Window,
16584 cx: &mut Context<Self>,
16585 ) {
16586 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16587 let snapshot = self.snapshot(window, cx);
16588 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16589 let mut ranges_by_buffer = HashMap::default();
16590 self.transact(window, cx, |editor, _window, cx| {
16591 for hunk in hunks {
16592 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16593 ranges_by_buffer
16594 .entry(buffer.clone())
16595 .or_insert_with(Vec::new)
16596 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16597 }
16598 }
16599
16600 for (buffer, ranges) in ranges_by_buffer {
16601 buffer.update(cx, |buffer, cx| {
16602 buffer.merge_into_base(ranges, cx);
16603 });
16604 }
16605 });
16606
16607 if let Some(project) = self.project.clone() {
16608 self.save(true, project, window, cx).detach_and_log_err(cx);
16609 }
16610 }
16611
16612 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16613 if hovered != self.gutter_hovered {
16614 self.gutter_hovered = hovered;
16615 cx.notify();
16616 }
16617 }
16618
16619 pub fn insert_blocks(
16620 &mut self,
16621 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16622 autoscroll: Option<Autoscroll>,
16623 cx: &mut Context<Self>,
16624 ) -> Vec<CustomBlockId> {
16625 let blocks = self
16626 .display_map
16627 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16628 if let Some(autoscroll) = autoscroll {
16629 self.request_autoscroll(autoscroll, cx);
16630 }
16631 cx.notify();
16632 blocks
16633 }
16634
16635 pub fn resize_blocks(
16636 &mut self,
16637 heights: HashMap<CustomBlockId, u32>,
16638 autoscroll: Option<Autoscroll>,
16639 cx: &mut Context<Self>,
16640 ) {
16641 self.display_map
16642 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16643 if let Some(autoscroll) = autoscroll {
16644 self.request_autoscroll(autoscroll, cx);
16645 }
16646 cx.notify();
16647 }
16648
16649 pub fn replace_blocks(
16650 &mut self,
16651 renderers: HashMap<CustomBlockId, RenderBlock>,
16652 autoscroll: Option<Autoscroll>,
16653 cx: &mut Context<Self>,
16654 ) {
16655 self.display_map
16656 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16657 if let Some(autoscroll) = autoscroll {
16658 self.request_autoscroll(autoscroll, cx);
16659 }
16660 cx.notify();
16661 }
16662
16663 pub fn remove_blocks(
16664 &mut self,
16665 block_ids: HashSet<CustomBlockId>,
16666 autoscroll: Option<Autoscroll>,
16667 cx: &mut Context<Self>,
16668 ) {
16669 self.display_map.update(cx, |display_map, cx| {
16670 display_map.remove_blocks(block_ids, cx)
16671 });
16672 if let Some(autoscroll) = autoscroll {
16673 self.request_autoscroll(autoscroll, cx);
16674 }
16675 cx.notify();
16676 }
16677
16678 pub fn row_for_block(
16679 &self,
16680 block_id: CustomBlockId,
16681 cx: &mut Context<Self>,
16682 ) -> Option<DisplayRow> {
16683 self.display_map
16684 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16685 }
16686
16687 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16688 self.focused_block = Some(focused_block);
16689 }
16690
16691 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16692 self.focused_block.take()
16693 }
16694
16695 pub fn insert_creases(
16696 &mut self,
16697 creases: impl IntoIterator<Item = Crease<Anchor>>,
16698 cx: &mut Context<Self>,
16699 ) -> Vec<CreaseId> {
16700 self.display_map
16701 .update(cx, |map, cx| map.insert_creases(creases, cx))
16702 }
16703
16704 pub fn remove_creases(
16705 &mut self,
16706 ids: impl IntoIterator<Item = CreaseId>,
16707 cx: &mut Context<Self>,
16708 ) -> Vec<(CreaseId, Range<Anchor>)> {
16709 self.display_map
16710 .update(cx, |map, cx| map.remove_creases(ids, cx))
16711 }
16712
16713 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16714 self.display_map
16715 .update(cx, |map, cx| map.snapshot(cx))
16716 .longest_row()
16717 }
16718
16719 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16720 self.display_map
16721 .update(cx, |map, cx| map.snapshot(cx))
16722 .max_point()
16723 }
16724
16725 pub fn text(&self, cx: &App) -> String {
16726 self.buffer.read(cx).read(cx).text()
16727 }
16728
16729 pub fn is_empty(&self, cx: &App) -> bool {
16730 self.buffer.read(cx).read(cx).is_empty()
16731 }
16732
16733 pub fn text_option(&self, cx: &App) -> Option<String> {
16734 let text = self.text(cx);
16735 let text = text.trim();
16736
16737 if text.is_empty() {
16738 return None;
16739 }
16740
16741 Some(text.to_string())
16742 }
16743
16744 pub fn set_text(
16745 &mut self,
16746 text: impl Into<Arc<str>>,
16747 window: &mut Window,
16748 cx: &mut Context<Self>,
16749 ) {
16750 self.transact(window, cx, |this, _, cx| {
16751 this.buffer
16752 .read(cx)
16753 .as_singleton()
16754 .expect("you can only call set_text on editors for singleton buffers")
16755 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16756 });
16757 }
16758
16759 pub fn display_text(&self, cx: &mut App) -> String {
16760 self.display_map
16761 .update(cx, |map, cx| map.snapshot(cx))
16762 .text()
16763 }
16764
16765 fn create_minimap(
16766 &self,
16767 minimap_settings: MinimapSettings,
16768 window: &mut Window,
16769 cx: &mut Context<Self>,
16770 ) -> Option<Entity<Self>> {
16771 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16772 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16773 }
16774
16775 fn initialize_new_minimap(
16776 &self,
16777 minimap_settings: MinimapSettings,
16778 window: &mut Window,
16779 cx: &mut Context<Self>,
16780 ) -> Entity<Self> {
16781 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16782
16783 let mut minimap = Editor::new_internal(
16784 EditorMode::Minimap {
16785 parent: cx.weak_entity(),
16786 },
16787 self.buffer.clone(),
16788 self.project.clone(),
16789 Some(self.display_map.clone()),
16790 window,
16791 cx,
16792 );
16793 minimap.scroll_manager.clone_state(&self.scroll_manager);
16794 minimap.set_text_style_refinement(TextStyleRefinement {
16795 font_size: Some(MINIMAP_FONT_SIZE),
16796 font_weight: Some(MINIMAP_FONT_WEIGHT),
16797 ..Default::default()
16798 });
16799 minimap.update_minimap_configuration(minimap_settings, cx);
16800 cx.new(|_| minimap)
16801 }
16802
16803 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16804 let current_line_highlight = minimap_settings
16805 .current_line_highlight
16806 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16807 self.set_current_line_highlight(Some(current_line_highlight));
16808 }
16809
16810 pub fn minimap(&self) -> Option<&Entity<Self>> {
16811 self.minimap
16812 .as_ref()
16813 .filter(|_| self.minimap_visibility.visible())
16814 }
16815
16816 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16817 let mut wrap_guides = smallvec![];
16818
16819 if self.show_wrap_guides == Some(false) {
16820 return wrap_guides;
16821 }
16822
16823 let settings = self.buffer.read(cx).language_settings(cx);
16824 if settings.show_wrap_guides {
16825 match self.soft_wrap_mode(cx) {
16826 SoftWrap::Column(soft_wrap) => {
16827 wrap_guides.push((soft_wrap as usize, true));
16828 }
16829 SoftWrap::Bounded(soft_wrap) => {
16830 wrap_guides.push((soft_wrap as usize, true));
16831 }
16832 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16833 }
16834 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16835 }
16836
16837 wrap_guides
16838 }
16839
16840 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16841 let settings = self.buffer.read(cx).language_settings(cx);
16842 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16843 match mode {
16844 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16845 SoftWrap::None
16846 }
16847 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16848 language_settings::SoftWrap::PreferredLineLength => {
16849 SoftWrap::Column(settings.preferred_line_length)
16850 }
16851 language_settings::SoftWrap::Bounded => {
16852 SoftWrap::Bounded(settings.preferred_line_length)
16853 }
16854 }
16855 }
16856
16857 pub fn set_soft_wrap_mode(
16858 &mut self,
16859 mode: language_settings::SoftWrap,
16860
16861 cx: &mut Context<Self>,
16862 ) {
16863 self.soft_wrap_mode_override = Some(mode);
16864 cx.notify();
16865 }
16866
16867 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16868 self.hard_wrap = hard_wrap;
16869 cx.notify();
16870 }
16871
16872 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16873 self.text_style_refinement = Some(style);
16874 }
16875
16876 /// called by the Element so we know what style we were most recently rendered with.
16877 pub(crate) fn set_style(
16878 &mut self,
16879 style: EditorStyle,
16880 window: &mut Window,
16881 cx: &mut Context<Self>,
16882 ) {
16883 // We intentionally do not inform the display map about the minimap style
16884 // so that wrapping is not recalculated and stays consistent for the editor
16885 // and its linked minimap.
16886 if !self.mode.is_minimap() {
16887 let rem_size = window.rem_size();
16888 self.display_map.update(cx, |map, cx| {
16889 map.set_font(
16890 style.text.font(),
16891 style.text.font_size.to_pixels(rem_size),
16892 cx,
16893 )
16894 });
16895 }
16896 self.style = Some(style);
16897 }
16898
16899 pub fn style(&self) -> Option<&EditorStyle> {
16900 self.style.as_ref()
16901 }
16902
16903 // Called by the element. This method is not designed to be called outside of the editor
16904 // element's layout code because it does not notify when rewrapping is computed synchronously.
16905 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16906 self.display_map
16907 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16908 }
16909
16910 pub fn set_soft_wrap(&mut self) {
16911 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16912 }
16913
16914 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16915 if self.soft_wrap_mode_override.is_some() {
16916 self.soft_wrap_mode_override.take();
16917 } else {
16918 let soft_wrap = match self.soft_wrap_mode(cx) {
16919 SoftWrap::GitDiff => return,
16920 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16921 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16922 language_settings::SoftWrap::None
16923 }
16924 };
16925 self.soft_wrap_mode_override = Some(soft_wrap);
16926 }
16927 cx.notify();
16928 }
16929
16930 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16931 let Some(workspace) = self.workspace() else {
16932 return;
16933 };
16934 let fs = workspace.read(cx).app_state().fs.clone();
16935 let current_show = TabBarSettings::get_global(cx).show;
16936 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16937 setting.show = Some(!current_show);
16938 });
16939 }
16940
16941 pub fn toggle_indent_guides(
16942 &mut self,
16943 _: &ToggleIndentGuides,
16944 _: &mut Window,
16945 cx: &mut Context<Self>,
16946 ) {
16947 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16948 self.buffer
16949 .read(cx)
16950 .language_settings(cx)
16951 .indent_guides
16952 .enabled
16953 });
16954 self.show_indent_guides = Some(!currently_enabled);
16955 cx.notify();
16956 }
16957
16958 fn should_show_indent_guides(&self) -> Option<bool> {
16959 self.show_indent_guides
16960 }
16961
16962 pub fn toggle_line_numbers(
16963 &mut self,
16964 _: &ToggleLineNumbers,
16965 _: &mut Window,
16966 cx: &mut Context<Self>,
16967 ) {
16968 let mut editor_settings = EditorSettings::get_global(cx).clone();
16969 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16970 EditorSettings::override_global(editor_settings, cx);
16971 }
16972
16973 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16974 if let Some(show_line_numbers) = self.show_line_numbers {
16975 return show_line_numbers;
16976 }
16977 EditorSettings::get_global(cx).gutter.line_numbers
16978 }
16979
16980 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16981 self.use_relative_line_numbers
16982 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16983 }
16984
16985 pub fn toggle_relative_line_numbers(
16986 &mut self,
16987 _: &ToggleRelativeLineNumbers,
16988 _: &mut Window,
16989 cx: &mut Context<Self>,
16990 ) {
16991 let is_relative = self.should_use_relative_line_numbers(cx);
16992 self.set_relative_line_number(Some(!is_relative), cx)
16993 }
16994
16995 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16996 self.use_relative_line_numbers = is_relative;
16997 cx.notify();
16998 }
16999
17000 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17001 self.show_gutter = show_gutter;
17002 cx.notify();
17003 }
17004
17005 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17006 self.show_scrollbars = ScrollbarAxes {
17007 horizontal: show,
17008 vertical: show,
17009 };
17010 cx.notify();
17011 }
17012
17013 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17014 self.show_scrollbars.vertical = show;
17015 cx.notify();
17016 }
17017
17018 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17019 self.show_scrollbars.horizontal = show;
17020 cx.notify();
17021 }
17022
17023 pub fn set_minimap_visibility(
17024 &mut self,
17025 minimap_visibility: MinimapVisibility,
17026 window: &mut Window,
17027 cx: &mut Context<Self>,
17028 ) {
17029 if self.minimap_visibility != minimap_visibility {
17030 if minimap_visibility.visible() && self.minimap.is_none() {
17031 let minimap_settings = EditorSettings::get_global(cx).minimap;
17032 self.minimap =
17033 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17034 }
17035 self.minimap_visibility = minimap_visibility;
17036 cx.notify();
17037 }
17038 }
17039
17040 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17041 self.set_show_scrollbars(false, cx);
17042 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17043 }
17044
17045 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17046 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17047 }
17048
17049 /// Normally the text in full mode and auto height editors is padded on the
17050 /// left side by roughly half a character width for improved hit testing.
17051 ///
17052 /// Use this method to disable this for cases where this is not wanted (e.g.
17053 /// if you want to align the editor text with some other text above or below)
17054 /// or if you want to add this padding to single-line editors.
17055 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17056 self.offset_content = offset_content;
17057 cx.notify();
17058 }
17059
17060 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17061 self.show_line_numbers = Some(show_line_numbers);
17062 cx.notify();
17063 }
17064
17065 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17066 self.disable_expand_excerpt_buttons = true;
17067 cx.notify();
17068 }
17069
17070 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17071 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17072 cx.notify();
17073 }
17074
17075 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17076 self.show_code_actions = Some(show_code_actions);
17077 cx.notify();
17078 }
17079
17080 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17081 self.show_runnables = Some(show_runnables);
17082 cx.notify();
17083 }
17084
17085 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17086 self.show_breakpoints = Some(show_breakpoints);
17087 cx.notify();
17088 }
17089
17090 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17091 if self.display_map.read(cx).masked != masked {
17092 self.display_map.update(cx, |map, _| map.masked = masked);
17093 }
17094 cx.notify()
17095 }
17096
17097 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17098 self.show_wrap_guides = Some(show_wrap_guides);
17099 cx.notify();
17100 }
17101
17102 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17103 self.show_indent_guides = Some(show_indent_guides);
17104 cx.notify();
17105 }
17106
17107 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17108 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17109 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17110 if let Some(dir) = file.abs_path(cx).parent() {
17111 return Some(dir.to_owned());
17112 }
17113 }
17114
17115 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17116 return Some(project_path.path.to_path_buf());
17117 }
17118 }
17119
17120 None
17121 }
17122
17123 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17124 self.active_excerpt(cx)?
17125 .1
17126 .read(cx)
17127 .file()
17128 .and_then(|f| f.as_local())
17129 }
17130
17131 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17132 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17133 let buffer = buffer.read(cx);
17134 if let Some(project_path) = buffer.project_path(cx) {
17135 let project = self.project.as_ref()?.read(cx);
17136 project.absolute_path(&project_path, cx)
17137 } else {
17138 buffer
17139 .file()
17140 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17141 }
17142 })
17143 }
17144
17145 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17146 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17147 let project_path = buffer.read(cx).project_path(cx)?;
17148 let project = self.project.as_ref()?.read(cx);
17149 let entry = project.entry_for_path(&project_path, cx)?;
17150 let path = entry.path.to_path_buf();
17151 Some(path)
17152 })
17153 }
17154
17155 pub fn reveal_in_finder(
17156 &mut self,
17157 _: &RevealInFileManager,
17158 _window: &mut Window,
17159 cx: &mut Context<Self>,
17160 ) {
17161 if let Some(target) = self.target_file(cx) {
17162 cx.reveal_path(&target.abs_path(cx));
17163 }
17164 }
17165
17166 pub fn copy_path(
17167 &mut self,
17168 _: &zed_actions::workspace::CopyPath,
17169 _window: &mut Window,
17170 cx: &mut Context<Self>,
17171 ) {
17172 if let Some(path) = self.target_file_abs_path(cx) {
17173 if let Some(path) = path.to_str() {
17174 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17175 }
17176 }
17177 }
17178
17179 pub fn copy_relative_path(
17180 &mut self,
17181 _: &zed_actions::workspace::CopyRelativePath,
17182 _window: &mut Window,
17183 cx: &mut Context<Self>,
17184 ) {
17185 if let Some(path) = self.target_file_path(cx) {
17186 if let Some(path) = path.to_str() {
17187 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17188 }
17189 }
17190 }
17191
17192 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17193 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17194 buffer.read(cx).project_path(cx)
17195 } else {
17196 None
17197 }
17198 }
17199
17200 // Returns true if the editor handled a go-to-line request
17201 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17202 maybe!({
17203 let breakpoint_store = self.breakpoint_store.as_ref()?;
17204
17205 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17206 else {
17207 self.clear_row_highlights::<ActiveDebugLine>();
17208 return None;
17209 };
17210
17211 let position = active_stack_frame.position;
17212 let buffer_id = position.buffer_id?;
17213 let snapshot = self
17214 .project
17215 .as_ref()?
17216 .read(cx)
17217 .buffer_for_id(buffer_id, cx)?
17218 .read(cx)
17219 .snapshot();
17220
17221 let mut handled = false;
17222 for (id, ExcerptRange { context, .. }) in
17223 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17224 {
17225 if context.start.cmp(&position, &snapshot).is_ge()
17226 || context.end.cmp(&position, &snapshot).is_lt()
17227 {
17228 continue;
17229 }
17230 let snapshot = self.buffer.read(cx).snapshot(cx);
17231 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17232
17233 handled = true;
17234 self.clear_row_highlights::<ActiveDebugLine>();
17235
17236 self.go_to_line::<ActiveDebugLine>(
17237 multibuffer_anchor,
17238 Some(cx.theme().colors().editor_debugger_active_line_background),
17239 window,
17240 cx,
17241 );
17242
17243 cx.notify();
17244 }
17245
17246 handled.then_some(())
17247 })
17248 .is_some()
17249 }
17250
17251 pub fn copy_file_name_without_extension(
17252 &mut self,
17253 _: &CopyFileNameWithoutExtension,
17254 _: &mut Window,
17255 cx: &mut Context<Self>,
17256 ) {
17257 if let Some(file) = self.target_file(cx) {
17258 if let Some(file_stem) = file.path().file_stem() {
17259 if let Some(name) = file_stem.to_str() {
17260 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17261 }
17262 }
17263 }
17264 }
17265
17266 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17267 if let Some(file) = self.target_file(cx) {
17268 if let Some(file_name) = file.path().file_name() {
17269 if let Some(name) = file_name.to_str() {
17270 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17271 }
17272 }
17273 }
17274 }
17275
17276 pub fn toggle_git_blame(
17277 &mut self,
17278 _: &::git::Blame,
17279 window: &mut Window,
17280 cx: &mut Context<Self>,
17281 ) {
17282 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17283
17284 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17285 self.start_git_blame(true, window, cx);
17286 }
17287
17288 cx.notify();
17289 }
17290
17291 pub fn toggle_git_blame_inline(
17292 &mut self,
17293 _: &ToggleGitBlameInline,
17294 window: &mut Window,
17295 cx: &mut Context<Self>,
17296 ) {
17297 self.toggle_git_blame_inline_internal(true, window, cx);
17298 cx.notify();
17299 }
17300
17301 pub fn open_git_blame_commit(
17302 &mut self,
17303 _: &OpenGitBlameCommit,
17304 window: &mut Window,
17305 cx: &mut Context<Self>,
17306 ) {
17307 self.open_git_blame_commit_internal(window, cx);
17308 }
17309
17310 fn open_git_blame_commit_internal(
17311 &mut self,
17312 window: &mut Window,
17313 cx: &mut Context<Self>,
17314 ) -> Option<()> {
17315 let blame = self.blame.as_ref()?;
17316 let snapshot = self.snapshot(window, cx);
17317 let cursor = self.selections.newest::<Point>(cx).head();
17318 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17319 let blame_entry = blame
17320 .update(cx, |blame, cx| {
17321 blame
17322 .blame_for_rows(
17323 &[RowInfo {
17324 buffer_id: Some(buffer.remote_id()),
17325 buffer_row: Some(point.row),
17326 ..Default::default()
17327 }],
17328 cx,
17329 )
17330 .next()
17331 })
17332 .flatten()?;
17333 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17334 let repo = blame.read(cx).repository(cx)?;
17335 let workspace = self.workspace()?.downgrade();
17336 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17337 None
17338 }
17339
17340 pub fn git_blame_inline_enabled(&self) -> bool {
17341 self.git_blame_inline_enabled
17342 }
17343
17344 pub fn toggle_selection_menu(
17345 &mut self,
17346 _: &ToggleSelectionMenu,
17347 _: &mut Window,
17348 cx: &mut Context<Self>,
17349 ) {
17350 self.show_selection_menu = self
17351 .show_selection_menu
17352 .map(|show_selections_menu| !show_selections_menu)
17353 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17354
17355 cx.notify();
17356 }
17357
17358 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17359 self.show_selection_menu
17360 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17361 }
17362
17363 fn start_git_blame(
17364 &mut self,
17365 user_triggered: bool,
17366 window: &mut Window,
17367 cx: &mut Context<Self>,
17368 ) {
17369 if let Some(project) = self.project.as_ref() {
17370 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17371 return;
17372 };
17373
17374 if buffer.read(cx).file().is_none() {
17375 return;
17376 }
17377
17378 let focused = self.focus_handle(cx).contains_focused(window, cx);
17379
17380 let project = project.clone();
17381 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17382 self.blame_subscription =
17383 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17384 self.blame = Some(blame);
17385 }
17386 }
17387
17388 fn toggle_git_blame_inline_internal(
17389 &mut self,
17390 user_triggered: bool,
17391 window: &mut Window,
17392 cx: &mut Context<Self>,
17393 ) {
17394 if self.git_blame_inline_enabled {
17395 self.git_blame_inline_enabled = false;
17396 self.show_git_blame_inline = false;
17397 self.show_git_blame_inline_delay_task.take();
17398 } else {
17399 self.git_blame_inline_enabled = true;
17400 self.start_git_blame_inline(user_triggered, window, cx);
17401 }
17402
17403 cx.notify();
17404 }
17405
17406 fn start_git_blame_inline(
17407 &mut self,
17408 user_triggered: bool,
17409 window: &mut Window,
17410 cx: &mut Context<Self>,
17411 ) {
17412 self.start_git_blame(user_triggered, window, cx);
17413
17414 if ProjectSettings::get_global(cx)
17415 .git
17416 .inline_blame_delay()
17417 .is_some()
17418 {
17419 self.start_inline_blame_timer(window, cx);
17420 } else {
17421 self.show_git_blame_inline = true
17422 }
17423 }
17424
17425 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17426 self.blame.as_ref()
17427 }
17428
17429 pub fn show_git_blame_gutter(&self) -> bool {
17430 self.show_git_blame_gutter
17431 }
17432
17433 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17434 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17435 }
17436
17437 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17438 self.show_git_blame_inline
17439 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17440 && !self.newest_selection_head_on_empty_line(cx)
17441 && self.has_blame_entries(cx)
17442 }
17443
17444 fn has_blame_entries(&self, cx: &App) -> bool {
17445 self.blame()
17446 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17447 }
17448
17449 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17450 let cursor_anchor = self.selections.newest_anchor().head();
17451
17452 let snapshot = self.buffer.read(cx).snapshot(cx);
17453 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17454
17455 snapshot.line_len(buffer_row) == 0
17456 }
17457
17458 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17459 let buffer_and_selection = maybe!({
17460 let selection = self.selections.newest::<Point>(cx);
17461 let selection_range = selection.range();
17462
17463 let multi_buffer = self.buffer().read(cx);
17464 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17465 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17466
17467 let (buffer, range, _) = if selection.reversed {
17468 buffer_ranges.first()
17469 } else {
17470 buffer_ranges.last()
17471 }?;
17472
17473 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17474 ..text::ToPoint::to_point(&range.end, &buffer).row;
17475 Some((
17476 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17477 selection,
17478 ))
17479 });
17480
17481 let Some((buffer, selection)) = buffer_and_selection else {
17482 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17483 };
17484
17485 let Some(project) = self.project.as_ref() else {
17486 return Task::ready(Err(anyhow!("editor does not have project")));
17487 };
17488
17489 project.update(cx, |project, cx| {
17490 project.get_permalink_to_line(&buffer, selection, cx)
17491 })
17492 }
17493
17494 pub fn copy_permalink_to_line(
17495 &mut self,
17496 _: &CopyPermalinkToLine,
17497 window: &mut Window,
17498 cx: &mut Context<Self>,
17499 ) {
17500 let permalink_task = self.get_permalink_to_line(cx);
17501 let workspace = self.workspace();
17502
17503 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17504 Ok(permalink) => {
17505 cx.update(|_, cx| {
17506 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17507 })
17508 .ok();
17509 }
17510 Err(err) => {
17511 let message = format!("Failed to copy permalink: {err}");
17512
17513 anyhow::Result::<()>::Err(err).log_err();
17514
17515 if let Some(workspace) = workspace {
17516 workspace
17517 .update_in(cx, |workspace, _, cx| {
17518 struct CopyPermalinkToLine;
17519
17520 workspace.show_toast(
17521 Toast::new(
17522 NotificationId::unique::<CopyPermalinkToLine>(),
17523 message,
17524 ),
17525 cx,
17526 )
17527 })
17528 .ok();
17529 }
17530 }
17531 })
17532 .detach();
17533 }
17534
17535 pub fn copy_file_location(
17536 &mut self,
17537 _: &CopyFileLocation,
17538 _: &mut Window,
17539 cx: &mut Context<Self>,
17540 ) {
17541 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17542 if let Some(file) = self.target_file(cx) {
17543 if let Some(path) = file.path().to_str() {
17544 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17545 }
17546 }
17547 }
17548
17549 pub fn open_permalink_to_line(
17550 &mut self,
17551 _: &OpenPermalinkToLine,
17552 window: &mut Window,
17553 cx: &mut Context<Self>,
17554 ) {
17555 let permalink_task = self.get_permalink_to_line(cx);
17556 let workspace = self.workspace();
17557
17558 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17559 Ok(permalink) => {
17560 cx.update(|_, cx| {
17561 cx.open_url(permalink.as_ref());
17562 })
17563 .ok();
17564 }
17565 Err(err) => {
17566 let message = format!("Failed to open permalink: {err}");
17567
17568 anyhow::Result::<()>::Err(err).log_err();
17569
17570 if let Some(workspace) = workspace {
17571 workspace
17572 .update(cx, |workspace, cx| {
17573 struct OpenPermalinkToLine;
17574
17575 workspace.show_toast(
17576 Toast::new(
17577 NotificationId::unique::<OpenPermalinkToLine>(),
17578 message,
17579 ),
17580 cx,
17581 )
17582 })
17583 .ok();
17584 }
17585 }
17586 })
17587 .detach();
17588 }
17589
17590 pub fn insert_uuid_v4(
17591 &mut self,
17592 _: &InsertUuidV4,
17593 window: &mut Window,
17594 cx: &mut Context<Self>,
17595 ) {
17596 self.insert_uuid(UuidVersion::V4, window, cx);
17597 }
17598
17599 pub fn insert_uuid_v7(
17600 &mut self,
17601 _: &InsertUuidV7,
17602 window: &mut Window,
17603 cx: &mut Context<Self>,
17604 ) {
17605 self.insert_uuid(UuidVersion::V7, window, cx);
17606 }
17607
17608 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17609 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17610 self.transact(window, cx, |this, window, cx| {
17611 let edits = this
17612 .selections
17613 .all::<Point>(cx)
17614 .into_iter()
17615 .map(|selection| {
17616 let uuid = match version {
17617 UuidVersion::V4 => uuid::Uuid::new_v4(),
17618 UuidVersion::V7 => uuid::Uuid::now_v7(),
17619 };
17620
17621 (selection.range(), uuid.to_string())
17622 });
17623 this.edit(edits, cx);
17624 this.refresh_inline_completion(true, false, window, cx);
17625 });
17626 }
17627
17628 pub fn open_selections_in_multibuffer(
17629 &mut self,
17630 _: &OpenSelectionsInMultibuffer,
17631 window: &mut Window,
17632 cx: &mut Context<Self>,
17633 ) {
17634 let multibuffer = self.buffer.read(cx);
17635
17636 let Some(buffer) = multibuffer.as_singleton() else {
17637 return;
17638 };
17639
17640 let Some(workspace) = self.workspace() else {
17641 return;
17642 };
17643
17644 let locations = self
17645 .selections
17646 .disjoint_anchors()
17647 .iter()
17648 .map(|selection| {
17649 let range = if selection.reversed {
17650 selection.end.text_anchor..selection.start.text_anchor
17651 } else {
17652 selection.start.text_anchor..selection.end.text_anchor
17653 };
17654 Location {
17655 buffer: buffer.clone(),
17656 range,
17657 }
17658 })
17659 .collect::<Vec<_>>();
17660
17661 let title = multibuffer.title(cx).to_string();
17662
17663 cx.spawn_in(window, async move |_, cx| {
17664 workspace.update_in(cx, |workspace, window, cx| {
17665 Self::open_locations_in_multibuffer(
17666 workspace,
17667 locations,
17668 format!("Selections for '{title}'"),
17669 false,
17670 MultibufferSelectionMode::All,
17671 window,
17672 cx,
17673 );
17674 })
17675 })
17676 .detach();
17677 }
17678
17679 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17680 /// last highlight added will be used.
17681 ///
17682 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17683 pub fn highlight_rows<T: 'static>(
17684 &mut self,
17685 range: Range<Anchor>,
17686 color: Hsla,
17687 options: RowHighlightOptions,
17688 cx: &mut Context<Self>,
17689 ) {
17690 let snapshot = self.buffer().read(cx).snapshot(cx);
17691 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17692 let ix = row_highlights.binary_search_by(|highlight| {
17693 Ordering::Equal
17694 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17695 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17696 });
17697
17698 if let Err(mut ix) = ix {
17699 let index = post_inc(&mut self.highlight_order);
17700
17701 // If this range intersects with the preceding highlight, then merge it with
17702 // the preceding highlight. Otherwise insert a new highlight.
17703 let mut merged = false;
17704 if ix > 0 {
17705 let prev_highlight = &mut row_highlights[ix - 1];
17706 if prev_highlight
17707 .range
17708 .end
17709 .cmp(&range.start, &snapshot)
17710 .is_ge()
17711 {
17712 ix -= 1;
17713 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17714 prev_highlight.range.end = range.end;
17715 }
17716 merged = true;
17717 prev_highlight.index = index;
17718 prev_highlight.color = color;
17719 prev_highlight.options = options;
17720 }
17721 }
17722
17723 if !merged {
17724 row_highlights.insert(
17725 ix,
17726 RowHighlight {
17727 range: range.clone(),
17728 index,
17729 color,
17730 options,
17731 type_id: TypeId::of::<T>(),
17732 },
17733 );
17734 }
17735
17736 // If any of the following highlights intersect with this one, merge them.
17737 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17738 let highlight = &row_highlights[ix];
17739 if next_highlight
17740 .range
17741 .start
17742 .cmp(&highlight.range.end, &snapshot)
17743 .is_le()
17744 {
17745 if next_highlight
17746 .range
17747 .end
17748 .cmp(&highlight.range.end, &snapshot)
17749 .is_gt()
17750 {
17751 row_highlights[ix].range.end = next_highlight.range.end;
17752 }
17753 row_highlights.remove(ix + 1);
17754 } else {
17755 break;
17756 }
17757 }
17758 }
17759 }
17760
17761 /// Remove any highlighted row ranges of the given type that intersect the
17762 /// given ranges.
17763 pub fn remove_highlighted_rows<T: 'static>(
17764 &mut self,
17765 ranges_to_remove: Vec<Range<Anchor>>,
17766 cx: &mut Context<Self>,
17767 ) {
17768 let snapshot = self.buffer().read(cx).snapshot(cx);
17769 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17770 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17771 row_highlights.retain(|highlight| {
17772 while let Some(range_to_remove) = ranges_to_remove.peek() {
17773 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17774 Ordering::Less | Ordering::Equal => {
17775 ranges_to_remove.next();
17776 }
17777 Ordering::Greater => {
17778 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17779 Ordering::Less | Ordering::Equal => {
17780 return false;
17781 }
17782 Ordering::Greater => break,
17783 }
17784 }
17785 }
17786 }
17787
17788 true
17789 })
17790 }
17791
17792 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17793 pub fn clear_row_highlights<T: 'static>(&mut self) {
17794 self.highlighted_rows.remove(&TypeId::of::<T>());
17795 }
17796
17797 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17798 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17799 self.highlighted_rows
17800 .get(&TypeId::of::<T>())
17801 .map_or(&[] as &[_], |vec| vec.as_slice())
17802 .iter()
17803 .map(|highlight| (highlight.range.clone(), highlight.color))
17804 }
17805
17806 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17807 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17808 /// Allows to ignore certain kinds of highlights.
17809 pub fn highlighted_display_rows(
17810 &self,
17811 window: &mut Window,
17812 cx: &mut App,
17813 ) -> BTreeMap<DisplayRow, LineHighlight> {
17814 let snapshot = self.snapshot(window, cx);
17815 let mut used_highlight_orders = HashMap::default();
17816 self.highlighted_rows
17817 .iter()
17818 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17819 .fold(
17820 BTreeMap::<DisplayRow, LineHighlight>::new(),
17821 |mut unique_rows, highlight| {
17822 let start = highlight.range.start.to_display_point(&snapshot);
17823 let end = highlight.range.end.to_display_point(&snapshot);
17824 let start_row = start.row().0;
17825 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17826 && end.column() == 0
17827 {
17828 end.row().0.saturating_sub(1)
17829 } else {
17830 end.row().0
17831 };
17832 for row in start_row..=end_row {
17833 let used_index =
17834 used_highlight_orders.entry(row).or_insert(highlight.index);
17835 if highlight.index >= *used_index {
17836 *used_index = highlight.index;
17837 unique_rows.insert(
17838 DisplayRow(row),
17839 LineHighlight {
17840 include_gutter: highlight.options.include_gutter,
17841 border: None,
17842 background: highlight.color.into(),
17843 type_id: Some(highlight.type_id),
17844 },
17845 );
17846 }
17847 }
17848 unique_rows
17849 },
17850 )
17851 }
17852
17853 pub fn highlighted_display_row_for_autoscroll(
17854 &self,
17855 snapshot: &DisplaySnapshot,
17856 ) -> Option<DisplayRow> {
17857 self.highlighted_rows
17858 .values()
17859 .flat_map(|highlighted_rows| highlighted_rows.iter())
17860 .filter_map(|highlight| {
17861 if highlight.options.autoscroll {
17862 Some(highlight.range.start.to_display_point(snapshot).row())
17863 } else {
17864 None
17865 }
17866 })
17867 .min()
17868 }
17869
17870 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17871 self.highlight_background::<SearchWithinRange>(
17872 ranges,
17873 |colors| colors.editor_document_highlight_read_background,
17874 cx,
17875 )
17876 }
17877
17878 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17879 self.breadcrumb_header = Some(new_header);
17880 }
17881
17882 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17883 self.clear_background_highlights::<SearchWithinRange>(cx);
17884 }
17885
17886 pub fn highlight_background<T: 'static>(
17887 &mut self,
17888 ranges: &[Range<Anchor>],
17889 color_fetcher: fn(&ThemeColors) -> Hsla,
17890 cx: &mut Context<Self>,
17891 ) {
17892 self.background_highlights
17893 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17894 self.scrollbar_marker_state.dirty = true;
17895 cx.notify();
17896 }
17897
17898 pub fn clear_background_highlights<T: 'static>(
17899 &mut self,
17900 cx: &mut Context<Self>,
17901 ) -> Option<BackgroundHighlight> {
17902 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17903 if !text_highlights.1.is_empty() {
17904 self.scrollbar_marker_state.dirty = true;
17905 cx.notify();
17906 }
17907 Some(text_highlights)
17908 }
17909
17910 pub fn highlight_gutter<T: 'static>(
17911 &mut self,
17912 ranges: &[Range<Anchor>],
17913 color_fetcher: fn(&App) -> Hsla,
17914 cx: &mut Context<Self>,
17915 ) {
17916 self.gutter_highlights
17917 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17918 cx.notify();
17919 }
17920
17921 pub fn clear_gutter_highlights<T: 'static>(
17922 &mut self,
17923 cx: &mut Context<Self>,
17924 ) -> Option<GutterHighlight> {
17925 cx.notify();
17926 self.gutter_highlights.remove(&TypeId::of::<T>())
17927 }
17928
17929 #[cfg(feature = "test-support")]
17930 pub fn all_text_background_highlights(
17931 &self,
17932 window: &mut Window,
17933 cx: &mut Context<Self>,
17934 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17935 let snapshot = self.snapshot(window, cx);
17936 let buffer = &snapshot.buffer_snapshot;
17937 let start = buffer.anchor_before(0);
17938 let end = buffer.anchor_after(buffer.len());
17939 let theme = cx.theme().colors();
17940 self.background_highlights_in_range(start..end, &snapshot, theme)
17941 }
17942
17943 #[cfg(feature = "test-support")]
17944 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17945 let snapshot = self.buffer().read(cx).snapshot(cx);
17946
17947 let highlights = self
17948 .background_highlights
17949 .get(&TypeId::of::<items::BufferSearchHighlights>());
17950
17951 if let Some((_color, ranges)) = highlights {
17952 ranges
17953 .iter()
17954 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17955 .collect_vec()
17956 } else {
17957 vec![]
17958 }
17959 }
17960
17961 fn document_highlights_for_position<'a>(
17962 &'a self,
17963 position: Anchor,
17964 buffer: &'a MultiBufferSnapshot,
17965 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17966 let read_highlights = self
17967 .background_highlights
17968 .get(&TypeId::of::<DocumentHighlightRead>())
17969 .map(|h| &h.1);
17970 let write_highlights = self
17971 .background_highlights
17972 .get(&TypeId::of::<DocumentHighlightWrite>())
17973 .map(|h| &h.1);
17974 let left_position = position.bias_left(buffer);
17975 let right_position = position.bias_right(buffer);
17976 read_highlights
17977 .into_iter()
17978 .chain(write_highlights)
17979 .flat_map(move |ranges| {
17980 let start_ix = match ranges.binary_search_by(|probe| {
17981 let cmp = probe.end.cmp(&left_position, buffer);
17982 if cmp.is_ge() {
17983 Ordering::Greater
17984 } else {
17985 Ordering::Less
17986 }
17987 }) {
17988 Ok(i) | Err(i) => i,
17989 };
17990
17991 ranges[start_ix..]
17992 .iter()
17993 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17994 })
17995 }
17996
17997 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17998 self.background_highlights
17999 .get(&TypeId::of::<T>())
18000 .map_or(false, |(_, highlights)| !highlights.is_empty())
18001 }
18002
18003 pub fn background_highlights_in_range(
18004 &self,
18005 search_range: Range<Anchor>,
18006 display_snapshot: &DisplaySnapshot,
18007 theme: &ThemeColors,
18008 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18009 let mut results = Vec::new();
18010 for (color_fetcher, ranges) in self.background_highlights.values() {
18011 let color = color_fetcher(theme);
18012 let start_ix = match ranges.binary_search_by(|probe| {
18013 let cmp = probe
18014 .end
18015 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18016 if cmp.is_gt() {
18017 Ordering::Greater
18018 } else {
18019 Ordering::Less
18020 }
18021 }) {
18022 Ok(i) | Err(i) => i,
18023 };
18024 for range in &ranges[start_ix..] {
18025 if range
18026 .start
18027 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18028 .is_ge()
18029 {
18030 break;
18031 }
18032
18033 let start = range.start.to_display_point(display_snapshot);
18034 let end = range.end.to_display_point(display_snapshot);
18035 results.push((start..end, color))
18036 }
18037 }
18038 results
18039 }
18040
18041 pub fn background_highlight_row_ranges<T: 'static>(
18042 &self,
18043 search_range: Range<Anchor>,
18044 display_snapshot: &DisplaySnapshot,
18045 count: usize,
18046 ) -> Vec<RangeInclusive<DisplayPoint>> {
18047 let mut results = Vec::new();
18048 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18049 return vec![];
18050 };
18051
18052 let start_ix = match ranges.binary_search_by(|probe| {
18053 let cmp = probe
18054 .end
18055 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18056 if cmp.is_gt() {
18057 Ordering::Greater
18058 } else {
18059 Ordering::Less
18060 }
18061 }) {
18062 Ok(i) | Err(i) => i,
18063 };
18064 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18065 if let (Some(start_display), Some(end_display)) = (start, end) {
18066 results.push(
18067 start_display.to_display_point(display_snapshot)
18068 ..=end_display.to_display_point(display_snapshot),
18069 );
18070 }
18071 };
18072 let mut start_row: Option<Point> = None;
18073 let mut end_row: Option<Point> = None;
18074 if ranges.len() > count {
18075 return Vec::new();
18076 }
18077 for range in &ranges[start_ix..] {
18078 if range
18079 .start
18080 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18081 .is_ge()
18082 {
18083 break;
18084 }
18085 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18086 if let Some(current_row) = &end_row {
18087 if end.row == current_row.row {
18088 continue;
18089 }
18090 }
18091 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18092 if start_row.is_none() {
18093 assert_eq!(end_row, None);
18094 start_row = Some(start);
18095 end_row = Some(end);
18096 continue;
18097 }
18098 if let Some(current_end) = end_row.as_mut() {
18099 if start.row > current_end.row + 1 {
18100 push_region(start_row, end_row);
18101 start_row = Some(start);
18102 end_row = Some(end);
18103 } else {
18104 // Merge two hunks.
18105 *current_end = end;
18106 }
18107 } else {
18108 unreachable!();
18109 }
18110 }
18111 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18112 push_region(start_row, end_row);
18113 results
18114 }
18115
18116 pub fn gutter_highlights_in_range(
18117 &self,
18118 search_range: Range<Anchor>,
18119 display_snapshot: &DisplaySnapshot,
18120 cx: &App,
18121 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18122 let mut results = Vec::new();
18123 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18124 let color = color_fetcher(cx);
18125 let start_ix = match ranges.binary_search_by(|probe| {
18126 let cmp = probe
18127 .end
18128 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18129 if cmp.is_gt() {
18130 Ordering::Greater
18131 } else {
18132 Ordering::Less
18133 }
18134 }) {
18135 Ok(i) | Err(i) => i,
18136 };
18137 for range in &ranges[start_ix..] {
18138 if range
18139 .start
18140 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18141 .is_ge()
18142 {
18143 break;
18144 }
18145
18146 let start = range.start.to_display_point(display_snapshot);
18147 let end = range.end.to_display_point(display_snapshot);
18148 results.push((start..end, color))
18149 }
18150 }
18151 results
18152 }
18153
18154 /// Get the text ranges corresponding to the redaction query
18155 pub fn redacted_ranges(
18156 &self,
18157 search_range: Range<Anchor>,
18158 display_snapshot: &DisplaySnapshot,
18159 cx: &App,
18160 ) -> Vec<Range<DisplayPoint>> {
18161 display_snapshot
18162 .buffer_snapshot
18163 .redacted_ranges(search_range, |file| {
18164 if let Some(file) = file {
18165 file.is_private()
18166 && EditorSettings::get(
18167 Some(SettingsLocation {
18168 worktree_id: file.worktree_id(cx),
18169 path: file.path().as_ref(),
18170 }),
18171 cx,
18172 )
18173 .redact_private_values
18174 } else {
18175 false
18176 }
18177 })
18178 .map(|range| {
18179 range.start.to_display_point(display_snapshot)
18180 ..range.end.to_display_point(display_snapshot)
18181 })
18182 .collect()
18183 }
18184
18185 pub fn highlight_text<T: 'static>(
18186 &mut self,
18187 ranges: Vec<Range<Anchor>>,
18188 style: HighlightStyle,
18189 cx: &mut Context<Self>,
18190 ) {
18191 self.display_map.update(cx, |map, _| {
18192 map.highlight_text(TypeId::of::<T>(), ranges, style)
18193 });
18194 cx.notify();
18195 }
18196
18197 pub(crate) fn highlight_inlays<T: 'static>(
18198 &mut self,
18199 highlights: Vec<InlayHighlight>,
18200 style: HighlightStyle,
18201 cx: &mut Context<Self>,
18202 ) {
18203 self.display_map.update(cx, |map, _| {
18204 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18205 });
18206 cx.notify();
18207 }
18208
18209 pub fn text_highlights<'a, T: 'static>(
18210 &'a self,
18211 cx: &'a App,
18212 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18213 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18214 }
18215
18216 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18217 let cleared = self
18218 .display_map
18219 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18220 if cleared {
18221 cx.notify();
18222 }
18223 }
18224
18225 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18226 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18227 && self.focus_handle.is_focused(window)
18228 }
18229
18230 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18231 self.show_cursor_when_unfocused = is_enabled;
18232 cx.notify();
18233 }
18234
18235 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18236 cx.notify();
18237 }
18238
18239 fn on_debug_session_event(
18240 &mut self,
18241 _session: Entity<Session>,
18242 event: &SessionEvent,
18243 cx: &mut Context<Self>,
18244 ) {
18245 match event {
18246 SessionEvent::InvalidateInlineValue => {
18247 self.refresh_inline_values(cx);
18248 }
18249 _ => {}
18250 }
18251 }
18252
18253 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18254 let Some(project) = self.project.clone() else {
18255 return;
18256 };
18257
18258 if !self.inline_value_cache.enabled {
18259 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18260 self.splice_inlays(&inlays, Vec::new(), cx);
18261 return;
18262 }
18263
18264 let current_execution_position = self
18265 .highlighted_rows
18266 .get(&TypeId::of::<ActiveDebugLine>())
18267 .and_then(|lines| lines.last().map(|line| line.range.start));
18268
18269 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18270 let inline_values = editor
18271 .update(cx, |editor, cx| {
18272 let Some(current_execution_position) = current_execution_position else {
18273 return Some(Task::ready(Ok(Vec::new())));
18274 };
18275
18276 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18277 let snapshot = buffer.snapshot(cx);
18278
18279 let excerpt = snapshot.excerpt_containing(
18280 current_execution_position..current_execution_position,
18281 )?;
18282
18283 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18284 })?;
18285
18286 let range =
18287 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18288
18289 project.inline_values(buffer, range, cx)
18290 })
18291 .ok()
18292 .flatten()?
18293 .await
18294 .context("refreshing debugger inlays")
18295 .log_err()?;
18296
18297 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18298
18299 for (buffer_id, inline_value) in inline_values
18300 .into_iter()
18301 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18302 {
18303 buffer_inline_values
18304 .entry(buffer_id)
18305 .or_default()
18306 .push(inline_value);
18307 }
18308
18309 editor
18310 .update(cx, |editor, cx| {
18311 let snapshot = editor.buffer.read(cx).snapshot(cx);
18312 let mut new_inlays = Vec::default();
18313
18314 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18315 let buffer_id = buffer_snapshot.remote_id();
18316 buffer_inline_values
18317 .get(&buffer_id)
18318 .into_iter()
18319 .flatten()
18320 .for_each(|hint| {
18321 let inlay = Inlay::debugger_hint(
18322 post_inc(&mut editor.next_inlay_id),
18323 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18324 hint.text(),
18325 );
18326
18327 new_inlays.push(inlay);
18328 });
18329 }
18330
18331 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18332 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18333
18334 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18335 })
18336 .ok()?;
18337 Some(())
18338 });
18339 }
18340
18341 fn on_buffer_event(
18342 &mut self,
18343 multibuffer: &Entity<MultiBuffer>,
18344 event: &multi_buffer::Event,
18345 window: &mut Window,
18346 cx: &mut Context<Self>,
18347 ) {
18348 match event {
18349 multi_buffer::Event::Edited {
18350 singleton_buffer_edited,
18351 edited_buffer: buffer_edited,
18352 } => {
18353 self.scrollbar_marker_state.dirty = true;
18354 self.active_indent_guides_state.dirty = true;
18355 self.refresh_active_diagnostics(cx);
18356 self.refresh_code_actions(window, cx);
18357 self.refresh_selected_text_highlights(true, window, cx);
18358 refresh_matching_bracket_highlights(self, window, cx);
18359 if self.has_active_inline_completion() {
18360 self.update_visible_inline_completion(window, cx);
18361 }
18362 if let Some(buffer) = buffer_edited {
18363 let buffer_id = buffer.read(cx).remote_id();
18364 if !self.registered_buffers.contains_key(&buffer_id) {
18365 if let Some(project) = self.project.as_ref() {
18366 project.update(cx, |project, cx| {
18367 self.registered_buffers.insert(
18368 buffer_id,
18369 project.register_buffer_with_language_servers(&buffer, cx),
18370 );
18371 })
18372 }
18373 }
18374 }
18375 cx.emit(EditorEvent::BufferEdited);
18376 cx.emit(SearchEvent::MatchesInvalidated);
18377 if *singleton_buffer_edited {
18378 if let Some(project) = &self.project {
18379 #[allow(clippy::mutable_key_type)]
18380 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18381 multibuffer
18382 .all_buffers()
18383 .into_iter()
18384 .filter_map(|buffer| {
18385 buffer.update(cx, |buffer, cx| {
18386 let language = buffer.language()?;
18387 let should_discard = project.update(cx, |project, cx| {
18388 project.is_local()
18389 && !project.has_language_servers_for(buffer, cx)
18390 });
18391 should_discard.not().then_some(language.clone())
18392 })
18393 })
18394 .collect::<HashSet<_>>()
18395 });
18396 if !languages_affected.is_empty() {
18397 self.refresh_inlay_hints(
18398 InlayHintRefreshReason::BufferEdited(languages_affected),
18399 cx,
18400 );
18401 }
18402 }
18403 }
18404
18405 let Some(project) = &self.project else { return };
18406 let (telemetry, is_via_ssh) = {
18407 let project = project.read(cx);
18408 let telemetry = project.client().telemetry().clone();
18409 let is_via_ssh = project.is_via_ssh();
18410 (telemetry, is_via_ssh)
18411 };
18412 refresh_linked_ranges(self, window, cx);
18413 telemetry.log_edit_event("editor", is_via_ssh);
18414 }
18415 multi_buffer::Event::ExcerptsAdded {
18416 buffer,
18417 predecessor,
18418 excerpts,
18419 } => {
18420 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18421 let buffer_id = buffer.read(cx).remote_id();
18422 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18423 if let Some(project) = &self.project {
18424 update_uncommitted_diff_for_buffer(
18425 cx.entity(),
18426 project,
18427 [buffer.clone()],
18428 self.buffer.clone(),
18429 cx,
18430 )
18431 .detach();
18432 }
18433 }
18434 cx.emit(EditorEvent::ExcerptsAdded {
18435 buffer: buffer.clone(),
18436 predecessor: *predecessor,
18437 excerpts: excerpts.clone(),
18438 });
18439 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18440 }
18441 multi_buffer::Event::ExcerptsRemoved {
18442 ids,
18443 removed_buffer_ids,
18444 } => {
18445 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18446 let buffer = self.buffer.read(cx);
18447 self.registered_buffers
18448 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18449 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18450 cx.emit(EditorEvent::ExcerptsRemoved {
18451 ids: ids.clone(),
18452 removed_buffer_ids: removed_buffer_ids.clone(),
18453 })
18454 }
18455 multi_buffer::Event::ExcerptsEdited {
18456 excerpt_ids,
18457 buffer_ids,
18458 } => {
18459 self.display_map.update(cx, |map, cx| {
18460 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18461 });
18462 cx.emit(EditorEvent::ExcerptsEdited {
18463 ids: excerpt_ids.clone(),
18464 })
18465 }
18466 multi_buffer::Event::ExcerptsExpanded { ids } => {
18467 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18468 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18469 }
18470 multi_buffer::Event::Reparsed(buffer_id) => {
18471 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18472 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18473
18474 cx.emit(EditorEvent::Reparsed(*buffer_id));
18475 }
18476 multi_buffer::Event::DiffHunksToggled => {
18477 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18478 }
18479 multi_buffer::Event::LanguageChanged(buffer_id) => {
18480 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18481 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18482 cx.emit(EditorEvent::Reparsed(*buffer_id));
18483 cx.notify();
18484 }
18485 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18486 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18487 multi_buffer::Event::FileHandleChanged
18488 | multi_buffer::Event::Reloaded
18489 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18490 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18491 multi_buffer::Event::DiagnosticsUpdated => {
18492 self.refresh_active_diagnostics(cx);
18493 self.refresh_inline_diagnostics(true, window, cx);
18494 self.scrollbar_marker_state.dirty = true;
18495 cx.notify();
18496 }
18497 _ => {}
18498 };
18499 }
18500
18501 pub fn start_temporary_diff_override(&mut self) {
18502 self.load_diff_task.take();
18503 self.temporary_diff_override = true;
18504 }
18505
18506 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18507 self.temporary_diff_override = false;
18508 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18509 self.buffer.update(cx, |buffer, cx| {
18510 buffer.set_all_diff_hunks_collapsed(cx);
18511 });
18512
18513 if let Some(project) = self.project.clone() {
18514 self.load_diff_task = Some(
18515 update_uncommitted_diff_for_buffer(
18516 cx.entity(),
18517 &project,
18518 self.buffer.read(cx).all_buffers(),
18519 self.buffer.clone(),
18520 cx,
18521 )
18522 .shared(),
18523 );
18524 }
18525 }
18526
18527 fn on_display_map_changed(
18528 &mut self,
18529 _: Entity<DisplayMap>,
18530 _: &mut Window,
18531 cx: &mut Context<Self>,
18532 ) {
18533 cx.notify();
18534 }
18535
18536 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18537 let new_severity = if self.diagnostics_enabled() {
18538 EditorSettings::get_global(cx)
18539 .diagnostics_max_severity
18540 .unwrap_or(DiagnosticSeverity::Hint)
18541 } else {
18542 DiagnosticSeverity::Off
18543 };
18544 self.set_max_diagnostics_severity(new_severity, cx);
18545 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18546 self.update_edit_prediction_settings(cx);
18547 self.refresh_inline_completion(true, false, window, cx);
18548 self.refresh_inlay_hints(
18549 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18550 self.selections.newest_anchor().head(),
18551 &self.buffer.read(cx).snapshot(cx),
18552 cx,
18553 )),
18554 cx,
18555 );
18556
18557 let old_cursor_shape = self.cursor_shape;
18558
18559 {
18560 let editor_settings = EditorSettings::get_global(cx);
18561 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18562 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18563 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18564 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18565 }
18566
18567 if old_cursor_shape != self.cursor_shape {
18568 cx.emit(EditorEvent::CursorShapeChanged);
18569 }
18570
18571 let project_settings = ProjectSettings::get_global(cx);
18572 self.serialize_dirty_buffers =
18573 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18574
18575 if self.mode.is_full() {
18576 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18577 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18578 if self.show_inline_diagnostics != show_inline_diagnostics {
18579 self.show_inline_diagnostics = show_inline_diagnostics;
18580 self.refresh_inline_diagnostics(false, window, cx);
18581 }
18582
18583 if self.git_blame_inline_enabled != inline_blame_enabled {
18584 self.toggle_git_blame_inline_internal(false, window, cx);
18585 }
18586
18587 let minimap_settings = EditorSettings::get_global(cx).minimap;
18588 if self.minimap_visibility.settings_visibility() != minimap_settings.minimap_enabled() {
18589 self.set_minimap_visibility(
18590 MinimapVisibility::for_mode(self.mode(), cx),
18591 window,
18592 cx,
18593 );
18594 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18595 minimap_entity.update(cx, |minimap_editor, cx| {
18596 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18597 })
18598 }
18599 }
18600
18601 cx.notify();
18602 }
18603
18604 pub fn set_searchable(&mut self, searchable: bool) {
18605 self.searchable = searchable;
18606 }
18607
18608 pub fn searchable(&self) -> bool {
18609 self.searchable
18610 }
18611
18612 fn open_proposed_changes_editor(
18613 &mut self,
18614 _: &OpenProposedChangesEditor,
18615 window: &mut Window,
18616 cx: &mut Context<Self>,
18617 ) {
18618 let Some(workspace) = self.workspace() else {
18619 cx.propagate();
18620 return;
18621 };
18622
18623 let selections = self.selections.all::<usize>(cx);
18624 let multi_buffer = self.buffer.read(cx);
18625 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18626 let mut new_selections_by_buffer = HashMap::default();
18627 for selection in selections {
18628 for (buffer, range, _) in
18629 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18630 {
18631 let mut range = range.to_point(buffer);
18632 range.start.column = 0;
18633 range.end.column = buffer.line_len(range.end.row);
18634 new_selections_by_buffer
18635 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18636 .or_insert(Vec::new())
18637 .push(range)
18638 }
18639 }
18640
18641 let proposed_changes_buffers = new_selections_by_buffer
18642 .into_iter()
18643 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18644 .collect::<Vec<_>>();
18645 let proposed_changes_editor = cx.new(|cx| {
18646 ProposedChangesEditor::new(
18647 "Proposed changes",
18648 proposed_changes_buffers,
18649 self.project.clone(),
18650 window,
18651 cx,
18652 )
18653 });
18654
18655 window.defer(cx, move |window, cx| {
18656 workspace.update(cx, |workspace, cx| {
18657 workspace.active_pane().update(cx, |pane, cx| {
18658 pane.add_item(
18659 Box::new(proposed_changes_editor),
18660 true,
18661 true,
18662 None,
18663 window,
18664 cx,
18665 );
18666 });
18667 });
18668 });
18669 }
18670
18671 pub fn open_excerpts_in_split(
18672 &mut self,
18673 _: &OpenExcerptsSplit,
18674 window: &mut Window,
18675 cx: &mut Context<Self>,
18676 ) {
18677 self.open_excerpts_common(None, true, window, cx)
18678 }
18679
18680 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18681 self.open_excerpts_common(None, false, window, cx)
18682 }
18683
18684 fn open_excerpts_common(
18685 &mut self,
18686 jump_data: Option<JumpData>,
18687 split: bool,
18688 window: &mut Window,
18689 cx: &mut Context<Self>,
18690 ) {
18691 let Some(workspace) = self.workspace() else {
18692 cx.propagate();
18693 return;
18694 };
18695
18696 if self.buffer.read(cx).is_singleton() {
18697 cx.propagate();
18698 return;
18699 }
18700
18701 let mut new_selections_by_buffer = HashMap::default();
18702 match &jump_data {
18703 Some(JumpData::MultiBufferPoint {
18704 excerpt_id,
18705 position,
18706 anchor,
18707 line_offset_from_top,
18708 }) => {
18709 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18710 if let Some(buffer) = multi_buffer_snapshot
18711 .buffer_id_for_excerpt(*excerpt_id)
18712 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18713 {
18714 let buffer_snapshot = buffer.read(cx).snapshot();
18715 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18716 language::ToPoint::to_point(anchor, &buffer_snapshot)
18717 } else {
18718 buffer_snapshot.clip_point(*position, Bias::Left)
18719 };
18720 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18721 new_selections_by_buffer.insert(
18722 buffer,
18723 (
18724 vec![jump_to_offset..jump_to_offset],
18725 Some(*line_offset_from_top),
18726 ),
18727 );
18728 }
18729 }
18730 Some(JumpData::MultiBufferRow {
18731 row,
18732 line_offset_from_top,
18733 }) => {
18734 let point = MultiBufferPoint::new(row.0, 0);
18735 if let Some((buffer, buffer_point, _)) =
18736 self.buffer.read(cx).point_to_buffer_point(point, cx)
18737 {
18738 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18739 new_selections_by_buffer
18740 .entry(buffer)
18741 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18742 .0
18743 .push(buffer_offset..buffer_offset)
18744 }
18745 }
18746 None => {
18747 let selections = self.selections.all::<usize>(cx);
18748 let multi_buffer = self.buffer.read(cx);
18749 for selection in selections {
18750 for (snapshot, range, _, anchor) in multi_buffer
18751 .snapshot(cx)
18752 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18753 {
18754 if let Some(anchor) = anchor {
18755 // selection is in a deleted hunk
18756 let Some(buffer_id) = anchor.buffer_id else {
18757 continue;
18758 };
18759 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18760 continue;
18761 };
18762 let offset = text::ToOffset::to_offset(
18763 &anchor.text_anchor,
18764 &buffer_handle.read(cx).snapshot(),
18765 );
18766 let range = offset..offset;
18767 new_selections_by_buffer
18768 .entry(buffer_handle)
18769 .or_insert((Vec::new(), None))
18770 .0
18771 .push(range)
18772 } else {
18773 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18774 else {
18775 continue;
18776 };
18777 new_selections_by_buffer
18778 .entry(buffer_handle)
18779 .or_insert((Vec::new(), None))
18780 .0
18781 .push(range)
18782 }
18783 }
18784 }
18785 }
18786 }
18787
18788 new_selections_by_buffer
18789 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18790
18791 if new_selections_by_buffer.is_empty() {
18792 return;
18793 }
18794
18795 // We defer the pane interaction because we ourselves are a workspace item
18796 // and activating a new item causes the pane to call a method on us reentrantly,
18797 // which panics if we're on the stack.
18798 window.defer(cx, move |window, cx| {
18799 workspace.update(cx, |workspace, cx| {
18800 let pane = if split {
18801 workspace.adjacent_pane(window, cx)
18802 } else {
18803 workspace.active_pane().clone()
18804 };
18805
18806 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18807 let editor = buffer
18808 .read(cx)
18809 .file()
18810 .is_none()
18811 .then(|| {
18812 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18813 // so `workspace.open_project_item` will never find them, always opening a new editor.
18814 // Instead, we try to activate the existing editor in the pane first.
18815 let (editor, pane_item_index) =
18816 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18817 let editor = item.downcast::<Editor>()?;
18818 let singleton_buffer =
18819 editor.read(cx).buffer().read(cx).as_singleton()?;
18820 if singleton_buffer == buffer {
18821 Some((editor, i))
18822 } else {
18823 None
18824 }
18825 })?;
18826 pane.update(cx, |pane, cx| {
18827 pane.activate_item(pane_item_index, true, true, window, cx)
18828 });
18829 Some(editor)
18830 })
18831 .flatten()
18832 .unwrap_or_else(|| {
18833 workspace.open_project_item::<Self>(
18834 pane.clone(),
18835 buffer,
18836 true,
18837 true,
18838 window,
18839 cx,
18840 )
18841 });
18842
18843 editor.update(cx, |editor, cx| {
18844 let autoscroll = match scroll_offset {
18845 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18846 None => Autoscroll::newest(),
18847 };
18848 let nav_history = editor.nav_history.take();
18849 editor.change_selections(Some(autoscroll), window, cx, |s| {
18850 s.select_ranges(ranges);
18851 });
18852 editor.nav_history = nav_history;
18853 });
18854 }
18855 })
18856 });
18857 }
18858
18859 // For now, don't allow opening excerpts in buffers that aren't backed by
18860 // regular project files.
18861 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18862 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18863 }
18864
18865 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18866 let snapshot = self.buffer.read(cx).read(cx);
18867 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18868 Some(
18869 ranges
18870 .iter()
18871 .map(move |range| {
18872 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18873 })
18874 .collect(),
18875 )
18876 }
18877
18878 fn selection_replacement_ranges(
18879 &self,
18880 range: Range<OffsetUtf16>,
18881 cx: &mut App,
18882 ) -> Vec<Range<OffsetUtf16>> {
18883 let selections = self.selections.all::<OffsetUtf16>(cx);
18884 let newest_selection = selections
18885 .iter()
18886 .max_by_key(|selection| selection.id)
18887 .unwrap();
18888 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18889 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18890 let snapshot = self.buffer.read(cx).read(cx);
18891 selections
18892 .into_iter()
18893 .map(|mut selection| {
18894 selection.start.0 =
18895 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18896 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18897 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18898 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18899 })
18900 .collect()
18901 }
18902
18903 fn report_editor_event(
18904 &self,
18905 event_type: &'static str,
18906 file_extension: Option<String>,
18907 cx: &App,
18908 ) {
18909 if cfg!(any(test, feature = "test-support")) {
18910 return;
18911 }
18912
18913 let Some(project) = &self.project else { return };
18914
18915 // If None, we are in a file without an extension
18916 let file = self
18917 .buffer
18918 .read(cx)
18919 .as_singleton()
18920 .and_then(|b| b.read(cx).file());
18921 let file_extension = file_extension.or(file
18922 .as_ref()
18923 .and_then(|file| Path::new(file.file_name(cx)).extension())
18924 .and_then(|e| e.to_str())
18925 .map(|a| a.to_string()));
18926
18927 let vim_mode = vim_enabled(cx);
18928
18929 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18930 let copilot_enabled = edit_predictions_provider
18931 == language::language_settings::EditPredictionProvider::Copilot;
18932 let copilot_enabled_for_language = self
18933 .buffer
18934 .read(cx)
18935 .language_settings(cx)
18936 .show_edit_predictions;
18937
18938 let project = project.read(cx);
18939 telemetry::event!(
18940 event_type,
18941 file_extension,
18942 vim_mode,
18943 copilot_enabled,
18944 copilot_enabled_for_language,
18945 edit_predictions_provider,
18946 is_via_ssh = project.is_via_ssh(),
18947 );
18948 }
18949
18950 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18951 /// with each line being an array of {text, highlight} objects.
18952 fn copy_highlight_json(
18953 &mut self,
18954 _: &CopyHighlightJson,
18955 window: &mut Window,
18956 cx: &mut Context<Self>,
18957 ) {
18958 #[derive(Serialize)]
18959 struct Chunk<'a> {
18960 text: String,
18961 highlight: Option<&'a str>,
18962 }
18963
18964 let snapshot = self.buffer.read(cx).snapshot(cx);
18965 let range = self
18966 .selected_text_range(false, window, cx)
18967 .and_then(|selection| {
18968 if selection.range.is_empty() {
18969 None
18970 } else {
18971 Some(selection.range)
18972 }
18973 })
18974 .unwrap_or_else(|| 0..snapshot.len());
18975
18976 let chunks = snapshot.chunks(range, true);
18977 let mut lines = Vec::new();
18978 let mut line: VecDeque<Chunk> = VecDeque::new();
18979
18980 let Some(style) = self.style.as_ref() else {
18981 return;
18982 };
18983
18984 for chunk in chunks {
18985 let highlight = chunk
18986 .syntax_highlight_id
18987 .and_then(|id| id.name(&style.syntax));
18988 let mut chunk_lines = chunk.text.split('\n').peekable();
18989 while let Some(text) = chunk_lines.next() {
18990 let mut merged_with_last_token = false;
18991 if let Some(last_token) = line.back_mut() {
18992 if last_token.highlight == highlight {
18993 last_token.text.push_str(text);
18994 merged_with_last_token = true;
18995 }
18996 }
18997
18998 if !merged_with_last_token {
18999 line.push_back(Chunk {
19000 text: text.into(),
19001 highlight,
19002 });
19003 }
19004
19005 if chunk_lines.peek().is_some() {
19006 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19007 line.pop_front();
19008 }
19009 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19010 line.pop_back();
19011 }
19012
19013 lines.push(mem::take(&mut line));
19014 }
19015 }
19016 }
19017
19018 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19019 return;
19020 };
19021 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19022 }
19023
19024 pub fn open_context_menu(
19025 &mut self,
19026 _: &OpenContextMenu,
19027 window: &mut Window,
19028 cx: &mut Context<Self>,
19029 ) {
19030 self.request_autoscroll(Autoscroll::newest(), cx);
19031 let position = self.selections.newest_display(cx).start;
19032 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19033 }
19034
19035 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19036 &self.inlay_hint_cache
19037 }
19038
19039 pub fn replay_insert_event(
19040 &mut self,
19041 text: &str,
19042 relative_utf16_range: Option<Range<isize>>,
19043 window: &mut Window,
19044 cx: &mut Context<Self>,
19045 ) {
19046 if !self.input_enabled {
19047 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19048 return;
19049 }
19050 if let Some(relative_utf16_range) = relative_utf16_range {
19051 let selections = self.selections.all::<OffsetUtf16>(cx);
19052 self.change_selections(None, window, cx, |s| {
19053 let new_ranges = selections.into_iter().map(|range| {
19054 let start = OffsetUtf16(
19055 range
19056 .head()
19057 .0
19058 .saturating_add_signed(relative_utf16_range.start),
19059 );
19060 let end = OffsetUtf16(
19061 range
19062 .head()
19063 .0
19064 .saturating_add_signed(relative_utf16_range.end),
19065 );
19066 start..end
19067 });
19068 s.select_ranges(new_ranges);
19069 });
19070 }
19071
19072 self.handle_input(text, window, cx);
19073 }
19074
19075 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19076 let Some(provider) = self.semantics_provider.as_ref() else {
19077 return false;
19078 };
19079
19080 let mut supports = false;
19081 self.buffer().update(cx, |this, cx| {
19082 this.for_each_buffer(|buffer| {
19083 supports |= provider.supports_inlay_hints(buffer, cx);
19084 });
19085 });
19086
19087 supports
19088 }
19089
19090 pub fn is_focused(&self, window: &Window) -> bool {
19091 self.focus_handle.is_focused(window)
19092 }
19093
19094 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19095 cx.emit(EditorEvent::Focused);
19096
19097 if let Some(descendant) = self
19098 .last_focused_descendant
19099 .take()
19100 .and_then(|descendant| descendant.upgrade())
19101 {
19102 window.focus(&descendant);
19103 } else {
19104 if let Some(blame) = self.blame.as_ref() {
19105 blame.update(cx, GitBlame::focus)
19106 }
19107
19108 self.blink_manager.update(cx, BlinkManager::enable);
19109 self.show_cursor_names(window, cx);
19110 self.buffer.update(cx, |buffer, cx| {
19111 buffer.finalize_last_transaction(cx);
19112 if self.leader_id.is_none() {
19113 buffer.set_active_selections(
19114 &self.selections.disjoint_anchors(),
19115 self.selections.line_mode,
19116 self.cursor_shape,
19117 cx,
19118 );
19119 }
19120 });
19121 }
19122 }
19123
19124 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19125 cx.emit(EditorEvent::FocusedIn)
19126 }
19127
19128 fn handle_focus_out(
19129 &mut self,
19130 event: FocusOutEvent,
19131 _window: &mut Window,
19132 cx: &mut Context<Self>,
19133 ) {
19134 if event.blurred != self.focus_handle {
19135 self.last_focused_descendant = Some(event.blurred);
19136 }
19137 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19138 }
19139
19140 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19141 self.blink_manager.update(cx, BlinkManager::disable);
19142 self.buffer
19143 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19144
19145 if let Some(blame) = self.blame.as_ref() {
19146 blame.update(cx, GitBlame::blur)
19147 }
19148 if !self.hover_state.focused(window, cx) {
19149 hide_hover(self, cx);
19150 }
19151 if !self
19152 .context_menu
19153 .borrow()
19154 .as_ref()
19155 .is_some_and(|context_menu| context_menu.focused(window, cx))
19156 {
19157 self.hide_context_menu(window, cx);
19158 }
19159 self.discard_inline_completion(false, cx);
19160 cx.emit(EditorEvent::Blurred);
19161 cx.notify();
19162 }
19163
19164 pub fn register_action<A: Action>(
19165 &mut self,
19166 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19167 ) -> Subscription {
19168 let id = self.next_editor_action_id.post_inc();
19169 let listener = Arc::new(listener);
19170 self.editor_actions.borrow_mut().insert(
19171 id,
19172 Box::new(move |window, _| {
19173 let listener = listener.clone();
19174 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19175 let action = action.downcast_ref().unwrap();
19176 if phase == DispatchPhase::Bubble {
19177 listener(action, window, cx)
19178 }
19179 })
19180 }),
19181 );
19182
19183 let editor_actions = self.editor_actions.clone();
19184 Subscription::new(move || {
19185 editor_actions.borrow_mut().remove(&id);
19186 })
19187 }
19188
19189 pub fn file_header_size(&self) -> u32 {
19190 FILE_HEADER_HEIGHT
19191 }
19192
19193 pub fn restore(
19194 &mut self,
19195 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19196 window: &mut Window,
19197 cx: &mut Context<Self>,
19198 ) {
19199 let workspace = self.workspace();
19200 let project = self.project.as_ref();
19201 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19202 let mut tasks = Vec::new();
19203 for (buffer_id, changes) in revert_changes {
19204 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19205 buffer.update(cx, |buffer, cx| {
19206 buffer.edit(
19207 changes
19208 .into_iter()
19209 .map(|(range, text)| (range, text.to_string())),
19210 None,
19211 cx,
19212 );
19213 });
19214
19215 if let Some(project) =
19216 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19217 {
19218 project.update(cx, |project, cx| {
19219 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19220 })
19221 }
19222 }
19223 }
19224 tasks
19225 });
19226 cx.spawn_in(window, async move |_, cx| {
19227 for (buffer, task) in save_tasks {
19228 let result = task.await;
19229 if result.is_err() {
19230 let Some(path) = buffer
19231 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19232 .ok()
19233 else {
19234 continue;
19235 };
19236 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19237 let Some(task) = cx
19238 .update_window_entity(&workspace, |workspace, window, cx| {
19239 workspace
19240 .open_path_preview(path, None, false, false, false, window, cx)
19241 })
19242 .ok()
19243 else {
19244 continue;
19245 };
19246 task.await.log_err();
19247 }
19248 }
19249 }
19250 })
19251 .detach();
19252 self.change_selections(None, window, cx, |selections| selections.refresh());
19253 }
19254
19255 pub fn to_pixel_point(
19256 &self,
19257 source: multi_buffer::Anchor,
19258 editor_snapshot: &EditorSnapshot,
19259 window: &mut Window,
19260 ) -> Option<gpui::Point<Pixels>> {
19261 let source_point = source.to_display_point(editor_snapshot);
19262 self.display_to_pixel_point(source_point, editor_snapshot, window)
19263 }
19264
19265 pub fn display_to_pixel_point(
19266 &self,
19267 source: DisplayPoint,
19268 editor_snapshot: &EditorSnapshot,
19269 window: &mut Window,
19270 ) -> Option<gpui::Point<Pixels>> {
19271 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19272 let text_layout_details = self.text_layout_details(window);
19273 let scroll_top = text_layout_details
19274 .scroll_anchor
19275 .scroll_position(editor_snapshot)
19276 .y;
19277
19278 if source.row().as_f32() < scroll_top.floor() {
19279 return None;
19280 }
19281 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19282 let source_y = line_height * (source.row().as_f32() - scroll_top);
19283 Some(gpui::Point::new(source_x, source_y))
19284 }
19285
19286 pub fn has_visible_completions_menu(&self) -> bool {
19287 !self.edit_prediction_preview_is_active()
19288 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19289 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19290 })
19291 }
19292
19293 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19294 if self.mode.is_minimap() {
19295 return;
19296 }
19297 self.addons
19298 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19299 }
19300
19301 pub fn unregister_addon<T: Addon>(&mut self) {
19302 self.addons.remove(&std::any::TypeId::of::<T>());
19303 }
19304
19305 pub fn addon<T: Addon>(&self) -> Option<&T> {
19306 let type_id = std::any::TypeId::of::<T>();
19307 self.addons
19308 .get(&type_id)
19309 .and_then(|item| item.to_any().downcast_ref::<T>())
19310 }
19311
19312 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19313 let type_id = std::any::TypeId::of::<T>();
19314 self.addons
19315 .get_mut(&type_id)
19316 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19317 }
19318
19319 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19320 let text_layout_details = self.text_layout_details(window);
19321 let style = &text_layout_details.editor_style;
19322 let font_id = window.text_system().resolve_font(&style.text.font());
19323 let font_size = style.text.font_size.to_pixels(window.rem_size());
19324 let line_height = style.text.line_height_in_pixels(window.rem_size());
19325 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19326
19327 gpui::Size::new(em_width, line_height)
19328 }
19329
19330 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19331 self.load_diff_task.clone()
19332 }
19333
19334 fn read_metadata_from_db(
19335 &mut self,
19336 item_id: u64,
19337 workspace_id: WorkspaceId,
19338 window: &mut Window,
19339 cx: &mut Context<Editor>,
19340 ) {
19341 if self.is_singleton(cx)
19342 && !self.mode.is_minimap()
19343 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19344 {
19345 let buffer_snapshot = OnceCell::new();
19346
19347 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19348 if !folds.is_empty() {
19349 let snapshot =
19350 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19351 self.fold_ranges(
19352 folds
19353 .into_iter()
19354 .map(|(start, end)| {
19355 snapshot.clip_offset(start, Bias::Left)
19356 ..snapshot.clip_offset(end, Bias::Right)
19357 })
19358 .collect(),
19359 false,
19360 window,
19361 cx,
19362 );
19363 }
19364 }
19365
19366 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19367 if !selections.is_empty() {
19368 let snapshot =
19369 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19370 self.change_selections(None, window, cx, |s| {
19371 s.select_ranges(selections.into_iter().map(|(start, end)| {
19372 snapshot.clip_offset(start, Bias::Left)
19373 ..snapshot.clip_offset(end, Bias::Right)
19374 }));
19375 });
19376 }
19377 };
19378 }
19379
19380 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19381 }
19382}
19383
19384fn vim_enabled(cx: &App) -> bool {
19385 cx.global::<SettingsStore>()
19386 .raw_user_settings()
19387 .get("vim_mode")
19388 == Some(&serde_json::Value::Bool(true))
19389}
19390
19391// Consider user intent and default settings
19392fn choose_completion_range(
19393 completion: &Completion,
19394 intent: CompletionIntent,
19395 buffer: &Entity<Buffer>,
19396 cx: &mut Context<Editor>,
19397) -> Range<usize> {
19398 fn should_replace(
19399 completion: &Completion,
19400 insert_range: &Range<text::Anchor>,
19401 intent: CompletionIntent,
19402 completion_mode_setting: LspInsertMode,
19403 buffer: &Buffer,
19404 ) -> bool {
19405 // specific actions take precedence over settings
19406 match intent {
19407 CompletionIntent::CompleteWithInsert => return false,
19408 CompletionIntent::CompleteWithReplace => return true,
19409 CompletionIntent::Complete | CompletionIntent::Compose => {}
19410 }
19411
19412 match completion_mode_setting {
19413 LspInsertMode::Insert => false,
19414 LspInsertMode::Replace => true,
19415 LspInsertMode::ReplaceSubsequence => {
19416 let mut text_to_replace = buffer.chars_for_range(
19417 buffer.anchor_before(completion.replace_range.start)
19418 ..buffer.anchor_after(completion.replace_range.end),
19419 );
19420 let mut completion_text = completion.new_text.chars();
19421
19422 // is `text_to_replace` a subsequence of `completion_text`
19423 text_to_replace
19424 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19425 }
19426 LspInsertMode::ReplaceSuffix => {
19427 let range_after_cursor = insert_range.end..completion.replace_range.end;
19428
19429 let text_after_cursor = buffer
19430 .text_for_range(
19431 buffer.anchor_before(range_after_cursor.start)
19432 ..buffer.anchor_after(range_after_cursor.end),
19433 )
19434 .collect::<String>();
19435 completion.new_text.ends_with(&text_after_cursor)
19436 }
19437 }
19438 }
19439
19440 let buffer = buffer.read(cx);
19441
19442 if let CompletionSource::Lsp {
19443 insert_range: Some(insert_range),
19444 ..
19445 } = &completion.source
19446 {
19447 let completion_mode_setting =
19448 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19449 .completions
19450 .lsp_insert_mode;
19451
19452 if !should_replace(
19453 completion,
19454 &insert_range,
19455 intent,
19456 completion_mode_setting,
19457 buffer,
19458 ) {
19459 return insert_range.to_offset(buffer);
19460 }
19461 }
19462
19463 completion.replace_range.to_offset(buffer)
19464}
19465
19466fn insert_extra_newline_brackets(
19467 buffer: &MultiBufferSnapshot,
19468 range: Range<usize>,
19469 language: &language::LanguageScope,
19470) -> bool {
19471 let leading_whitespace_len = buffer
19472 .reversed_chars_at(range.start)
19473 .take_while(|c| c.is_whitespace() && *c != '\n')
19474 .map(|c| c.len_utf8())
19475 .sum::<usize>();
19476 let trailing_whitespace_len = buffer
19477 .chars_at(range.end)
19478 .take_while(|c| c.is_whitespace() && *c != '\n')
19479 .map(|c| c.len_utf8())
19480 .sum::<usize>();
19481 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19482
19483 language.brackets().any(|(pair, enabled)| {
19484 let pair_start = pair.start.trim_end();
19485 let pair_end = pair.end.trim_start();
19486
19487 enabled
19488 && pair.newline
19489 && buffer.contains_str_at(range.end, pair_end)
19490 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19491 })
19492}
19493
19494fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19495 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19496 [(buffer, range, _)] => (*buffer, range.clone()),
19497 _ => return false,
19498 };
19499 let pair = {
19500 let mut result: Option<BracketMatch> = None;
19501
19502 for pair in buffer
19503 .all_bracket_ranges(range.clone())
19504 .filter(move |pair| {
19505 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19506 })
19507 {
19508 let len = pair.close_range.end - pair.open_range.start;
19509
19510 if let Some(existing) = &result {
19511 let existing_len = existing.close_range.end - existing.open_range.start;
19512 if len > existing_len {
19513 continue;
19514 }
19515 }
19516
19517 result = Some(pair);
19518 }
19519
19520 result
19521 };
19522 let Some(pair) = pair else {
19523 return false;
19524 };
19525 pair.newline_only
19526 && buffer
19527 .chars_for_range(pair.open_range.end..range.start)
19528 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19529 .all(|c| c.is_whitespace() && c != '\n')
19530}
19531
19532fn update_uncommitted_diff_for_buffer(
19533 editor: Entity<Editor>,
19534 project: &Entity<Project>,
19535 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19536 buffer: Entity<MultiBuffer>,
19537 cx: &mut App,
19538) -> Task<()> {
19539 let mut tasks = Vec::new();
19540 project.update(cx, |project, cx| {
19541 for buffer in buffers {
19542 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19543 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19544 }
19545 }
19546 });
19547 cx.spawn(async move |cx| {
19548 let diffs = future::join_all(tasks).await;
19549 if editor
19550 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19551 .unwrap_or(false)
19552 {
19553 return;
19554 }
19555
19556 buffer
19557 .update(cx, |buffer, cx| {
19558 for diff in diffs.into_iter().flatten() {
19559 buffer.add_diff(diff, cx);
19560 }
19561 })
19562 .ok();
19563 })
19564}
19565
19566pub trait CollaborationHub {
19567 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19568 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19569 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19570}
19571
19572impl CollaborationHub for Entity<Project> {
19573 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19574 self.read(cx).collaborators()
19575 }
19576
19577 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19578 self.read(cx).user_store().read(cx).participant_indices()
19579 }
19580
19581 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19582 let this = self.read(cx);
19583 let user_ids = this.collaborators().values().map(|c| c.user_id);
19584 this.user_store().read(cx).participant_names(user_ids, cx)
19585 }
19586}
19587
19588pub trait SemanticsProvider {
19589 fn hover(
19590 &self,
19591 buffer: &Entity<Buffer>,
19592 position: text::Anchor,
19593 cx: &mut App,
19594 ) -> Option<Task<Vec<project::Hover>>>;
19595
19596 fn inline_values(
19597 &self,
19598 buffer_handle: Entity<Buffer>,
19599 range: Range<text::Anchor>,
19600 cx: &mut App,
19601 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19602
19603 fn inlay_hints(
19604 &self,
19605 buffer_handle: Entity<Buffer>,
19606 range: Range<text::Anchor>,
19607 cx: &mut App,
19608 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19609
19610 fn resolve_inlay_hint(
19611 &self,
19612 hint: InlayHint,
19613 buffer_handle: Entity<Buffer>,
19614 server_id: LanguageServerId,
19615 cx: &mut App,
19616 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19617
19618 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19619
19620 fn document_highlights(
19621 &self,
19622 buffer: &Entity<Buffer>,
19623 position: text::Anchor,
19624 cx: &mut App,
19625 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19626
19627 fn definitions(
19628 &self,
19629 buffer: &Entity<Buffer>,
19630 position: text::Anchor,
19631 kind: GotoDefinitionKind,
19632 cx: &mut App,
19633 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19634
19635 fn range_for_rename(
19636 &self,
19637 buffer: &Entity<Buffer>,
19638 position: text::Anchor,
19639 cx: &mut App,
19640 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19641
19642 fn perform_rename(
19643 &self,
19644 buffer: &Entity<Buffer>,
19645 position: text::Anchor,
19646 new_name: String,
19647 cx: &mut App,
19648 ) -> Option<Task<Result<ProjectTransaction>>>;
19649}
19650
19651pub trait CompletionProvider {
19652 fn completions(
19653 &self,
19654 excerpt_id: ExcerptId,
19655 buffer: &Entity<Buffer>,
19656 buffer_position: text::Anchor,
19657 trigger: CompletionContext,
19658 window: &mut Window,
19659 cx: &mut Context<Editor>,
19660 ) -> Task<Result<Option<Vec<Completion>>>>;
19661
19662 fn resolve_completions(
19663 &self,
19664 buffer: Entity<Buffer>,
19665 completion_indices: Vec<usize>,
19666 completions: Rc<RefCell<Box<[Completion]>>>,
19667 cx: &mut Context<Editor>,
19668 ) -> Task<Result<bool>>;
19669
19670 fn apply_additional_edits_for_completion(
19671 &self,
19672 _buffer: Entity<Buffer>,
19673 _completions: Rc<RefCell<Box<[Completion]>>>,
19674 _completion_index: usize,
19675 _push_to_history: bool,
19676 _cx: &mut Context<Editor>,
19677 ) -> Task<Result<Option<language::Transaction>>> {
19678 Task::ready(Ok(None))
19679 }
19680
19681 fn is_completion_trigger(
19682 &self,
19683 buffer: &Entity<Buffer>,
19684 position: language::Anchor,
19685 text: &str,
19686 trigger_in_words: bool,
19687 cx: &mut Context<Editor>,
19688 ) -> bool;
19689
19690 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
19691
19692 fn sort_completions(&self) -> bool {
19693 true
19694 }
19695
19696 fn filter_completions(&self) -> bool {
19697 true
19698 }
19699}
19700
19701pub trait CodeActionProvider {
19702 fn id(&self) -> Arc<str>;
19703
19704 fn code_actions(
19705 &self,
19706 buffer: &Entity<Buffer>,
19707 range: Range<text::Anchor>,
19708 window: &mut Window,
19709 cx: &mut App,
19710 ) -> Task<Result<Vec<CodeAction>>>;
19711
19712 fn apply_code_action(
19713 &self,
19714 buffer_handle: Entity<Buffer>,
19715 action: CodeAction,
19716 excerpt_id: ExcerptId,
19717 push_to_history: bool,
19718 window: &mut Window,
19719 cx: &mut App,
19720 ) -> Task<Result<ProjectTransaction>>;
19721}
19722
19723impl CodeActionProvider for Entity<Project> {
19724 fn id(&self) -> Arc<str> {
19725 "project".into()
19726 }
19727
19728 fn code_actions(
19729 &self,
19730 buffer: &Entity<Buffer>,
19731 range: Range<text::Anchor>,
19732 _window: &mut Window,
19733 cx: &mut App,
19734 ) -> Task<Result<Vec<CodeAction>>> {
19735 self.update(cx, |project, cx| {
19736 let code_lens = project.code_lens(buffer, range.clone(), cx);
19737 let code_actions = project.code_actions(buffer, range, None, cx);
19738 cx.background_spawn(async move {
19739 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19740 Ok(code_lens
19741 .context("code lens fetch")?
19742 .into_iter()
19743 .chain(code_actions.context("code action fetch")?)
19744 .collect())
19745 })
19746 })
19747 }
19748
19749 fn apply_code_action(
19750 &self,
19751 buffer_handle: Entity<Buffer>,
19752 action: CodeAction,
19753 _excerpt_id: ExcerptId,
19754 push_to_history: bool,
19755 _window: &mut Window,
19756 cx: &mut App,
19757 ) -> Task<Result<ProjectTransaction>> {
19758 self.update(cx, |project, cx| {
19759 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19760 })
19761 }
19762}
19763
19764fn snippet_completions(
19765 project: &Project,
19766 buffer: &Entity<Buffer>,
19767 buffer_position: text::Anchor,
19768 cx: &mut App,
19769) -> Task<Result<Vec<Completion>>> {
19770 let languages = buffer.read(cx).languages_at(buffer_position);
19771 let snippet_store = project.snippets().read(cx);
19772
19773 let scopes: Vec<_> = languages
19774 .iter()
19775 .filter_map(|language| {
19776 let language_name = language.lsp_id();
19777 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19778
19779 if snippets.is_empty() {
19780 None
19781 } else {
19782 Some((language.default_scope(), snippets))
19783 }
19784 })
19785 .collect();
19786
19787 if scopes.is_empty() {
19788 return Task::ready(Ok(vec![]));
19789 }
19790
19791 let snapshot = buffer.read(cx).text_snapshot();
19792 let chars: String = snapshot
19793 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19794 .collect();
19795 let executor = cx.background_executor().clone();
19796
19797 cx.background_spawn(async move {
19798 let mut all_results: Vec<Completion> = Vec::new();
19799 for (scope, snippets) in scopes.into_iter() {
19800 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19801 let mut last_word = chars
19802 .chars()
19803 .take_while(|c| classifier.is_word(*c))
19804 .collect::<String>();
19805 last_word = last_word.chars().rev().collect();
19806
19807 if last_word.is_empty() {
19808 return Ok(vec![]);
19809 }
19810
19811 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19812 let to_lsp = |point: &text::Anchor| {
19813 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19814 point_to_lsp(end)
19815 };
19816 let lsp_end = to_lsp(&buffer_position);
19817
19818 let candidates = snippets
19819 .iter()
19820 .enumerate()
19821 .flat_map(|(ix, snippet)| {
19822 snippet
19823 .prefix
19824 .iter()
19825 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19826 })
19827 .collect::<Vec<StringMatchCandidate>>();
19828
19829 let mut matches = fuzzy::match_strings(
19830 &candidates,
19831 &last_word,
19832 last_word.chars().any(|c| c.is_uppercase()),
19833 100,
19834 &Default::default(),
19835 executor.clone(),
19836 )
19837 .await;
19838
19839 // Remove all candidates where the query's start does not match the start of any word in the candidate
19840 if let Some(query_start) = last_word.chars().next() {
19841 matches.retain(|string_match| {
19842 split_words(&string_match.string).any(|word| {
19843 // Check that the first codepoint of the word as lowercase matches the first
19844 // codepoint of the query as lowercase
19845 word.chars()
19846 .flat_map(|codepoint| codepoint.to_lowercase())
19847 .zip(query_start.to_lowercase())
19848 .all(|(word_cp, query_cp)| word_cp == query_cp)
19849 })
19850 });
19851 }
19852
19853 let matched_strings = matches
19854 .into_iter()
19855 .map(|m| m.string)
19856 .collect::<HashSet<_>>();
19857
19858 let mut result: Vec<Completion> = snippets
19859 .iter()
19860 .filter_map(|snippet| {
19861 let matching_prefix = snippet
19862 .prefix
19863 .iter()
19864 .find(|prefix| matched_strings.contains(*prefix))?;
19865 let start = as_offset - last_word.len();
19866 let start = snapshot.anchor_before(start);
19867 let range = start..buffer_position;
19868 let lsp_start = to_lsp(&start);
19869 let lsp_range = lsp::Range {
19870 start: lsp_start,
19871 end: lsp_end,
19872 };
19873 Some(Completion {
19874 replace_range: range,
19875 new_text: snippet.body.clone(),
19876 source: CompletionSource::Lsp {
19877 insert_range: None,
19878 server_id: LanguageServerId(usize::MAX),
19879 resolved: true,
19880 lsp_completion: Box::new(lsp::CompletionItem {
19881 label: snippet.prefix.first().unwrap().clone(),
19882 kind: Some(CompletionItemKind::SNIPPET),
19883 label_details: snippet.description.as_ref().map(|description| {
19884 lsp::CompletionItemLabelDetails {
19885 detail: Some(description.clone()),
19886 description: None,
19887 }
19888 }),
19889 insert_text_format: Some(InsertTextFormat::SNIPPET),
19890 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19891 lsp::InsertReplaceEdit {
19892 new_text: snippet.body.clone(),
19893 insert: lsp_range,
19894 replace: lsp_range,
19895 },
19896 )),
19897 filter_text: Some(snippet.body.clone()),
19898 sort_text: Some(char::MAX.to_string()),
19899 ..lsp::CompletionItem::default()
19900 }),
19901 lsp_defaults: None,
19902 },
19903 label: CodeLabel {
19904 text: matching_prefix.clone(),
19905 runs: Vec::new(),
19906 filter_range: 0..matching_prefix.len(),
19907 },
19908 icon_path: None,
19909 documentation: Some(
19910 CompletionDocumentation::SingleLineAndMultiLinePlainText {
19911 single_line: snippet.name.clone().into(),
19912 plain_text: snippet
19913 .description
19914 .clone()
19915 .map(|description| description.into()),
19916 },
19917 ),
19918 insert_text_mode: None,
19919 confirm: None,
19920 })
19921 })
19922 .collect();
19923
19924 all_results.append(&mut result);
19925 }
19926
19927 Ok(all_results)
19928 })
19929}
19930
19931impl CompletionProvider for Entity<Project> {
19932 fn completions(
19933 &self,
19934 _excerpt_id: ExcerptId,
19935 buffer: &Entity<Buffer>,
19936 buffer_position: text::Anchor,
19937 options: CompletionContext,
19938 _window: &mut Window,
19939 cx: &mut Context<Editor>,
19940 ) -> Task<Result<Option<Vec<Completion>>>> {
19941 self.update(cx, |project, cx| {
19942 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19943 let project_completions = project.completions(buffer, buffer_position, options, cx);
19944 cx.background_spawn(async move {
19945 let snippets_completions = snippets.await?;
19946 match project_completions.await? {
19947 Some(mut completions) => {
19948 completions.extend(snippets_completions);
19949 Ok(Some(completions))
19950 }
19951 None => {
19952 if snippets_completions.is_empty() {
19953 Ok(None)
19954 } else {
19955 Ok(Some(snippets_completions))
19956 }
19957 }
19958 }
19959 })
19960 })
19961 }
19962
19963 fn resolve_completions(
19964 &self,
19965 buffer: Entity<Buffer>,
19966 completion_indices: Vec<usize>,
19967 completions: Rc<RefCell<Box<[Completion]>>>,
19968 cx: &mut Context<Editor>,
19969 ) -> Task<Result<bool>> {
19970 self.update(cx, |project, cx| {
19971 project.lsp_store().update(cx, |lsp_store, cx| {
19972 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19973 })
19974 })
19975 }
19976
19977 fn apply_additional_edits_for_completion(
19978 &self,
19979 buffer: Entity<Buffer>,
19980 completions: Rc<RefCell<Box<[Completion]>>>,
19981 completion_index: usize,
19982 push_to_history: bool,
19983 cx: &mut Context<Editor>,
19984 ) -> Task<Result<Option<language::Transaction>>> {
19985 self.update(cx, |project, cx| {
19986 project.lsp_store().update(cx, |lsp_store, cx| {
19987 lsp_store.apply_additional_edits_for_completion(
19988 buffer,
19989 completions,
19990 completion_index,
19991 push_to_history,
19992 cx,
19993 )
19994 })
19995 })
19996 }
19997
19998 fn is_completion_trigger(
19999 &self,
20000 buffer: &Entity<Buffer>,
20001 position: language::Anchor,
20002 text: &str,
20003 trigger_in_words: bool,
20004 cx: &mut Context<Editor>,
20005 ) -> bool {
20006 let mut chars = text.chars();
20007 let char = if let Some(char) = chars.next() {
20008 char
20009 } else {
20010 return false;
20011 };
20012 if chars.next().is_some() {
20013 return false;
20014 }
20015
20016 let buffer = buffer.read(cx);
20017 let snapshot = buffer.snapshot();
20018 if !snapshot.settings_at(position, cx).show_completions_on_input {
20019 return false;
20020 }
20021 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20022 if trigger_in_words && classifier.is_word(char) {
20023 return true;
20024 }
20025
20026 buffer.completion_triggers().contains(text)
20027 }
20028}
20029
20030impl SemanticsProvider for Entity<Project> {
20031 fn hover(
20032 &self,
20033 buffer: &Entity<Buffer>,
20034 position: text::Anchor,
20035 cx: &mut App,
20036 ) -> Option<Task<Vec<project::Hover>>> {
20037 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20038 }
20039
20040 fn document_highlights(
20041 &self,
20042 buffer: &Entity<Buffer>,
20043 position: text::Anchor,
20044 cx: &mut App,
20045 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20046 Some(self.update(cx, |project, cx| {
20047 project.document_highlights(buffer, position, cx)
20048 }))
20049 }
20050
20051 fn definitions(
20052 &self,
20053 buffer: &Entity<Buffer>,
20054 position: text::Anchor,
20055 kind: GotoDefinitionKind,
20056 cx: &mut App,
20057 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20058 Some(self.update(cx, |project, cx| match kind {
20059 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20060 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20061 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20062 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20063 }))
20064 }
20065
20066 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20067 // TODO: make this work for remote projects
20068 self.update(cx, |project, cx| {
20069 if project
20070 .active_debug_session(cx)
20071 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20072 {
20073 return true;
20074 }
20075
20076 buffer.update(cx, |buffer, cx| {
20077 project.any_language_server_supports_inlay_hints(buffer, cx)
20078 })
20079 })
20080 }
20081
20082 fn inline_values(
20083 &self,
20084 buffer_handle: Entity<Buffer>,
20085
20086 range: Range<text::Anchor>,
20087 cx: &mut App,
20088 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20089 self.update(cx, |project, cx| {
20090 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20091
20092 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20093 })
20094 }
20095
20096 fn inlay_hints(
20097 &self,
20098 buffer_handle: Entity<Buffer>,
20099 range: Range<text::Anchor>,
20100 cx: &mut App,
20101 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20102 Some(self.update(cx, |project, cx| {
20103 project.inlay_hints(buffer_handle, range, cx)
20104 }))
20105 }
20106
20107 fn resolve_inlay_hint(
20108 &self,
20109 hint: InlayHint,
20110 buffer_handle: Entity<Buffer>,
20111 server_id: LanguageServerId,
20112 cx: &mut App,
20113 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20114 Some(self.update(cx, |project, cx| {
20115 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20116 }))
20117 }
20118
20119 fn range_for_rename(
20120 &self,
20121 buffer: &Entity<Buffer>,
20122 position: text::Anchor,
20123 cx: &mut App,
20124 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20125 Some(self.update(cx, |project, cx| {
20126 let buffer = buffer.clone();
20127 let task = project.prepare_rename(buffer.clone(), position, cx);
20128 cx.spawn(async move |_, cx| {
20129 Ok(match task.await? {
20130 PrepareRenameResponse::Success(range) => Some(range),
20131 PrepareRenameResponse::InvalidPosition => None,
20132 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20133 // Fallback on using TreeSitter info to determine identifier range
20134 buffer.read_with(cx, |buffer, _| {
20135 let snapshot = buffer.snapshot();
20136 let (range, kind) = snapshot.surrounding_word(position);
20137 if kind != Some(CharKind::Word) {
20138 return None;
20139 }
20140 Some(
20141 snapshot.anchor_before(range.start)
20142 ..snapshot.anchor_after(range.end),
20143 )
20144 })?
20145 }
20146 })
20147 })
20148 }))
20149 }
20150
20151 fn perform_rename(
20152 &self,
20153 buffer: &Entity<Buffer>,
20154 position: text::Anchor,
20155 new_name: String,
20156 cx: &mut App,
20157 ) -> Option<Task<Result<ProjectTransaction>>> {
20158 Some(self.update(cx, |project, cx| {
20159 project.perform_rename(buffer.clone(), position, new_name, cx)
20160 }))
20161 }
20162}
20163
20164fn inlay_hint_settings(
20165 location: Anchor,
20166 snapshot: &MultiBufferSnapshot,
20167 cx: &mut Context<Editor>,
20168) -> InlayHintSettings {
20169 let file = snapshot.file_at(location);
20170 let language = snapshot.language_at(location).map(|l| l.name());
20171 language_settings(language, file, cx).inlay_hints
20172}
20173
20174fn consume_contiguous_rows(
20175 contiguous_row_selections: &mut Vec<Selection<Point>>,
20176 selection: &Selection<Point>,
20177 display_map: &DisplaySnapshot,
20178 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20179) -> (MultiBufferRow, MultiBufferRow) {
20180 contiguous_row_selections.push(selection.clone());
20181 let start_row = MultiBufferRow(selection.start.row);
20182 let mut end_row = ending_row(selection, display_map);
20183
20184 while let Some(next_selection) = selections.peek() {
20185 if next_selection.start.row <= end_row.0 {
20186 end_row = ending_row(next_selection, display_map);
20187 contiguous_row_selections.push(selections.next().unwrap().clone());
20188 } else {
20189 break;
20190 }
20191 }
20192 (start_row, end_row)
20193}
20194
20195fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20196 if next_selection.end.column > 0 || next_selection.is_empty() {
20197 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20198 } else {
20199 MultiBufferRow(next_selection.end.row)
20200 }
20201}
20202
20203impl EditorSnapshot {
20204 pub fn remote_selections_in_range<'a>(
20205 &'a self,
20206 range: &'a Range<Anchor>,
20207 collaboration_hub: &dyn CollaborationHub,
20208 cx: &'a App,
20209 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20210 let participant_names = collaboration_hub.user_names(cx);
20211 let participant_indices = collaboration_hub.user_participant_indices(cx);
20212 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20213 let collaborators_by_replica_id = collaborators_by_peer_id
20214 .values()
20215 .map(|collaborator| (collaborator.replica_id, collaborator))
20216 .collect::<HashMap<_, _>>();
20217 self.buffer_snapshot
20218 .selections_in_range(range, false)
20219 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20220 if replica_id == AGENT_REPLICA_ID {
20221 Some(RemoteSelection {
20222 replica_id,
20223 selection,
20224 cursor_shape,
20225 line_mode,
20226 collaborator_id: CollaboratorId::Agent,
20227 user_name: Some("Agent".into()),
20228 color: cx.theme().players().agent(),
20229 })
20230 } else {
20231 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20232 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20233 let user_name = participant_names.get(&collaborator.user_id).cloned();
20234 Some(RemoteSelection {
20235 replica_id,
20236 selection,
20237 cursor_shape,
20238 line_mode,
20239 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20240 user_name,
20241 color: if let Some(index) = participant_index {
20242 cx.theme().players().color_for_participant(index.0)
20243 } else {
20244 cx.theme().players().absent()
20245 },
20246 })
20247 }
20248 })
20249 }
20250
20251 pub fn hunks_for_ranges(
20252 &self,
20253 ranges: impl IntoIterator<Item = Range<Point>>,
20254 ) -> Vec<MultiBufferDiffHunk> {
20255 let mut hunks = Vec::new();
20256 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20257 HashMap::default();
20258 for query_range in ranges {
20259 let query_rows =
20260 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20261 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20262 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20263 ) {
20264 // Include deleted hunks that are adjacent to the query range, because
20265 // otherwise they would be missed.
20266 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20267 if hunk.status().is_deleted() {
20268 intersects_range |= hunk.row_range.start == query_rows.end;
20269 intersects_range |= hunk.row_range.end == query_rows.start;
20270 }
20271 if intersects_range {
20272 if !processed_buffer_rows
20273 .entry(hunk.buffer_id)
20274 .or_default()
20275 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20276 {
20277 continue;
20278 }
20279 hunks.push(hunk);
20280 }
20281 }
20282 }
20283
20284 hunks
20285 }
20286
20287 fn display_diff_hunks_for_rows<'a>(
20288 &'a self,
20289 display_rows: Range<DisplayRow>,
20290 folded_buffers: &'a HashSet<BufferId>,
20291 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20292 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20293 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20294
20295 self.buffer_snapshot
20296 .diff_hunks_in_range(buffer_start..buffer_end)
20297 .filter_map(|hunk| {
20298 if folded_buffers.contains(&hunk.buffer_id) {
20299 return None;
20300 }
20301
20302 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20303 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20304
20305 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20306 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20307
20308 let display_hunk = if hunk_display_start.column() != 0 {
20309 DisplayDiffHunk::Folded {
20310 display_row: hunk_display_start.row(),
20311 }
20312 } else {
20313 let mut end_row = hunk_display_end.row();
20314 if hunk_display_end.column() > 0 {
20315 end_row.0 += 1;
20316 }
20317 let is_created_file = hunk.is_created_file();
20318 DisplayDiffHunk::Unfolded {
20319 status: hunk.status(),
20320 diff_base_byte_range: hunk.diff_base_byte_range,
20321 display_row_range: hunk_display_start.row()..end_row,
20322 multi_buffer_range: Anchor::range_in_buffer(
20323 hunk.excerpt_id,
20324 hunk.buffer_id,
20325 hunk.buffer_range,
20326 ),
20327 is_created_file,
20328 }
20329 };
20330
20331 Some(display_hunk)
20332 })
20333 }
20334
20335 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20336 self.display_snapshot.buffer_snapshot.language_at(position)
20337 }
20338
20339 pub fn is_focused(&self) -> bool {
20340 self.is_focused
20341 }
20342
20343 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20344 self.placeholder_text.as_ref()
20345 }
20346
20347 pub fn scroll_position(&self) -> gpui::Point<f32> {
20348 self.scroll_anchor.scroll_position(&self.display_snapshot)
20349 }
20350
20351 fn gutter_dimensions(
20352 &self,
20353 font_id: FontId,
20354 font_size: Pixels,
20355 max_line_number_width: Pixels,
20356 cx: &App,
20357 ) -> Option<GutterDimensions> {
20358 if !self.show_gutter {
20359 return None;
20360 }
20361
20362 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20363 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20364
20365 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20366 matches!(
20367 ProjectSettings::get_global(cx).git.git_gutter,
20368 Some(GitGutterSetting::TrackedFiles)
20369 )
20370 });
20371 let gutter_settings = EditorSettings::get_global(cx).gutter;
20372 let show_line_numbers = self
20373 .show_line_numbers
20374 .unwrap_or(gutter_settings.line_numbers);
20375 let line_gutter_width = if show_line_numbers {
20376 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20377 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20378 max_line_number_width.max(min_width_for_number_on_gutter)
20379 } else {
20380 0.0.into()
20381 };
20382
20383 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20384 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20385
20386 let git_blame_entries_width =
20387 self.git_blame_gutter_max_author_length
20388 .map(|max_author_length| {
20389 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20390 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20391
20392 /// The number of characters to dedicate to gaps and margins.
20393 const SPACING_WIDTH: usize = 4;
20394
20395 let max_char_count = max_author_length.min(renderer.max_author_length())
20396 + ::git::SHORT_SHA_LENGTH
20397 + MAX_RELATIVE_TIMESTAMP.len()
20398 + SPACING_WIDTH;
20399
20400 em_advance * max_char_count
20401 });
20402
20403 let is_singleton = self.buffer_snapshot.is_singleton();
20404
20405 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20406 left_padding += if !is_singleton {
20407 em_width * 4.0
20408 } else if show_runnables || show_breakpoints {
20409 em_width * 3.0
20410 } else if show_git_gutter && show_line_numbers {
20411 em_width * 2.0
20412 } else if show_git_gutter || show_line_numbers {
20413 em_width
20414 } else {
20415 px(0.)
20416 };
20417
20418 let shows_folds = is_singleton && gutter_settings.folds;
20419
20420 let right_padding = if shows_folds && show_line_numbers {
20421 em_width * 4.0
20422 } else if shows_folds || (!is_singleton && show_line_numbers) {
20423 em_width * 3.0
20424 } else if show_line_numbers {
20425 em_width
20426 } else {
20427 px(0.)
20428 };
20429
20430 Some(GutterDimensions {
20431 left_padding,
20432 right_padding,
20433 width: line_gutter_width + left_padding + right_padding,
20434 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20435 git_blame_entries_width,
20436 })
20437 }
20438
20439 pub fn render_crease_toggle(
20440 &self,
20441 buffer_row: MultiBufferRow,
20442 row_contains_cursor: bool,
20443 editor: Entity<Editor>,
20444 window: &mut Window,
20445 cx: &mut App,
20446 ) -> Option<AnyElement> {
20447 let folded = self.is_line_folded(buffer_row);
20448 let mut is_foldable = false;
20449
20450 if let Some(crease) = self
20451 .crease_snapshot
20452 .query_row(buffer_row, &self.buffer_snapshot)
20453 {
20454 is_foldable = true;
20455 match crease {
20456 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20457 if let Some(render_toggle) = render_toggle {
20458 let toggle_callback =
20459 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20460 if folded {
20461 editor.update(cx, |editor, cx| {
20462 editor.fold_at(buffer_row, window, cx)
20463 });
20464 } else {
20465 editor.update(cx, |editor, cx| {
20466 editor.unfold_at(buffer_row, window, cx)
20467 });
20468 }
20469 });
20470 return Some((render_toggle)(
20471 buffer_row,
20472 folded,
20473 toggle_callback,
20474 window,
20475 cx,
20476 ));
20477 }
20478 }
20479 }
20480 }
20481
20482 is_foldable |= self.starts_indent(buffer_row);
20483
20484 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20485 Some(
20486 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20487 .toggle_state(folded)
20488 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20489 if folded {
20490 this.unfold_at(buffer_row, window, cx);
20491 } else {
20492 this.fold_at(buffer_row, window, cx);
20493 }
20494 }))
20495 .into_any_element(),
20496 )
20497 } else {
20498 None
20499 }
20500 }
20501
20502 pub fn render_crease_trailer(
20503 &self,
20504 buffer_row: MultiBufferRow,
20505 window: &mut Window,
20506 cx: &mut App,
20507 ) -> Option<AnyElement> {
20508 let folded = self.is_line_folded(buffer_row);
20509 if let Crease::Inline { render_trailer, .. } = self
20510 .crease_snapshot
20511 .query_row(buffer_row, &self.buffer_snapshot)?
20512 {
20513 let render_trailer = render_trailer.as_ref()?;
20514 Some(render_trailer(buffer_row, folded, window, cx))
20515 } else {
20516 None
20517 }
20518 }
20519}
20520
20521impl Deref for EditorSnapshot {
20522 type Target = DisplaySnapshot;
20523
20524 fn deref(&self) -> &Self::Target {
20525 &self.display_snapshot
20526 }
20527}
20528
20529#[derive(Clone, Debug, PartialEq, Eq)]
20530pub enum EditorEvent {
20531 InputIgnored {
20532 text: Arc<str>,
20533 },
20534 InputHandled {
20535 utf16_range_to_replace: Option<Range<isize>>,
20536 text: Arc<str>,
20537 },
20538 ExcerptsAdded {
20539 buffer: Entity<Buffer>,
20540 predecessor: ExcerptId,
20541 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20542 },
20543 ExcerptsRemoved {
20544 ids: Vec<ExcerptId>,
20545 removed_buffer_ids: Vec<BufferId>,
20546 },
20547 BufferFoldToggled {
20548 ids: Vec<ExcerptId>,
20549 folded: bool,
20550 },
20551 ExcerptsEdited {
20552 ids: Vec<ExcerptId>,
20553 },
20554 ExcerptsExpanded {
20555 ids: Vec<ExcerptId>,
20556 },
20557 BufferEdited,
20558 Edited {
20559 transaction_id: clock::Lamport,
20560 },
20561 Reparsed(BufferId),
20562 Focused,
20563 FocusedIn,
20564 Blurred,
20565 DirtyChanged,
20566 Saved,
20567 TitleChanged,
20568 DiffBaseChanged,
20569 SelectionsChanged {
20570 local: bool,
20571 },
20572 ScrollPositionChanged {
20573 local: bool,
20574 autoscroll: bool,
20575 },
20576 Closed,
20577 TransactionUndone {
20578 transaction_id: clock::Lamport,
20579 },
20580 TransactionBegun {
20581 transaction_id: clock::Lamport,
20582 },
20583 Reloaded,
20584 CursorShapeChanged,
20585 PushedToNavHistory {
20586 anchor: Anchor,
20587 is_deactivate: bool,
20588 },
20589}
20590
20591impl EventEmitter<EditorEvent> for Editor {}
20592
20593impl Focusable for Editor {
20594 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20595 self.focus_handle.clone()
20596 }
20597}
20598
20599impl Render for Editor {
20600 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20601 let settings = ThemeSettings::get_global(cx);
20602
20603 let mut text_style = match self.mode {
20604 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20605 color: cx.theme().colors().editor_foreground,
20606 font_family: settings.ui_font.family.clone(),
20607 font_features: settings.ui_font.features.clone(),
20608 font_fallbacks: settings.ui_font.fallbacks.clone(),
20609 font_size: rems(0.875).into(),
20610 font_weight: settings.ui_font.weight,
20611 line_height: relative(settings.buffer_line_height.value()),
20612 ..Default::default()
20613 },
20614 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20615 color: cx.theme().colors().editor_foreground,
20616 font_family: settings.buffer_font.family.clone(),
20617 font_features: settings.buffer_font.features.clone(),
20618 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20619 font_size: settings.buffer_font_size(cx).into(),
20620 font_weight: settings.buffer_font.weight,
20621 line_height: relative(settings.buffer_line_height.value()),
20622 ..Default::default()
20623 },
20624 };
20625 if let Some(text_style_refinement) = &self.text_style_refinement {
20626 text_style.refine(text_style_refinement)
20627 }
20628
20629 let background = match self.mode {
20630 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20631 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20632 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20633 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20634 };
20635
20636 EditorElement::new(
20637 &cx.entity(),
20638 EditorStyle {
20639 background,
20640 local_player: cx.theme().players().local(),
20641 text: text_style,
20642 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20643 syntax: cx.theme().syntax().clone(),
20644 status: cx.theme().status().clone(),
20645 inlay_hints_style: make_inlay_hints_style(cx),
20646 inline_completion_styles: make_suggestion_styles(cx),
20647 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20648 show_underlines: !self.mode.is_minimap(),
20649 },
20650 )
20651 }
20652}
20653
20654impl EntityInputHandler for Editor {
20655 fn text_for_range(
20656 &mut self,
20657 range_utf16: Range<usize>,
20658 adjusted_range: &mut Option<Range<usize>>,
20659 _: &mut Window,
20660 cx: &mut Context<Self>,
20661 ) -> Option<String> {
20662 let snapshot = self.buffer.read(cx).read(cx);
20663 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20664 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20665 if (start.0..end.0) != range_utf16 {
20666 adjusted_range.replace(start.0..end.0);
20667 }
20668 Some(snapshot.text_for_range(start..end).collect())
20669 }
20670
20671 fn selected_text_range(
20672 &mut self,
20673 ignore_disabled_input: bool,
20674 _: &mut Window,
20675 cx: &mut Context<Self>,
20676 ) -> Option<UTF16Selection> {
20677 // Prevent the IME menu from appearing when holding down an alphabetic key
20678 // while input is disabled.
20679 if !ignore_disabled_input && !self.input_enabled {
20680 return None;
20681 }
20682
20683 let selection = self.selections.newest::<OffsetUtf16>(cx);
20684 let range = selection.range();
20685
20686 Some(UTF16Selection {
20687 range: range.start.0..range.end.0,
20688 reversed: selection.reversed,
20689 })
20690 }
20691
20692 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20693 let snapshot = self.buffer.read(cx).read(cx);
20694 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20695 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20696 }
20697
20698 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20699 self.clear_highlights::<InputComposition>(cx);
20700 self.ime_transaction.take();
20701 }
20702
20703 fn replace_text_in_range(
20704 &mut self,
20705 range_utf16: Option<Range<usize>>,
20706 text: &str,
20707 window: &mut Window,
20708 cx: &mut Context<Self>,
20709 ) {
20710 if !self.input_enabled {
20711 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20712 return;
20713 }
20714
20715 self.transact(window, cx, |this, window, cx| {
20716 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20717 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20718 Some(this.selection_replacement_ranges(range_utf16, cx))
20719 } else {
20720 this.marked_text_ranges(cx)
20721 };
20722
20723 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20724 let newest_selection_id = this.selections.newest_anchor().id;
20725 this.selections
20726 .all::<OffsetUtf16>(cx)
20727 .iter()
20728 .zip(ranges_to_replace.iter())
20729 .find_map(|(selection, range)| {
20730 if selection.id == newest_selection_id {
20731 Some(
20732 (range.start.0 as isize - selection.head().0 as isize)
20733 ..(range.end.0 as isize - selection.head().0 as isize),
20734 )
20735 } else {
20736 None
20737 }
20738 })
20739 });
20740
20741 cx.emit(EditorEvent::InputHandled {
20742 utf16_range_to_replace: range_to_replace,
20743 text: text.into(),
20744 });
20745
20746 if let Some(new_selected_ranges) = new_selected_ranges {
20747 this.change_selections(None, window, cx, |selections| {
20748 selections.select_ranges(new_selected_ranges)
20749 });
20750 this.backspace(&Default::default(), window, cx);
20751 }
20752
20753 this.handle_input(text, window, cx);
20754 });
20755
20756 if let Some(transaction) = self.ime_transaction {
20757 self.buffer.update(cx, |buffer, cx| {
20758 buffer.group_until_transaction(transaction, cx);
20759 });
20760 }
20761
20762 self.unmark_text(window, cx);
20763 }
20764
20765 fn replace_and_mark_text_in_range(
20766 &mut self,
20767 range_utf16: Option<Range<usize>>,
20768 text: &str,
20769 new_selected_range_utf16: Option<Range<usize>>,
20770 window: &mut Window,
20771 cx: &mut Context<Self>,
20772 ) {
20773 if !self.input_enabled {
20774 return;
20775 }
20776
20777 let transaction = self.transact(window, cx, |this, window, cx| {
20778 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20779 let snapshot = this.buffer.read(cx).read(cx);
20780 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20781 for marked_range in &mut marked_ranges {
20782 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20783 marked_range.start.0 += relative_range_utf16.start;
20784 marked_range.start =
20785 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20786 marked_range.end =
20787 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20788 }
20789 }
20790 Some(marked_ranges)
20791 } else if let Some(range_utf16) = range_utf16 {
20792 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20793 Some(this.selection_replacement_ranges(range_utf16, cx))
20794 } else {
20795 None
20796 };
20797
20798 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20799 let newest_selection_id = this.selections.newest_anchor().id;
20800 this.selections
20801 .all::<OffsetUtf16>(cx)
20802 .iter()
20803 .zip(ranges_to_replace.iter())
20804 .find_map(|(selection, range)| {
20805 if selection.id == newest_selection_id {
20806 Some(
20807 (range.start.0 as isize - selection.head().0 as isize)
20808 ..(range.end.0 as isize - selection.head().0 as isize),
20809 )
20810 } else {
20811 None
20812 }
20813 })
20814 });
20815
20816 cx.emit(EditorEvent::InputHandled {
20817 utf16_range_to_replace: range_to_replace,
20818 text: text.into(),
20819 });
20820
20821 if let Some(ranges) = ranges_to_replace {
20822 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20823 }
20824
20825 let marked_ranges = {
20826 let snapshot = this.buffer.read(cx).read(cx);
20827 this.selections
20828 .disjoint_anchors()
20829 .iter()
20830 .map(|selection| {
20831 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20832 })
20833 .collect::<Vec<_>>()
20834 };
20835
20836 if text.is_empty() {
20837 this.unmark_text(window, cx);
20838 } else {
20839 this.highlight_text::<InputComposition>(
20840 marked_ranges.clone(),
20841 HighlightStyle {
20842 underline: Some(UnderlineStyle {
20843 thickness: px(1.),
20844 color: None,
20845 wavy: false,
20846 }),
20847 ..Default::default()
20848 },
20849 cx,
20850 );
20851 }
20852
20853 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20854 let use_autoclose = this.use_autoclose;
20855 let use_auto_surround = this.use_auto_surround;
20856 this.set_use_autoclose(false);
20857 this.set_use_auto_surround(false);
20858 this.handle_input(text, window, cx);
20859 this.set_use_autoclose(use_autoclose);
20860 this.set_use_auto_surround(use_auto_surround);
20861
20862 if let Some(new_selected_range) = new_selected_range_utf16 {
20863 let snapshot = this.buffer.read(cx).read(cx);
20864 let new_selected_ranges = marked_ranges
20865 .into_iter()
20866 .map(|marked_range| {
20867 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20868 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20869 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20870 snapshot.clip_offset_utf16(new_start, Bias::Left)
20871 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20872 })
20873 .collect::<Vec<_>>();
20874
20875 drop(snapshot);
20876 this.change_selections(None, window, cx, |selections| {
20877 selections.select_ranges(new_selected_ranges)
20878 });
20879 }
20880 });
20881
20882 self.ime_transaction = self.ime_transaction.or(transaction);
20883 if let Some(transaction) = self.ime_transaction {
20884 self.buffer.update(cx, |buffer, cx| {
20885 buffer.group_until_transaction(transaction, cx);
20886 });
20887 }
20888
20889 if self.text_highlights::<InputComposition>(cx).is_none() {
20890 self.ime_transaction.take();
20891 }
20892 }
20893
20894 fn bounds_for_range(
20895 &mut self,
20896 range_utf16: Range<usize>,
20897 element_bounds: gpui::Bounds<Pixels>,
20898 window: &mut Window,
20899 cx: &mut Context<Self>,
20900 ) -> Option<gpui::Bounds<Pixels>> {
20901 let text_layout_details = self.text_layout_details(window);
20902 let gpui::Size {
20903 width: em_width,
20904 height: line_height,
20905 } = self.character_size(window);
20906
20907 let snapshot = self.snapshot(window, cx);
20908 let scroll_position = snapshot.scroll_position();
20909 let scroll_left = scroll_position.x * em_width;
20910
20911 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20912 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20913 + self.gutter_dimensions.width
20914 + self.gutter_dimensions.margin;
20915 let y = line_height * (start.row().as_f32() - scroll_position.y);
20916
20917 Some(Bounds {
20918 origin: element_bounds.origin + point(x, y),
20919 size: size(em_width, line_height),
20920 })
20921 }
20922
20923 fn character_index_for_point(
20924 &mut self,
20925 point: gpui::Point<Pixels>,
20926 _window: &mut Window,
20927 _cx: &mut Context<Self>,
20928 ) -> Option<usize> {
20929 let position_map = self.last_position_map.as_ref()?;
20930 if !position_map.text_hitbox.contains(&point) {
20931 return None;
20932 }
20933 let display_point = position_map.point_for_position(point).previous_valid;
20934 let anchor = position_map
20935 .snapshot
20936 .display_point_to_anchor(display_point, Bias::Left);
20937 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20938 Some(utf16_offset.0)
20939 }
20940}
20941
20942trait SelectionExt {
20943 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20944 fn spanned_rows(
20945 &self,
20946 include_end_if_at_line_start: bool,
20947 map: &DisplaySnapshot,
20948 ) -> Range<MultiBufferRow>;
20949}
20950
20951impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20952 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20953 let start = self
20954 .start
20955 .to_point(&map.buffer_snapshot)
20956 .to_display_point(map);
20957 let end = self
20958 .end
20959 .to_point(&map.buffer_snapshot)
20960 .to_display_point(map);
20961 if self.reversed {
20962 end..start
20963 } else {
20964 start..end
20965 }
20966 }
20967
20968 fn spanned_rows(
20969 &self,
20970 include_end_if_at_line_start: bool,
20971 map: &DisplaySnapshot,
20972 ) -> Range<MultiBufferRow> {
20973 let start = self.start.to_point(&map.buffer_snapshot);
20974 let mut end = self.end.to_point(&map.buffer_snapshot);
20975 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20976 end.row -= 1;
20977 }
20978
20979 let buffer_start = map.prev_line_boundary(start).0;
20980 let buffer_end = map.next_line_boundary(end).0;
20981 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20982 }
20983}
20984
20985impl<T: InvalidationRegion> InvalidationStack<T> {
20986 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20987 where
20988 S: Clone + ToOffset,
20989 {
20990 while let Some(region) = self.last() {
20991 let all_selections_inside_invalidation_ranges =
20992 if selections.len() == region.ranges().len() {
20993 selections
20994 .iter()
20995 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20996 .all(|(selection, invalidation_range)| {
20997 let head = selection.head().to_offset(buffer);
20998 invalidation_range.start <= head && invalidation_range.end >= head
20999 })
21000 } else {
21001 false
21002 };
21003
21004 if all_selections_inside_invalidation_ranges {
21005 break;
21006 } else {
21007 self.pop();
21008 }
21009 }
21010 }
21011}
21012
21013impl<T> Default for InvalidationStack<T> {
21014 fn default() -> Self {
21015 Self(Default::default())
21016 }
21017}
21018
21019impl<T> Deref for InvalidationStack<T> {
21020 type Target = Vec<T>;
21021
21022 fn deref(&self) -> &Self::Target {
21023 &self.0
21024 }
21025}
21026
21027impl<T> DerefMut for InvalidationStack<T> {
21028 fn deref_mut(&mut self) -> &mut Self::Target {
21029 &mut self.0
21030 }
21031}
21032
21033impl InvalidationRegion for SnippetState {
21034 fn ranges(&self) -> &[Range<Anchor>] {
21035 &self.ranges[self.active_index]
21036 }
21037}
21038
21039fn inline_completion_edit_text(
21040 current_snapshot: &BufferSnapshot,
21041 edits: &[(Range<Anchor>, String)],
21042 edit_preview: &EditPreview,
21043 include_deletions: bool,
21044 cx: &App,
21045) -> HighlightedText {
21046 let edits = edits
21047 .iter()
21048 .map(|(anchor, text)| {
21049 (
21050 anchor.start.text_anchor..anchor.end.text_anchor,
21051 text.clone(),
21052 )
21053 })
21054 .collect::<Vec<_>>();
21055
21056 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21057}
21058
21059pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21060 match severity {
21061 lsp::DiagnosticSeverity::ERROR => colors.error,
21062 lsp::DiagnosticSeverity::WARNING => colors.warning,
21063 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21064 lsp::DiagnosticSeverity::HINT => colors.info,
21065 _ => colors.ignored,
21066 }
21067}
21068
21069pub fn styled_runs_for_code_label<'a>(
21070 label: &'a CodeLabel,
21071 syntax_theme: &'a theme::SyntaxTheme,
21072) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21073 let fade_out = HighlightStyle {
21074 fade_out: Some(0.35),
21075 ..Default::default()
21076 };
21077
21078 let mut prev_end = label.filter_range.end;
21079 label
21080 .runs
21081 .iter()
21082 .enumerate()
21083 .flat_map(move |(ix, (range, highlight_id))| {
21084 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21085 style
21086 } else {
21087 return Default::default();
21088 };
21089 let mut muted_style = style;
21090 muted_style.highlight(fade_out);
21091
21092 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21093 if range.start >= label.filter_range.end {
21094 if range.start > prev_end {
21095 runs.push((prev_end..range.start, fade_out));
21096 }
21097 runs.push((range.clone(), muted_style));
21098 } else if range.end <= label.filter_range.end {
21099 runs.push((range.clone(), style));
21100 } else {
21101 runs.push((range.start..label.filter_range.end, style));
21102 runs.push((label.filter_range.end..range.end, muted_style));
21103 }
21104 prev_end = cmp::max(prev_end, range.end);
21105
21106 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21107 runs.push((prev_end..label.text.len(), fade_out));
21108 }
21109
21110 runs
21111 })
21112}
21113
21114pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21115 let mut prev_index = 0;
21116 let mut prev_codepoint: Option<char> = None;
21117 text.char_indices()
21118 .chain([(text.len(), '\0')])
21119 .filter_map(move |(index, codepoint)| {
21120 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21121 let is_boundary = index == text.len()
21122 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21123 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21124 if is_boundary {
21125 let chunk = &text[prev_index..index];
21126 prev_index = index;
21127 Some(chunk)
21128 } else {
21129 None
21130 }
21131 })
21132}
21133
21134pub trait RangeToAnchorExt: Sized {
21135 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21136
21137 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21138 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21139 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21140 }
21141}
21142
21143impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21144 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21145 let start_offset = self.start.to_offset(snapshot);
21146 let end_offset = self.end.to_offset(snapshot);
21147 if start_offset == end_offset {
21148 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21149 } else {
21150 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21151 }
21152 }
21153}
21154
21155pub trait RowExt {
21156 fn as_f32(&self) -> f32;
21157
21158 fn next_row(&self) -> Self;
21159
21160 fn previous_row(&self) -> Self;
21161
21162 fn minus(&self, other: Self) -> u32;
21163}
21164
21165impl RowExt for DisplayRow {
21166 fn as_f32(&self) -> f32 {
21167 self.0 as f32
21168 }
21169
21170 fn next_row(&self) -> Self {
21171 Self(self.0 + 1)
21172 }
21173
21174 fn previous_row(&self) -> Self {
21175 Self(self.0.saturating_sub(1))
21176 }
21177
21178 fn minus(&self, other: Self) -> u32 {
21179 self.0 - other.0
21180 }
21181}
21182
21183impl RowExt for MultiBufferRow {
21184 fn as_f32(&self) -> f32 {
21185 self.0 as f32
21186 }
21187
21188 fn next_row(&self) -> Self {
21189 Self(self.0 + 1)
21190 }
21191
21192 fn previous_row(&self) -> Self {
21193 Self(self.0.saturating_sub(1))
21194 }
21195
21196 fn minus(&self, other: Self) -> u32 {
21197 self.0 - other.0
21198 }
21199}
21200
21201trait RowRangeExt {
21202 type Row;
21203
21204 fn len(&self) -> usize;
21205
21206 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21207}
21208
21209impl RowRangeExt for Range<MultiBufferRow> {
21210 type Row = MultiBufferRow;
21211
21212 fn len(&self) -> usize {
21213 (self.end.0 - self.start.0) as usize
21214 }
21215
21216 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21217 (self.start.0..self.end.0).map(MultiBufferRow)
21218 }
21219}
21220
21221impl RowRangeExt for Range<DisplayRow> {
21222 type Row = DisplayRow;
21223
21224 fn len(&self) -> usize {
21225 (self.end.0 - self.start.0) as usize
21226 }
21227
21228 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21229 (self.start.0..self.end.0).map(DisplayRow)
21230 }
21231}
21232
21233/// If select range has more than one line, we
21234/// just point the cursor to range.start.
21235fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21236 if range.start.row == range.end.row {
21237 range
21238 } else {
21239 range.start..range.start
21240 }
21241}
21242pub struct KillRing(ClipboardItem);
21243impl Global for KillRing {}
21244
21245const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21246
21247enum BreakpointPromptEditAction {
21248 Log,
21249 Condition,
21250 HitCondition,
21251}
21252
21253struct BreakpointPromptEditor {
21254 pub(crate) prompt: Entity<Editor>,
21255 editor: WeakEntity<Editor>,
21256 breakpoint_anchor: Anchor,
21257 breakpoint: Breakpoint,
21258 edit_action: BreakpointPromptEditAction,
21259 block_ids: HashSet<CustomBlockId>,
21260 editor_margins: Arc<Mutex<EditorMargins>>,
21261 _subscriptions: Vec<Subscription>,
21262}
21263
21264impl BreakpointPromptEditor {
21265 const MAX_LINES: u8 = 4;
21266
21267 fn new(
21268 editor: WeakEntity<Editor>,
21269 breakpoint_anchor: Anchor,
21270 breakpoint: Breakpoint,
21271 edit_action: BreakpointPromptEditAction,
21272 window: &mut Window,
21273 cx: &mut Context<Self>,
21274 ) -> Self {
21275 let base_text = match edit_action {
21276 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21277 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21278 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21279 }
21280 .map(|msg| msg.to_string())
21281 .unwrap_or_default();
21282
21283 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21284 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21285
21286 let prompt = cx.new(|cx| {
21287 let mut prompt = Editor::new(
21288 EditorMode::AutoHeight {
21289 max_lines: Self::MAX_LINES as usize,
21290 },
21291 buffer,
21292 None,
21293 window,
21294 cx,
21295 );
21296 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21297 prompt.set_show_cursor_when_unfocused(false, cx);
21298 prompt.set_placeholder_text(
21299 match edit_action {
21300 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21301 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21302 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21303 },
21304 cx,
21305 );
21306
21307 prompt
21308 });
21309
21310 Self {
21311 prompt,
21312 editor,
21313 breakpoint_anchor,
21314 breakpoint,
21315 edit_action,
21316 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21317 block_ids: Default::default(),
21318 _subscriptions: vec![],
21319 }
21320 }
21321
21322 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21323 self.block_ids.extend(block_ids)
21324 }
21325
21326 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21327 if let Some(editor) = self.editor.upgrade() {
21328 let message = self
21329 .prompt
21330 .read(cx)
21331 .buffer
21332 .read(cx)
21333 .as_singleton()
21334 .expect("A multi buffer in breakpoint prompt isn't possible")
21335 .read(cx)
21336 .as_rope()
21337 .to_string();
21338
21339 editor.update(cx, |editor, cx| {
21340 editor.edit_breakpoint_at_anchor(
21341 self.breakpoint_anchor,
21342 self.breakpoint.clone(),
21343 match self.edit_action {
21344 BreakpointPromptEditAction::Log => {
21345 BreakpointEditAction::EditLogMessage(message.into())
21346 }
21347 BreakpointPromptEditAction::Condition => {
21348 BreakpointEditAction::EditCondition(message.into())
21349 }
21350 BreakpointPromptEditAction::HitCondition => {
21351 BreakpointEditAction::EditHitCondition(message.into())
21352 }
21353 },
21354 cx,
21355 );
21356
21357 editor.remove_blocks(self.block_ids.clone(), None, cx);
21358 cx.focus_self(window);
21359 });
21360 }
21361 }
21362
21363 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21364 self.editor
21365 .update(cx, |editor, cx| {
21366 editor.remove_blocks(self.block_ids.clone(), None, cx);
21367 window.focus(&editor.focus_handle);
21368 })
21369 .log_err();
21370 }
21371
21372 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21373 let settings = ThemeSettings::get_global(cx);
21374 let text_style = TextStyle {
21375 color: if self.prompt.read(cx).read_only(cx) {
21376 cx.theme().colors().text_disabled
21377 } else {
21378 cx.theme().colors().text
21379 },
21380 font_family: settings.buffer_font.family.clone(),
21381 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21382 font_size: settings.buffer_font_size(cx).into(),
21383 font_weight: settings.buffer_font.weight,
21384 line_height: relative(settings.buffer_line_height.value()),
21385 ..Default::default()
21386 };
21387 EditorElement::new(
21388 &self.prompt,
21389 EditorStyle {
21390 background: cx.theme().colors().editor_background,
21391 local_player: cx.theme().players().local(),
21392 text: text_style,
21393 ..Default::default()
21394 },
21395 )
21396 }
21397}
21398
21399impl Render for BreakpointPromptEditor {
21400 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21401 let editor_margins = *self.editor_margins.lock();
21402 let gutter_dimensions = editor_margins.gutter;
21403 h_flex()
21404 .key_context("Editor")
21405 .bg(cx.theme().colors().editor_background)
21406 .border_y_1()
21407 .border_color(cx.theme().status().info_border)
21408 .size_full()
21409 .py(window.line_height() / 2.5)
21410 .on_action(cx.listener(Self::confirm))
21411 .on_action(cx.listener(Self::cancel))
21412 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21413 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21414 }
21415}
21416
21417impl Focusable for BreakpointPromptEditor {
21418 fn focus_handle(&self, cx: &App) -> FocusHandle {
21419 self.prompt.focus_handle(cx)
21420 }
21421}
21422
21423fn all_edits_insertions_or_deletions(
21424 edits: &Vec<(Range<Anchor>, String)>,
21425 snapshot: &MultiBufferSnapshot,
21426) -> bool {
21427 let mut all_insertions = true;
21428 let mut all_deletions = true;
21429
21430 for (range, new_text) in edits.iter() {
21431 let range_is_empty = range.to_offset(&snapshot).is_empty();
21432 let text_is_empty = new_text.is_empty();
21433
21434 if range_is_empty != text_is_empty {
21435 if range_is_empty {
21436 all_deletions = false;
21437 } else {
21438 all_insertions = false;
21439 }
21440 } else {
21441 return false;
21442 }
21443
21444 if !all_insertions && !all_deletions {
21445 return false;
21446 }
21447 }
21448 all_insertions || all_deletions
21449}
21450
21451struct MissingEditPredictionKeybindingTooltip;
21452
21453impl Render for MissingEditPredictionKeybindingTooltip {
21454 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21455 ui::tooltip_container(window, cx, |container, _, cx| {
21456 container
21457 .flex_shrink_0()
21458 .max_w_80()
21459 .min_h(rems_from_px(124.))
21460 .justify_between()
21461 .child(
21462 v_flex()
21463 .flex_1()
21464 .text_ui_sm(cx)
21465 .child(Label::new("Conflict with Accept Keybinding"))
21466 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21467 )
21468 .child(
21469 h_flex()
21470 .pb_1()
21471 .gap_1()
21472 .items_end()
21473 .w_full()
21474 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21475 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21476 }))
21477 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21478 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21479 })),
21480 )
21481 })
21482 }
21483}
21484
21485#[derive(Debug, Clone, Copy, PartialEq)]
21486pub struct LineHighlight {
21487 pub background: Background,
21488 pub border: Option<gpui::Hsla>,
21489 pub include_gutter: bool,
21490 pub type_id: Option<TypeId>,
21491}
21492
21493fn render_diff_hunk_controls(
21494 row: u32,
21495 status: &DiffHunkStatus,
21496 hunk_range: Range<Anchor>,
21497 is_created_file: bool,
21498 line_height: Pixels,
21499 editor: &Entity<Editor>,
21500 _window: &mut Window,
21501 cx: &mut App,
21502) -> AnyElement {
21503 h_flex()
21504 .h(line_height)
21505 .mr_1()
21506 .gap_1()
21507 .px_0p5()
21508 .pb_1()
21509 .border_x_1()
21510 .border_b_1()
21511 .border_color(cx.theme().colors().border_variant)
21512 .rounded_b_lg()
21513 .bg(cx.theme().colors().editor_background)
21514 .gap_1()
21515 .stop_mouse_events_except_scroll()
21516 .shadow_md()
21517 .child(if status.has_secondary_hunk() {
21518 Button::new(("stage", row as u64), "Stage")
21519 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21520 .tooltip({
21521 let focus_handle = editor.focus_handle(cx);
21522 move |window, cx| {
21523 Tooltip::for_action_in(
21524 "Stage Hunk",
21525 &::git::ToggleStaged,
21526 &focus_handle,
21527 window,
21528 cx,
21529 )
21530 }
21531 })
21532 .on_click({
21533 let editor = editor.clone();
21534 move |_event, _window, cx| {
21535 editor.update(cx, |editor, cx| {
21536 editor.stage_or_unstage_diff_hunks(
21537 true,
21538 vec![hunk_range.start..hunk_range.start],
21539 cx,
21540 );
21541 });
21542 }
21543 })
21544 } else {
21545 Button::new(("unstage", row as u64), "Unstage")
21546 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21547 .tooltip({
21548 let focus_handle = editor.focus_handle(cx);
21549 move |window, cx| {
21550 Tooltip::for_action_in(
21551 "Unstage Hunk",
21552 &::git::ToggleStaged,
21553 &focus_handle,
21554 window,
21555 cx,
21556 )
21557 }
21558 })
21559 .on_click({
21560 let editor = editor.clone();
21561 move |_event, _window, cx| {
21562 editor.update(cx, |editor, cx| {
21563 editor.stage_or_unstage_diff_hunks(
21564 false,
21565 vec![hunk_range.start..hunk_range.start],
21566 cx,
21567 );
21568 });
21569 }
21570 })
21571 })
21572 .child(
21573 Button::new(("restore", row as u64), "Restore")
21574 .tooltip({
21575 let focus_handle = editor.focus_handle(cx);
21576 move |window, cx| {
21577 Tooltip::for_action_in(
21578 "Restore Hunk",
21579 &::git::Restore,
21580 &focus_handle,
21581 window,
21582 cx,
21583 )
21584 }
21585 })
21586 .on_click({
21587 let editor = editor.clone();
21588 move |_event, window, cx| {
21589 editor.update(cx, |editor, cx| {
21590 let snapshot = editor.snapshot(window, cx);
21591 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21592 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21593 });
21594 }
21595 })
21596 .disabled(is_created_file),
21597 )
21598 .when(
21599 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21600 |el| {
21601 el.child(
21602 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21603 .shape(IconButtonShape::Square)
21604 .icon_size(IconSize::Small)
21605 // .disabled(!has_multiple_hunks)
21606 .tooltip({
21607 let focus_handle = editor.focus_handle(cx);
21608 move |window, cx| {
21609 Tooltip::for_action_in(
21610 "Next Hunk",
21611 &GoToHunk,
21612 &focus_handle,
21613 window,
21614 cx,
21615 )
21616 }
21617 })
21618 .on_click({
21619 let editor = editor.clone();
21620 move |_event, window, cx| {
21621 editor.update(cx, |editor, cx| {
21622 let snapshot = editor.snapshot(window, cx);
21623 let position =
21624 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21625 editor.go_to_hunk_before_or_after_position(
21626 &snapshot,
21627 position,
21628 Direction::Next,
21629 window,
21630 cx,
21631 );
21632 editor.expand_selected_diff_hunks(cx);
21633 });
21634 }
21635 }),
21636 )
21637 .child(
21638 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21639 .shape(IconButtonShape::Square)
21640 .icon_size(IconSize::Small)
21641 // .disabled(!has_multiple_hunks)
21642 .tooltip({
21643 let focus_handle = editor.focus_handle(cx);
21644 move |window, cx| {
21645 Tooltip::for_action_in(
21646 "Previous Hunk",
21647 &GoToPreviousHunk,
21648 &focus_handle,
21649 window,
21650 cx,
21651 )
21652 }
21653 })
21654 .on_click({
21655 let editor = editor.clone();
21656 move |_event, window, cx| {
21657 editor.update(cx, |editor, cx| {
21658 let snapshot = editor.snapshot(window, cx);
21659 let point =
21660 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21661 editor.go_to_hunk_before_or_after_position(
21662 &snapshot,
21663 point,
21664 Direction::Prev,
21665 window,
21666 cx,
21667 );
21668 editor.expand_selected_diff_hunks(cx);
21669 });
21670 }
21671 }),
21672 )
21673 },
21674 )
21675 .into_any_element()
21676}