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, SearchSettings,
67 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(bool),
720}
721
722impl MinimapVisibility {
723 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
724 if mode.is_full() {
725 Self::Enabled(EditorSettings::get_global(cx).minimap.minimap_enabled())
726 } else {
727 Self::Disabled
728 }
729 }
730
731 fn disabled(&self) -> bool {
732 match *self {
733 Self::Disabled => true,
734 _ => false,
735 }
736 }
737
738 fn visible(&self) -> bool {
739 match *self {
740 Self::Enabled(visible) => visible,
741 _ => false,
742 }
743 }
744
745 fn toggle_visibility(&self) -> Self {
746 match *self {
747 Self::Enabled(visible) => Self::Enabled(!visible),
748 Self::Disabled => Self::Disabled,
749 }
750 }
751}
752
753#[derive(Clone, Debug)]
754struct RunnableTasks {
755 templates: Vec<(TaskSourceKind, TaskTemplate)>,
756 offset: multi_buffer::Anchor,
757 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
758 column: u32,
759 // Values of all named captures, including those starting with '_'
760 extra_variables: HashMap<String, String>,
761 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
762 context_range: Range<BufferOffset>,
763}
764
765impl RunnableTasks {
766 fn resolve<'a>(
767 &'a self,
768 cx: &'a task::TaskContext,
769 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
770 self.templates.iter().filter_map(|(kind, template)| {
771 template
772 .resolve_task(&kind.to_id_base(), cx)
773 .map(|task| (kind.clone(), task))
774 })
775 }
776}
777
778#[derive(Clone)]
779pub struct ResolvedTasks {
780 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
781 position: Anchor,
782}
783
784#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
785struct BufferOffset(usize);
786
787// Addons allow storing per-editor state in other crates (e.g. Vim)
788pub trait Addon: 'static {
789 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
790
791 fn render_buffer_header_controls(
792 &self,
793 _: &ExcerptInfo,
794 _: &Window,
795 _: &App,
796 ) -> Option<AnyElement> {
797 None
798 }
799
800 fn to_any(&self) -> &dyn std::any::Any;
801
802 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
803 None
804 }
805}
806
807/// A set of caret positions, registered when the editor was edited.
808pub struct ChangeList {
809 changes: Vec<Vec<Anchor>>,
810 /// Currently "selected" change.
811 position: Option<usize>,
812}
813
814impl ChangeList {
815 pub fn new() -> Self {
816 Self {
817 changes: Vec::new(),
818 position: None,
819 }
820 }
821
822 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
823 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
824 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
825 if self.changes.is_empty() {
826 return None;
827 }
828
829 let prev = self.position.unwrap_or(self.changes.len());
830 let next = if direction == Direction::Prev {
831 prev.saturating_sub(count)
832 } else {
833 (prev + count).min(self.changes.len() - 1)
834 };
835 self.position = Some(next);
836 self.changes.get(next).map(|anchors| anchors.as_slice())
837 }
838
839 /// Adds a new change to the list, resetting the change list position.
840 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
841 self.position.take();
842 if pop_state {
843 self.changes.pop();
844 }
845 self.changes.push(new_positions.clone());
846 }
847
848 pub fn last(&self) -> Option<&[Anchor]> {
849 self.changes.last().map(|anchors| anchors.as_slice())
850 }
851}
852
853#[derive(Clone)]
854struct InlineBlamePopoverState {
855 scroll_handle: ScrollHandle,
856 commit_message: Option<ParsedCommitMessage>,
857 markdown: Entity<Markdown>,
858}
859
860struct InlineBlamePopover {
861 position: gpui::Point<Pixels>,
862 show_task: Option<Task<()>>,
863 hide_task: Option<Task<()>>,
864 popover_bounds: Option<Bounds<Pixels>>,
865 popover_state: InlineBlamePopoverState,
866}
867
868/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
869/// a breakpoint on them.
870#[derive(Clone, Copy, Debug)]
871struct PhantomBreakpointIndicator {
872 display_row: DisplayRow,
873 /// There's a small debounce between hovering over the line and showing the indicator.
874 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
875 is_active: bool,
876 collides_with_existing_breakpoint: bool,
877}
878/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
879///
880/// See the [module level documentation](self) for more information.
881pub struct Editor {
882 focus_handle: FocusHandle,
883 last_focused_descendant: Option<WeakFocusHandle>,
884 /// The text buffer being edited
885 buffer: Entity<MultiBuffer>,
886 /// Map of how text in the buffer should be displayed.
887 /// Handles soft wraps, folds, fake inlay text insertions, etc.
888 pub display_map: Entity<DisplayMap>,
889 pub selections: SelectionsCollection,
890 pub scroll_manager: ScrollManager,
891 /// When inline assist editors are linked, they all render cursors because
892 /// typing enters text into each of them, even the ones that aren't focused.
893 pub(crate) show_cursor_when_unfocused: bool,
894 columnar_selection_tail: Option<Anchor>,
895 add_selections_state: Option<AddSelectionsState>,
896 select_next_state: Option<SelectNextState>,
897 select_prev_state: Option<SelectNextState>,
898 selection_history: SelectionHistory,
899 autoclose_regions: Vec<AutocloseRegion>,
900 snippet_stack: InvalidationStack<SnippetState>,
901 select_syntax_node_history: SelectSyntaxNodeHistory,
902 ime_transaction: Option<TransactionId>,
903 pub diagnostics_max_severity: DiagnosticSeverity,
904 active_diagnostics: ActiveDiagnostic,
905 show_inline_diagnostics: bool,
906 inline_diagnostics_update: Task<()>,
907 inline_diagnostics_enabled: bool,
908 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
909 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
910 hard_wrap: Option<usize>,
911
912 // TODO: make this a access method
913 pub project: Option<Entity<Project>>,
914 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
915 completion_provider: Option<Rc<dyn CompletionProvider>>,
916 collaboration_hub: Option<Box<dyn CollaborationHub>>,
917 blink_manager: Entity<BlinkManager>,
918 show_cursor_names: bool,
919 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
920 pub show_local_selections: bool,
921 mode: EditorMode,
922 show_breadcrumbs: bool,
923 show_gutter: bool,
924 show_scrollbars: bool,
925 minimap_visibility: MinimapVisibility,
926 offset_content: bool,
927 disable_expand_excerpt_buttons: bool,
928 show_line_numbers: Option<bool>,
929 use_relative_line_numbers: Option<bool>,
930 show_git_diff_gutter: Option<bool>,
931 show_code_actions: Option<bool>,
932 show_runnables: Option<bool>,
933 show_breakpoints: Option<bool>,
934 show_wrap_guides: Option<bool>,
935 show_indent_guides: Option<bool>,
936 placeholder_text: Option<Arc<str>>,
937 highlight_order: usize,
938 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
939 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
940 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
941 scrollbar_marker_state: ScrollbarMarkerState,
942 active_indent_guides_state: ActiveIndentGuidesState,
943 nav_history: Option<ItemNavHistory>,
944 context_menu: RefCell<Option<CodeContextMenu>>,
945 context_menu_options: Option<ContextMenuOptions>,
946 mouse_context_menu: Option<MouseContextMenu>,
947 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
948 inline_blame_popover: Option<InlineBlamePopover>,
949 signature_help_state: SignatureHelpState,
950 auto_signature_help: Option<bool>,
951 find_all_references_task_sources: Vec<Anchor>,
952 next_completion_id: CompletionId,
953 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
954 code_actions_task: Option<Task<Result<()>>>,
955 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
956 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
957 document_highlights_task: Option<Task<()>>,
958 linked_editing_range_task: Option<Task<Option<()>>>,
959 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
960 pending_rename: Option<RenameState>,
961 searchable: bool,
962 cursor_shape: CursorShape,
963 current_line_highlight: Option<CurrentLineHighlight>,
964 collapse_matches: bool,
965 autoindent_mode: Option<AutoindentMode>,
966 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
967 input_enabled: bool,
968 use_modal_editing: bool,
969 read_only: bool,
970 leader_id: Option<CollaboratorId>,
971 remote_id: Option<ViewId>,
972 pub hover_state: HoverState,
973 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
974 gutter_hovered: bool,
975 hovered_link_state: Option<HoveredLinkState>,
976 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
977 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
978 active_inline_completion: Option<InlineCompletionState>,
979 /// Used to prevent flickering as the user types while the menu is open
980 stale_inline_completion_in_menu: Option<InlineCompletionState>,
981 edit_prediction_settings: EditPredictionSettings,
982 inline_completions_hidden_for_vim_mode: bool,
983 show_inline_completions_override: Option<bool>,
984 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
985 edit_prediction_preview: EditPredictionPreview,
986 edit_prediction_indent_conflict: bool,
987 edit_prediction_requires_modifier_in_indent_conflict: bool,
988 inlay_hint_cache: InlayHintCache,
989 next_inlay_id: usize,
990 _subscriptions: Vec<Subscription>,
991 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
992 gutter_dimensions: GutterDimensions,
993 style: Option<EditorStyle>,
994 text_style_refinement: Option<TextStyleRefinement>,
995 next_editor_action_id: EditorActionId,
996 editor_actions:
997 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
998 use_autoclose: bool,
999 use_auto_surround: bool,
1000 auto_replace_emoji_shortcode: bool,
1001 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1002 show_git_blame_gutter: bool,
1003 show_git_blame_inline: bool,
1004 show_git_blame_inline_delay_task: Option<Task<()>>,
1005 git_blame_inline_enabled: bool,
1006 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1007 serialize_dirty_buffers: bool,
1008 show_selection_menu: Option<bool>,
1009 blame: Option<Entity<GitBlame>>,
1010 blame_subscription: Option<Subscription>,
1011 custom_context_menu: Option<
1012 Box<
1013 dyn 'static
1014 + Fn(
1015 &mut Self,
1016 DisplayPoint,
1017 &mut Window,
1018 &mut Context<Self>,
1019 ) -> Option<Entity<ui::ContextMenu>>,
1020 >,
1021 >,
1022 last_bounds: Option<Bounds<Pixels>>,
1023 last_position_map: Option<Rc<PositionMap>>,
1024 expect_bounds_change: Option<Bounds<Pixels>>,
1025 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1026 tasks_update_task: Option<Task<()>>,
1027 breakpoint_store: Option<Entity<BreakpointStore>>,
1028 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1029 in_project_search: bool,
1030 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1031 breadcrumb_header: Option<String>,
1032 focused_block: Option<FocusedBlock>,
1033 next_scroll_position: NextScrollCursorCenterTopBottom,
1034 addons: HashMap<TypeId, Box<dyn Addon>>,
1035 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1036 load_diff_task: Option<Shared<Task<()>>>,
1037 /// Whether we are temporarily displaying a diff other than git's
1038 temporary_diff_override: bool,
1039 selection_mark_mode: bool,
1040 toggle_fold_multiple_buffers: Task<()>,
1041 _scroll_cursor_center_top_bottom_task: Task<()>,
1042 serialize_selections: Task<()>,
1043 serialize_folds: Task<()>,
1044 mouse_cursor_hidden: bool,
1045 minimap: Option<Entity<Self>>,
1046 hide_mouse_mode: HideMouseMode,
1047 pub change_list: ChangeList,
1048 inline_value_cache: InlineValueCache,
1049}
1050
1051#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1052enum NextScrollCursorCenterTopBottom {
1053 #[default]
1054 Center,
1055 Top,
1056 Bottom,
1057}
1058
1059impl NextScrollCursorCenterTopBottom {
1060 fn next(&self) -> Self {
1061 match self {
1062 Self::Center => Self::Top,
1063 Self::Top => Self::Bottom,
1064 Self::Bottom => Self::Center,
1065 }
1066 }
1067}
1068
1069#[derive(Clone)]
1070pub struct EditorSnapshot {
1071 pub mode: EditorMode,
1072 show_gutter: bool,
1073 show_line_numbers: Option<bool>,
1074 show_git_diff_gutter: Option<bool>,
1075 show_code_actions: Option<bool>,
1076 show_runnables: Option<bool>,
1077 show_breakpoints: Option<bool>,
1078 git_blame_gutter_max_author_length: Option<usize>,
1079 pub display_snapshot: DisplaySnapshot,
1080 pub placeholder_text: Option<Arc<str>>,
1081 is_focused: bool,
1082 scroll_anchor: ScrollAnchor,
1083 ongoing_scroll: OngoingScroll,
1084 current_line_highlight: CurrentLineHighlight,
1085 gutter_hovered: bool,
1086}
1087
1088#[derive(Default, Debug, Clone, Copy)]
1089pub struct GutterDimensions {
1090 pub left_padding: Pixels,
1091 pub right_padding: Pixels,
1092 pub width: Pixels,
1093 pub margin: Pixels,
1094 pub git_blame_entries_width: Option<Pixels>,
1095}
1096
1097impl GutterDimensions {
1098 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1099 Self {
1100 margin: Self::default_gutter_margin(font_id, font_size, cx),
1101 ..Default::default()
1102 }
1103 }
1104
1105 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1106 -cx.text_system().descent(font_id, font_size)
1107 }
1108 /// The full width of the space taken up by the gutter.
1109 pub fn full_width(&self) -> Pixels {
1110 self.margin + self.width
1111 }
1112
1113 /// The width of the space reserved for the fold indicators,
1114 /// use alongside 'justify_end' and `gutter_width` to
1115 /// right align content with the line numbers
1116 pub fn fold_area_width(&self) -> Pixels {
1117 self.margin + self.right_padding
1118 }
1119}
1120
1121#[derive(Debug)]
1122pub struct RemoteSelection {
1123 pub replica_id: ReplicaId,
1124 pub selection: Selection<Anchor>,
1125 pub cursor_shape: CursorShape,
1126 pub collaborator_id: CollaboratorId,
1127 pub line_mode: bool,
1128 pub user_name: Option<SharedString>,
1129 pub color: PlayerColor,
1130}
1131
1132#[derive(Clone, Debug)]
1133struct SelectionHistoryEntry {
1134 selections: Arc<[Selection<Anchor>]>,
1135 select_next_state: Option<SelectNextState>,
1136 select_prev_state: Option<SelectNextState>,
1137 add_selections_state: Option<AddSelectionsState>,
1138}
1139
1140enum SelectionHistoryMode {
1141 Normal,
1142 Undoing,
1143 Redoing,
1144}
1145
1146#[derive(Clone, PartialEq, Eq, Hash)]
1147struct HoveredCursor {
1148 replica_id: u16,
1149 selection_id: usize,
1150}
1151
1152impl Default for SelectionHistoryMode {
1153 fn default() -> Self {
1154 Self::Normal
1155 }
1156}
1157
1158#[derive(Default)]
1159struct SelectionHistory {
1160 #[allow(clippy::type_complexity)]
1161 selections_by_transaction:
1162 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1163 mode: SelectionHistoryMode,
1164 undo_stack: VecDeque<SelectionHistoryEntry>,
1165 redo_stack: VecDeque<SelectionHistoryEntry>,
1166}
1167
1168impl SelectionHistory {
1169 fn insert_transaction(
1170 &mut self,
1171 transaction_id: TransactionId,
1172 selections: Arc<[Selection<Anchor>]>,
1173 ) {
1174 self.selections_by_transaction
1175 .insert(transaction_id, (selections, None));
1176 }
1177
1178 #[allow(clippy::type_complexity)]
1179 fn transaction(
1180 &self,
1181 transaction_id: TransactionId,
1182 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1183 self.selections_by_transaction.get(&transaction_id)
1184 }
1185
1186 #[allow(clippy::type_complexity)]
1187 fn transaction_mut(
1188 &mut self,
1189 transaction_id: TransactionId,
1190 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1191 self.selections_by_transaction.get_mut(&transaction_id)
1192 }
1193
1194 fn push(&mut self, entry: SelectionHistoryEntry) {
1195 if !entry.selections.is_empty() {
1196 match self.mode {
1197 SelectionHistoryMode::Normal => {
1198 self.push_undo(entry);
1199 self.redo_stack.clear();
1200 }
1201 SelectionHistoryMode::Undoing => self.push_redo(entry),
1202 SelectionHistoryMode::Redoing => self.push_undo(entry),
1203 }
1204 }
1205 }
1206
1207 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1208 if self
1209 .undo_stack
1210 .back()
1211 .map_or(true, |e| e.selections != entry.selections)
1212 {
1213 self.undo_stack.push_back(entry);
1214 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1215 self.undo_stack.pop_front();
1216 }
1217 }
1218 }
1219
1220 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1221 if self
1222 .redo_stack
1223 .back()
1224 .map_or(true, |e| e.selections != entry.selections)
1225 {
1226 self.redo_stack.push_back(entry);
1227 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1228 self.redo_stack.pop_front();
1229 }
1230 }
1231 }
1232}
1233
1234#[derive(Clone, Copy)]
1235pub struct RowHighlightOptions {
1236 pub autoscroll: bool,
1237 pub include_gutter: bool,
1238}
1239
1240impl Default for RowHighlightOptions {
1241 fn default() -> Self {
1242 Self {
1243 autoscroll: Default::default(),
1244 include_gutter: true,
1245 }
1246 }
1247}
1248
1249struct RowHighlight {
1250 index: usize,
1251 range: Range<Anchor>,
1252 color: Hsla,
1253 options: RowHighlightOptions,
1254 type_id: TypeId,
1255}
1256
1257#[derive(Clone, Debug)]
1258struct AddSelectionsState {
1259 above: bool,
1260 stack: Vec<usize>,
1261}
1262
1263#[derive(Clone)]
1264struct SelectNextState {
1265 query: AhoCorasick,
1266 wordwise: bool,
1267 done: bool,
1268}
1269
1270impl std::fmt::Debug for SelectNextState {
1271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1272 f.debug_struct(std::any::type_name::<Self>())
1273 .field("wordwise", &self.wordwise)
1274 .field("done", &self.done)
1275 .finish()
1276 }
1277}
1278
1279#[derive(Debug)]
1280struct AutocloseRegion {
1281 selection_id: usize,
1282 range: Range<Anchor>,
1283 pair: BracketPair,
1284}
1285
1286#[derive(Debug)]
1287struct SnippetState {
1288 ranges: Vec<Vec<Range<Anchor>>>,
1289 active_index: usize,
1290 choices: Vec<Option<Vec<String>>>,
1291}
1292
1293#[doc(hidden)]
1294pub struct RenameState {
1295 pub range: Range<Anchor>,
1296 pub old_name: Arc<str>,
1297 pub editor: Entity<Editor>,
1298 block_id: CustomBlockId,
1299}
1300
1301struct InvalidationStack<T>(Vec<T>);
1302
1303struct RegisteredInlineCompletionProvider {
1304 provider: Arc<dyn InlineCompletionProviderHandle>,
1305 _subscription: Subscription,
1306}
1307
1308#[derive(Debug, PartialEq, Eq)]
1309pub struct ActiveDiagnosticGroup {
1310 pub active_range: Range<Anchor>,
1311 pub active_message: String,
1312 pub group_id: usize,
1313 pub blocks: HashSet<CustomBlockId>,
1314}
1315
1316#[derive(Debug, PartialEq, Eq)]
1317
1318pub(crate) enum ActiveDiagnostic {
1319 None,
1320 All,
1321 Group(ActiveDiagnosticGroup),
1322}
1323
1324#[derive(Serialize, Deserialize, Clone, Debug)]
1325pub struct ClipboardSelection {
1326 /// The number of bytes in this selection.
1327 pub len: usize,
1328 /// Whether this was a full-line selection.
1329 pub is_entire_line: bool,
1330 /// The indentation of the first line when this content was originally copied.
1331 pub first_line_indent: u32,
1332}
1333
1334// selections, scroll behavior, was newest selection reversed
1335type SelectSyntaxNodeHistoryState = (
1336 Box<[Selection<usize>]>,
1337 SelectSyntaxNodeScrollBehavior,
1338 bool,
1339);
1340
1341#[derive(Default)]
1342struct SelectSyntaxNodeHistory {
1343 stack: Vec<SelectSyntaxNodeHistoryState>,
1344 // disable temporarily to allow changing selections without losing the stack
1345 pub disable_clearing: bool,
1346}
1347
1348impl SelectSyntaxNodeHistory {
1349 pub fn try_clear(&mut self) {
1350 if !self.disable_clearing {
1351 self.stack.clear();
1352 }
1353 }
1354
1355 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1356 self.stack.push(selection);
1357 }
1358
1359 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1360 self.stack.pop()
1361 }
1362}
1363
1364enum SelectSyntaxNodeScrollBehavior {
1365 CursorTop,
1366 FitSelection,
1367 CursorBottom,
1368}
1369
1370#[derive(Debug)]
1371pub(crate) struct NavigationData {
1372 cursor_anchor: Anchor,
1373 cursor_position: Point,
1374 scroll_anchor: ScrollAnchor,
1375 scroll_top_row: u32,
1376}
1377
1378#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1379pub enum GotoDefinitionKind {
1380 Symbol,
1381 Declaration,
1382 Type,
1383 Implementation,
1384}
1385
1386#[derive(Debug, Clone)]
1387enum InlayHintRefreshReason {
1388 ModifiersChanged(bool),
1389 Toggle(bool),
1390 SettingsChange(InlayHintSettings),
1391 NewLinesShown,
1392 BufferEdited(HashSet<Arc<Language>>),
1393 RefreshRequested,
1394 ExcerptsRemoved(Vec<ExcerptId>),
1395}
1396
1397impl InlayHintRefreshReason {
1398 fn description(&self) -> &'static str {
1399 match self {
1400 Self::ModifiersChanged(_) => "modifiers changed",
1401 Self::Toggle(_) => "toggle",
1402 Self::SettingsChange(_) => "settings change",
1403 Self::NewLinesShown => "new lines shown",
1404 Self::BufferEdited(_) => "buffer edited",
1405 Self::RefreshRequested => "refresh requested",
1406 Self::ExcerptsRemoved(_) => "excerpts removed",
1407 }
1408 }
1409}
1410
1411pub enum FormatTarget {
1412 Buffers,
1413 Ranges(Vec<Range<MultiBufferPoint>>),
1414}
1415
1416pub(crate) struct FocusedBlock {
1417 id: BlockId,
1418 focus_handle: WeakFocusHandle,
1419}
1420
1421#[derive(Clone)]
1422enum JumpData {
1423 MultiBufferRow {
1424 row: MultiBufferRow,
1425 line_offset_from_top: u32,
1426 },
1427 MultiBufferPoint {
1428 excerpt_id: ExcerptId,
1429 position: Point,
1430 anchor: text::Anchor,
1431 line_offset_from_top: u32,
1432 },
1433}
1434
1435pub enum MultibufferSelectionMode {
1436 First,
1437 All,
1438}
1439
1440#[derive(Clone, Copy, Debug, Default)]
1441pub struct RewrapOptions {
1442 pub override_language_settings: bool,
1443 pub preserve_existing_whitespace: bool,
1444}
1445
1446impl Editor {
1447 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1448 let buffer = cx.new(|cx| Buffer::local("", cx));
1449 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1450 Self::new(
1451 EditorMode::SingleLine { auto_width: false },
1452 buffer,
1453 None,
1454 window,
1455 cx,
1456 )
1457 }
1458
1459 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1460 let buffer = cx.new(|cx| Buffer::local("", cx));
1461 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1462 Self::new(EditorMode::full(), buffer, None, window, cx)
1463 }
1464
1465 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1466 let buffer = cx.new(|cx| Buffer::local("", cx));
1467 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1468 Self::new(
1469 EditorMode::SingleLine { auto_width: true },
1470 buffer,
1471 None,
1472 window,
1473 cx,
1474 )
1475 }
1476
1477 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1478 let buffer = cx.new(|cx| Buffer::local("", cx));
1479 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1480 Self::new(
1481 EditorMode::AutoHeight { max_lines },
1482 buffer,
1483 None,
1484 window,
1485 cx,
1486 )
1487 }
1488
1489 pub fn for_buffer(
1490 buffer: Entity<Buffer>,
1491 project: Option<Entity<Project>>,
1492 window: &mut Window,
1493 cx: &mut Context<Self>,
1494 ) -> Self {
1495 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1496 Self::new(EditorMode::full(), buffer, project, window, cx)
1497 }
1498
1499 pub fn for_multibuffer(
1500 buffer: Entity<MultiBuffer>,
1501 project: Option<Entity<Project>>,
1502 window: &mut Window,
1503 cx: &mut Context<Self>,
1504 ) -> Self {
1505 Self::new(EditorMode::full(), buffer, project, window, cx)
1506 }
1507
1508 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1509 let mut clone = Self::new(
1510 self.mode.clone(),
1511 self.buffer.clone(),
1512 self.project.clone(),
1513 window,
1514 cx,
1515 );
1516 self.display_map.update(cx, |display_map, cx| {
1517 let snapshot = display_map.snapshot(cx);
1518 clone.display_map.update(cx, |display_map, cx| {
1519 display_map.set_state(&snapshot, cx);
1520 });
1521 });
1522 clone.folds_did_change(cx);
1523 clone.selections.clone_state(&self.selections);
1524 clone.scroll_manager.clone_state(&self.scroll_manager);
1525 clone.searchable = self.searchable;
1526 clone.read_only = self.read_only;
1527 clone
1528 }
1529
1530 pub fn new(
1531 mode: EditorMode,
1532 buffer: Entity<MultiBuffer>,
1533 project: Option<Entity<Project>>,
1534 window: &mut Window,
1535 cx: &mut Context<Self>,
1536 ) -> Self {
1537 Editor::new_internal(mode, buffer, project, None, window, cx)
1538 }
1539
1540 fn new_internal(
1541 mode: EditorMode,
1542 buffer: Entity<MultiBuffer>,
1543 project: Option<Entity<Project>>,
1544 display_map: Option<Entity<DisplayMap>>,
1545 window: &mut Window,
1546 cx: &mut Context<Self>,
1547 ) -> Self {
1548 debug_assert!(
1549 display_map.is_none() || mode.is_minimap(),
1550 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1551 );
1552
1553 let full_mode = mode.is_full();
1554 let diagnostics_max_severity = if full_mode {
1555 EditorSettings::get_global(cx)
1556 .diagnostics_max_severity
1557 .unwrap_or(DiagnosticSeverity::Hint)
1558 } else {
1559 DiagnosticSeverity::Off
1560 };
1561 let style = window.text_style();
1562 let font_size = style.font_size.to_pixels(window.rem_size());
1563 let editor = cx.entity().downgrade();
1564 let fold_placeholder = FoldPlaceholder {
1565 constrain_width: true,
1566 render: Arc::new(move |fold_id, fold_range, cx| {
1567 let editor = editor.clone();
1568 div()
1569 .id(fold_id)
1570 .bg(cx.theme().colors().ghost_element_background)
1571 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1572 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1573 .rounded_xs()
1574 .size_full()
1575 .cursor_pointer()
1576 .child("⋯")
1577 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1578 .on_click(move |_, _window, cx| {
1579 editor
1580 .update(cx, |editor, cx| {
1581 editor.unfold_ranges(
1582 &[fold_range.start..fold_range.end],
1583 true,
1584 false,
1585 cx,
1586 );
1587 cx.stop_propagation();
1588 })
1589 .ok();
1590 })
1591 .into_any()
1592 }),
1593 merge_adjacent: true,
1594 ..FoldPlaceholder::default()
1595 };
1596 let display_map = display_map.unwrap_or_else(|| {
1597 cx.new(|cx| {
1598 DisplayMap::new(
1599 buffer.clone(),
1600 style.font(),
1601 font_size,
1602 None,
1603 FILE_HEADER_HEIGHT,
1604 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1605 fold_placeholder,
1606 diagnostics_max_severity,
1607 cx,
1608 )
1609 })
1610 });
1611
1612 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1613
1614 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1615
1616 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1617 .then(|| language_settings::SoftWrap::None);
1618
1619 let mut project_subscriptions = Vec::new();
1620 if mode.is_full() {
1621 if let Some(project) = project.as_ref() {
1622 project_subscriptions.push(cx.subscribe_in(
1623 project,
1624 window,
1625 |editor, _, event, window, cx| match event {
1626 project::Event::RefreshCodeLens => {
1627 // we always query lens with actions, without storing them, always refreshing them
1628 }
1629 project::Event::RefreshInlayHints => {
1630 editor
1631 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1632 }
1633 project::Event::SnippetEdit(id, snippet_edits) => {
1634 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1635 let focus_handle = editor.focus_handle(cx);
1636 if focus_handle.is_focused(window) {
1637 let snapshot = buffer.read(cx).snapshot();
1638 for (range, snippet) in snippet_edits {
1639 let editor_range =
1640 language::range_from_lsp(*range).to_offset(&snapshot);
1641 editor
1642 .insert_snippet(
1643 &[editor_range],
1644 snippet.clone(),
1645 window,
1646 cx,
1647 )
1648 .ok();
1649 }
1650 }
1651 }
1652 }
1653 _ => {}
1654 },
1655 ));
1656 if let Some(task_inventory) = project
1657 .read(cx)
1658 .task_store()
1659 .read(cx)
1660 .task_inventory()
1661 .cloned()
1662 {
1663 project_subscriptions.push(cx.observe_in(
1664 &task_inventory,
1665 window,
1666 |editor, _, window, cx| {
1667 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1668 },
1669 ));
1670 };
1671
1672 project_subscriptions.push(cx.subscribe_in(
1673 &project.read(cx).breakpoint_store(),
1674 window,
1675 |editor, _, event, window, cx| match event {
1676 BreakpointStoreEvent::ClearDebugLines => {
1677 editor.clear_row_highlights::<ActiveDebugLine>();
1678 editor.refresh_inline_values(cx);
1679 }
1680 BreakpointStoreEvent::SetDebugLine => {
1681 if editor.go_to_active_debug_line(window, cx) {
1682 cx.stop_propagation();
1683 }
1684
1685 editor.refresh_inline_values(cx);
1686 }
1687 _ => {}
1688 },
1689 ));
1690 }
1691 }
1692
1693 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1694
1695 let inlay_hint_settings =
1696 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1697 let focus_handle = cx.focus_handle();
1698 cx.on_focus(&focus_handle, window, Self::handle_focus)
1699 .detach();
1700 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1701 .detach();
1702 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1703 .detach();
1704 cx.on_blur(&focus_handle, window, Self::handle_blur)
1705 .detach();
1706
1707 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1708 Some(false)
1709 } else {
1710 None
1711 };
1712
1713 let breakpoint_store = match (&mode, project.as_ref()) {
1714 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1715 _ => None,
1716 };
1717
1718 let mut code_action_providers = Vec::new();
1719 let mut load_uncommitted_diff = None;
1720 if let Some(project) = project.clone() {
1721 load_uncommitted_diff = Some(
1722 update_uncommitted_diff_for_buffer(
1723 cx.entity(),
1724 &project,
1725 buffer.read(cx).all_buffers(),
1726 buffer.clone(),
1727 cx,
1728 )
1729 .shared(),
1730 );
1731 code_action_providers.push(Rc::new(project) as Rc<_>);
1732 }
1733
1734 let mut this = Self {
1735 focus_handle,
1736 show_cursor_when_unfocused: false,
1737 last_focused_descendant: None,
1738 buffer: buffer.clone(),
1739 display_map: display_map.clone(),
1740 selections,
1741 scroll_manager: ScrollManager::new(cx),
1742 columnar_selection_tail: None,
1743 add_selections_state: None,
1744 select_next_state: None,
1745 select_prev_state: None,
1746 selection_history: SelectionHistory::default(),
1747 autoclose_regions: Vec::new(),
1748 snippet_stack: InvalidationStack::default(),
1749 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1750 ime_transaction: None,
1751 active_diagnostics: ActiveDiagnostic::None,
1752 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1753 inline_diagnostics_update: Task::ready(()),
1754 inline_diagnostics: Vec::new(),
1755 soft_wrap_mode_override,
1756 diagnostics_max_severity,
1757 hard_wrap: None,
1758 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1759 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1760 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1761 project,
1762 blink_manager: blink_manager.clone(),
1763 show_local_selections: true,
1764 show_scrollbars: full_mode,
1765 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1766 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1767 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1768 show_gutter: mode.is_full(),
1769 show_line_numbers: None,
1770 use_relative_line_numbers: None,
1771 disable_expand_excerpt_buttons: false,
1772 show_git_diff_gutter: None,
1773 show_code_actions: None,
1774 show_runnables: None,
1775 show_breakpoints: None,
1776 show_wrap_guides: None,
1777 show_indent_guides,
1778 placeholder_text: None,
1779 highlight_order: 0,
1780 highlighted_rows: HashMap::default(),
1781 background_highlights: TreeMap::default(),
1782 gutter_highlights: TreeMap::default(),
1783 scrollbar_marker_state: ScrollbarMarkerState::default(),
1784 active_indent_guides_state: ActiveIndentGuidesState::default(),
1785 nav_history: None,
1786 context_menu: RefCell::new(None),
1787 context_menu_options: None,
1788 mouse_context_menu: None,
1789 completion_tasks: Vec::new(),
1790 inline_blame_popover: None,
1791 signature_help_state: SignatureHelpState::default(),
1792 auto_signature_help: None,
1793 find_all_references_task_sources: Vec::new(),
1794 next_completion_id: 0,
1795 next_inlay_id: 0,
1796 code_action_providers,
1797 available_code_actions: None,
1798 code_actions_task: None,
1799 quick_selection_highlight_task: None,
1800 debounced_selection_highlight_task: None,
1801 document_highlights_task: None,
1802 linked_editing_range_task: None,
1803 pending_rename: None,
1804 searchable: true,
1805 cursor_shape: EditorSettings::get_global(cx)
1806 .cursor_shape
1807 .unwrap_or_default(),
1808 current_line_highlight: None,
1809 autoindent_mode: Some(AutoindentMode::EachLine),
1810 collapse_matches: false,
1811 workspace: None,
1812 input_enabled: true,
1813 use_modal_editing: mode.is_full(),
1814 read_only: mode.is_minimap(),
1815 use_autoclose: true,
1816 use_auto_surround: true,
1817 auto_replace_emoji_shortcode: false,
1818 jsx_tag_auto_close_enabled_in_any_buffer: false,
1819 leader_id: None,
1820 remote_id: None,
1821 hover_state: HoverState::default(),
1822 pending_mouse_down: None,
1823 hovered_link_state: None,
1824 edit_prediction_provider: None,
1825 active_inline_completion: None,
1826 stale_inline_completion_in_menu: None,
1827 edit_prediction_preview: EditPredictionPreview::Inactive {
1828 released_too_fast: false,
1829 },
1830 inline_diagnostics_enabled: mode.is_full(),
1831 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1832 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1833
1834 gutter_hovered: false,
1835 pixel_position_of_newest_cursor: None,
1836 last_bounds: None,
1837 last_position_map: None,
1838 expect_bounds_change: None,
1839 gutter_dimensions: GutterDimensions::default(),
1840 style: None,
1841 show_cursor_names: false,
1842 hovered_cursors: HashMap::default(),
1843 next_editor_action_id: EditorActionId::default(),
1844 editor_actions: Rc::default(),
1845 inline_completions_hidden_for_vim_mode: false,
1846 show_inline_completions_override: None,
1847 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1848 edit_prediction_settings: EditPredictionSettings::Disabled,
1849 edit_prediction_indent_conflict: false,
1850 edit_prediction_requires_modifier_in_indent_conflict: true,
1851 custom_context_menu: None,
1852 show_git_blame_gutter: false,
1853 show_git_blame_inline: false,
1854 show_selection_menu: None,
1855 show_git_blame_inline_delay_task: None,
1856 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1857 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1858 serialize_dirty_buffers: !mode.is_minimap()
1859 && ProjectSettings::get_global(cx)
1860 .session
1861 .restore_unsaved_buffers,
1862 blame: None,
1863 blame_subscription: None,
1864 tasks: BTreeMap::default(),
1865
1866 breakpoint_store,
1867 gutter_breakpoint_indicator: (None, None),
1868 _subscriptions: vec![
1869 cx.observe(&buffer, Self::on_buffer_changed),
1870 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1871 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1872 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1873 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1874 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1875 cx.observe_window_activation(window, |editor, window, cx| {
1876 let active = window.is_window_active();
1877 editor.blink_manager.update(cx, |blink_manager, cx| {
1878 if active {
1879 blink_manager.enable(cx);
1880 } else {
1881 blink_manager.disable(cx);
1882 }
1883 });
1884 }),
1885 ],
1886 tasks_update_task: None,
1887 linked_edit_ranges: Default::default(),
1888 in_project_search: false,
1889 previous_search_ranges: None,
1890 breadcrumb_header: None,
1891 focused_block: None,
1892 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1893 addons: HashMap::default(),
1894 registered_buffers: HashMap::default(),
1895 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1896 selection_mark_mode: false,
1897 toggle_fold_multiple_buffers: Task::ready(()),
1898 serialize_selections: Task::ready(()),
1899 serialize_folds: Task::ready(()),
1900 text_style_refinement: None,
1901 load_diff_task: load_uncommitted_diff,
1902 temporary_diff_override: false,
1903 mouse_cursor_hidden: false,
1904 minimap: None,
1905 hide_mouse_mode: EditorSettings::get_global(cx)
1906 .hide_mouse
1907 .unwrap_or_default(),
1908 change_list: ChangeList::new(),
1909 mode,
1910 };
1911 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1912 this._subscriptions
1913 .push(cx.observe(breakpoints, |_, _, cx| {
1914 cx.notify();
1915 }));
1916 }
1917 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1918 this._subscriptions.extend(project_subscriptions);
1919
1920 this._subscriptions.push(cx.subscribe_in(
1921 &cx.entity(),
1922 window,
1923 |editor, _, e: &EditorEvent, window, cx| match e {
1924 EditorEvent::ScrollPositionChanged { local, .. } => {
1925 if *local {
1926 let new_anchor = editor.scroll_manager.anchor();
1927 let snapshot = editor.snapshot(window, cx);
1928 editor.update_restoration_data(cx, move |data| {
1929 data.scroll_position = (
1930 new_anchor.top_row(&snapshot.buffer_snapshot),
1931 new_anchor.offset,
1932 );
1933 });
1934 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1935 editor.inline_blame_popover.take();
1936 }
1937 }
1938 EditorEvent::Edited { .. } => {
1939 if !vim_enabled(cx) {
1940 let (map, selections) = editor.selections.all_adjusted_display(cx);
1941 let pop_state = editor
1942 .change_list
1943 .last()
1944 .map(|previous| {
1945 previous.len() == selections.len()
1946 && previous.iter().enumerate().all(|(ix, p)| {
1947 p.to_display_point(&map).row()
1948 == selections[ix].head().row()
1949 })
1950 })
1951 .unwrap_or(false);
1952 let new_positions = selections
1953 .into_iter()
1954 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1955 .collect();
1956 editor
1957 .change_list
1958 .push_to_change_list(pop_state, new_positions);
1959 }
1960 }
1961 _ => (),
1962 },
1963 ));
1964
1965 if let Some(dap_store) = this
1966 .project
1967 .as_ref()
1968 .map(|project| project.read(cx).dap_store())
1969 {
1970 let weak_editor = cx.weak_entity();
1971
1972 this._subscriptions
1973 .push(
1974 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
1975 let session_entity = cx.entity();
1976 weak_editor
1977 .update(cx, |editor, cx| {
1978 editor._subscriptions.push(
1979 cx.subscribe(&session_entity, Self::on_debug_session_event),
1980 );
1981 })
1982 .ok();
1983 }),
1984 );
1985
1986 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
1987 this._subscriptions
1988 .push(cx.subscribe(&session, Self::on_debug_session_event));
1989 }
1990 }
1991
1992 this.end_selection(window, cx);
1993 this.scroll_manager.show_scrollbars(window, cx);
1994 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
1995
1996 if full_mode {
1997 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
1998 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
1999
2000 if this.git_blame_inline_enabled {
2001 this.start_git_blame_inline(false, window, cx);
2002 }
2003
2004 this.go_to_active_debug_line(window, cx);
2005
2006 if let Some(buffer) = buffer.read(cx).as_singleton() {
2007 if let Some(project) = this.project.as_ref() {
2008 let handle = project.update(cx, |project, cx| {
2009 project.register_buffer_with_language_servers(&buffer, cx)
2010 });
2011 this.registered_buffers
2012 .insert(buffer.read(cx).remote_id(), handle);
2013 }
2014 }
2015
2016 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2017 }
2018
2019 this.report_editor_event("Editor Opened", None, cx);
2020 this
2021 }
2022
2023 pub fn deploy_mouse_context_menu(
2024 &mut self,
2025 position: gpui::Point<Pixels>,
2026 context_menu: Entity<ContextMenu>,
2027 window: &mut Window,
2028 cx: &mut Context<Self>,
2029 ) {
2030 self.mouse_context_menu = Some(MouseContextMenu::new(
2031 self,
2032 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2033 context_menu,
2034 window,
2035 cx,
2036 ));
2037 }
2038
2039 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2040 self.mouse_context_menu
2041 .as_ref()
2042 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2043 }
2044
2045 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2046 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2047 }
2048
2049 fn key_context_internal(
2050 &self,
2051 has_active_edit_prediction: bool,
2052 window: &Window,
2053 cx: &App,
2054 ) -> KeyContext {
2055 let mut key_context = KeyContext::new_with_defaults();
2056 key_context.add("Editor");
2057 let mode = match self.mode {
2058 EditorMode::SingleLine { .. } => "single_line",
2059 EditorMode::AutoHeight { .. } => "auto_height",
2060 EditorMode::Minimap { .. } => "minimap",
2061 EditorMode::Full { .. } => "full",
2062 };
2063
2064 if EditorSettings::jupyter_enabled(cx) {
2065 key_context.add("jupyter");
2066 }
2067
2068 key_context.set("mode", mode);
2069 if self.pending_rename.is_some() {
2070 key_context.add("renaming");
2071 }
2072
2073 match self.context_menu.borrow().as_ref() {
2074 Some(CodeContextMenu::Completions(_)) => {
2075 key_context.add("menu");
2076 key_context.add("showing_completions");
2077 }
2078 Some(CodeContextMenu::CodeActions(_)) => {
2079 key_context.add("menu");
2080 key_context.add("showing_code_actions")
2081 }
2082 None => {}
2083 }
2084
2085 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2086 if !self.focus_handle(cx).contains_focused(window, cx)
2087 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2088 {
2089 for addon in self.addons.values() {
2090 addon.extend_key_context(&mut key_context, cx)
2091 }
2092 }
2093
2094 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2095 if let Some(extension) = singleton_buffer
2096 .read(cx)
2097 .file()
2098 .and_then(|file| file.path().extension()?.to_str())
2099 {
2100 key_context.set("extension", extension.to_string());
2101 }
2102 } else {
2103 key_context.add("multibuffer");
2104 }
2105
2106 if has_active_edit_prediction {
2107 if self.edit_prediction_in_conflict() {
2108 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2109 } else {
2110 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2111 key_context.add("copilot_suggestion");
2112 }
2113 }
2114
2115 if self.selection_mark_mode {
2116 key_context.add("selection_mode");
2117 }
2118
2119 key_context
2120 }
2121
2122 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2123 self.mouse_cursor_hidden = match origin {
2124 HideMouseCursorOrigin::TypingAction => {
2125 matches!(
2126 self.hide_mouse_mode,
2127 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2128 )
2129 }
2130 HideMouseCursorOrigin::MovementAction => {
2131 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2132 }
2133 };
2134 }
2135
2136 pub fn edit_prediction_in_conflict(&self) -> bool {
2137 if !self.show_edit_predictions_in_menu() {
2138 return false;
2139 }
2140
2141 let showing_completions = self
2142 .context_menu
2143 .borrow()
2144 .as_ref()
2145 .map_or(false, |context| {
2146 matches!(context, CodeContextMenu::Completions(_))
2147 });
2148
2149 showing_completions
2150 || self.edit_prediction_requires_modifier()
2151 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2152 // bindings to insert tab characters.
2153 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2154 }
2155
2156 pub fn accept_edit_prediction_keybind(
2157 &self,
2158 window: &Window,
2159 cx: &App,
2160 ) -> AcceptEditPredictionBinding {
2161 let key_context = self.key_context_internal(true, window, cx);
2162 let in_conflict = self.edit_prediction_in_conflict();
2163
2164 AcceptEditPredictionBinding(
2165 window
2166 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2167 .into_iter()
2168 .filter(|binding| {
2169 !in_conflict
2170 || binding
2171 .keystrokes()
2172 .first()
2173 .map_or(false, |keystroke| keystroke.modifiers.modified())
2174 })
2175 .rev()
2176 .min_by_key(|binding| {
2177 binding
2178 .keystrokes()
2179 .first()
2180 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2181 }),
2182 )
2183 }
2184
2185 pub fn new_file(
2186 workspace: &mut Workspace,
2187 _: &workspace::NewFile,
2188 window: &mut Window,
2189 cx: &mut Context<Workspace>,
2190 ) {
2191 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2192 "Failed to create buffer",
2193 window,
2194 cx,
2195 |e, _, _| match e.error_code() {
2196 ErrorCode::RemoteUpgradeRequired => Some(format!(
2197 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2198 e.error_tag("required").unwrap_or("the latest version")
2199 )),
2200 _ => None,
2201 },
2202 );
2203 }
2204
2205 pub fn new_in_workspace(
2206 workspace: &mut Workspace,
2207 window: &mut Window,
2208 cx: &mut Context<Workspace>,
2209 ) -> Task<Result<Entity<Editor>>> {
2210 let project = workspace.project().clone();
2211 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2212
2213 cx.spawn_in(window, async move |workspace, cx| {
2214 let buffer = create.await?;
2215 workspace.update_in(cx, |workspace, window, cx| {
2216 let editor =
2217 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2218 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2219 editor
2220 })
2221 })
2222 }
2223
2224 fn new_file_vertical(
2225 workspace: &mut Workspace,
2226 _: &workspace::NewFileSplitVertical,
2227 window: &mut Window,
2228 cx: &mut Context<Workspace>,
2229 ) {
2230 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2231 }
2232
2233 fn new_file_horizontal(
2234 workspace: &mut Workspace,
2235 _: &workspace::NewFileSplitHorizontal,
2236 window: &mut Window,
2237 cx: &mut Context<Workspace>,
2238 ) {
2239 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2240 }
2241
2242 fn new_file_in_direction(
2243 workspace: &mut Workspace,
2244 direction: SplitDirection,
2245 window: &mut Window,
2246 cx: &mut Context<Workspace>,
2247 ) {
2248 let project = workspace.project().clone();
2249 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2250
2251 cx.spawn_in(window, async move |workspace, cx| {
2252 let buffer = create.await?;
2253 workspace.update_in(cx, move |workspace, window, cx| {
2254 workspace.split_item(
2255 direction,
2256 Box::new(
2257 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2258 ),
2259 window,
2260 cx,
2261 )
2262 })?;
2263 anyhow::Ok(())
2264 })
2265 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2266 match e.error_code() {
2267 ErrorCode::RemoteUpgradeRequired => Some(format!(
2268 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2269 e.error_tag("required").unwrap_or("the latest version")
2270 )),
2271 _ => None,
2272 }
2273 });
2274 }
2275
2276 pub fn leader_id(&self) -> Option<CollaboratorId> {
2277 self.leader_id
2278 }
2279
2280 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2281 &self.buffer
2282 }
2283
2284 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2285 self.workspace.as_ref()?.0.upgrade()
2286 }
2287
2288 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2289 self.buffer().read(cx).title(cx)
2290 }
2291
2292 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2293 let git_blame_gutter_max_author_length = self
2294 .render_git_blame_gutter(cx)
2295 .then(|| {
2296 if let Some(blame) = self.blame.as_ref() {
2297 let max_author_length =
2298 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2299 Some(max_author_length)
2300 } else {
2301 None
2302 }
2303 })
2304 .flatten();
2305
2306 EditorSnapshot {
2307 mode: self.mode.clone(),
2308 show_gutter: self.show_gutter,
2309 show_line_numbers: self.show_line_numbers,
2310 show_git_diff_gutter: self.show_git_diff_gutter,
2311 show_code_actions: self.show_code_actions,
2312 show_runnables: self.show_runnables,
2313 show_breakpoints: self.show_breakpoints,
2314 git_blame_gutter_max_author_length,
2315 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2316 scroll_anchor: self.scroll_manager.anchor(),
2317 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2318 placeholder_text: self.placeholder_text.clone(),
2319 is_focused: self.focus_handle.is_focused(window),
2320 current_line_highlight: self
2321 .current_line_highlight
2322 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2323 gutter_hovered: self.gutter_hovered,
2324 }
2325 }
2326
2327 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2328 self.buffer.read(cx).language_at(point, cx)
2329 }
2330
2331 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2332 self.buffer.read(cx).read(cx).file_at(point).cloned()
2333 }
2334
2335 pub fn active_excerpt(
2336 &self,
2337 cx: &App,
2338 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2339 self.buffer
2340 .read(cx)
2341 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2342 }
2343
2344 pub fn mode(&self) -> &EditorMode {
2345 &self.mode
2346 }
2347
2348 pub fn set_mode(&mut self, mode: EditorMode) {
2349 self.mode = mode;
2350 }
2351
2352 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2353 self.collaboration_hub.as_deref()
2354 }
2355
2356 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2357 self.collaboration_hub = Some(hub);
2358 }
2359
2360 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2361 self.in_project_search = in_project_search;
2362 }
2363
2364 pub fn set_custom_context_menu(
2365 &mut self,
2366 f: impl 'static
2367 + Fn(
2368 &mut Self,
2369 DisplayPoint,
2370 &mut Window,
2371 &mut Context<Self>,
2372 ) -> Option<Entity<ui::ContextMenu>>,
2373 ) {
2374 self.custom_context_menu = Some(Box::new(f))
2375 }
2376
2377 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2378 self.completion_provider = provider;
2379 }
2380
2381 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2382 self.semantics_provider.clone()
2383 }
2384
2385 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2386 self.semantics_provider = provider;
2387 }
2388
2389 pub fn set_edit_prediction_provider<T>(
2390 &mut self,
2391 provider: Option<Entity<T>>,
2392 window: &mut Window,
2393 cx: &mut Context<Self>,
2394 ) where
2395 T: EditPredictionProvider,
2396 {
2397 self.edit_prediction_provider =
2398 provider.map(|provider| RegisteredInlineCompletionProvider {
2399 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2400 if this.focus_handle.is_focused(window) {
2401 this.update_visible_inline_completion(window, cx);
2402 }
2403 }),
2404 provider: Arc::new(provider),
2405 });
2406 self.update_edit_prediction_settings(cx);
2407 self.refresh_inline_completion(false, false, window, cx);
2408 }
2409
2410 pub fn placeholder_text(&self) -> Option<&str> {
2411 self.placeholder_text.as_deref()
2412 }
2413
2414 pub fn set_placeholder_text(
2415 &mut self,
2416 placeholder_text: impl Into<Arc<str>>,
2417 cx: &mut Context<Self>,
2418 ) {
2419 let placeholder_text = Some(placeholder_text.into());
2420 if self.placeholder_text != placeholder_text {
2421 self.placeholder_text = placeholder_text;
2422 cx.notify();
2423 }
2424 }
2425
2426 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2427 self.cursor_shape = cursor_shape;
2428
2429 // Disrupt blink for immediate user feedback that the cursor shape has changed
2430 self.blink_manager.update(cx, BlinkManager::show_cursor);
2431
2432 cx.notify();
2433 }
2434
2435 pub fn set_current_line_highlight(
2436 &mut self,
2437 current_line_highlight: Option<CurrentLineHighlight>,
2438 ) {
2439 self.current_line_highlight = current_line_highlight;
2440 }
2441
2442 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2443 self.collapse_matches = collapse_matches;
2444 }
2445
2446 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2447 let buffers = self.buffer.read(cx).all_buffers();
2448 let Some(project) = self.project.as_ref() else {
2449 return;
2450 };
2451 project.update(cx, |project, cx| {
2452 for buffer in buffers {
2453 self.registered_buffers
2454 .entry(buffer.read(cx).remote_id())
2455 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2456 }
2457 })
2458 }
2459
2460 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2461 if self.collapse_matches {
2462 return range.start..range.start;
2463 }
2464 range.clone()
2465 }
2466
2467 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2468 if self.display_map.read(cx).clip_at_line_ends != clip {
2469 self.display_map
2470 .update(cx, |map, _| map.clip_at_line_ends = clip);
2471 }
2472 }
2473
2474 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2475 self.input_enabled = input_enabled;
2476 }
2477
2478 pub fn set_inline_completions_hidden_for_vim_mode(
2479 &mut self,
2480 hidden: bool,
2481 window: &mut Window,
2482 cx: &mut Context<Self>,
2483 ) {
2484 if hidden != self.inline_completions_hidden_for_vim_mode {
2485 self.inline_completions_hidden_for_vim_mode = hidden;
2486 if hidden {
2487 self.update_visible_inline_completion(window, cx);
2488 } else {
2489 self.refresh_inline_completion(true, false, window, cx);
2490 }
2491 }
2492 }
2493
2494 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2495 self.menu_inline_completions_policy = value;
2496 }
2497
2498 pub fn set_autoindent(&mut self, autoindent: bool) {
2499 if autoindent {
2500 self.autoindent_mode = Some(AutoindentMode::EachLine);
2501 } else {
2502 self.autoindent_mode = None;
2503 }
2504 }
2505
2506 pub fn read_only(&self, cx: &App) -> bool {
2507 self.read_only || self.buffer.read(cx).read_only()
2508 }
2509
2510 pub fn set_read_only(&mut self, read_only: bool) {
2511 self.read_only = read_only;
2512 }
2513
2514 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2515 self.use_autoclose = autoclose;
2516 }
2517
2518 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2519 self.use_auto_surround = auto_surround;
2520 }
2521
2522 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2523 self.auto_replace_emoji_shortcode = auto_replace;
2524 }
2525
2526 pub fn toggle_edit_predictions(
2527 &mut self,
2528 _: &ToggleEditPrediction,
2529 window: &mut Window,
2530 cx: &mut Context<Self>,
2531 ) {
2532 if self.show_inline_completions_override.is_some() {
2533 self.set_show_edit_predictions(None, window, cx);
2534 } else {
2535 let show_edit_predictions = !self.edit_predictions_enabled();
2536 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2537 }
2538 }
2539
2540 pub fn set_show_edit_predictions(
2541 &mut self,
2542 show_edit_predictions: Option<bool>,
2543 window: &mut Window,
2544 cx: &mut Context<Self>,
2545 ) {
2546 self.show_inline_completions_override = show_edit_predictions;
2547 self.update_edit_prediction_settings(cx);
2548
2549 if let Some(false) = show_edit_predictions {
2550 self.discard_inline_completion(false, cx);
2551 } else {
2552 self.refresh_inline_completion(false, true, window, cx);
2553 }
2554 }
2555
2556 fn inline_completions_disabled_in_scope(
2557 &self,
2558 buffer: &Entity<Buffer>,
2559 buffer_position: language::Anchor,
2560 cx: &App,
2561 ) -> bool {
2562 let snapshot = buffer.read(cx).snapshot();
2563 let settings = snapshot.settings_at(buffer_position, cx);
2564
2565 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2566 return false;
2567 };
2568
2569 scope.override_name().map_or(false, |scope_name| {
2570 settings
2571 .edit_predictions_disabled_in
2572 .iter()
2573 .any(|s| s == scope_name)
2574 })
2575 }
2576
2577 pub fn set_use_modal_editing(&mut self, to: bool) {
2578 self.use_modal_editing = to;
2579 }
2580
2581 pub fn use_modal_editing(&self) -> bool {
2582 self.use_modal_editing
2583 }
2584
2585 fn selections_did_change(
2586 &mut self,
2587 local: bool,
2588 old_cursor_position: &Anchor,
2589 show_completions: bool,
2590 window: &mut Window,
2591 cx: &mut Context<Self>,
2592 ) {
2593 window.invalidate_character_coordinates();
2594
2595 // Copy selections to primary selection buffer
2596 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2597 if local {
2598 let selections = self.selections.all::<usize>(cx);
2599 let buffer_handle = self.buffer.read(cx).read(cx);
2600
2601 let mut text = String::new();
2602 for (index, selection) in selections.iter().enumerate() {
2603 let text_for_selection = buffer_handle
2604 .text_for_range(selection.start..selection.end)
2605 .collect::<String>();
2606
2607 text.push_str(&text_for_selection);
2608 if index != selections.len() - 1 {
2609 text.push('\n');
2610 }
2611 }
2612
2613 if !text.is_empty() {
2614 cx.write_to_primary(ClipboardItem::new_string(text));
2615 }
2616 }
2617
2618 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2619 self.buffer.update(cx, |buffer, cx| {
2620 buffer.set_active_selections(
2621 &self.selections.disjoint_anchors(),
2622 self.selections.line_mode,
2623 self.cursor_shape,
2624 cx,
2625 )
2626 });
2627 }
2628 let display_map = self
2629 .display_map
2630 .update(cx, |display_map, cx| display_map.snapshot(cx));
2631 let buffer = &display_map.buffer_snapshot;
2632 self.add_selections_state = None;
2633 self.select_next_state = None;
2634 self.select_prev_state = None;
2635 self.select_syntax_node_history.try_clear();
2636 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2637 self.snippet_stack
2638 .invalidate(&self.selections.disjoint_anchors(), buffer);
2639 self.take_rename(false, window, cx);
2640
2641 let new_cursor_position = self.selections.newest_anchor().head();
2642
2643 self.push_to_nav_history(
2644 *old_cursor_position,
2645 Some(new_cursor_position.to_point(buffer)),
2646 false,
2647 cx,
2648 );
2649
2650 if local {
2651 let new_cursor_position = self.selections.newest_anchor().head();
2652 let mut context_menu = self.context_menu.borrow_mut();
2653 let completion_menu = match context_menu.as_ref() {
2654 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2655 _ => {
2656 *context_menu = None;
2657 None
2658 }
2659 };
2660 if let Some(buffer_id) = new_cursor_position.buffer_id {
2661 if !self.registered_buffers.contains_key(&buffer_id) {
2662 if let Some(project) = self.project.as_ref() {
2663 project.update(cx, |project, cx| {
2664 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2665 return;
2666 };
2667 self.registered_buffers.insert(
2668 buffer_id,
2669 project.register_buffer_with_language_servers(&buffer, cx),
2670 );
2671 })
2672 }
2673 }
2674 }
2675
2676 if let Some(completion_menu) = completion_menu {
2677 let cursor_position = new_cursor_position.to_offset(buffer);
2678 let (word_range, kind) =
2679 buffer.surrounding_word(completion_menu.initial_position, true);
2680 if kind == Some(CharKind::Word)
2681 && word_range.to_inclusive().contains(&cursor_position)
2682 {
2683 let mut completion_menu = completion_menu.clone();
2684 drop(context_menu);
2685
2686 let query = Self::completion_query(buffer, cursor_position);
2687 let completion_provider = self.completion_provider.clone();
2688 cx.spawn_in(window, async move |this, cx| {
2689 completion_menu
2690 .filter(query.as_deref(), completion_provider, this.clone(), cx)
2691 .await;
2692
2693 this.update(cx, |this, cx| {
2694 let mut context_menu = this.context_menu.borrow_mut();
2695 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2696 else {
2697 return;
2698 };
2699
2700 if menu.id > completion_menu.id {
2701 return;
2702 }
2703
2704 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2705 drop(context_menu);
2706 cx.notify();
2707 })
2708 })
2709 .detach();
2710
2711 if show_completions {
2712 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2713 }
2714 } else {
2715 drop(context_menu);
2716 self.hide_context_menu(window, cx);
2717 }
2718 } else {
2719 drop(context_menu);
2720 }
2721
2722 hide_hover(self, cx);
2723
2724 if old_cursor_position.to_display_point(&display_map).row()
2725 != new_cursor_position.to_display_point(&display_map).row()
2726 {
2727 self.available_code_actions.take();
2728 }
2729 self.refresh_code_actions(window, cx);
2730 self.refresh_document_highlights(cx);
2731 self.refresh_selected_text_highlights(false, window, cx);
2732 refresh_matching_bracket_highlights(self, window, cx);
2733 self.update_visible_inline_completion(window, cx);
2734 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2735 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2736 self.inline_blame_popover.take();
2737 if self.git_blame_inline_enabled {
2738 self.start_inline_blame_timer(window, cx);
2739 }
2740 }
2741
2742 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2743 cx.emit(EditorEvent::SelectionsChanged { local });
2744
2745 let selections = &self.selections.disjoint;
2746 if selections.len() == 1 {
2747 cx.emit(SearchEvent::ActiveMatchChanged)
2748 }
2749 if local {
2750 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2751 let inmemory_selections = selections
2752 .iter()
2753 .map(|s| {
2754 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2755 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2756 })
2757 .collect();
2758 self.update_restoration_data(cx, |data| {
2759 data.selections = inmemory_selections;
2760 });
2761
2762 if WorkspaceSettings::get(None, cx).restore_on_startup
2763 != RestoreOnStartupBehavior::None
2764 {
2765 if let Some(workspace_id) =
2766 self.workspace.as_ref().and_then(|workspace| workspace.1)
2767 {
2768 let snapshot = self.buffer().read(cx).snapshot(cx);
2769 let selections = selections.clone();
2770 let background_executor = cx.background_executor().clone();
2771 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2772 self.serialize_selections = cx.background_spawn(async move {
2773 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2774 let db_selections = selections
2775 .iter()
2776 .map(|selection| {
2777 (
2778 selection.start.to_offset(&snapshot),
2779 selection.end.to_offset(&snapshot),
2780 )
2781 })
2782 .collect();
2783
2784 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2785 .await
2786 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2787 .log_err();
2788 });
2789 }
2790 }
2791 }
2792 }
2793
2794 cx.notify();
2795 }
2796
2797 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2798 use text::ToOffset as _;
2799 use text::ToPoint as _;
2800
2801 if self.mode.is_minimap()
2802 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2803 {
2804 return;
2805 }
2806
2807 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2808 return;
2809 };
2810
2811 let snapshot = singleton.read(cx).snapshot();
2812 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2813 let display_snapshot = display_map.snapshot(cx);
2814
2815 display_snapshot
2816 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2817 .map(|fold| {
2818 fold.range.start.text_anchor.to_point(&snapshot)
2819 ..fold.range.end.text_anchor.to_point(&snapshot)
2820 })
2821 .collect()
2822 });
2823 self.update_restoration_data(cx, |data| {
2824 data.folds = inmemory_folds;
2825 });
2826
2827 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2828 return;
2829 };
2830 let background_executor = cx.background_executor().clone();
2831 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2832 let db_folds = self.display_map.update(cx, |display_map, cx| {
2833 display_map
2834 .snapshot(cx)
2835 .folds_in_range(0..snapshot.len())
2836 .map(|fold| {
2837 (
2838 fold.range.start.text_anchor.to_offset(&snapshot),
2839 fold.range.end.text_anchor.to_offset(&snapshot),
2840 )
2841 })
2842 .collect()
2843 });
2844 self.serialize_folds = cx.background_spawn(async move {
2845 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2846 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2847 .await
2848 .with_context(|| {
2849 format!(
2850 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2851 )
2852 })
2853 .log_err();
2854 });
2855 }
2856
2857 pub fn sync_selections(
2858 &mut self,
2859 other: Entity<Editor>,
2860 cx: &mut Context<Self>,
2861 ) -> gpui::Subscription {
2862 let other_selections = other.read(cx).selections.disjoint.to_vec();
2863 self.selections.change_with(cx, |selections| {
2864 selections.select_anchors(other_selections);
2865 });
2866
2867 let other_subscription =
2868 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2869 EditorEvent::SelectionsChanged { local: true } => {
2870 let other_selections = other.read(cx).selections.disjoint.to_vec();
2871 if other_selections.is_empty() {
2872 return;
2873 }
2874 this.selections.change_with(cx, |selections| {
2875 selections.select_anchors(other_selections);
2876 });
2877 }
2878 _ => {}
2879 });
2880
2881 let this_subscription =
2882 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2883 EditorEvent::SelectionsChanged { local: true } => {
2884 let these_selections = this.selections.disjoint.to_vec();
2885 if these_selections.is_empty() {
2886 return;
2887 }
2888 other.update(cx, |other_editor, cx| {
2889 other_editor.selections.change_with(cx, |selections| {
2890 selections.select_anchors(these_selections);
2891 })
2892 });
2893 }
2894 _ => {}
2895 });
2896
2897 Subscription::join(other_subscription, this_subscription)
2898 }
2899
2900 pub fn change_selections<R>(
2901 &mut self,
2902 autoscroll: Option<Autoscroll>,
2903 window: &mut Window,
2904 cx: &mut Context<Self>,
2905 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2906 ) -> R {
2907 self.change_selections_inner(autoscroll, true, window, cx, change)
2908 }
2909
2910 fn change_selections_inner<R>(
2911 &mut self,
2912 autoscroll: Option<Autoscroll>,
2913 request_completions: bool,
2914 window: &mut Window,
2915 cx: &mut Context<Self>,
2916 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2917 ) -> R {
2918 let old_cursor_position = self.selections.newest_anchor().head();
2919 self.push_to_selection_history();
2920
2921 let (changed, result) = self.selections.change_with(cx, change);
2922
2923 if changed {
2924 if let Some(autoscroll) = autoscroll {
2925 self.request_autoscroll(autoscroll, cx);
2926 }
2927 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2928
2929 if self.should_open_signature_help_automatically(
2930 &old_cursor_position,
2931 self.signature_help_state.backspace_pressed(),
2932 cx,
2933 ) {
2934 self.show_signature_help(&ShowSignatureHelp, window, cx);
2935 }
2936 self.signature_help_state.set_backspace_pressed(false);
2937 }
2938
2939 result
2940 }
2941
2942 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2943 where
2944 I: IntoIterator<Item = (Range<S>, T)>,
2945 S: ToOffset,
2946 T: Into<Arc<str>>,
2947 {
2948 if self.read_only(cx) {
2949 return;
2950 }
2951
2952 self.buffer
2953 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
2954 }
2955
2956 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2957 where
2958 I: IntoIterator<Item = (Range<S>, T)>,
2959 S: ToOffset,
2960 T: Into<Arc<str>>,
2961 {
2962 if self.read_only(cx) {
2963 return;
2964 }
2965
2966 self.buffer.update(cx, |buffer, cx| {
2967 buffer.edit(edits, self.autoindent_mode.clone(), cx)
2968 });
2969 }
2970
2971 pub fn edit_with_block_indent<I, S, T>(
2972 &mut self,
2973 edits: I,
2974 original_indent_columns: Vec<Option<u32>>,
2975 cx: &mut Context<Self>,
2976 ) where
2977 I: IntoIterator<Item = (Range<S>, T)>,
2978 S: ToOffset,
2979 T: Into<Arc<str>>,
2980 {
2981 if self.read_only(cx) {
2982 return;
2983 }
2984
2985 self.buffer.update(cx, |buffer, cx| {
2986 buffer.edit(
2987 edits,
2988 Some(AutoindentMode::Block {
2989 original_indent_columns,
2990 }),
2991 cx,
2992 )
2993 });
2994 }
2995
2996 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
2997 self.hide_context_menu(window, cx);
2998
2999 match phase {
3000 SelectPhase::Begin {
3001 position,
3002 add,
3003 click_count,
3004 } => self.begin_selection(position, add, click_count, window, cx),
3005 SelectPhase::BeginColumnar {
3006 position,
3007 goal_column,
3008 reset,
3009 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3010 SelectPhase::Extend {
3011 position,
3012 click_count,
3013 } => self.extend_selection(position, click_count, window, cx),
3014 SelectPhase::Update {
3015 position,
3016 goal_column,
3017 scroll_delta,
3018 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3019 SelectPhase::End => self.end_selection(window, cx),
3020 }
3021 }
3022
3023 fn extend_selection(
3024 &mut self,
3025 position: DisplayPoint,
3026 click_count: usize,
3027 window: &mut Window,
3028 cx: &mut Context<Self>,
3029 ) {
3030 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3031 let tail = self.selections.newest::<usize>(cx).tail();
3032 self.begin_selection(position, false, click_count, window, cx);
3033
3034 let position = position.to_offset(&display_map, Bias::Left);
3035 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3036
3037 let mut pending_selection = self
3038 .selections
3039 .pending_anchor()
3040 .expect("extend_selection not called with pending selection");
3041 if position >= tail {
3042 pending_selection.start = tail_anchor;
3043 } else {
3044 pending_selection.end = tail_anchor;
3045 pending_selection.reversed = true;
3046 }
3047
3048 let mut pending_mode = self.selections.pending_mode().unwrap();
3049 match &mut pending_mode {
3050 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3051 _ => {}
3052 }
3053
3054 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3055
3056 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3057 s.set_pending(pending_selection, pending_mode)
3058 });
3059 }
3060
3061 fn begin_selection(
3062 &mut self,
3063 position: DisplayPoint,
3064 add: bool,
3065 click_count: usize,
3066 window: &mut Window,
3067 cx: &mut Context<Self>,
3068 ) {
3069 if !self.focus_handle.is_focused(window) {
3070 self.last_focused_descendant = None;
3071 window.focus(&self.focus_handle);
3072 }
3073
3074 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3075 let buffer = &display_map.buffer_snapshot;
3076 let position = display_map.clip_point(position, Bias::Left);
3077
3078 let start;
3079 let end;
3080 let mode;
3081 let mut auto_scroll;
3082 match click_count {
3083 1 => {
3084 start = buffer.anchor_before(position.to_point(&display_map));
3085 end = start;
3086 mode = SelectMode::Character;
3087 auto_scroll = true;
3088 }
3089 2 => {
3090 let range = movement::surrounding_word(&display_map, position);
3091 start = buffer.anchor_before(range.start.to_point(&display_map));
3092 end = buffer.anchor_before(range.end.to_point(&display_map));
3093 mode = SelectMode::Word(start..end);
3094 auto_scroll = true;
3095 }
3096 3 => {
3097 let position = display_map
3098 .clip_point(position, Bias::Left)
3099 .to_point(&display_map);
3100 let line_start = display_map.prev_line_boundary(position).0;
3101 let next_line_start = buffer.clip_point(
3102 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3103 Bias::Left,
3104 );
3105 start = buffer.anchor_before(line_start);
3106 end = buffer.anchor_before(next_line_start);
3107 mode = SelectMode::Line(start..end);
3108 auto_scroll = true;
3109 }
3110 _ => {
3111 start = buffer.anchor_before(0);
3112 end = buffer.anchor_before(buffer.len());
3113 mode = SelectMode::All;
3114 auto_scroll = false;
3115 }
3116 }
3117 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3118
3119 let point_to_delete: Option<usize> = {
3120 let selected_points: Vec<Selection<Point>> =
3121 self.selections.disjoint_in_range(start..end, cx);
3122
3123 if !add || click_count > 1 {
3124 None
3125 } else if !selected_points.is_empty() {
3126 Some(selected_points[0].id)
3127 } else {
3128 let clicked_point_already_selected =
3129 self.selections.disjoint.iter().find(|selection| {
3130 selection.start.to_point(buffer) == start.to_point(buffer)
3131 || selection.end.to_point(buffer) == end.to_point(buffer)
3132 });
3133
3134 clicked_point_already_selected.map(|selection| selection.id)
3135 }
3136 };
3137
3138 let selections_count = self.selections.count();
3139
3140 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3141 if let Some(point_to_delete) = point_to_delete {
3142 s.delete(point_to_delete);
3143
3144 if selections_count == 1 {
3145 s.set_pending_anchor_range(start..end, mode);
3146 }
3147 } else {
3148 if !add {
3149 s.clear_disjoint();
3150 }
3151
3152 s.set_pending_anchor_range(start..end, mode);
3153 }
3154 });
3155 }
3156
3157 fn begin_columnar_selection(
3158 &mut self,
3159 position: DisplayPoint,
3160 goal_column: u32,
3161 reset: bool,
3162 window: &mut Window,
3163 cx: &mut Context<Self>,
3164 ) {
3165 if !self.focus_handle.is_focused(window) {
3166 self.last_focused_descendant = None;
3167 window.focus(&self.focus_handle);
3168 }
3169
3170 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3171
3172 if reset {
3173 let pointer_position = display_map
3174 .buffer_snapshot
3175 .anchor_before(position.to_point(&display_map));
3176
3177 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3178 s.clear_disjoint();
3179 s.set_pending_anchor_range(
3180 pointer_position..pointer_position,
3181 SelectMode::Character,
3182 );
3183 });
3184 }
3185
3186 let tail = self.selections.newest::<Point>(cx).tail();
3187 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3188
3189 if !reset {
3190 self.select_columns(
3191 tail.to_display_point(&display_map),
3192 position,
3193 goal_column,
3194 &display_map,
3195 window,
3196 cx,
3197 );
3198 }
3199 }
3200
3201 fn update_selection(
3202 &mut self,
3203 position: DisplayPoint,
3204 goal_column: u32,
3205 scroll_delta: gpui::Point<f32>,
3206 window: &mut Window,
3207 cx: &mut Context<Self>,
3208 ) {
3209 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3210
3211 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3212 let tail = tail.to_display_point(&display_map);
3213 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3214 } else if let Some(mut pending) = self.selections.pending_anchor() {
3215 let buffer = self.buffer.read(cx).snapshot(cx);
3216 let head;
3217 let tail;
3218 let mode = self.selections.pending_mode().unwrap();
3219 match &mode {
3220 SelectMode::Character => {
3221 head = position.to_point(&display_map);
3222 tail = pending.tail().to_point(&buffer);
3223 }
3224 SelectMode::Word(original_range) => {
3225 let original_display_range = original_range.start.to_display_point(&display_map)
3226 ..original_range.end.to_display_point(&display_map);
3227 let original_buffer_range = original_display_range.start.to_point(&display_map)
3228 ..original_display_range.end.to_point(&display_map);
3229 if movement::is_inside_word(&display_map, position)
3230 || original_display_range.contains(&position)
3231 {
3232 let word_range = movement::surrounding_word(&display_map, position);
3233 if word_range.start < original_display_range.start {
3234 head = word_range.start.to_point(&display_map);
3235 } else {
3236 head = word_range.end.to_point(&display_map);
3237 }
3238 } else {
3239 head = position.to_point(&display_map);
3240 }
3241
3242 if head <= original_buffer_range.start {
3243 tail = original_buffer_range.end;
3244 } else {
3245 tail = original_buffer_range.start;
3246 }
3247 }
3248 SelectMode::Line(original_range) => {
3249 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3250
3251 let position = display_map
3252 .clip_point(position, Bias::Left)
3253 .to_point(&display_map);
3254 let line_start = display_map.prev_line_boundary(position).0;
3255 let next_line_start = buffer.clip_point(
3256 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3257 Bias::Left,
3258 );
3259
3260 if line_start < original_range.start {
3261 head = line_start
3262 } else {
3263 head = next_line_start
3264 }
3265
3266 if head <= original_range.start {
3267 tail = original_range.end;
3268 } else {
3269 tail = original_range.start;
3270 }
3271 }
3272 SelectMode::All => {
3273 return;
3274 }
3275 };
3276
3277 if head < tail {
3278 pending.start = buffer.anchor_before(head);
3279 pending.end = buffer.anchor_before(tail);
3280 pending.reversed = true;
3281 } else {
3282 pending.start = buffer.anchor_before(tail);
3283 pending.end = buffer.anchor_before(head);
3284 pending.reversed = false;
3285 }
3286
3287 self.change_selections(None, window, cx, |s| {
3288 s.set_pending(pending, mode);
3289 });
3290 } else {
3291 log::error!("update_selection dispatched with no pending selection");
3292 return;
3293 }
3294
3295 self.apply_scroll_delta(scroll_delta, window, cx);
3296 cx.notify();
3297 }
3298
3299 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3300 self.columnar_selection_tail.take();
3301 if self.selections.pending_anchor().is_some() {
3302 let selections = self.selections.all::<usize>(cx);
3303 self.change_selections(None, window, cx, |s| {
3304 s.select(selections);
3305 s.clear_pending();
3306 });
3307 }
3308 }
3309
3310 fn select_columns(
3311 &mut self,
3312 tail: DisplayPoint,
3313 head: DisplayPoint,
3314 goal_column: u32,
3315 display_map: &DisplaySnapshot,
3316 window: &mut Window,
3317 cx: &mut Context<Self>,
3318 ) {
3319 let start_row = cmp::min(tail.row(), head.row());
3320 let end_row = cmp::max(tail.row(), head.row());
3321 let start_column = cmp::min(tail.column(), goal_column);
3322 let end_column = cmp::max(tail.column(), goal_column);
3323 let reversed = start_column < tail.column();
3324
3325 let selection_ranges = (start_row.0..=end_row.0)
3326 .map(DisplayRow)
3327 .filter_map(|row| {
3328 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3329 let start = display_map
3330 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3331 .to_point(display_map);
3332 let end = display_map
3333 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3334 .to_point(display_map);
3335 if reversed {
3336 Some(end..start)
3337 } else {
3338 Some(start..end)
3339 }
3340 } else {
3341 None
3342 }
3343 })
3344 .collect::<Vec<_>>();
3345
3346 self.change_selections(None, window, cx, |s| {
3347 s.select_ranges(selection_ranges);
3348 });
3349 cx.notify();
3350 }
3351
3352 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3353 self.selections
3354 .all_adjusted(cx)
3355 .iter()
3356 .any(|selection| !selection.is_empty())
3357 }
3358
3359 pub fn has_pending_nonempty_selection(&self) -> bool {
3360 let pending_nonempty_selection = match self.selections.pending_anchor() {
3361 Some(Selection { start, end, .. }) => start != end,
3362 None => false,
3363 };
3364
3365 pending_nonempty_selection
3366 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3367 }
3368
3369 pub fn has_pending_selection(&self) -> bool {
3370 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3371 }
3372
3373 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3374 self.selection_mark_mode = false;
3375
3376 if self.clear_expanded_diff_hunks(cx) {
3377 cx.notify();
3378 return;
3379 }
3380 if self.dismiss_menus_and_popups(true, window, cx) {
3381 return;
3382 }
3383
3384 if self.mode.is_full()
3385 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3386 {
3387 return;
3388 }
3389
3390 cx.propagate();
3391 }
3392
3393 pub fn dismiss_menus_and_popups(
3394 &mut self,
3395 is_user_requested: bool,
3396 window: &mut Window,
3397 cx: &mut Context<Self>,
3398 ) -> bool {
3399 if self.take_rename(false, window, cx).is_some() {
3400 return true;
3401 }
3402
3403 if hide_hover(self, cx) {
3404 return true;
3405 }
3406
3407 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3408 return true;
3409 }
3410
3411 if self.hide_context_menu(window, cx).is_some() {
3412 return true;
3413 }
3414
3415 if self.mouse_context_menu.take().is_some() {
3416 return true;
3417 }
3418
3419 if is_user_requested && self.discard_inline_completion(true, cx) {
3420 return true;
3421 }
3422
3423 if self.snippet_stack.pop().is_some() {
3424 return true;
3425 }
3426
3427 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3428 self.dismiss_diagnostics(cx);
3429 return true;
3430 }
3431
3432 false
3433 }
3434
3435 fn linked_editing_ranges_for(
3436 &self,
3437 selection: Range<text::Anchor>,
3438 cx: &App,
3439 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3440 if self.linked_edit_ranges.is_empty() {
3441 return None;
3442 }
3443 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3444 selection.end.buffer_id.and_then(|end_buffer_id| {
3445 if selection.start.buffer_id != Some(end_buffer_id) {
3446 return None;
3447 }
3448 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3449 let snapshot = buffer.read(cx).snapshot();
3450 self.linked_edit_ranges
3451 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3452 .map(|ranges| (ranges, snapshot, buffer))
3453 })?;
3454 use text::ToOffset as TO;
3455 // find offset from the start of current range to current cursor position
3456 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3457
3458 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3459 let start_difference = start_offset - start_byte_offset;
3460 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3461 let end_difference = end_offset - start_byte_offset;
3462 // Current range has associated linked ranges.
3463 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3464 for range in linked_ranges.iter() {
3465 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3466 let end_offset = start_offset + end_difference;
3467 let start_offset = start_offset + start_difference;
3468 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3469 continue;
3470 }
3471 if self.selections.disjoint_anchor_ranges().any(|s| {
3472 if s.start.buffer_id != selection.start.buffer_id
3473 || s.end.buffer_id != selection.end.buffer_id
3474 {
3475 return false;
3476 }
3477 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3478 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3479 }) {
3480 continue;
3481 }
3482 let start = buffer_snapshot.anchor_after(start_offset);
3483 let end = buffer_snapshot.anchor_after(end_offset);
3484 linked_edits
3485 .entry(buffer.clone())
3486 .or_default()
3487 .push(start..end);
3488 }
3489 Some(linked_edits)
3490 }
3491
3492 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3493 let text: Arc<str> = text.into();
3494
3495 if self.read_only(cx) {
3496 return;
3497 }
3498
3499 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3500
3501 let selections = self.selections.all_adjusted(cx);
3502 let mut bracket_inserted = false;
3503 let mut edits = Vec::new();
3504 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3505 let mut new_selections = Vec::with_capacity(selections.len());
3506 let mut new_autoclose_regions = Vec::new();
3507 let snapshot = self.buffer.read(cx).read(cx);
3508 let mut clear_linked_edit_ranges = false;
3509
3510 for (selection, autoclose_region) in
3511 self.selections_with_autoclose_regions(selections, &snapshot)
3512 {
3513 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3514 // Determine if the inserted text matches the opening or closing
3515 // bracket of any of this language's bracket pairs.
3516 let mut bracket_pair = None;
3517 let mut is_bracket_pair_start = false;
3518 let mut is_bracket_pair_end = false;
3519 if !text.is_empty() {
3520 let mut bracket_pair_matching_end = None;
3521 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3522 // and they are removing the character that triggered IME popup.
3523 for (pair, enabled) in scope.brackets() {
3524 if !pair.close && !pair.surround {
3525 continue;
3526 }
3527
3528 if enabled && pair.start.ends_with(text.as_ref()) {
3529 let prefix_len = pair.start.len() - text.len();
3530 let preceding_text_matches_prefix = prefix_len == 0
3531 || (selection.start.column >= (prefix_len as u32)
3532 && snapshot.contains_str_at(
3533 Point::new(
3534 selection.start.row,
3535 selection.start.column - (prefix_len as u32),
3536 ),
3537 &pair.start[..prefix_len],
3538 ));
3539 if preceding_text_matches_prefix {
3540 bracket_pair = Some(pair.clone());
3541 is_bracket_pair_start = true;
3542 break;
3543 }
3544 }
3545 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3546 {
3547 // take first bracket pair matching end, but don't break in case a later bracket
3548 // pair matches start
3549 bracket_pair_matching_end = Some(pair.clone());
3550 }
3551 }
3552 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3553 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3554 is_bracket_pair_end = true;
3555 }
3556 }
3557
3558 if let Some(bracket_pair) = bracket_pair {
3559 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3560 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3561 let auto_surround =
3562 self.use_auto_surround && snapshot_settings.use_auto_surround;
3563 if selection.is_empty() {
3564 if is_bracket_pair_start {
3565 // If the inserted text is a suffix of an opening bracket and the
3566 // selection is preceded by the rest of the opening bracket, then
3567 // insert the closing bracket.
3568 let following_text_allows_autoclose = snapshot
3569 .chars_at(selection.start)
3570 .next()
3571 .map_or(true, |c| scope.should_autoclose_before(c));
3572
3573 let preceding_text_allows_autoclose = selection.start.column == 0
3574 || snapshot.reversed_chars_at(selection.start).next().map_or(
3575 true,
3576 |c| {
3577 bracket_pair.start != bracket_pair.end
3578 || !snapshot
3579 .char_classifier_at(selection.start)
3580 .is_word(c)
3581 },
3582 );
3583
3584 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3585 && bracket_pair.start.len() == 1
3586 {
3587 let target = bracket_pair.start.chars().next().unwrap();
3588 let current_line_count = snapshot
3589 .reversed_chars_at(selection.start)
3590 .take_while(|&c| c != '\n')
3591 .filter(|&c| c == target)
3592 .count();
3593 current_line_count % 2 == 1
3594 } else {
3595 false
3596 };
3597
3598 if autoclose
3599 && bracket_pair.close
3600 && following_text_allows_autoclose
3601 && preceding_text_allows_autoclose
3602 && !is_closing_quote
3603 {
3604 let anchor = snapshot.anchor_before(selection.end);
3605 new_selections.push((selection.map(|_| anchor), text.len()));
3606 new_autoclose_regions.push((
3607 anchor,
3608 text.len(),
3609 selection.id,
3610 bracket_pair.clone(),
3611 ));
3612 edits.push((
3613 selection.range(),
3614 format!("{}{}", text, bracket_pair.end).into(),
3615 ));
3616 bracket_inserted = true;
3617 continue;
3618 }
3619 }
3620
3621 if let Some(region) = autoclose_region {
3622 // If the selection is followed by an auto-inserted closing bracket,
3623 // then don't insert that closing bracket again; just move the selection
3624 // past the closing bracket.
3625 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3626 && text.as_ref() == region.pair.end.as_str();
3627 if should_skip {
3628 let anchor = snapshot.anchor_after(selection.end);
3629 new_selections
3630 .push((selection.map(|_| anchor), region.pair.end.len()));
3631 continue;
3632 }
3633 }
3634
3635 let always_treat_brackets_as_autoclosed = snapshot
3636 .language_settings_at(selection.start, cx)
3637 .always_treat_brackets_as_autoclosed;
3638 if always_treat_brackets_as_autoclosed
3639 && is_bracket_pair_end
3640 && snapshot.contains_str_at(selection.end, text.as_ref())
3641 {
3642 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3643 // and the inserted text is a closing bracket and the selection is followed
3644 // by the closing bracket then move the selection past the closing bracket.
3645 let anchor = snapshot.anchor_after(selection.end);
3646 new_selections.push((selection.map(|_| anchor), text.len()));
3647 continue;
3648 }
3649 }
3650 // If an opening bracket is 1 character long and is typed while
3651 // text is selected, then surround that text with the bracket pair.
3652 else if auto_surround
3653 && bracket_pair.surround
3654 && is_bracket_pair_start
3655 && bracket_pair.start.chars().count() == 1
3656 {
3657 edits.push((selection.start..selection.start, text.clone()));
3658 edits.push((
3659 selection.end..selection.end,
3660 bracket_pair.end.as_str().into(),
3661 ));
3662 bracket_inserted = true;
3663 new_selections.push((
3664 Selection {
3665 id: selection.id,
3666 start: snapshot.anchor_after(selection.start),
3667 end: snapshot.anchor_before(selection.end),
3668 reversed: selection.reversed,
3669 goal: selection.goal,
3670 },
3671 0,
3672 ));
3673 continue;
3674 }
3675 }
3676 }
3677
3678 if self.auto_replace_emoji_shortcode
3679 && selection.is_empty()
3680 && text.as_ref().ends_with(':')
3681 {
3682 if let Some(possible_emoji_short_code) =
3683 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3684 {
3685 if !possible_emoji_short_code.is_empty() {
3686 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3687 let emoji_shortcode_start = Point::new(
3688 selection.start.row,
3689 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3690 );
3691
3692 // Remove shortcode from buffer
3693 edits.push((
3694 emoji_shortcode_start..selection.start,
3695 "".to_string().into(),
3696 ));
3697 new_selections.push((
3698 Selection {
3699 id: selection.id,
3700 start: snapshot.anchor_after(emoji_shortcode_start),
3701 end: snapshot.anchor_before(selection.start),
3702 reversed: selection.reversed,
3703 goal: selection.goal,
3704 },
3705 0,
3706 ));
3707
3708 // Insert emoji
3709 let selection_start_anchor = snapshot.anchor_after(selection.start);
3710 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3711 edits.push((selection.start..selection.end, emoji.to_string().into()));
3712
3713 continue;
3714 }
3715 }
3716 }
3717 }
3718
3719 // If not handling any auto-close operation, then just replace the selected
3720 // text with the given input and move the selection to the end of the
3721 // newly inserted text.
3722 let anchor = snapshot.anchor_after(selection.end);
3723 if !self.linked_edit_ranges.is_empty() {
3724 let start_anchor = snapshot.anchor_before(selection.start);
3725
3726 let is_word_char = text.chars().next().map_or(true, |char| {
3727 let classifier = snapshot
3728 .char_classifier_at(start_anchor.to_offset(&snapshot))
3729 .ignore_punctuation(true);
3730 classifier.is_word(char)
3731 });
3732
3733 if is_word_char {
3734 if let Some(ranges) = self
3735 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3736 {
3737 for (buffer, edits) in ranges {
3738 linked_edits
3739 .entry(buffer.clone())
3740 .or_default()
3741 .extend(edits.into_iter().map(|range| (range, text.clone())));
3742 }
3743 }
3744 } else {
3745 clear_linked_edit_ranges = true;
3746 }
3747 }
3748
3749 new_selections.push((selection.map(|_| anchor), 0));
3750 edits.push((selection.start..selection.end, text.clone()));
3751 }
3752
3753 drop(snapshot);
3754
3755 self.transact(window, cx, |this, window, cx| {
3756 if clear_linked_edit_ranges {
3757 this.linked_edit_ranges.clear();
3758 }
3759 let initial_buffer_versions =
3760 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3761
3762 this.buffer.update(cx, |buffer, cx| {
3763 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3764 });
3765 for (buffer, edits) in linked_edits {
3766 buffer.update(cx, |buffer, cx| {
3767 let snapshot = buffer.snapshot();
3768 let edits = edits
3769 .into_iter()
3770 .map(|(range, text)| {
3771 use text::ToPoint as TP;
3772 let end_point = TP::to_point(&range.end, &snapshot);
3773 let start_point = TP::to_point(&range.start, &snapshot);
3774 (start_point..end_point, text)
3775 })
3776 .sorted_by_key(|(range, _)| range.start);
3777 buffer.edit(edits, None, cx);
3778 })
3779 }
3780 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3781 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3782 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3783 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3784 .zip(new_selection_deltas)
3785 .map(|(selection, delta)| Selection {
3786 id: selection.id,
3787 start: selection.start + delta,
3788 end: selection.end + delta,
3789 reversed: selection.reversed,
3790 goal: SelectionGoal::None,
3791 })
3792 .collect::<Vec<_>>();
3793
3794 let mut i = 0;
3795 for (position, delta, selection_id, pair) in new_autoclose_regions {
3796 let position = position.to_offset(&map.buffer_snapshot) + delta;
3797 let start = map.buffer_snapshot.anchor_before(position);
3798 let end = map.buffer_snapshot.anchor_after(position);
3799 while let Some(existing_state) = this.autoclose_regions.get(i) {
3800 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3801 Ordering::Less => i += 1,
3802 Ordering::Greater => break,
3803 Ordering::Equal => {
3804 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3805 Ordering::Less => i += 1,
3806 Ordering::Equal => break,
3807 Ordering::Greater => break,
3808 }
3809 }
3810 }
3811 }
3812 this.autoclose_regions.insert(
3813 i,
3814 AutocloseRegion {
3815 selection_id,
3816 range: start..end,
3817 pair,
3818 },
3819 );
3820 }
3821
3822 let had_active_inline_completion = this.has_active_inline_completion();
3823 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3824 s.select(new_selections)
3825 });
3826
3827 if !bracket_inserted {
3828 if let Some(on_type_format_task) =
3829 this.trigger_on_type_formatting(text.to_string(), window, cx)
3830 {
3831 on_type_format_task.detach_and_log_err(cx);
3832 }
3833 }
3834
3835 let editor_settings = EditorSettings::get_global(cx);
3836 if bracket_inserted
3837 && (editor_settings.auto_signature_help
3838 || editor_settings.show_signature_help_after_edits)
3839 {
3840 this.show_signature_help(&ShowSignatureHelp, window, cx);
3841 }
3842
3843 let trigger_in_words =
3844 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3845 if this.hard_wrap.is_some() {
3846 let latest: Range<Point> = this.selections.newest(cx).range();
3847 if latest.is_empty()
3848 && this
3849 .buffer()
3850 .read(cx)
3851 .snapshot(cx)
3852 .line_len(MultiBufferRow(latest.start.row))
3853 == latest.start.column
3854 {
3855 this.rewrap_impl(
3856 RewrapOptions {
3857 override_language_settings: true,
3858 preserve_existing_whitespace: true,
3859 },
3860 cx,
3861 )
3862 }
3863 }
3864 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3865 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3866 this.refresh_inline_completion(true, false, window, cx);
3867 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3868 });
3869 }
3870
3871 fn find_possible_emoji_shortcode_at_position(
3872 snapshot: &MultiBufferSnapshot,
3873 position: Point,
3874 ) -> Option<String> {
3875 let mut chars = Vec::new();
3876 let mut found_colon = false;
3877 for char in snapshot.reversed_chars_at(position).take(100) {
3878 // Found a possible emoji shortcode in the middle of the buffer
3879 if found_colon {
3880 if char.is_whitespace() {
3881 chars.reverse();
3882 return Some(chars.iter().collect());
3883 }
3884 // If the previous character is not a whitespace, we are in the middle of a word
3885 // and we only want to complete the shortcode if the word is made up of other emojis
3886 let mut containing_word = String::new();
3887 for ch in snapshot
3888 .reversed_chars_at(position)
3889 .skip(chars.len() + 1)
3890 .take(100)
3891 {
3892 if ch.is_whitespace() {
3893 break;
3894 }
3895 containing_word.push(ch);
3896 }
3897 let containing_word = containing_word.chars().rev().collect::<String>();
3898 if util::word_consists_of_emojis(containing_word.as_str()) {
3899 chars.reverse();
3900 return Some(chars.iter().collect());
3901 }
3902 }
3903
3904 if char.is_whitespace() || !char.is_ascii() {
3905 return None;
3906 }
3907 if char == ':' {
3908 found_colon = true;
3909 } else {
3910 chars.push(char);
3911 }
3912 }
3913 // Found a possible emoji shortcode at the beginning of the buffer
3914 chars.reverse();
3915 Some(chars.iter().collect())
3916 }
3917
3918 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3919 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3920 self.transact(window, cx, |this, window, cx| {
3921 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3922 let selections = this.selections.all::<usize>(cx);
3923 let multi_buffer = this.buffer.read(cx);
3924 let buffer = multi_buffer.snapshot(cx);
3925 selections
3926 .iter()
3927 .map(|selection| {
3928 let start_point = selection.start.to_point(&buffer);
3929 let mut existing_indent =
3930 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3931 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3932 let start = selection.start;
3933 let end = selection.end;
3934 let selection_is_empty = start == end;
3935 let language_scope = buffer.language_scope_at(start);
3936 let (
3937 comment_delimiter,
3938 doc_delimiter,
3939 insert_extra_newline,
3940 indent_on_newline,
3941 indent_on_extra_newline,
3942 ) = if let Some(language) = &language_scope {
3943 let mut insert_extra_newline =
3944 insert_extra_newline_brackets(&buffer, start..end, language)
3945 || insert_extra_newline_tree_sitter(&buffer, start..end);
3946
3947 // Comment extension on newline is allowed only for cursor selections
3948 let comment_delimiter = maybe!({
3949 if !selection_is_empty {
3950 return None;
3951 }
3952
3953 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
3954 return None;
3955 }
3956
3957 let delimiters = language.line_comment_prefixes();
3958 let max_len_of_delimiter =
3959 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
3960 let (snapshot, range) =
3961 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
3962
3963 let num_of_whitespaces = snapshot
3964 .chars_for_range(range.clone())
3965 .take_while(|c| c.is_whitespace())
3966 .count();
3967 let comment_candidate = snapshot
3968 .chars_for_range(range)
3969 .skip(num_of_whitespaces)
3970 .take(max_len_of_delimiter)
3971 .collect::<String>();
3972 let (delimiter, trimmed_len) = delimiters
3973 .iter()
3974 .filter_map(|delimiter| {
3975 let prefix = delimiter.trim_end();
3976 if comment_candidate.starts_with(prefix) {
3977 Some((delimiter, prefix.len()))
3978 } else {
3979 None
3980 }
3981 })
3982 .max_by_key(|(_, len)| *len)?;
3983
3984 let cursor_is_placed_after_comment_marker =
3985 num_of_whitespaces + trimmed_len <= start_point.column as usize;
3986 if cursor_is_placed_after_comment_marker {
3987 Some(delimiter.clone())
3988 } else {
3989 None
3990 }
3991 });
3992
3993 let mut indent_on_newline = IndentSize::spaces(0);
3994 let mut indent_on_extra_newline = IndentSize::spaces(0);
3995
3996 let doc_delimiter = maybe!({
3997 if !selection_is_empty {
3998 return None;
3999 }
4000
4001 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4002 return None;
4003 }
4004
4005 let DocumentationConfig {
4006 start: start_tag,
4007 end: end_tag,
4008 prefix: delimiter,
4009 tab_size: len,
4010 } = language.documentation()?;
4011
4012 let is_within_block_comment = buffer
4013 .language_scope_at(start_point)
4014 .is_some_and(|scope| scope.override_name() == Some("comment"));
4015 if !is_within_block_comment {
4016 return None;
4017 }
4018
4019 let (snapshot, range) =
4020 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4021
4022 let num_of_whitespaces = snapshot
4023 .chars_for_range(range.clone())
4024 .take_while(|c| c.is_whitespace())
4025 .count();
4026
4027 // 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.
4028 let column = start_point.column;
4029 let cursor_is_after_start_tag = {
4030 let start_tag_len = start_tag.len();
4031 let start_tag_line = snapshot
4032 .chars_for_range(range.clone())
4033 .skip(num_of_whitespaces)
4034 .take(start_tag_len)
4035 .collect::<String>();
4036 if start_tag_line.starts_with(start_tag.as_ref()) {
4037 num_of_whitespaces + start_tag_len <= column as usize
4038 } else {
4039 false
4040 }
4041 };
4042
4043 let cursor_is_after_delimiter = {
4044 let delimiter_trim = delimiter.trim_end();
4045 let delimiter_line = snapshot
4046 .chars_for_range(range.clone())
4047 .skip(num_of_whitespaces)
4048 .take(delimiter_trim.len())
4049 .collect::<String>();
4050 if delimiter_line.starts_with(delimiter_trim) {
4051 num_of_whitespaces + delimiter_trim.len() <= column as usize
4052 } else {
4053 false
4054 }
4055 };
4056
4057 let cursor_is_before_end_tag_if_exists = {
4058 let mut char_position = 0u32;
4059 let mut end_tag_offset = None;
4060
4061 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4062 if let Some(byte_pos) = chunk.find(&**end_tag) {
4063 let chars_before_match =
4064 chunk[..byte_pos].chars().count() as u32;
4065 end_tag_offset =
4066 Some(char_position + chars_before_match);
4067 break 'outer;
4068 }
4069 char_position += chunk.chars().count() as u32;
4070 }
4071
4072 if let Some(end_tag_offset) = end_tag_offset {
4073 let cursor_is_before_end_tag = column <= end_tag_offset;
4074 if cursor_is_after_start_tag {
4075 if cursor_is_before_end_tag {
4076 insert_extra_newline = true;
4077 }
4078 let cursor_is_at_start_of_end_tag =
4079 column == end_tag_offset;
4080 if cursor_is_at_start_of_end_tag {
4081 indent_on_extra_newline.len = (*len).into();
4082 }
4083 }
4084 cursor_is_before_end_tag
4085 } else {
4086 true
4087 }
4088 };
4089
4090 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4091 && cursor_is_before_end_tag_if_exists
4092 {
4093 if cursor_is_after_start_tag {
4094 indent_on_newline.len = (*len).into();
4095 }
4096 Some(delimiter.clone())
4097 } else {
4098 None
4099 }
4100 });
4101
4102 (
4103 comment_delimiter,
4104 doc_delimiter,
4105 insert_extra_newline,
4106 indent_on_newline,
4107 indent_on_extra_newline,
4108 )
4109 } else {
4110 (
4111 None,
4112 None,
4113 false,
4114 IndentSize::default(),
4115 IndentSize::default(),
4116 )
4117 };
4118
4119 let prevent_auto_indent = doc_delimiter.is_some();
4120 let delimiter = comment_delimiter.or(doc_delimiter);
4121
4122 let capacity_for_delimiter =
4123 delimiter.as_deref().map(str::len).unwrap_or_default();
4124 let mut new_text = String::with_capacity(
4125 1 + capacity_for_delimiter
4126 + existing_indent.len as usize
4127 + indent_on_newline.len as usize
4128 + indent_on_extra_newline.len as usize,
4129 );
4130 new_text.push('\n');
4131 new_text.extend(existing_indent.chars());
4132 new_text.extend(indent_on_newline.chars());
4133
4134 if let Some(delimiter) = &delimiter {
4135 new_text.push_str(delimiter);
4136 }
4137
4138 if insert_extra_newline {
4139 new_text.push('\n');
4140 new_text.extend(existing_indent.chars());
4141 new_text.extend(indent_on_extra_newline.chars());
4142 }
4143
4144 let anchor = buffer.anchor_after(end);
4145 let new_selection = selection.map(|_| anchor);
4146 (
4147 ((start..end, new_text), prevent_auto_indent),
4148 (insert_extra_newline, new_selection),
4149 )
4150 })
4151 .unzip()
4152 };
4153
4154 let mut auto_indent_edits = Vec::new();
4155 let mut edits = Vec::new();
4156 for (edit, prevent_auto_indent) in edits_with_flags {
4157 if prevent_auto_indent {
4158 edits.push(edit);
4159 } else {
4160 auto_indent_edits.push(edit);
4161 }
4162 }
4163 if !edits.is_empty() {
4164 this.edit(edits, cx);
4165 }
4166 if !auto_indent_edits.is_empty() {
4167 this.edit_with_autoindent(auto_indent_edits, cx);
4168 }
4169
4170 let buffer = this.buffer.read(cx).snapshot(cx);
4171 let new_selections = selection_info
4172 .into_iter()
4173 .map(|(extra_newline_inserted, new_selection)| {
4174 let mut cursor = new_selection.end.to_point(&buffer);
4175 if extra_newline_inserted {
4176 cursor.row -= 1;
4177 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4178 }
4179 new_selection.map(|_| cursor)
4180 })
4181 .collect();
4182
4183 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4184 s.select(new_selections)
4185 });
4186 this.refresh_inline_completion(true, false, window, cx);
4187 });
4188 }
4189
4190 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4191 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4192
4193 let buffer = self.buffer.read(cx);
4194 let snapshot = buffer.snapshot(cx);
4195
4196 let mut edits = Vec::new();
4197 let mut rows = Vec::new();
4198
4199 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4200 let cursor = selection.head();
4201 let row = cursor.row;
4202
4203 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4204
4205 let newline = "\n".to_string();
4206 edits.push((start_of_line..start_of_line, newline));
4207
4208 rows.push(row + rows_inserted as u32);
4209 }
4210
4211 self.transact(window, cx, |editor, window, cx| {
4212 editor.edit(edits, cx);
4213
4214 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4215 let mut index = 0;
4216 s.move_cursors_with(|map, _, _| {
4217 let row = rows[index];
4218 index += 1;
4219
4220 let point = Point::new(row, 0);
4221 let boundary = map.next_line_boundary(point).1;
4222 let clipped = map.clip_point(boundary, Bias::Left);
4223
4224 (clipped, SelectionGoal::None)
4225 });
4226 });
4227
4228 let mut indent_edits = Vec::new();
4229 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4230 for row in rows {
4231 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4232 for (row, indent) in indents {
4233 if indent.len == 0 {
4234 continue;
4235 }
4236
4237 let text = match indent.kind {
4238 IndentKind::Space => " ".repeat(indent.len as usize),
4239 IndentKind::Tab => "\t".repeat(indent.len as usize),
4240 };
4241 let point = Point::new(row.0, 0);
4242 indent_edits.push((point..point, text));
4243 }
4244 }
4245 editor.edit(indent_edits, cx);
4246 });
4247 }
4248
4249 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4250 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4251
4252 let buffer = self.buffer.read(cx);
4253 let snapshot = buffer.snapshot(cx);
4254
4255 let mut edits = Vec::new();
4256 let mut rows = Vec::new();
4257 let mut rows_inserted = 0;
4258
4259 for selection in self.selections.all_adjusted(cx) {
4260 let cursor = selection.head();
4261 let row = cursor.row;
4262
4263 let point = Point::new(row + 1, 0);
4264 let start_of_line = snapshot.clip_point(point, Bias::Left);
4265
4266 let newline = "\n".to_string();
4267 edits.push((start_of_line..start_of_line, newline));
4268
4269 rows_inserted += 1;
4270 rows.push(row + rows_inserted);
4271 }
4272
4273 self.transact(window, cx, |editor, window, cx| {
4274 editor.edit(edits, cx);
4275
4276 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4277 let mut index = 0;
4278 s.move_cursors_with(|map, _, _| {
4279 let row = rows[index];
4280 index += 1;
4281
4282 let point = Point::new(row, 0);
4283 let boundary = map.next_line_boundary(point).1;
4284 let clipped = map.clip_point(boundary, Bias::Left);
4285
4286 (clipped, SelectionGoal::None)
4287 });
4288 });
4289
4290 let mut indent_edits = Vec::new();
4291 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4292 for row in rows {
4293 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4294 for (row, indent) in indents {
4295 if indent.len == 0 {
4296 continue;
4297 }
4298
4299 let text = match indent.kind {
4300 IndentKind::Space => " ".repeat(indent.len as usize),
4301 IndentKind::Tab => "\t".repeat(indent.len as usize),
4302 };
4303 let point = Point::new(row.0, 0);
4304 indent_edits.push((point..point, text));
4305 }
4306 }
4307 editor.edit(indent_edits, cx);
4308 });
4309 }
4310
4311 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4312 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4313 original_indent_columns: Vec::new(),
4314 });
4315 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4316 }
4317
4318 fn insert_with_autoindent_mode(
4319 &mut self,
4320 text: &str,
4321 autoindent_mode: Option<AutoindentMode>,
4322 window: &mut Window,
4323 cx: &mut Context<Self>,
4324 ) {
4325 if self.read_only(cx) {
4326 return;
4327 }
4328
4329 let text: Arc<str> = text.into();
4330 self.transact(window, cx, |this, window, cx| {
4331 let old_selections = this.selections.all_adjusted(cx);
4332 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4333 let anchors = {
4334 let snapshot = buffer.read(cx);
4335 old_selections
4336 .iter()
4337 .map(|s| {
4338 let anchor = snapshot.anchor_after(s.head());
4339 s.map(|_| anchor)
4340 })
4341 .collect::<Vec<_>>()
4342 };
4343 buffer.edit(
4344 old_selections
4345 .iter()
4346 .map(|s| (s.start..s.end, text.clone())),
4347 autoindent_mode,
4348 cx,
4349 );
4350 anchors
4351 });
4352
4353 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4354 s.select_anchors(selection_anchors);
4355 });
4356
4357 cx.notify();
4358 });
4359 }
4360
4361 fn trigger_completion_on_input(
4362 &mut self,
4363 text: &str,
4364 trigger_in_words: bool,
4365 window: &mut Window,
4366 cx: &mut Context<Self>,
4367 ) {
4368 let ignore_completion_provider = self
4369 .context_menu
4370 .borrow()
4371 .as_ref()
4372 .map(|menu| match menu {
4373 CodeContextMenu::Completions(completions_menu) => {
4374 completions_menu.ignore_completion_provider
4375 }
4376 CodeContextMenu::CodeActions(_) => false,
4377 })
4378 .unwrap_or(false);
4379
4380 if ignore_completion_provider {
4381 self.show_word_completions(&ShowWordCompletions, window, cx);
4382 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4383 self.show_completions(
4384 &ShowCompletions {
4385 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4386 },
4387 window,
4388 cx,
4389 );
4390 } else {
4391 self.hide_context_menu(window, cx);
4392 }
4393 }
4394
4395 fn is_completion_trigger(
4396 &self,
4397 text: &str,
4398 trigger_in_words: bool,
4399 cx: &mut Context<Self>,
4400 ) -> bool {
4401 let position = self.selections.newest_anchor().head();
4402 let multibuffer = self.buffer.read(cx);
4403 let Some(buffer) = position
4404 .buffer_id
4405 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4406 else {
4407 return false;
4408 };
4409
4410 if let Some(completion_provider) = &self.completion_provider {
4411 completion_provider.is_completion_trigger(
4412 &buffer,
4413 position.text_anchor,
4414 text,
4415 trigger_in_words,
4416 cx,
4417 )
4418 } else {
4419 false
4420 }
4421 }
4422
4423 /// If any empty selections is touching the start of its innermost containing autoclose
4424 /// region, expand it to select the brackets.
4425 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4426 let selections = self.selections.all::<usize>(cx);
4427 let buffer = self.buffer.read(cx).read(cx);
4428 let new_selections = self
4429 .selections_with_autoclose_regions(selections, &buffer)
4430 .map(|(mut selection, region)| {
4431 if !selection.is_empty() {
4432 return selection;
4433 }
4434
4435 if let Some(region) = region {
4436 let mut range = region.range.to_offset(&buffer);
4437 if selection.start == range.start && range.start >= region.pair.start.len() {
4438 range.start -= region.pair.start.len();
4439 if buffer.contains_str_at(range.start, ®ion.pair.start)
4440 && buffer.contains_str_at(range.end, ®ion.pair.end)
4441 {
4442 range.end += region.pair.end.len();
4443 selection.start = range.start;
4444 selection.end = range.end;
4445
4446 return selection;
4447 }
4448 }
4449 }
4450
4451 let always_treat_brackets_as_autoclosed = buffer
4452 .language_settings_at(selection.start, cx)
4453 .always_treat_brackets_as_autoclosed;
4454
4455 if !always_treat_brackets_as_autoclosed {
4456 return selection;
4457 }
4458
4459 if let Some(scope) = buffer.language_scope_at(selection.start) {
4460 for (pair, enabled) in scope.brackets() {
4461 if !enabled || !pair.close {
4462 continue;
4463 }
4464
4465 if buffer.contains_str_at(selection.start, &pair.end) {
4466 let pair_start_len = pair.start.len();
4467 if buffer.contains_str_at(
4468 selection.start.saturating_sub(pair_start_len),
4469 &pair.start,
4470 ) {
4471 selection.start -= pair_start_len;
4472 selection.end += pair.end.len();
4473
4474 return selection;
4475 }
4476 }
4477 }
4478 }
4479
4480 selection
4481 })
4482 .collect();
4483
4484 drop(buffer);
4485 self.change_selections(None, window, cx, |selections| {
4486 selections.select(new_selections)
4487 });
4488 }
4489
4490 /// Iterate the given selections, and for each one, find the smallest surrounding
4491 /// autoclose region. This uses the ordering of the selections and the autoclose
4492 /// regions to avoid repeated comparisons.
4493 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4494 &'a self,
4495 selections: impl IntoIterator<Item = Selection<D>>,
4496 buffer: &'a MultiBufferSnapshot,
4497 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4498 let mut i = 0;
4499 let mut regions = self.autoclose_regions.as_slice();
4500 selections.into_iter().map(move |selection| {
4501 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4502
4503 let mut enclosing = None;
4504 while let Some(pair_state) = regions.get(i) {
4505 if pair_state.range.end.to_offset(buffer) < range.start {
4506 regions = ®ions[i + 1..];
4507 i = 0;
4508 } else if pair_state.range.start.to_offset(buffer) > range.end {
4509 break;
4510 } else {
4511 if pair_state.selection_id == selection.id {
4512 enclosing = Some(pair_state);
4513 }
4514 i += 1;
4515 }
4516 }
4517
4518 (selection, enclosing)
4519 })
4520 }
4521
4522 /// Remove any autoclose regions that no longer contain their selection.
4523 fn invalidate_autoclose_regions(
4524 &mut self,
4525 mut selections: &[Selection<Anchor>],
4526 buffer: &MultiBufferSnapshot,
4527 ) {
4528 self.autoclose_regions.retain(|state| {
4529 let mut i = 0;
4530 while let Some(selection) = selections.get(i) {
4531 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4532 selections = &selections[1..];
4533 continue;
4534 }
4535 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4536 break;
4537 }
4538 if selection.id == state.selection_id {
4539 return true;
4540 } else {
4541 i += 1;
4542 }
4543 }
4544 false
4545 });
4546 }
4547
4548 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4549 let offset = position.to_offset(buffer);
4550 let (word_range, kind) = buffer.surrounding_word(offset, true);
4551 if offset > word_range.start && kind == Some(CharKind::Word) {
4552 Some(
4553 buffer
4554 .text_for_range(word_range.start..offset)
4555 .collect::<String>(),
4556 )
4557 } else {
4558 None
4559 }
4560 }
4561
4562 pub fn toggle_inline_values(
4563 &mut self,
4564 _: &ToggleInlineValues,
4565 _: &mut Window,
4566 cx: &mut Context<Self>,
4567 ) {
4568 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4569
4570 self.refresh_inline_values(cx);
4571 }
4572
4573 pub fn toggle_inlay_hints(
4574 &mut self,
4575 _: &ToggleInlayHints,
4576 _: &mut Window,
4577 cx: &mut Context<Self>,
4578 ) {
4579 self.refresh_inlay_hints(
4580 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4581 cx,
4582 );
4583 }
4584
4585 pub fn inlay_hints_enabled(&self) -> bool {
4586 self.inlay_hint_cache.enabled
4587 }
4588
4589 pub fn inline_values_enabled(&self) -> bool {
4590 self.inline_value_cache.enabled
4591 }
4592
4593 #[cfg(any(test, feature = "test-support"))]
4594 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4595 self.display_map
4596 .read(cx)
4597 .current_inlays()
4598 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4599 .cloned()
4600 .collect()
4601 }
4602
4603 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4604 if self.semantics_provider.is_none() || !self.mode.is_full() {
4605 return;
4606 }
4607
4608 let reason_description = reason.description();
4609 let ignore_debounce = matches!(
4610 reason,
4611 InlayHintRefreshReason::SettingsChange(_)
4612 | InlayHintRefreshReason::Toggle(_)
4613 | InlayHintRefreshReason::ExcerptsRemoved(_)
4614 | InlayHintRefreshReason::ModifiersChanged(_)
4615 );
4616 let (invalidate_cache, required_languages) = match reason {
4617 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4618 match self.inlay_hint_cache.modifiers_override(enabled) {
4619 Some(enabled) => {
4620 if enabled {
4621 (InvalidationStrategy::RefreshRequested, None)
4622 } else {
4623 self.splice_inlays(
4624 &self
4625 .visible_inlay_hints(cx)
4626 .iter()
4627 .map(|inlay| inlay.id)
4628 .collect::<Vec<InlayId>>(),
4629 Vec::new(),
4630 cx,
4631 );
4632 return;
4633 }
4634 }
4635 None => return,
4636 }
4637 }
4638 InlayHintRefreshReason::Toggle(enabled) => {
4639 if self.inlay_hint_cache.toggle(enabled) {
4640 if enabled {
4641 (InvalidationStrategy::RefreshRequested, None)
4642 } else {
4643 self.splice_inlays(
4644 &self
4645 .visible_inlay_hints(cx)
4646 .iter()
4647 .map(|inlay| inlay.id)
4648 .collect::<Vec<InlayId>>(),
4649 Vec::new(),
4650 cx,
4651 );
4652 return;
4653 }
4654 } else {
4655 return;
4656 }
4657 }
4658 InlayHintRefreshReason::SettingsChange(new_settings) => {
4659 match self.inlay_hint_cache.update_settings(
4660 &self.buffer,
4661 new_settings,
4662 self.visible_inlay_hints(cx),
4663 cx,
4664 ) {
4665 ControlFlow::Break(Some(InlaySplice {
4666 to_remove,
4667 to_insert,
4668 })) => {
4669 self.splice_inlays(&to_remove, to_insert, cx);
4670 return;
4671 }
4672 ControlFlow::Break(None) => return,
4673 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4674 }
4675 }
4676 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4677 if let Some(InlaySplice {
4678 to_remove,
4679 to_insert,
4680 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4681 {
4682 self.splice_inlays(&to_remove, to_insert, cx);
4683 }
4684 self.display_map.update(cx, |display_map, _| {
4685 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4686 });
4687 return;
4688 }
4689 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4690 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4691 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4692 }
4693 InlayHintRefreshReason::RefreshRequested => {
4694 (InvalidationStrategy::RefreshRequested, None)
4695 }
4696 };
4697
4698 if let Some(InlaySplice {
4699 to_remove,
4700 to_insert,
4701 }) = self.inlay_hint_cache.spawn_hint_refresh(
4702 reason_description,
4703 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4704 invalidate_cache,
4705 ignore_debounce,
4706 cx,
4707 ) {
4708 self.splice_inlays(&to_remove, to_insert, cx);
4709 }
4710 }
4711
4712 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4713 self.display_map
4714 .read(cx)
4715 .current_inlays()
4716 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4717 .cloned()
4718 .collect()
4719 }
4720
4721 pub fn excerpts_for_inlay_hints_query(
4722 &self,
4723 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4724 cx: &mut Context<Editor>,
4725 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4726 let Some(project) = self.project.as_ref() else {
4727 return HashMap::default();
4728 };
4729 let project = project.read(cx);
4730 let multi_buffer = self.buffer().read(cx);
4731 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4732 let multi_buffer_visible_start = self
4733 .scroll_manager
4734 .anchor()
4735 .anchor
4736 .to_point(&multi_buffer_snapshot);
4737 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4738 multi_buffer_visible_start
4739 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4740 Bias::Left,
4741 );
4742 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4743 multi_buffer_snapshot
4744 .range_to_buffer_ranges(multi_buffer_visible_range)
4745 .into_iter()
4746 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4747 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4748 let buffer_file = project::File::from_dyn(buffer.file())?;
4749 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4750 let worktree_entry = buffer_worktree
4751 .read(cx)
4752 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4753 if worktree_entry.is_ignored {
4754 return None;
4755 }
4756
4757 let language = buffer.language()?;
4758 if let Some(restrict_to_languages) = restrict_to_languages {
4759 if !restrict_to_languages.contains(language) {
4760 return None;
4761 }
4762 }
4763 Some((
4764 excerpt_id,
4765 (
4766 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4767 buffer.version().clone(),
4768 excerpt_visible_range,
4769 ),
4770 ))
4771 })
4772 .collect()
4773 }
4774
4775 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4776 TextLayoutDetails {
4777 text_system: window.text_system().clone(),
4778 editor_style: self.style.clone().unwrap(),
4779 rem_size: window.rem_size(),
4780 scroll_anchor: self.scroll_manager.anchor(),
4781 visible_rows: self.visible_line_count(),
4782 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4783 }
4784 }
4785
4786 pub fn splice_inlays(
4787 &self,
4788 to_remove: &[InlayId],
4789 to_insert: Vec<Inlay>,
4790 cx: &mut Context<Self>,
4791 ) {
4792 self.display_map.update(cx, |display_map, cx| {
4793 display_map.splice_inlays(to_remove, to_insert, cx)
4794 });
4795 cx.notify();
4796 }
4797
4798 fn trigger_on_type_formatting(
4799 &self,
4800 input: String,
4801 window: &mut Window,
4802 cx: &mut Context<Self>,
4803 ) -> Option<Task<Result<()>>> {
4804 if input.len() != 1 {
4805 return None;
4806 }
4807
4808 let project = self.project.as_ref()?;
4809 let position = self.selections.newest_anchor().head();
4810 let (buffer, buffer_position) = self
4811 .buffer
4812 .read(cx)
4813 .text_anchor_for_position(position, cx)?;
4814
4815 let settings = language_settings::language_settings(
4816 buffer
4817 .read(cx)
4818 .language_at(buffer_position)
4819 .map(|l| l.name()),
4820 buffer.read(cx).file(),
4821 cx,
4822 );
4823 if !settings.use_on_type_format {
4824 return None;
4825 }
4826
4827 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4828 // hence we do LSP request & edit on host side only — add formats to host's history.
4829 let push_to_lsp_host_history = true;
4830 // If this is not the host, append its history with new edits.
4831 let push_to_client_history = project.read(cx).is_via_collab();
4832
4833 let on_type_formatting = project.update(cx, |project, cx| {
4834 project.on_type_format(
4835 buffer.clone(),
4836 buffer_position,
4837 input,
4838 push_to_lsp_host_history,
4839 cx,
4840 )
4841 });
4842 Some(cx.spawn_in(window, async move |editor, cx| {
4843 if let Some(transaction) = on_type_formatting.await? {
4844 if push_to_client_history {
4845 buffer
4846 .update(cx, |buffer, _| {
4847 buffer.push_transaction(transaction, Instant::now());
4848 buffer.finalize_last_transaction();
4849 })
4850 .ok();
4851 }
4852 editor.update(cx, |editor, cx| {
4853 editor.refresh_document_highlights(cx);
4854 })?;
4855 }
4856 Ok(())
4857 }))
4858 }
4859
4860 pub fn show_word_completions(
4861 &mut self,
4862 _: &ShowWordCompletions,
4863 window: &mut Window,
4864 cx: &mut Context<Self>,
4865 ) {
4866 self.open_completions_menu(true, None, window, cx);
4867 }
4868
4869 pub fn show_completions(
4870 &mut self,
4871 options: &ShowCompletions,
4872 window: &mut Window,
4873 cx: &mut Context<Self>,
4874 ) {
4875 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4876 }
4877
4878 fn open_completions_menu(
4879 &mut self,
4880 ignore_completion_provider: bool,
4881 trigger: Option<&str>,
4882 window: &mut Window,
4883 cx: &mut Context<Self>,
4884 ) {
4885 if self.pending_rename.is_some() {
4886 return;
4887 }
4888 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4889 return;
4890 }
4891
4892 let position = self.selections.newest_anchor().head();
4893 if position.diff_base_anchor.is_some() {
4894 return;
4895 }
4896 let (buffer, buffer_position) =
4897 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4898 output
4899 } else {
4900 return;
4901 };
4902 let buffer_snapshot = buffer.read(cx).snapshot();
4903 let show_completion_documentation = buffer_snapshot
4904 .settings_at(buffer_position, cx)
4905 .show_completion_documentation;
4906
4907 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4908
4909 let trigger_kind = match trigger {
4910 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4911 CompletionTriggerKind::TRIGGER_CHARACTER
4912 }
4913 _ => CompletionTriggerKind::INVOKED,
4914 };
4915 let completion_context = CompletionContext {
4916 trigger_character: trigger.and_then(|trigger| {
4917 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4918 Some(String::from(trigger))
4919 } else {
4920 None
4921 }
4922 }),
4923 trigger_kind,
4924 };
4925
4926 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4927 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4928 let word_to_exclude = buffer_snapshot
4929 .text_for_range(old_range.clone())
4930 .collect::<String>();
4931 (
4932 buffer_snapshot.anchor_before(old_range.start)
4933 ..buffer_snapshot.anchor_after(old_range.end),
4934 Some(word_to_exclude),
4935 )
4936 } else {
4937 (buffer_position..buffer_position, None)
4938 };
4939
4940 let completion_settings = language_settings(
4941 buffer_snapshot
4942 .language_at(buffer_position)
4943 .map(|language| language.name()),
4944 buffer_snapshot.file(),
4945 cx,
4946 )
4947 .completions;
4948
4949 // The document can be large, so stay in reasonable bounds when searching for words,
4950 // otherwise completion pop-up might be slow to appear.
4951 const WORD_LOOKUP_ROWS: u32 = 5_000;
4952 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
4953 let min_word_search = buffer_snapshot.clip_point(
4954 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
4955 Bias::Left,
4956 );
4957 let max_word_search = buffer_snapshot.clip_point(
4958 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
4959 Bias::Right,
4960 );
4961 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
4962 ..buffer_snapshot.point_to_offset(max_word_search);
4963
4964 let provider = if ignore_completion_provider {
4965 None
4966 } else {
4967 self.completion_provider.clone()
4968 };
4969 let skip_digits = query
4970 .as_ref()
4971 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
4972
4973 let (mut words, provided_completions) = match &provider {
4974 Some(provider) => {
4975 let completions = provider.completions(
4976 position.excerpt_id,
4977 &buffer,
4978 buffer_position,
4979 completion_context,
4980 window,
4981 cx,
4982 );
4983
4984 let words = match completion_settings.words {
4985 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
4986 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
4987 .background_spawn(async move {
4988 buffer_snapshot.words_in_range(WordsQuery {
4989 fuzzy_contents: None,
4990 range: word_search_range,
4991 skip_digits,
4992 })
4993 }),
4994 };
4995
4996 (words, completions)
4997 }
4998 None => (
4999 cx.background_spawn(async move {
5000 buffer_snapshot.words_in_range(WordsQuery {
5001 fuzzy_contents: None,
5002 range: word_search_range,
5003 skip_digits,
5004 })
5005 }),
5006 Task::ready(Ok(None)),
5007 ),
5008 };
5009
5010 let sort_completions = provider
5011 .as_ref()
5012 .map_or(false, |provider| provider.sort_completions());
5013
5014 let filter_completions = provider
5015 .as_ref()
5016 .map_or(true, |provider| provider.filter_completions());
5017
5018 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5019
5020 let id = post_inc(&mut self.next_completion_id);
5021 let task = cx.spawn_in(window, async move |editor, cx| {
5022 async move {
5023 editor.update(cx, |this, _| {
5024 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5025 })?;
5026
5027 let mut completions = Vec::new();
5028 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5029 completions.extend(provided_completions);
5030 if completion_settings.words == WordsCompletionMode::Fallback {
5031 words = Task::ready(BTreeMap::default());
5032 }
5033 }
5034
5035 let mut words = words.await;
5036 if let Some(word_to_exclude) = &word_to_exclude {
5037 words.remove(word_to_exclude);
5038 }
5039 for lsp_completion in &completions {
5040 words.remove(&lsp_completion.new_text);
5041 }
5042 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5043 replace_range: old_range.clone(),
5044 new_text: word.clone(),
5045 label: CodeLabel::plain(word, None),
5046 icon_path: None,
5047 documentation: None,
5048 source: CompletionSource::BufferWord {
5049 word_range,
5050 resolved: false,
5051 },
5052 insert_text_mode: Some(InsertTextMode::AS_IS),
5053 confirm: None,
5054 }));
5055
5056 let menu = if completions.is_empty() {
5057 None
5058 } else {
5059 let mut menu = CompletionsMenu::new(
5060 id,
5061 sort_completions,
5062 show_completion_documentation,
5063 ignore_completion_provider,
5064 position,
5065 buffer.clone(),
5066 completions.into(),
5067 snippet_sort_order,
5068 );
5069
5070 menu.filter(
5071 if filter_completions {
5072 query.as_deref()
5073 } else {
5074 None
5075 },
5076 provider,
5077 editor.clone(),
5078 cx,
5079 )
5080 .await;
5081
5082 menu.visible().then_some(menu)
5083 };
5084
5085 editor.update_in(cx, |editor, window, cx| {
5086 match editor.context_menu.borrow().as_ref() {
5087 None => {}
5088 Some(CodeContextMenu::Completions(prev_menu)) => {
5089 if prev_menu.id > id {
5090 return;
5091 }
5092 }
5093 _ => return,
5094 }
5095
5096 if editor.focus_handle.is_focused(window) && menu.is_some() {
5097 let mut menu = menu.unwrap();
5098 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5099 crate::hover_popover::hide_hover(editor, cx);
5100 *editor.context_menu.borrow_mut() =
5101 Some(CodeContextMenu::Completions(menu));
5102
5103 if editor.show_edit_predictions_in_menu() {
5104 editor.update_visible_inline_completion(window, cx);
5105 } else {
5106 editor.discard_inline_completion(false, cx);
5107 }
5108
5109 cx.notify();
5110 } else if editor.completion_tasks.len() <= 1 {
5111 // If there are no more completion tasks and the last menu was
5112 // empty, we should hide it.
5113 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5114 // If it was already hidden and we don't show inline
5115 // completions in the menu, we should also show the
5116 // inline-completion when available.
5117 if was_hidden && editor.show_edit_predictions_in_menu() {
5118 editor.update_visible_inline_completion(window, cx);
5119 }
5120 }
5121 })?;
5122
5123 anyhow::Ok(())
5124 }
5125 .log_err()
5126 .await
5127 });
5128
5129 self.completion_tasks.push((id, task));
5130 }
5131
5132 #[cfg(feature = "test-support")]
5133 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5134 let menu = self.context_menu.borrow();
5135 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5136 let completions = menu.completions.borrow();
5137 Some(completions.to_vec())
5138 } else {
5139 None
5140 }
5141 }
5142
5143 pub fn confirm_completion(
5144 &mut self,
5145 action: &ConfirmCompletion,
5146 window: &mut Window,
5147 cx: &mut Context<Self>,
5148 ) -> Option<Task<Result<()>>> {
5149 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5150 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5151 }
5152
5153 pub fn confirm_completion_insert(
5154 &mut self,
5155 _: &ConfirmCompletionInsert,
5156 window: &mut Window,
5157 cx: &mut Context<Self>,
5158 ) -> Option<Task<Result<()>>> {
5159 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5160 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5161 }
5162
5163 pub fn confirm_completion_replace(
5164 &mut self,
5165 _: &ConfirmCompletionReplace,
5166 window: &mut Window,
5167 cx: &mut Context<Self>,
5168 ) -> Option<Task<Result<()>>> {
5169 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5170 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5171 }
5172
5173 pub fn compose_completion(
5174 &mut self,
5175 action: &ComposeCompletion,
5176 window: &mut Window,
5177 cx: &mut Context<Self>,
5178 ) -> Option<Task<Result<()>>> {
5179 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5180 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5181 }
5182
5183 fn do_completion(
5184 &mut self,
5185 item_ix: Option<usize>,
5186 intent: CompletionIntent,
5187 window: &mut Window,
5188 cx: &mut Context<Editor>,
5189 ) -> Option<Task<Result<()>>> {
5190 use language::ToOffset as _;
5191
5192 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5193 else {
5194 return None;
5195 };
5196
5197 let candidate_id = {
5198 let entries = completions_menu.entries.borrow();
5199 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5200 if self.show_edit_predictions_in_menu() {
5201 self.discard_inline_completion(true, cx);
5202 }
5203 mat.candidate_id
5204 };
5205
5206 let buffer_handle = completions_menu.buffer;
5207 let completion = completions_menu
5208 .completions
5209 .borrow()
5210 .get(candidate_id)?
5211 .clone();
5212 cx.stop_propagation();
5213
5214 let snapshot = self.buffer.read(cx).snapshot(cx);
5215 let newest_anchor = self.selections.newest_anchor();
5216
5217 let snippet;
5218 let new_text;
5219 if completion.is_snippet() {
5220 let mut snippet_source = completion.new_text.clone();
5221 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5222 if scope.prefers_label_for_snippet_in_completion() {
5223 if let Some(label) = completion.label() {
5224 if matches!(
5225 completion.kind(),
5226 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5227 ) {
5228 snippet_source = label;
5229 }
5230 }
5231 }
5232 }
5233 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5234 new_text = snippet.as_ref().unwrap().text.clone();
5235 } else {
5236 snippet = None;
5237 new_text = completion.new_text.clone();
5238 };
5239
5240 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5241 let buffer = buffer_handle.read(cx);
5242 let replace_range_multibuffer = {
5243 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5244 let multibuffer_anchor = snapshot
5245 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5246 .unwrap()
5247 ..snapshot
5248 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5249 .unwrap();
5250 multibuffer_anchor.start.to_offset(&snapshot)
5251 ..multibuffer_anchor.end.to_offset(&snapshot)
5252 };
5253 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5254 return None;
5255 }
5256
5257 let old_text = buffer
5258 .text_for_range(replace_range.clone())
5259 .collect::<String>();
5260 let lookbehind = newest_anchor
5261 .start
5262 .text_anchor
5263 .to_offset(buffer)
5264 .saturating_sub(replace_range.start);
5265 let lookahead = replace_range
5266 .end
5267 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5268 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5269 let suffix = &old_text[lookbehind.min(old_text.len())..];
5270
5271 let selections = self.selections.all::<usize>(cx);
5272 let mut ranges = Vec::new();
5273 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5274
5275 for selection in &selections {
5276 let range = if selection.id == newest_anchor.id {
5277 replace_range_multibuffer.clone()
5278 } else {
5279 let mut range = selection.range();
5280
5281 // if prefix is present, don't duplicate it
5282 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5283 range.start = range.start.saturating_sub(lookbehind);
5284
5285 // if suffix is also present, mimic the newest cursor and replace it
5286 if selection.id != newest_anchor.id
5287 && snapshot.contains_str_at(range.end, suffix)
5288 {
5289 range.end += lookahead;
5290 }
5291 }
5292 range
5293 };
5294
5295 ranges.push(range.clone());
5296
5297 if !self.linked_edit_ranges.is_empty() {
5298 let start_anchor = snapshot.anchor_before(range.start);
5299 let end_anchor = snapshot.anchor_after(range.end);
5300 if let Some(ranges) = self
5301 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5302 {
5303 for (buffer, edits) in ranges {
5304 linked_edits
5305 .entry(buffer.clone())
5306 .or_default()
5307 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5308 }
5309 }
5310 }
5311 }
5312
5313 cx.emit(EditorEvent::InputHandled {
5314 utf16_range_to_replace: None,
5315 text: new_text.clone().into(),
5316 });
5317
5318 self.transact(window, cx, |this, window, cx| {
5319 if let Some(mut snippet) = snippet {
5320 snippet.text = new_text.to_string();
5321 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5322 } else {
5323 this.buffer.update(cx, |buffer, cx| {
5324 let auto_indent = match completion.insert_text_mode {
5325 Some(InsertTextMode::AS_IS) => None,
5326 _ => this.autoindent_mode.clone(),
5327 };
5328 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5329 buffer.edit(edits, auto_indent, cx);
5330 });
5331 }
5332 for (buffer, edits) in linked_edits {
5333 buffer.update(cx, |buffer, cx| {
5334 let snapshot = buffer.snapshot();
5335 let edits = edits
5336 .into_iter()
5337 .map(|(range, text)| {
5338 use text::ToPoint as TP;
5339 let end_point = TP::to_point(&range.end, &snapshot);
5340 let start_point = TP::to_point(&range.start, &snapshot);
5341 (start_point..end_point, text)
5342 })
5343 .sorted_by_key(|(range, _)| range.start);
5344 buffer.edit(edits, None, cx);
5345 })
5346 }
5347
5348 this.refresh_inline_completion(true, false, window, cx);
5349 });
5350
5351 let show_new_completions_on_confirm = completion
5352 .confirm
5353 .as_ref()
5354 .map_or(false, |confirm| confirm(intent, window, cx));
5355 if show_new_completions_on_confirm {
5356 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5357 }
5358
5359 let provider = self.completion_provider.as_ref()?;
5360 drop(completion);
5361 let apply_edits = provider.apply_additional_edits_for_completion(
5362 buffer_handle,
5363 completions_menu.completions.clone(),
5364 candidate_id,
5365 true,
5366 cx,
5367 );
5368
5369 let editor_settings = EditorSettings::get_global(cx);
5370 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5371 // After the code completion is finished, users often want to know what signatures are needed.
5372 // so we should automatically call signature_help
5373 self.show_signature_help(&ShowSignatureHelp, window, cx);
5374 }
5375
5376 Some(cx.foreground_executor().spawn(async move {
5377 apply_edits.await?;
5378 Ok(())
5379 }))
5380 }
5381
5382 pub fn toggle_code_actions(
5383 &mut self,
5384 action: &ToggleCodeActions,
5385 window: &mut Window,
5386 cx: &mut Context<Self>,
5387 ) {
5388 let quick_launch = action.quick_launch;
5389 let mut context_menu = self.context_menu.borrow_mut();
5390 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5391 if code_actions.deployed_from == action.deployed_from {
5392 // Toggle if we're selecting the same one
5393 *context_menu = None;
5394 cx.notify();
5395 return;
5396 } else {
5397 // Otherwise, clear it and start a new one
5398 *context_menu = None;
5399 cx.notify();
5400 }
5401 }
5402 drop(context_menu);
5403 let snapshot = self.snapshot(window, cx);
5404 let deployed_from = action.deployed_from.clone();
5405 let mut task = self.code_actions_task.take();
5406 let action = action.clone();
5407 cx.spawn_in(window, async move |editor, cx| {
5408 while let Some(prev_task) = task {
5409 prev_task.await.log_err();
5410 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5411 }
5412
5413 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5414 if editor.focus_handle.is_focused(window) {
5415 let multibuffer_point = match &action.deployed_from {
5416 Some(CodeActionSource::Indicator(row)) => {
5417 DisplayPoint::new(*row, 0).to_point(&snapshot)
5418 }
5419 _ => editor.selections.newest::<Point>(cx).head(),
5420 };
5421 let (buffer, buffer_row) = snapshot
5422 .buffer_snapshot
5423 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5424 .and_then(|(buffer_snapshot, range)| {
5425 editor
5426 .buffer
5427 .read(cx)
5428 .buffer(buffer_snapshot.remote_id())
5429 .map(|buffer| (buffer, range.start.row))
5430 })?;
5431 let (_, code_actions) = editor
5432 .available_code_actions
5433 .clone()
5434 .and_then(|(location, code_actions)| {
5435 let snapshot = location.buffer.read(cx).snapshot();
5436 let point_range = location.range.to_point(&snapshot);
5437 let point_range = point_range.start.row..=point_range.end.row;
5438 if point_range.contains(&buffer_row) {
5439 Some((location, code_actions))
5440 } else {
5441 None
5442 }
5443 })
5444 .unzip();
5445 let buffer_id = buffer.read(cx).remote_id();
5446 let tasks = editor
5447 .tasks
5448 .get(&(buffer_id, buffer_row))
5449 .map(|t| Arc::new(t.to_owned()));
5450 if tasks.is_none() && code_actions.is_none() {
5451 return None;
5452 }
5453
5454 editor.completion_tasks.clear();
5455 editor.discard_inline_completion(false, cx);
5456 let task_context =
5457 tasks
5458 .as_ref()
5459 .zip(editor.project.clone())
5460 .map(|(tasks, project)| {
5461 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5462 });
5463
5464 Some(cx.spawn_in(window, async move |editor, cx| {
5465 let task_context = match task_context {
5466 Some(task_context) => task_context.await,
5467 None => None,
5468 };
5469 let resolved_tasks =
5470 tasks
5471 .zip(task_context.clone())
5472 .map(|(tasks, task_context)| ResolvedTasks {
5473 templates: tasks.resolve(&task_context).collect(),
5474 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5475 multibuffer_point.row,
5476 tasks.column,
5477 )),
5478 });
5479 let debug_scenarios = editor.update(cx, |editor, cx| {
5480 if cx.has_flag::<DebuggerFeatureFlag>() {
5481 maybe!({
5482 let project = editor.project.as_ref()?;
5483 let dap_store = project.read(cx).dap_store();
5484 let mut scenarios = vec![];
5485 let resolved_tasks = resolved_tasks.as_ref()?;
5486 let buffer = buffer.read(cx);
5487 let language = buffer.language()?;
5488 let file = buffer.file();
5489 let debug_adapter =
5490 language_settings(language.name().into(), file, cx)
5491 .debuggers
5492 .first()
5493 .map(SharedString::from)
5494 .or_else(|| {
5495 language
5496 .config()
5497 .debuggers
5498 .first()
5499 .map(SharedString::from)
5500 })?;
5501
5502 dap_store.update(cx, |dap_store, cx| {
5503 for (_, task) in &resolved_tasks.templates {
5504 if let Some(scenario) = dap_store
5505 .debug_scenario_for_build_task(
5506 task.original_task().clone(),
5507 debug_adapter.clone().into(),
5508 task.display_label().to_owned().into(),
5509 cx,
5510 )
5511 {
5512 scenarios.push(scenario);
5513 }
5514 }
5515 });
5516 Some(scenarios)
5517 })
5518 .unwrap_or_default()
5519 } else {
5520 vec![]
5521 }
5522 })?;
5523 let spawn_straight_away = quick_launch
5524 && resolved_tasks
5525 .as_ref()
5526 .map_or(false, |tasks| tasks.templates.len() == 1)
5527 && code_actions
5528 .as_ref()
5529 .map_or(true, |actions| actions.is_empty())
5530 && debug_scenarios.is_empty();
5531 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5532 crate::hover_popover::hide_hover(editor, cx);
5533 *editor.context_menu.borrow_mut() =
5534 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5535 buffer,
5536 actions: CodeActionContents::new(
5537 resolved_tasks,
5538 code_actions,
5539 debug_scenarios,
5540 task_context.unwrap_or_default(),
5541 ),
5542 selected_item: Default::default(),
5543 scroll_handle: UniformListScrollHandle::default(),
5544 deployed_from,
5545 }));
5546 if spawn_straight_away {
5547 if let Some(task) = editor.confirm_code_action(
5548 &ConfirmCodeAction { item_ix: Some(0) },
5549 window,
5550 cx,
5551 ) {
5552 cx.notify();
5553 return task;
5554 }
5555 }
5556 cx.notify();
5557 Task::ready(Ok(()))
5558 }) {
5559 task.await
5560 } else {
5561 Ok(())
5562 }
5563 }))
5564 } else {
5565 Some(Task::ready(Ok(())))
5566 }
5567 })?;
5568 if let Some(task) = spawned_test_task {
5569 task.await?;
5570 }
5571
5572 anyhow::Ok(())
5573 })
5574 .detach_and_log_err(cx);
5575 }
5576
5577 pub fn confirm_code_action(
5578 &mut self,
5579 action: &ConfirmCodeAction,
5580 window: &mut Window,
5581 cx: &mut Context<Self>,
5582 ) -> Option<Task<Result<()>>> {
5583 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5584
5585 let actions_menu =
5586 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5587 menu
5588 } else {
5589 return None;
5590 };
5591
5592 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5593 let action = actions_menu.actions.get(action_ix)?;
5594 let title = action.label();
5595 let buffer = actions_menu.buffer;
5596 let workspace = self.workspace()?;
5597
5598 match action {
5599 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5600 workspace.update(cx, |workspace, cx| {
5601 workspace.schedule_resolved_task(
5602 task_source_kind,
5603 resolved_task,
5604 false,
5605 window,
5606 cx,
5607 );
5608
5609 Some(Task::ready(Ok(())))
5610 })
5611 }
5612 CodeActionsItem::CodeAction {
5613 excerpt_id,
5614 action,
5615 provider,
5616 } => {
5617 let apply_code_action =
5618 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5619 let workspace = workspace.downgrade();
5620 Some(cx.spawn_in(window, async move |editor, cx| {
5621 let project_transaction = apply_code_action.await?;
5622 Self::open_project_transaction(
5623 &editor,
5624 workspace,
5625 project_transaction,
5626 title,
5627 cx,
5628 )
5629 .await
5630 }))
5631 }
5632 CodeActionsItem::DebugScenario(scenario) => {
5633 let context = actions_menu.actions.context.clone();
5634
5635 workspace.update(cx, |workspace, cx| {
5636 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5637 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5638 });
5639 Some(Task::ready(Ok(())))
5640 }
5641 }
5642 }
5643
5644 pub async fn open_project_transaction(
5645 this: &WeakEntity<Editor>,
5646 workspace: WeakEntity<Workspace>,
5647 transaction: ProjectTransaction,
5648 title: String,
5649 cx: &mut AsyncWindowContext,
5650 ) -> Result<()> {
5651 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5652 cx.update(|_, cx| {
5653 entries.sort_unstable_by_key(|(buffer, _)| {
5654 buffer.read(cx).file().map(|f| f.path().clone())
5655 });
5656 })?;
5657
5658 // If the project transaction's edits are all contained within this editor, then
5659 // avoid opening a new editor to display them.
5660
5661 if let Some((buffer, transaction)) = entries.first() {
5662 if entries.len() == 1 {
5663 let excerpt = this.update(cx, |editor, cx| {
5664 editor
5665 .buffer()
5666 .read(cx)
5667 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5668 })?;
5669 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5670 if excerpted_buffer == *buffer {
5671 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5672 let excerpt_range = excerpt_range.to_offset(buffer);
5673 buffer
5674 .edited_ranges_for_transaction::<usize>(transaction)
5675 .all(|range| {
5676 excerpt_range.start <= range.start
5677 && excerpt_range.end >= range.end
5678 })
5679 })?;
5680
5681 if all_edits_within_excerpt {
5682 return Ok(());
5683 }
5684 }
5685 }
5686 }
5687 } else {
5688 return Ok(());
5689 }
5690
5691 let mut ranges_to_highlight = Vec::new();
5692 let excerpt_buffer = cx.new(|cx| {
5693 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5694 for (buffer_handle, transaction) in &entries {
5695 let edited_ranges = buffer_handle
5696 .read(cx)
5697 .edited_ranges_for_transaction::<Point>(transaction)
5698 .collect::<Vec<_>>();
5699 let (ranges, _) = multibuffer.set_excerpts_for_path(
5700 PathKey::for_buffer(buffer_handle, cx),
5701 buffer_handle.clone(),
5702 edited_ranges,
5703 DEFAULT_MULTIBUFFER_CONTEXT,
5704 cx,
5705 );
5706
5707 ranges_to_highlight.extend(ranges);
5708 }
5709 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5710 multibuffer
5711 })?;
5712
5713 workspace.update_in(cx, |workspace, window, cx| {
5714 let project = workspace.project().clone();
5715 let editor =
5716 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5717 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5718 editor.update(cx, |editor, cx| {
5719 editor.highlight_background::<Self>(
5720 &ranges_to_highlight,
5721 |theme| theme.editor_highlighted_line_background,
5722 cx,
5723 );
5724 });
5725 })?;
5726
5727 Ok(())
5728 }
5729
5730 pub fn clear_code_action_providers(&mut self) {
5731 self.code_action_providers.clear();
5732 self.available_code_actions.take();
5733 }
5734
5735 pub fn add_code_action_provider(
5736 &mut self,
5737 provider: Rc<dyn CodeActionProvider>,
5738 window: &mut Window,
5739 cx: &mut Context<Self>,
5740 ) {
5741 if self
5742 .code_action_providers
5743 .iter()
5744 .any(|existing_provider| existing_provider.id() == provider.id())
5745 {
5746 return;
5747 }
5748
5749 self.code_action_providers.push(provider);
5750 self.refresh_code_actions(window, cx);
5751 }
5752
5753 pub fn remove_code_action_provider(
5754 &mut self,
5755 id: Arc<str>,
5756 window: &mut Window,
5757 cx: &mut Context<Self>,
5758 ) {
5759 self.code_action_providers
5760 .retain(|provider| provider.id() != id);
5761 self.refresh_code_actions(window, cx);
5762 }
5763
5764 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
5765 !self.code_action_providers.is_empty()
5766 && EditorSettings::get_global(cx).toolbar.code_actions
5767 }
5768
5769 pub fn has_available_code_actions(&self) -> bool {
5770 self.available_code_actions
5771 .as_ref()
5772 .is_some_and(|(_, actions)| !actions.is_empty())
5773 }
5774
5775 fn render_inline_code_actions(
5776 &self,
5777 icon_size: ui::IconSize,
5778 display_row: DisplayRow,
5779 is_active: bool,
5780 cx: &mut Context<Self>,
5781 ) -> AnyElement {
5782 let show_tooltip = !self.context_menu_visible();
5783 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
5784 .icon_size(icon_size)
5785 .shape(ui::IconButtonShape::Square)
5786 .style(ButtonStyle::Transparent)
5787 .icon_color(ui::Color::Hidden)
5788 .toggle_state(is_active)
5789 .when(show_tooltip, |this| {
5790 this.tooltip({
5791 let focus_handle = self.focus_handle.clone();
5792 move |window, cx| {
5793 Tooltip::for_action_in(
5794 "Toggle Code Actions",
5795 &ToggleCodeActions {
5796 deployed_from: None,
5797 quick_launch: false,
5798 },
5799 &focus_handle,
5800 window,
5801 cx,
5802 )
5803 }
5804 })
5805 })
5806 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
5807 window.focus(&editor.focus_handle(cx));
5808 editor.toggle_code_actions(
5809 &crate::actions::ToggleCodeActions {
5810 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
5811 display_row,
5812 )),
5813 quick_launch: false,
5814 },
5815 window,
5816 cx,
5817 );
5818 }))
5819 .into_any_element()
5820 }
5821
5822 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
5823 &self.context_menu
5824 }
5825
5826 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5827 let newest_selection = self.selections.newest_anchor().clone();
5828 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5829 let buffer = self.buffer.read(cx);
5830 if newest_selection.head().diff_base_anchor.is_some() {
5831 return None;
5832 }
5833 let (start_buffer, start) =
5834 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5835 let (end_buffer, end) =
5836 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5837 if start_buffer != end_buffer {
5838 return None;
5839 }
5840
5841 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5842 cx.background_executor()
5843 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5844 .await;
5845
5846 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5847 let providers = this.code_action_providers.clone();
5848 let tasks = this
5849 .code_action_providers
5850 .iter()
5851 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5852 .collect::<Vec<_>>();
5853 (providers, tasks)
5854 })?;
5855
5856 let mut actions = Vec::new();
5857 for (provider, provider_actions) in
5858 providers.into_iter().zip(future::join_all(tasks).await)
5859 {
5860 if let Some(provider_actions) = provider_actions.log_err() {
5861 actions.extend(provider_actions.into_iter().map(|action| {
5862 AvailableCodeAction {
5863 excerpt_id: newest_selection.start.excerpt_id,
5864 action,
5865 provider: provider.clone(),
5866 }
5867 }));
5868 }
5869 }
5870
5871 this.update(cx, |this, cx| {
5872 this.available_code_actions = if actions.is_empty() {
5873 None
5874 } else {
5875 Some((
5876 Location {
5877 buffer: start_buffer,
5878 range: start..end,
5879 },
5880 actions.into(),
5881 ))
5882 };
5883 cx.notify();
5884 })
5885 }));
5886 None
5887 }
5888
5889 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5890 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5891 self.show_git_blame_inline = false;
5892
5893 self.show_git_blame_inline_delay_task =
5894 Some(cx.spawn_in(window, async move |this, cx| {
5895 cx.background_executor().timer(delay).await;
5896
5897 this.update(cx, |this, cx| {
5898 this.show_git_blame_inline = true;
5899 cx.notify();
5900 })
5901 .log_err();
5902 }));
5903 }
5904 }
5905
5906 fn show_blame_popover(
5907 &mut self,
5908 blame_entry: &BlameEntry,
5909 position: gpui::Point<Pixels>,
5910 cx: &mut Context<Self>,
5911 ) {
5912 if let Some(state) = &mut self.inline_blame_popover {
5913 state.hide_task.take();
5914 cx.notify();
5915 } else {
5916 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5917 let show_task = cx.spawn(async move |editor, cx| {
5918 cx.background_executor()
5919 .timer(std::time::Duration::from_millis(delay))
5920 .await;
5921 editor
5922 .update(cx, |editor, cx| {
5923 if let Some(state) = &mut editor.inline_blame_popover {
5924 state.show_task = None;
5925 cx.notify();
5926 }
5927 })
5928 .ok();
5929 });
5930 let Some(blame) = self.blame.as_ref() else {
5931 return;
5932 };
5933 let blame = blame.read(cx);
5934 let details = blame.details_for_entry(&blame_entry);
5935 let markdown = cx.new(|cx| {
5936 Markdown::new(
5937 details
5938 .as_ref()
5939 .map(|message| message.message.clone())
5940 .unwrap_or_default(),
5941 None,
5942 None,
5943 cx,
5944 )
5945 });
5946 self.inline_blame_popover = Some(InlineBlamePopover {
5947 position,
5948 show_task: Some(show_task),
5949 hide_task: None,
5950 popover_bounds: None,
5951 popover_state: InlineBlamePopoverState {
5952 scroll_handle: ScrollHandle::new(),
5953 commit_message: details,
5954 markdown,
5955 },
5956 });
5957 }
5958 }
5959
5960 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
5961 if let Some(state) = &mut self.inline_blame_popover {
5962 if state.show_task.is_some() {
5963 self.inline_blame_popover.take();
5964 cx.notify();
5965 } else {
5966 let hide_task = cx.spawn(async move |editor, cx| {
5967 cx.background_executor()
5968 .timer(std::time::Duration::from_millis(100))
5969 .await;
5970 editor
5971 .update(cx, |editor, cx| {
5972 editor.inline_blame_popover.take();
5973 cx.notify();
5974 })
5975 .ok();
5976 });
5977 state.hide_task = Some(hide_task);
5978 }
5979 }
5980 }
5981
5982 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
5983 if self.pending_rename.is_some() {
5984 return None;
5985 }
5986
5987 let provider = self.semantics_provider.clone()?;
5988 let buffer = self.buffer.read(cx);
5989 let newest_selection = self.selections.newest_anchor().clone();
5990 let cursor_position = newest_selection.head();
5991 let (cursor_buffer, cursor_buffer_position) =
5992 buffer.text_anchor_for_position(cursor_position, cx)?;
5993 let (tail_buffer, tail_buffer_position) =
5994 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
5995 if cursor_buffer != tail_buffer {
5996 return None;
5997 }
5998
5999 let snapshot = cursor_buffer.read(cx).snapshot();
6000 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6001 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6002 if start_word_range != end_word_range {
6003 self.document_highlights_task.take();
6004 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6005 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6006 return None;
6007 }
6008
6009 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6010 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6011 cx.background_executor()
6012 .timer(Duration::from_millis(debounce))
6013 .await;
6014
6015 let highlights = if let Some(highlights) = cx
6016 .update(|cx| {
6017 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6018 })
6019 .ok()
6020 .flatten()
6021 {
6022 highlights.await.log_err()
6023 } else {
6024 None
6025 };
6026
6027 if let Some(highlights) = highlights {
6028 this.update(cx, |this, cx| {
6029 if this.pending_rename.is_some() {
6030 return;
6031 }
6032
6033 let buffer_id = cursor_position.buffer_id;
6034 let buffer = this.buffer.read(cx);
6035 if !buffer
6036 .text_anchor_for_position(cursor_position, cx)
6037 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6038 {
6039 return;
6040 }
6041
6042 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6043 let mut write_ranges = Vec::new();
6044 let mut read_ranges = Vec::new();
6045 for highlight in highlights {
6046 for (excerpt_id, excerpt_range) in
6047 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6048 {
6049 let start = highlight
6050 .range
6051 .start
6052 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6053 let end = highlight
6054 .range
6055 .end
6056 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6057 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6058 continue;
6059 }
6060
6061 let range = Anchor {
6062 buffer_id,
6063 excerpt_id,
6064 text_anchor: start,
6065 diff_base_anchor: None,
6066 }..Anchor {
6067 buffer_id,
6068 excerpt_id,
6069 text_anchor: end,
6070 diff_base_anchor: None,
6071 };
6072 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6073 write_ranges.push(range);
6074 } else {
6075 read_ranges.push(range);
6076 }
6077 }
6078 }
6079
6080 this.highlight_background::<DocumentHighlightRead>(
6081 &read_ranges,
6082 |theme| theme.editor_document_highlight_read_background,
6083 cx,
6084 );
6085 this.highlight_background::<DocumentHighlightWrite>(
6086 &write_ranges,
6087 |theme| theme.editor_document_highlight_write_background,
6088 cx,
6089 );
6090 cx.notify();
6091 })
6092 .log_err();
6093 }
6094 }));
6095 None
6096 }
6097
6098 fn prepare_highlight_query_from_selection(
6099 &mut self,
6100 cx: &mut Context<Editor>,
6101 ) -> Option<(String, Range<Anchor>)> {
6102 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6103 return None;
6104 }
6105 if !EditorSettings::get_global(cx).selection_highlight {
6106 return None;
6107 }
6108 if self.selections.count() != 1 || self.selections.line_mode {
6109 return None;
6110 }
6111 let selection = self.selections.newest::<Point>(cx);
6112 if selection.is_empty() || selection.start.row != selection.end.row {
6113 return None;
6114 }
6115 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6116 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6117 let query = multi_buffer_snapshot
6118 .text_for_range(selection_anchor_range.clone())
6119 .collect::<String>();
6120 if query.trim().is_empty() {
6121 return None;
6122 }
6123 Some((query, selection_anchor_range))
6124 }
6125
6126 fn update_selection_occurrence_highlights(
6127 &mut self,
6128 query_text: String,
6129 query_range: Range<Anchor>,
6130 multi_buffer_range_to_query: Range<Point>,
6131 use_debounce: bool,
6132 window: &mut Window,
6133 cx: &mut Context<Editor>,
6134 ) -> Task<()> {
6135 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6136 cx.spawn_in(window, async move |editor, cx| {
6137 if use_debounce {
6138 cx.background_executor()
6139 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6140 .await;
6141 }
6142 let match_task = cx.background_spawn(async move {
6143 let buffer_ranges = multi_buffer_snapshot
6144 .range_to_buffer_ranges(multi_buffer_range_to_query)
6145 .into_iter()
6146 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6147 let mut match_ranges = Vec::new();
6148 let Ok(regex) = project::search::SearchQuery::text(
6149 query_text.clone(),
6150 false,
6151 false,
6152 false,
6153 Default::default(),
6154 Default::default(),
6155 false,
6156 None,
6157 ) else {
6158 return Vec::default();
6159 };
6160 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6161 match_ranges.extend(
6162 regex
6163 .search(&buffer_snapshot, Some(search_range.clone()))
6164 .await
6165 .into_iter()
6166 .filter_map(|match_range| {
6167 let match_start = buffer_snapshot
6168 .anchor_after(search_range.start + match_range.start);
6169 let match_end = buffer_snapshot
6170 .anchor_before(search_range.start + match_range.end);
6171 let match_anchor_range = Anchor::range_in_buffer(
6172 excerpt_id,
6173 buffer_snapshot.remote_id(),
6174 match_start..match_end,
6175 );
6176 (match_anchor_range != query_range).then_some(match_anchor_range)
6177 }),
6178 );
6179 }
6180 match_ranges
6181 });
6182 let match_ranges = match_task.await;
6183 editor
6184 .update_in(cx, |editor, _, cx| {
6185 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6186 if !match_ranges.is_empty() {
6187 editor.highlight_background::<SelectedTextHighlight>(
6188 &match_ranges,
6189 |theme| theme.editor_document_highlight_bracket_background,
6190 cx,
6191 )
6192 }
6193 })
6194 .log_err();
6195 })
6196 }
6197
6198 fn refresh_selected_text_highlights(
6199 &mut self,
6200 on_buffer_edit: bool,
6201 window: &mut Window,
6202 cx: &mut Context<Editor>,
6203 ) {
6204 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6205 else {
6206 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6207 self.quick_selection_highlight_task.take();
6208 self.debounced_selection_highlight_task.take();
6209 return;
6210 };
6211 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6212 if on_buffer_edit
6213 || self
6214 .quick_selection_highlight_task
6215 .as_ref()
6216 .map_or(true, |(prev_anchor_range, _)| {
6217 prev_anchor_range != &query_range
6218 })
6219 {
6220 let multi_buffer_visible_start = self
6221 .scroll_manager
6222 .anchor()
6223 .anchor
6224 .to_point(&multi_buffer_snapshot);
6225 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6226 multi_buffer_visible_start
6227 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6228 Bias::Left,
6229 );
6230 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6231 self.quick_selection_highlight_task = Some((
6232 query_range.clone(),
6233 self.update_selection_occurrence_highlights(
6234 query_text.clone(),
6235 query_range.clone(),
6236 multi_buffer_visible_range,
6237 false,
6238 window,
6239 cx,
6240 ),
6241 ));
6242 }
6243 if on_buffer_edit
6244 || self
6245 .debounced_selection_highlight_task
6246 .as_ref()
6247 .map_or(true, |(prev_anchor_range, _)| {
6248 prev_anchor_range != &query_range
6249 })
6250 {
6251 let multi_buffer_start = multi_buffer_snapshot
6252 .anchor_before(0)
6253 .to_point(&multi_buffer_snapshot);
6254 let multi_buffer_end = multi_buffer_snapshot
6255 .anchor_after(multi_buffer_snapshot.len())
6256 .to_point(&multi_buffer_snapshot);
6257 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6258 self.debounced_selection_highlight_task = Some((
6259 query_range.clone(),
6260 self.update_selection_occurrence_highlights(
6261 query_text,
6262 query_range,
6263 multi_buffer_full_range,
6264 true,
6265 window,
6266 cx,
6267 ),
6268 ));
6269 }
6270 }
6271
6272 pub fn refresh_inline_completion(
6273 &mut self,
6274 debounce: bool,
6275 user_requested: bool,
6276 window: &mut Window,
6277 cx: &mut Context<Self>,
6278 ) -> Option<()> {
6279 let provider = self.edit_prediction_provider()?;
6280 let cursor = self.selections.newest_anchor().head();
6281 let (buffer, cursor_buffer_position) =
6282 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6283
6284 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6285 self.discard_inline_completion(false, cx);
6286 return None;
6287 }
6288
6289 if !user_requested
6290 && (!self.should_show_edit_predictions()
6291 || !self.is_focused(window)
6292 || buffer.read(cx).is_empty())
6293 {
6294 self.discard_inline_completion(false, cx);
6295 return None;
6296 }
6297
6298 self.update_visible_inline_completion(window, cx);
6299 provider.refresh(
6300 self.project.clone(),
6301 buffer,
6302 cursor_buffer_position,
6303 debounce,
6304 cx,
6305 );
6306 Some(())
6307 }
6308
6309 fn show_edit_predictions_in_menu(&self) -> bool {
6310 match self.edit_prediction_settings {
6311 EditPredictionSettings::Disabled => false,
6312 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6313 }
6314 }
6315
6316 pub fn edit_predictions_enabled(&self) -> bool {
6317 match self.edit_prediction_settings {
6318 EditPredictionSettings::Disabled => false,
6319 EditPredictionSettings::Enabled { .. } => true,
6320 }
6321 }
6322
6323 fn edit_prediction_requires_modifier(&self) -> bool {
6324 match self.edit_prediction_settings {
6325 EditPredictionSettings::Disabled => false,
6326 EditPredictionSettings::Enabled {
6327 preview_requires_modifier,
6328 ..
6329 } => preview_requires_modifier,
6330 }
6331 }
6332
6333 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6334 if self.edit_prediction_provider.is_none() {
6335 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6336 } else {
6337 let selection = self.selections.newest_anchor();
6338 let cursor = selection.head();
6339
6340 if let Some((buffer, cursor_buffer_position)) =
6341 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6342 {
6343 self.edit_prediction_settings =
6344 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6345 }
6346 }
6347 }
6348
6349 fn edit_prediction_settings_at_position(
6350 &self,
6351 buffer: &Entity<Buffer>,
6352 buffer_position: language::Anchor,
6353 cx: &App,
6354 ) -> EditPredictionSettings {
6355 if !self.mode.is_full()
6356 || !self.show_inline_completions_override.unwrap_or(true)
6357 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6358 {
6359 return EditPredictionSettings::Disabled;
6360 }
6361
6362 let buffer = buffer.read(cx);
6363
6364 let file = buffer.file();
6365
6366 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6367 return EditPredictionSettings::Disabled;
6368 };
6369
6370 let by_provider = matches!(
6371 self.menu_inline_completions_policy,
6372 MenuInlineCompletionsPolicy::ByProvider
6373 );
6374
6375 let show_in_menu = by_provider
6376 && self
6377 .edit_prediction_provider
6378 .as_ref()
6379 .map_or(false, |provider| {
6380 provider.provider.show_completions_in_menu()
6381 });
6382
6383 let preview_requires_modifier =
6384 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6385
6386 EditPredictionSettings::Enabled {
6387 show_in_menu,
6388 preview_requires_modifier,
6389 }
6390 }
6391
6392 fn should_show_edit_predictions(&self) -> bool {
6393 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6394 }
6395
6396 pub fn edit_prediction_preview_is_active(&self) -> bool {
6397 matches!(
6398 self.edit_prediction_preview,
6399 EditPredictionPreview::Active { .. }
6400 )
6401 }
6402
6403 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6404 let cursor = self.selections.newest_anchor().head();
6405 if let Some((buffer, cursor_position)) =
6406 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6407 {
6408 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6409 } else {
6410 false
6411 }
6412 }
6413
6414 pub fn supports_minimap(&self, cx: &App) -> bool {
6415 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6416 }
6417
6418 fn edit_predictions_enabled_in_buffer(
6419 &self,
6420 buffer: &Entity<Buffer>,
6421 buffer_position: language::Anchor,
6422 cx: &App,
6423 ) -> bool {
6424 maybe!({
6425 if self.read_only(cx) {
6426 return Some(false);
6427 }
6428 let provider = self.edit_prediction_provider()?;
6429 if !provider.is_enabled(&buffer, buffer_position, cx) {
6430 return Some(false);
6431 }
6432 let buffer = buffer.read(cx);
6433 let Some(file) = buffer.file() else {
6434 return Some(true);
6435 };
6436 let settings = all_language_settings(Some(file), cx);
6437 Some(settings.edit_predictions_enabled_for_file(file, cx))
6438 })
6439 .unwrap_or(false)
6440 }
6441
6442 fn cycle_inline_completion(
6443 &mut self,
6444 direction: Direction,
6445 window: &mut Window,
6446 cx: &mut Context<Self>,
6447 ) -> Option<()> {
6448 let provider = self.edit_prediction_provider()?;
6449 let cursor = self.selections.newest_anchor().head();
6450 let (buffer, cursor_buffer_position) =
6451 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6452 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6453 return None;
6454 }
6455
6456 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6457 self.update_visible_inline_completion(window, cx);
6458
6459 Some(())
6460 }
6461
6462 pub fn show_inline_completion(
6463 &mut self,
6464 _: &ShowEditPrediction,
6465 window: &mut Window,
6466 cx: &mut Context<Self>,
6467 ) {
6468 if !self.has_active_inline_completion() {
6469 self.refresh_inline_completion(false, true, window, cx);
6470 return;
6471 }
6472
6473 self.update_visible_inline_completion(window, cx);
6474 }
6475
6476 pub fn display_cursor_names(
6477 &mut self,
6478 _: &DisplayCursorNames,
6479 window: &mut Window,
6480 cx: &mut Context<Self>,
6481 ) {
6482 self.show_cursor_names(window, cx);
6483 }
6484
6485 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6486 self.show_cursor_names = true;
6487 cx.notify();
6488 cx.spawn_in(window, async move |this, cx| {
6489 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6490 this.update(cx, |this, cx| {
6491 this.show_cursor_names = false;
6492 cx.notify()
6493 })
6494 .ok()
6495 })
6496 .detach();
6497 }
6498
6499 pub fn next_edit_prediction(
6500 &mut self,
6501 _: &NextEditPrediction,
6502 window: &mut Window,
6503 cx: &mut Context<Self>,
6504 ) {
6505 if self.has_active_inline_completion() {
6506 self.cycle_inline_completion(Direction::Next, window, cx);
6507 } else {
6508 let is_copilot_disabled = self
6509 .refresh_inline_completion(false, true, window, cx)
6510 .is_none();
6511 if is_copilot_disabled {
6512 cx.propagate();
6513 }
6514 }
6515 }
6516
6517 pub fn previous_edit_prediction(
6518 &mut self,
6519 _: &PreviousEditPrediction,
6520 window: &mut Window,
6521 cx: &mut Context<Self>,
6522 ) {
6523 if self.has_active_inline_completion() {
6524 self.cycle_inline_completion(Direction::Prev, window, cx);
6525 } else {
6526 let is_copilot_disabled = self
6527 .refresh_inline_completion(false, true, window, cx)
6528 .is_none();
6529 if is_copilot_disabled {
6530 cx.propagate();
6531 }
6532 }
6533 }
6534
6535 pub fn accept_edit_prediction(
6536 &mut self,
6537 _: &AcceptEditPrediction,
6538 window: &mut Window,
6539 cx: &mut Context<Self>,
6540 ) {
6541 if self.show_edit_predictions_in_menu() {
6542 self.hide_context_menu(window, cx);
6543 }
6544
6545 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6546 return;
6547 };
6548
6549 self.report_inline_completion_event(
6550 active_inline_completion.completion_id.clone(),
6551 true,
6552 cx,
6553 );
6554
6555 match &active_inline_completion.completion {
6556 InlineCompletion::Move { target, .. } => {
6557 let target = *target;
6558
6559 if let Some(position_map) = &self.last_position_map {
6560 if position_map
6561 .visible_row_range
6562 .contains(&target.to_display_point(&position_map.snapshot).row())
6563 || !self.edit_prediction_requires_modifier()
6564 {
6565 self.unfold_ranges(&[target..target], true, false, cx);
6566 // Note that this is also done in vim's handler of the Tab action.
6567 self.change_selections(
6568 Some(Autoscroll::newest()),
6569 window,
6570 cx,
6571 |selections| {
6572 selections.select_anchor_ranges([target..target]);
6573 },
6574 );
6575 self.clear_row_highlights::<EditPredictionPreview>();
6576
6577 self.edit_prediction_preview
6578 .set_previous_scroll_position(None);
6579 } else {
6580 self.edit_prediction_preview
6581 .set_previous_scroll_position(Some(
6582 position_map.snapshot.scroll_anchor,
6583 ));
6584
6585 self.highlight_rows::<EditPredictionPreview>(
6586 target..target,
6587 cx.theme().colors().editor_highlighted_line_background,
6588 RowHighlightOptions {
6589 autoscroll: true,
6590 ..Default::default()
6591 },
6592 cx,
6593 );
6594 self.request_autoscroll(Autoscroll::fit(), cx);
6595 }
6596 }
6597 }
6598 InlineCompletion::Edit { edits, .. } => {
6599 if let Some(provider) = self.edit_prediction_provider() {
6600 provider.accept(cx);
6601 }
6602
6603 // Store the transaction ID and selections before applying the edit
6604 let transaction_id_prev =
6605 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
6606
6607 let snapshot = self.buffer.read(cx).snapshot(cx);
6608 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6609
6610 self.buffer.update(cx, |buffer, cx| {
6611 buffer.edit(edits.iter().cloned(), None, cx)
6612 });
6613
6614 self.change_selections(None, window, cx, |s| {
6615 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6616 });
6617
6618 let selections = self.selections.disjoint_anchors();
6619 if let Some(transaction_id_now) =
6620 self.buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
6621 {
6622 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6623 if has_new_transaction {
6624 self.selection_history
6625 .insert_transaction(transaction_id_now, selections);
6626 }
6627 }
6628
6629 self.update_visible_inline_completion(window, cx);
6630 if self.active_inline_completion.is_none() {
6631 self.refresh_inline_completion(true, true, window, cx);
6632 }
6633
6634 cx.notify();
6635 }
6636 }
6637
6638 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6639 }
6640
6641 pub fn accept_partial_inline_completion(
6642 &mut self,
6643 _: &AcceptPartialEditPrediction,
6644 window: &mut Window,
6645 cx: &mut Context<Self>,
6646 ) {
6647 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6648 return;
6649 };
6650 if self.selections.count() != 1 {
6651 return;
6652 }
6653
6654 self.report_inline_completion_event(
6655 active_inline_completion.completion_id.clone(),
6656 true,
6657 cx,
6658 );
6659
6660 match &active_inline_completion.completion {
6661 InlineCompletion::Move { target, .. } => {
6662 let target = *target;
6663 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6664 selections.select_anchor_ranges([target..target]);
6665 });
6666 }
6667 InlineCompletion::Edit { edits, .. } => {
6668 // Find an insertion that starts at the cursor position.
6669 let snapshot = self.buffer.read(cx).snapshot(cx);
6670 let cursor_offset = self.selections.newest::<usize>(cx).head();
6671 let insertion = edits.iter().find_map(|(range, text)| {
6672 let range = range.to_offset(&snapshot);
6673 if range.is_empty() && range.start == cursor_offset {
6674 Some(text)
6675 } else {
6676 None
6677 }
6678 });
6679
6680 if let Some(text) = insertion {
6681 let mut partial_completion = text
6682 .chars()
6683 .by_ref()
6684 .take_while(|c| c.is_alphabetic())
6685 .collect::<String>();
6686 if partial_completion.is_empty() {
6687 partial_completion = text
6688 .chars()
6689 .by_ref()
6690 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6691 .collect::<String>();
6692 }
6693
6694 cx.emit(EditorEvent::InputHandled {
6695 utf16_range_to_replace: None,
6696 text: partial_completion.clone().into(),
6697 });
6698
6699 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6700
6701 self.refresh_inline_completion(true, true, window, cx);
6702 cx.notify();
6703 } else {
6704 self.accept_edit_prediction(&Default::default(), window, cx);
6705 }
6706 }
6707 }
6708 }
6709
6710 fn discard_inline_completion(
6711 &mut self,
6712 should_report_inline_completion_event: bool,
6713 cx: &mut Context<Self>,
6714 ) -> bool {
6715 if should_report_inline_completion_event {
6716 let completion_id = self
6717 .active_inline_completion
6718 .as_ref()
6719 .and_then(|active_completion| active_completion.completion_id.clone());
6720
6721 self.report_inline_completion_event(completion_id, false, cx);
6722 }
6723
6724 if let Some(provider) = self.edit_prediction_provider() {
6725 provider.discard(cx);
6726 }
6727
6728 self.take_active_inline_completion(cx)
6729 }
6730
6731 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6732 let Some(provider) = self.edit_prediction_provider() else {
6733 return;
6734 };
6735
6736 let Some((_, buffer, _)) = self
6737 .buffer
6738 .read(cx)
6739 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6740 else {
6741 return;
6742 };
6743
6744 let extension = buffer
6745 .read(cx)
6746 .file()
6747 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6748
6749 let event_type = match accepted {
6750 true => "Edit Prediction Accepted",
6751 false => "Edit Prediction Discarded",
6752 };
6753 telemetry::event!(
6754 event_type,
6755 provider = provider.name(),
6756 prediction_id = id,
6757 suggestion_accepted = accepted,
6758 file_extension = extension,
6759 );
6760 }
6761
6762 pub fn has_active_inline_completion(&self) -> bool {
6763 self.active_inline_completion.is_some()
6764 }
6765
6766 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6767 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6768 return false;
6769 };
6770
6771 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6772 self.clear_highlights::<InlineCompletionHighlight>(cx);
6773 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6774 true
6775 }
6776
6777 /// Returns true when we're displaying the edit prediction popover below the cursor
6778 /// like we are not previewing and the LSP autocomplete menu is visible
6779 /// or we are in `when_holding_modifier` mode.
6780 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6781 if self.edit_prediction_preview_is_active()
6782 || !self.show_edit_predictions_in_menu()
6783 || !self.edit_predictions_enabled()
6784 {
6785 return false;
6786 }
6787
6788 if self.has_visible_completions_menu() {
6789 return true;
6790 }
6791
6792 has_completion && self.edit_prediction_requires_modifier()
6793 }
6794
6795 fn handle_modifiers_changed(
6796 &mut self,
6797 modifiers: Modifiers,
6798 position_map: &PositionMap,
6799 window: &mut Window,
6800 cx: &mut Context<Self>,
6801 ) {
6802 if self.show_edit_predictions_in_menu() {
6803 self.update_edit_prediction_preview(&modifiers, window, cx);
6804 }
6805
6806 self.update_selection_mode(&modifiers, position_map, window, cx);
6807
6808 let mouse_position = window.mouse_position();
6809 if !position_map.text_hitbox.is_hovered(window) {
6810 return;
6811 }
6812
6813 self.update_hovered_link(
6814 position_map.point_for_position(mouse_position),
6815 &position_map.snapshot,
6816 modifiers,
6817 window,
6818 cx,
6819 )
6820 }
6821
6822 fn update_selection_mode(
6823 &mut self,
6824 modifiers: &Modifiers,
6825 position_map: &PositionMap,
6826 window: &mut Window,
6827 cx: &mut Context<Self>,
6828 ) {
6829 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6830 return;
6831 }
6832
6833 let mouse_position = window.mouse_position();
6834 let point_for_position = position_map.point_for_position(mouse_position);
6835 let position = point_for_position.previous_valid;
6836
6837 self.select(
6838 SelectPhase::BeginColumnar {
6839 position,
6840 reset: false,
6841 goal_column: point_for_position.exact_unclipped.column(),
6842 },
6843 window,
6844 cx,
6845 );
6846 }
6847
6848 fn update_edit_prediction_preview(
6849 &mut self,
6850 modifiers: &Modifiers,
6851 window: &mut Window,
6852 cx: &mut Context<Self>,
6853 ) {
6854 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6855 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6856 return;
6857 };
6858
6859 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6860 if matches!(
6861 self.edit_prediction_preview,
6862 EditPredictionPreview::Inactive { .. }
6863 ) {
6864 self.edit_prediction_preview = EditPredictionPreview::Active {
6865 previous_scroll_position: None,
6866 since: Instant::now(),
6867 };
6868
6869 self.update_visible_inline_completion(window, cx);
6870 cx.notify();
6871 }
6872 } else if let EditPredictionPreview::Active {
6873 previous_scroll_position,
6874 since,
6875 } = self.edit_prediction_preview
6876 {
6877 if let (Some(previous_scroll_position), Some(position_map)) =
6878 (previous_scroll_position, self.last_position_map.as_ref())
6879 {
6880 self.set_scroll_position(
6881 previous_scroll_position
6882 .scroll_position(&position_map.snapshot.display_snapshot),
6883 window,
6884 cx,
6885 );
6886 }
6887
6888 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6889 released_too_fast: since.elapsed() < Duration::from_millis(200),
6890 };
6891 self.clear_row_highlights::<EditPredictionPreview>();
6892 self.update_visible_inline_completion(window, cx);
6893 cx.notify();
6894 }
6895 }
6896
6897 fn update_visible_inline_completion(
6898 &mut self,
6899 _window: &mut Window,
6900 cx: &mut Context<Self>,
6901 ) -> Option<()> {
6902 let selection = self.selections.newest_anchor();
6903 let cursor = selection.head();
6904 let multibuffer = self.buffer.read(cx).snapshot(cx);
6905 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6906 let excerpt_id = cursor.excerpt_id;
6907
6908 let show_in_menu = self.show_edit_predictions_in_menu();
6909 let completions_menu_has_precedence = !show_in_menu
6910 && (self.context_menu.borrow().is_some()
6911 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6912
6913 if completions_menu_has_precedence
6914 || !offset_selection.is_empty()
6915 || self
6916 .active_inline_completion
6917 .as_ref()
6918 .map_or(false, |completion| {
6919 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6920 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6921 !invalidation_range.contains(&offset_selection.head())
6922 })
6923 {
6924 self.discard_inline_completion(false, cx);
6925 return None;
6926 }
6927
6928 self.take_active_inline_completion(cx);
6929 let Some(provider) = self.edit_prediction_provider() else {
6930 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6931 return None;
6932 };
6933
6934 let (buffer, cursor_buffer_position) =
6935 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6936
6937 self.edit_prediction_settings =
6938 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6939
6940 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6941
6942 if self.edit_prediction_indent_conflict {
6943 let cursor_point = cursor.to_point(&multibuffer);
6944
6945 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6946
6947 if let Some((_, indent)) = indents.iter().next() {
6948 if indent.len == cursor_point.column {
6949 self.edit_prediction_indent_conflict = false;
6950 }
6951 }
6952 }
6953
6954 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6955 let edits = inline_completion
6956 .edits
6957 .into_iter()
6958 .flat_map(|(range, new_text)| {
6959 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
6960 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
6961 Some((start..end, new_text))
6962 })
6963 .collect::<Vec<_>>();
6964 if edits.is_empty() {
6965 return None;
6966 }
6967
6968 let first_edit_start = edits.first().unwrap().0.start;
6969 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
6970 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
6971
6972 let last_edit_end = edits.last().unwrap().0.end;
6973 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
6974 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
6975
6976 let cursor_row = cursor.to_point(&multibuffer).row;
6977
6978 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
6979
6980 let mut inlay_ids = Vec::new();
6981 let invalidation_row_range;
6982 let move_invalidation_row_range = if cursor_row < edit_start_row {
6983 Some(cursor_row..edit_end_row)
6984 } else if cursor_row > edit_end_row {
6985 Some(edit_start_row..cursor_row)
6986 } else {
6987 None
6988 };
6989 let is_move =
6990 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
6991 let completion = if is_move {
6992 invalidation_row_range =
6993 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
6994 let target = first_edit_start;
6995 InlineCompletion::Move { target, snapshot }
6996 } else {
6997 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
6998 && !self.inline_completions_hidden_for_vim_mode;
6999
7000 if show_completions_in_buffer {
7001 if edits
7002 .iter()
7003 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7004 {
7005 let mut inlays = Vec::new();
7006 for (range, new_text) in &edits {
7007 let inlay = Inlay::inline_completion(
7008 post_inc(&mut self.next_inlay_id),
7009 range.start,
7010 new_text.as_str(),
7011 );
7012 inlay_ids.push(inlay.id);
7013 inlays.push(inlay);
7014 }
7015
7016 self.splice_inlays(&[], inlays, cx);
7017 } else {
7018 let background_color = cx.theme().status().deleted_background;
7019 self.highlight_text::<InlineCompletionHighlight>(
7020 edits.iter().map(|(range, _)| range.clone()).collect(),
7021 HighlightStyle {
7022 background_color: Some(background_color),
7023 ..Default::default()
7024 },
7025 cx,
7026 );
7027 }
7028 }
7029
7030 invalidation_row_range = edit_start_row..edit_end_row;
7031
7032 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7033 if provider.show_tab_accept_marker() {
7034 EditDisplayMode::TabAccept
7035 } else {
7036 EditDisplayMode::Inline
7037 }
7038 } else {
7039 EditDisplayMode::DiffPopover
7040 };
7041
7042 InlineCompletion::Edit {
7043 edits,
7044 edit_preview: inline_completion.edit_preview,
7045 display_mode,
7046 snapshot,
7047 }
7048 };
7049
7050 let invalidation_range = multibuffer
7051 .anchor_before(Point::new(invalidation_row_range.start, 0))
7052 ..multibuffer.anchor_after(Point::new(
7053 invalidation_row_range.end,
7054 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7055 ));
7056
7057 self.stale_inline_completion_in_menu = None;
7058 self.active_inline_completion = Some(InlineCompletionState {
7059 inlay_ids,
7060 completion,
7061 completion_id: inline_completion.id,
7062 invalidation_range,
7063 });
7064
7065 cx.notify();
7066
7067 Some(())
7068 }
7069
7070 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7071 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7072 }
7073
7074 fn clear_tasks(&mut self) {
7075 self.tasks.clear()
7076 }
7077
7078 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7079 if self.tasks.insert(key, value).is_some() {
7080 // This case should hopefully be rare, but just in case...
7081 log::error!(
7082 "multiple different run targets found on a single line, only the last target will be rendered"
7083 )
7084 }
7085 }
7086
7087 /// Get all display points of breakpoints that will be rendered within editor
7088 ///
7089 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7090 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7091 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7092 fn active_breakpoints(
7093 &self,
7094 range: Range<DisplayRow>,
7095 window: &mut Window,
7096 cx: &mut Context<Self>,
7097 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7098 let mut breakpoint_display_points = HashMap::default();
7099
7100 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7101 return breakpoint_display_points;
7102 };
7103
7104 let snapshot = self.snapshot(window, cx);
7105
7106 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7107 let Some(project) = self.project.as_ref() else {
7108 return breakpoint_display_points;
7109 };
7110
7111 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7112 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7113
7114 for (buffer_snapshot, range, excerpt_id) in
7115 multi_buffer_snapshot.range_to_buffer_ranges(range)
7116 {
7117 let Some(buffer) = project.read_with(cx, |this, cx| {
7118 this.buffer_for_id(buffer_snapshot.remote_id(), cx)
7119 }) else {
7120 continue;
7121 };
7122 let breakpoints = breakpoint_store.read(cx).breakpoints(
7123 &buffer,
7124 Some(
7125 buffer_snapshot.anchor_before(range.start)
7126 ..buffer_snapshot.anchor_after(range.end),
7127 ),
7128 buffer_snapshot,
7129 cx,
7130 );
7131 for (breakpoint, state) in breakpoints {
7132 let multi_buffer_anchor =
7133 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7134 let position = multi_buffer_anchor
7135 .to_point(&multi_buffer_snapshot)
7136 .to_display_point(&snapshot);
7137
7138 breakpoint_display_points.insert(
7139 position.row(),
7140 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7141 );
7142 }
7143 }
7144
7145 breakpoint_display_points
7146 }
7147
7148 fn breakpoint_context_menu(
7149 &self,
7150 anchor: Anchor,
7151 window: &mut Window,
7152 cx: &mut Context<Self>,
7153 ) -> Entity<ui::ContextMenu> {
7154 let weak_editor = cx.weak_entity();
7155 let focus_handle = self.focus_handle(cx);
7156
7157 let row = self
7158 .buffer
7159 .read(cx)
7160 .snapshot(cx)
7161 .summary_for_anchor::<Point>(&anchor)
7162 .row;
7163
7164 let breakpoint = self
7165 .breakpoint_at_row(row, window, cx)
7166 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7167
7168 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7169 "Edit Log Breakpoint"
7170 } else {
7171 "Set Log Breakpoint"
7172 };
7173
7174 let condition_breakpoint_msg = if breakpoint
7175 .as_ref()
7176 .is_some_and(|bp| bp.1.condition.is_some())
7177 {
7178 "Edit Condition Breakpoint"
7179 } else {
7180 "Set Condition Breakpoint"
7181 };
7182
7183 let hit_condition_breakpoint_msg = if breakpoint
7184 .as_ref()
7185 .is_some_and(|bp| bp.1.hit_condition.is_some())
7186 {
7187 "Edit Hit Condition Breakpoint"
7188 } else {
7189 "Set Hit Condition Breakpoint"
7190 };
7191
7192 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7193 "Unset Breakpoint"
7194 } else {
7195 "Set Breakpoint"
7196 };
7197
7198 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7199 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7200
7201 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7202 BreakpointState::Enabled => Some("Disable"),
7203 BreakpointState::Disabled => Some("Enable"),
7204 });
7205
7206 let (anchor, breakpoint) =
7207 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7208
7209 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7210 menu.on_blur_subscription(Subscription::new(|| {}))
7211 .context(focus_handle)
7212 .when(run_to_cursor, |this| {
7213 let weak_editor = weak_editor.clone();
7214 this.entry("Run to cursor", None, move |window, cx| {
7215 weak_editor
7216 .update(cx, |editor, cx| {
7217 editor.change_selections(None, window, cx, |s| {
7218 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7219 });
7220 })
7221 .ok();
7222
7223 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7224 })
7225 .separator()
7226 })
7227 .when_some(toggle_state_msg, |this, msg| {
7228 this.entry(msg, None, {
7229 let weak_editor = weak_editor.clone();
7230 let breakpoint = breakpoint.clone();
7231 move |_window, cx| {
7232 weak_editor
7233 .update(cx, |this, cx| {
7234 this.edit_breakpoint_at_anchor(
7235 anchor,
7236 breakpoint.as_ref().clone(),
7237 BreakpointEditAction::InvertState,
7238 cx,
7239 );
7240 })
7241 .log_err();
7242 }
7243 })
7244 })
7245 .entry(set_breakpoint_msg, None, {
7246 let weak_editor = weak_editor.clone();
7247 let breakpoint = breakpoint.clone();
7248 move |_window, cx| {
7249 weak_editor
7250 .update(cx, |this, cx| {
7251 this.edit_breakpoint_at_anchor(
7252 anchor,
7253 breakpoint.as_ref().clone(),
7254 BreakpointEditAction::Toggle,
7255 cx,
7256 );
7257 })
7258 .log_err();
7259 }
7260 })
7261 .entry(log_breakpoint_msg, None, {
7262 let breakpoint = breakpoint.clone();
7263 let weak_editor = weak_editor.clone();
7264 move |window, cx| {
7265 weak_editor
7266 .update(cx, |this, cx| {
7267 this.add_edit_breakpoint_block(
7268 anchor,
7269 breakpoint.as_ref(),
7270 BreakpointPromptEditAction::Log,
7271 window,
7272 cx,
7273 );
7274 })
7275 .log_err();
7276 }
7277 })
7278 .entry(condition_breakpoint_msg, None, {
7279 let breakpoint = breakpoint.clone();
7280 let weak_editor = weak_editor.clone();
7281 move |window, cx| {
7282 weak_editor
7283 .update(cx, |this, cx| {
7284 this.add_edit_breakpoint_block(
7285 anchor,
7286 breakpoint.as_ref(),
7287 BreakpointPromptEditAction::Condition,
7288 window,
7289 cx,
7290 );
7291 })
7292 .log_err();
7293 }
7294 })
7295 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7296 weak_editor
7297 .update(cx, |this, cx| {
7298 this.add_edit_breakpoint_block(
7299 anchor,
7300 breakpoint.as_ref(),
7301 BreakpointPromptEditAction::HitCondition,
7302 window,
7303 cx,
7304 );
7305 })
7306 .log_err();
7307 })
7308 })
7309 }
7310
7311 fn render_breakpoint(
7312 &self,
7313 position: Anchor,
7314 row: DisplayRow,
7315 breakpoint: &Breakpoint,
7316 state: Option<BreakpointSessionState>,
7317 cx: &mut Context<Self>,
7318 ) -> IconButton {
7319 let is_rejected = state.is_some_and(|s| !s.verified);
7320 // Is it a breakpoint that shows up when hovering over gutter?
7321 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7322 (false, false),
7323 |PhantomBreakpointIndicator {
7324 is_active,
7325 display_row,
7326 collides_with_existing_breakpoint,
7327 }| {
7328 (
7329 is_active && display_row == row,
7330 collides_with_existing_breakpoint,
7331 )
7332 },
7333 );
7334
7335 let (color, icon) = {
7336 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7337 (false, false) => ui::IconName::DebugBreakpoint,
7338 (true, false) => ui::IconName::DebugLogBreakpoint,
7339 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7340 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7341 };
7342
7343 let color = if is_phantom {
7344 Color::Hint
7345 } else if is_rejected {
7346 Color::Disabled
7347 } else {
7348 Color::Debugger
7349 };
7350
7351 (color, icon)
7352 };
7353
7354 let breakpoint = Arc::from(breakpoint.clone());
7355
7356 let alt_as_text = gpui::Keystroke {
7357 modifiers: Modifiers::secondary_key(),
7358 ..Default::default()
7359 };
7360 let primary_action_text = if breakpoint.is_disabled() {
7361 "Enable breakpoint"
7362 } else if is_phantom && !collides_with_existing {
7363 "Set breakpoint"
7364 } else {
7365 "Unset breakpoint"
7366 };
7367 let focus_handle = self.focus_handle.clone();
7368
7369 let meta = if is_rejected {
7370 SharedString::from("No executable code is associated with this line.")
7371 } else if collides_with_existing && !breakpoint.is_disabled() {
7372 SharedString::from(format!(
7373 "{alt_as_text}-click to disable,\nright-click for more options."
7374 ))
7375 } else {
7376 SharedString::from("Right-click for more options.")
7377 };
7378 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7379 .icon_size(IconSize::XSmall)
7380 .size(ui::ButtonSize::None)
7381 .when(is_rejected, |this| {
7382 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7383 })
7384 .icon_color(color)
7385 .style(ButtonStyle::Transparent)
7386 .on_click(cx.listener({
7387 let breakpoint = breakpoint.clone();
7388
7389 move |editor, event: &ClickEvent, window, cx| {
7390 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7391 BreakpointEditAction::InvertState
7392 } else {
7393 BreakpointEditAction::Toggle
7394 };
7395
7396 window.focus(&editor.focus_handle(cx));
7397 editor.edit_breakpoint_at_anchor(
7398 position,
7399 breakpoint.as_ref().clone(),
7400 edit_action,
7401 cx,
7402 );
7403 }
7404 }))
7405 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7406 editor.set_breakpoint_context_menu(
7407 row,
7408 Some(position),
7409 event.down.position,
7410 window,
7411 cx,
7412 );
7413 }))
7414 .tooltip(move |window, cx| {
7415 Tooltip::with_meta_in(
7416 primary_action_text,
7417 Some(&ToggleBreakpoint),
7418 meta.clone(),
7419 &focus_handle,
7420 window,
7421 cx,
7422 )
7423 })
7424 }
7425
7426 fn build_tasks_context(
7427 project: &Entity<Project>,
7428 buffer: &Entity<Buffer>,
7429 buffer_row: u32,
7430 tasks: &Arc<RunnableTasks>,
7431 cx: &mut Context<Self>,
7432 ) -> Task<Option<task::TaskContext>> {
7433 let position = Point::new(buffer_row, tasks.column);
7434 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7435 let location = Location {
7436 buffer: buffer.clone(),
7437 range: range_start..range_start,
7438 };
7439 // Fill in the environmental variables from the tree-sitter captures
7440 let mut captured_task_variables = TaskVariables::default();
7441 for (capture_name, value) in tasks.extra_variables.clone() {
7442 captured_task_variables.insert(
7443 task::VariableName::Custom(capture_name.into()),
7444 value.clone(),
7445 );
7446 }
7447 project.update(cx, |project, cx| {
7448 project.task_store().update(cx, |task_store, cx| {
7449 task_store.task_context_for_location(captured_task_variables, location, cx)
7450 })
7451 })
7452 }
7453
7454 pub fn spawn_nearest_task(
7455 &mut self,
7456 action: &SpawnNearestTask,
7457 window: &mut Window,
7458 cx: &mut Context<Self>,
7459 ) {
7460 let Some((workspace, _)) = self.workspace.clone() else {
7461 return;
7462 };
7463 let Some(project) = self.project.clone() else {
7464 return;
7465 };
7466
7467 // Try to find a closest, enclosing node using tree-sitter that has a
7468 // task
7469 let Some((buffer, buffer_row, tasks)) = self
7470 .find_enclosing_node_task(cx)
7471 // Or find the task that's closest in row-distance.
7472 .or_else(|| self.find_closest_task(cx))
7473 else {
7474 return;
7475 };
7476
7477 let reveal_strategy = action.reveal;
7478 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7479 cx.spawn_in(window, async move |_, cx| {
7480 let context = task_context.await?;
7481 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7482
7483 let resolved = &mut resolved_task.resolved;
7484 resolved.reveal = reveal_strategy;
7485
7486 workspace
7487 .update_in(cx, |workspace, window, cx| {
7488 workspace.schedule_resolved_task(
7489 task_source_kind,
7490 resolved_task,
7491 false,
7492 window,
7493 cx,
7494 );
7495 })
7496 .ok()
7497 })
7498 .detach();
7499 }
7500
7501 fn find_closest_task(
7502 &mut self,
7503 cx: &mut Context<Self>,
7504 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7505 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7506
7507 let ((buffer_id, row), tasks) = self
7508 .tasks
7509 .iter()
7510 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7511
7512 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7513 let tasks = Arc::new(tasks.to_owned());
7514 Some((buffer, *row, tasks))
7515 }
7516
7517 fn find_enclosing_node_task(
7518 &mut self,
7519 cx: &mut Context<Self>,
7520 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7521 let snapshot = self.buffer.read(cx).snapshot(cx);
7522 let offset = self.selections.newest::<usize>(cx).head();
7523 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7524 let buffer_id = excerpt.buffer().remote_id();
7525
7526 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7527 let mut cursor = layer.node().walk();
7528
7529 while cursor.goto_first_child_for_byte(offset).is_some() {
7530 if cursor.node().end_byte() == offset {
7531 cursor.goto_next_sibling();
7532 }
7533 }
7534
7535 // Ascend to the smallest ancestor that contains the range and has a task.
7536 loop {
7537 let node = cursor.node();
7538 let node_range = node.byte_range();
7539 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7540
7541 // Check if this node contains our offset
7542 if node_range.start <= offset && node_range.end >= offset {
7543 // If it contains offset, check for task
7544 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7545 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7546 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7547 }
7548 }
7549
7550 if !cursor.goto_parent() {
7551 break;
7552 }
7553 }
7554 None
7555 }
7556
7557 fn render_run_indicator(
7558 &self,
7559 _style: &EditorStyle,
7560 is_active: bool,
7561 row: DisplayRow,
7562 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7563 cx: &mut Context<Self>,
7564 ) -> IconButton {
7565 let color = Color::Muted;
7566 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7567
7568 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7569 .shape(ui::IconButtonShape::Square)
7570 .icon_size(IconSize::XSmall)
7571 .icon_color(color)
7572 .toggle_state(is_active)
7573 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7574 let quick_launch = e.down.button == MouseButton::Left;
7575 window.focus(&editor.focus_handle(cx));
7576 editor.toggle_code_actions(
7577 &ToggleCodeActions {
7578 deployed_from: Some(CodeActionSource::Indicator(row)),
7579 quick_launch,
7580 },
7581 window,
7582 cx,
7583 );
7584 }))
7585 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7586 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7587 }))
7588 }
7589
7590 pub fn context_menu_visible(&self) -> bool {
7591 !self.edit_prediction_preview_is_active()
7592 && self
7593 .context_menu
7594 .borrow()
7595 .as_ref()
7596 .map_or(false, |menu| menu.visible())
7597 }
7598
7599 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7600 self.context_menu
7601 .borrow()
7602 .as_ref()
7603 .map(|menu| menu.origin())
7604 }
7605
7606 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7607 self.context_menu_options = Some(options);
7608 }
7609
7610 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7611 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7612
7613 fn render_edit_prediction_popover(
7614 &mut self,
7615 text_bounds: &Bounds<Pixels>,
7616 content_origin: gpui::Point<Pixels>,
7617 right_margin: Pixels,
7618 editor_snapshot: &EditorSnapshot,
7619 visible_row_range: Range<DisplayRow>,
7620 scroll_top: f32,
7621 scroll_bottom: f32,
7622 line_layouts: &[LineWithInvisibles],
7623 line_height: Pixels,
7624 scroll_pixel_position: gpui::Point<Pixels>,
7625 newest_selection_head: Option<DisplayPoint>,
7626 editor_width: Pixels,
7627 style: &EditorStyle,
7628 window: &mut Window,
7629 cx: &mut App,
7630 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7631 if self.mode().is_minimap() {
7632 return None;
7633 }
7634 let active_inline_completion = self.active_inline_completion.as_ref()?;
7635
7636 if self.edit_prediction_visible_in_cursor_popover(true) {
7637 return None;
7638 }
7639
7640 match &active_inline_completion.completion {
7641 InlineCompletion::Move { target, .. } => {
7642 let target_display_point = target.to_display_point(editor_snapshot);
7643
7644 if self.edit_prediction_requires_modifier() {
7645 if !self.edit_prediction_preview_is_active() {
7646 return None;
7647 }
7648
7649 self.render_edit_prediction_modifier_jump_popover(
7650 text_bounds,
7651 content_origin,
7652 visible_row_range,
7653 line_layouts,
7654 line_height,
7655 scroll_pixel_position,
7656 newest_selection_head,
7657 target_display_point,
7658 window,
7659 cx,
7660 )
7661 } else {
7662 self.render_edit_prediction_eager_jump_popover(
7663 text_bounds,
7664 content_origin,
7665 editor_snapshot,
7666 visible_row_range,
7667 scroll_top,
7668 scroll_bottom,
7669 line_height,
7670 scroll_pixel_position,
7671 target_display_point,
7672 editor_width,
7673 window,
7674 cx,
7675 )
7676 }
7677 }
7678 InlineCompletion::Edit {
7679 display_mode: EditDisplayMode::Inline,
7680 ..
7681 } => None,
7682 InlineCompletion::Edit {
7683 display_mode: EditDisplayMode::TabAccept,
7684 edits,
7685 ..
7686 } => {
7687 let range = &edits.first()?.0;
7688 let target_display_point = range.end.to_display_point(editor_snapshot);
7689
7690 self.render_edit_prediction_end_of_line_popover(
7691 "Accept",
7692 editor_snapshot,
7693 visible_row_range,
7694 target_display_point,
7695 line_height,
7696 scroll_pixel_position,
7697 content_origin,
7698 editor_width,
7699 window,
7700 cx,
7701 )
7702 }
7703 InlineCompletion::Edit {
7704 edits,
7705 edit_preview,
7706 display_mode: EditDisplayMode::DiffPopover,
7707 snapshot,
7708 } => self.render_edit_prediction_diff_popover(
7709 text_bounds,
7710 content_origin,
7711 right_margin,
7712 editor_snapshot,
7713 visible_row_range,
7714 line_layouts,
7715 line_height,
7716 scroll_pixel_position,
7717 newest_selection_head,
7718 editor_width,
7719 style,
7720 edits,
7721 edit_preview,
7722 snapshot,
7723 window,
7724 cx,
7725 ),
7726 }
7727 }
7728
7729 fn render_edit_prediction_modifier_jump_popover(
7730 &mut self,
7731 text_bounds: &Bounds<Pixels>,
7732 content_origin: gpui::Point<Pixels>,
7733 visible_row_range: Range<DisplayRow>,
7734 line_layouts: &[LineWithInvisibles],
7735 line_height: Pixels,
7736 scroll_pixel_position: gpui::Point<Pixels>,
7737 newest_selection_head: Option<DisplayPoint>,
7738 target_display_point: DisplayPoint,
7739 window: &mut Window,
7740 cx: &mut App,
7741 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7742 let scrolled_content_origin =
7743 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7744
7745 const SCROLL_PADDING_Y: Pixels = px(12.);
7746
7747 if target_display_point.row() < visible_row_range.start {
7748 return self.render_edit_prediction_scroll_popover(
7749 |_| SCROLL_PADDING_Y,
7750 IconName::ArrowUp,
7751 visible_row_range,
7752 line_layouts,
7753 newest_selection_head,
7754 scrolled_content_origin,
7755 window,
7756 cx,
7757 );
7758 } else if target_display_point.row() >= visible_row_range.end {
7759 return self.render_edit_prediction_scroll_popover(
7760 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7761 IconName::ArrowDown,
7762 visible_row_range,
7763 line_layouts,
7764 newest_selection_head,
7765 scrolled_content_origin,
7766 window,
7767 cx,
7768 );
7769 }
7770
7771 const POLE_WIDTH: Pixels = px(2.);
7772
7773 let line_layout =
7774 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7775 let target_column = target_display_point.column() as usize;
7776
7777 let target_x = line_layout.x_for_index(target_column);
7778 let target_y =
7779 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7780
7781 let flag_on_right = target_x < text_bounds.size.width / 2.;
7782
7783 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7784 border_color.l += 0.001;
7785
7786 let mut element = v_flex()
7787 .items_end()
7788 .when(flag_on_right, |el| el.items_start())
7789 .child(if flag_on_right {
7790 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7791 .rounded_bl(px(0.))
7792 .rounded_tl(px(0.))
7793 .border_l_2()
7794 .border_color(border_color)
7795 } else {
7796 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7797 .rounded_br(px(0.))
7798 .rounded_tr(px(0.))
7799 .border_r_2()
7800 .border_color(border_color)
7801 })
7802 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7803 .into_any();
7804
7805 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7806
7807 let mut origin = scrolled_content_origin + point(target_x, target_y)
7808 - point(
7809 if flag_on_right {
7810 POLE_WIDTH
7811 } else {
7812 size.width - POLE_WIDTH
7813 },
7814 size.height - line_height,
7815 );
7816
7817 origin.x = origin.x.max(content_origin.x);
7818
7819 element.prepaint_at(origin, window, cx);
7820
7821 Some((element, origin))
7822 }
7823
7824 fn render_edit_prediction_scroll_popover(
7825 &mut self,
7826 to_y: impl Fn(Size<Pixels>) -> Pixels,
7827 scroll_icon: IconName,
7828 visible_row_range: Range<DisplayRow>,
7829 line_layouts: &[LineWithInvisibles],
7830 newest_selection_head: Option<DisplayPoint>,
7831 scrolled_content_origin: gpui::Point<Pixels>,
7832 window: &mut Window,
7833 cx: &mut App,
7834 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7835 let mut element = self
7836 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7837 .into_any();
7838
7839 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7840
7841 let cursor = newest_selection_head?;
7842 let cursor_row_layout =
7843 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7844 let cursor_column = cursor.column() as usize;
7845
7846 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7847
7848 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7849
7850 element.prepaint_at(origin, window, cx);
7851 Some((element, origin))
7852 }
7853
7854 fn render_edit_prediction_eager_jump_popover(
7855 &mut self,
7856 text_bounds: &Bounds<Pixels>,
7857 content_origin: gpui::Point<Pixels>,
7858 editor_snapshot: &EditorSnapshot,
7859 visible_row_range: Range<DisplayRow>,
7860 scroll_top: f32,
7861 scroll_bottom: f32,
7862 line_height: Pixels,
7863 scroll_pixel_position: gpui::Point<Pixels>,
7864 target_display_point: DisplayPoint,
7865 editor_width: Pixels,
7866 window: &mut Window,
7867 cx: &mut App,
7868 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7869 if target_display_point.row().as_f32() < scroll_top {
7870 let mut element = self
7871 .render_edit_prediction_line_popover(
7872 "Jump to Edit",
7873 Some(IconName::ArrowUp),
7874 window,
7875 cx,
7876 )?
7877 .into_any();
7878
7879 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7880 let offset = point(
7881 (text_bounds.size.width - size.width) / 2.,
7882 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7883 );
7884
7885 let origin = text_bounds.origin + offset;
7886 element.prepaint_at(origin, window, cx);
7887 Some((element, origin))
7888 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7889 let mut element = self
7890 .render_edit_prediction_line_popover(
7891 "Jump to Edit",
7892 Some(IconName::ArrowDown),
7893 window,
7894 cx,
7895 )?
7896 .into_any();
7897
7898 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7899 let offset = point(
7900 (text_bounds.size.width - size.width) / 2.,
7901 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7902 );
7903
7904 let origin = text_bounds.origin + offset;
7905 element.prepaint_at(origin, window, cx);
7906 Some((element, origin))
7907 } else {
7908 self.render_edit_prediction_end_of_line_popover(
7909 "Jump to Edit",
7910 editor_snapshot,
7911 visible_row_range,
7912 target_display_point,
7913 line_height,
7914 scroll_pixel_position,
7915 content_origin,
7916 editor_width,
7917 window,
7918 cx,
7919 )
7920 }
7921 }
7922
7923 fn render_edit_prediction_end_of_line_popover(
7924 self: &mut Editor,
7925 label: &'static str,
7926 editor_snapshot: &EditorSnapshot,
7927 visible_row_range: Range<DisplayRow>,
7928 target_display_point: DisplayPoint,
7929 line_height: Pixels,
7930 scroll_pixel_position: gpui::Point<Pixels>,
7931 content_origin: gpui::Point<Pixels>,
7932 editor_width: Pixels,
7933 window: &mut Window,
7934 cx: &mut App,
7935 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7936 let target_line_end = DisplayPoint::new(
7937 target_display_point.row(),
7938 editor_snapshot.line_len(target_display_point.row()),
7939 );
7940
7941 let mut element = self
7942 .render_edit_prediction_line_popover(label, None, window, cx)?
7943 .into_any();
7944
7945 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7946
7947 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7948
7949 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7950 let mut origin = start_point
7951 + line_origin
7952 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7953 origin.x = origin.x.max(content_origin.x);
7954
7955 let max_x = content_origin.x + editor_width - size.width;
7956
7957 if origin.x > max_x {
7958 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
7959
7960 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
7961 origin.y += offset;
7962 IconName::ArrowUp
7963 } else {
7964 origin.y -= offset;
7965 IconName::ArrowDown
7966 };
7967
7968 element = self
7969 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
7970 .into_any();
7971
7972 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7973
7974 origin.x = content_origin.x + editor_width - size.width - px(2.);
7975 }
7976
7977 element.prepaint_at(origin, window, cx);
7978 Some((element, origin))
7979 }
7980
7981 fn render_edit_prediction_diff_popover(
7982 self: &Editor,
7983 text_bounds: &Bounds<Pixels>,
7984 content_origin: gpui::Point<Pixels>,
7985 right_margin: Pixels,
7986 editor_snapshot: &EditorSnapshot,
7987 visible_row_range: Range<DisplayRow>,
7988 line_layouts: &[LineWithInvisibles],
7989 line_height: Pixels,
7990 scroll_pixel_position: gpui::Point<Pixels>,
7991 newest_selection_head: Option<DisplayPoint>,
7992 editor_width: Pixels,
7993 style: &EditorStyle,
7994 edits: &Vec<(Range<Anchor>, String)>,
7995 edit_preview: &Option<language::EditPreview>,
7996 snapshot: &language::BufferSnapshot,
7997 window: &mut Window,
7998 cx: &mut App,
7999 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8000 let edit_start = edits
8001 .first()
8002 .unwrap()
8003 .0
8004 .start
8005 .to_display_point(editor_snapshot);
8006 let edit_end = edits
8007 .last()
8008 .unwrap()
8009 .0
8010 .end
8011 .to_display_point(editor_snapshot);
8012
8013 let is_visible = visible_row_range.contains(&edit_start.row())
8014 || visible_row_range.contains(&edit_end.row());
8015 if !is_visible {
8016 return None;
8017 }
8018
8019 let highlighted_edits =
8020 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8021
8022 let styled_text = highlighted_edits.to_styled_text(&style.text);
8023 let line_count = highlighted_edits.text.lines().count();
8024
8025 const BORDER_WIDTH: Pixels = px(1.);
8026
8027 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8028 let has_keybind = keybind.is_some();
8029
8030 let mut element = h_flex()
8031 .items_start()
8032 .child(
8033 h_flex()
8034 .bg(cx.theme().colors().editor_background)
8035 .border(BORDER_WIDTH)
8036 .shadow_sm()
8037 .border_color(cx.theme().colors().border)
8038 .rounded_l_lg()
8039 .when(line_count > 1, |el| el.rounded_br_lg())
8040 .pr_1()
8041 .child(styled_text),
8042 )
8043 .child(
8044 h_flex()
8045 .h(line_height + BORDER_WIDTH * 2.)
8046 .px_1p5()
8047 .gap_1()
8048 // Workaround: For some reason, there's a gap if we don't do this
8049 .ml(-BORDER_WIDTH)
8050 .shadow(vec![gpui::BoxShadow {
8051 color: gpui::black().opacity(0.05),
8052 offset: point(px(1.), px(1.)),
8053 blur_radius: px(2.),
8054 spread_radius: px(0.),
8055 }])
8056 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8057 .border(BORDER_WIDTH)
8058 .border_color(cx.theme().colors().border)
8059 .rounded_r_lg()
8060 .id("edit_prediction_diff_popover_keybind")
8061 .when(!has_keybind, |el| {
8062 let status_colors = cx.theme().status();
8063
8064 el.bg(status_colors.error_background)
8065 .border_color(status_colors.error.opacity(0.6))
8066 .child(Icon::new(IconName::Info).color(Color::Error))
8067 .cursor_default()
8068 .hoverable_tooltip(move |_window, cx| {
8069 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8070 })
8071 })
8072 .children(keybind),
8073 )
8074 .into_any();
8075
8076 let longest_row =
8077 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8078 let longest_line_width = if visible_row_range.contains(&longest_row) {
8079 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8080 } else {
8081 layout_line(
8082 longest_row,
8083 editor_snapshot,
8084 style,
8085 editor_width,
8086 |_| false,
8087 window,
8088 cx,
8089 )
8090 .width
8091 };
8092
8093 let viewport_bounds =
8094 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8095 right: -right_margin,
8096 ..Default::default()
8097 });
8098
8099 let x_after_longest =
8100 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8101 - scroll_pixel_position.x;
8102
8103 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8104
8105 // Fully visible if it can be displayed within the window (allow overlapping other
8106 // panes). However, this is only allowed if the popover starts within text_bounds.
8107 let can_position_to_the_right = x_after_longest < text_bounds.right()
8108 && x_after_longest + element_bounds.width < viewport_bounds.right();
8109
8110 let mut origin = if can_position_to_the_right {
8111 point(
8112 x_after_longest,
8113 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8114 - scroll_pixel_position.y,
8115 )
8116 } else {
8117 let cursor_row = newest_selection_head.map(|head| head.row());
8118 let above_edit = edit_start
8119 .row()
8120 .0
8121 .checked_sub(line_count as u32)
8122 .map(DisplayRow);
8123 let below_edit = Some(edit_end.row() + 1);
8124 let above_cursor =
8125 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8126 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8127
8128 // Place the edit popover adjacent to the edit if there is a location
8129 // available that is onscreen and does not obscure the cursor. Otherwise,
8130 // place it adjacent to the cursor.
8131 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8132 .into_iter()
8133 .flatten()
8134 .find(|&start_row| {
8135 let end_row = start_row + line_count as u32;
8136 visible_row_range.contains(&start_row)
8137 && visible_row_range.contains(&end_row)
8138 && cursor_row.map_or(true, |cursor_row| {
8139 !((start_row..end_row).contains(&cursor_row))
8140 })
8141 })?;
8142
8143 content_origin
8144 + point(
8145 -scroll_pixel_position.x,
8146 row_target.as_f32() * line_height - scroll_pixel_position.y,
8147 )
8148 };
8149
8150 origin.x -= BORDER_WIDTH;
8151
8152 window.defer_draw(element, origin, 1);
8153
8154 // Do not return an element, since it will already be drawn due to defer_draw.
8155 None
8156 }
8157
8158 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8159 px(30.)
8160 }
8161
8162 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8163 if self.read_only(cx) {
8164 cx.theme().players().read_only()
8165 } else {
8166 self.style.as_ref().unwrap().local_player
8167 }
8168 }
8169
8170 fn render_edit_prediction_accept_keybind(
8171 &self,
8172 window: &mut Window,
8173 cx: &App,
8174 ) -> Option<AnyElement> {
8175 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8176 let accept_keystroke = accept_binding.keystroke()?;
8177
8178 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8179
8180 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8181 Color::Accent
8182 } else {
8183 Color::Muted
8184 };
8185
8186 h_flex()
8187 .px_0p5()
8188 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8189 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8190 .text_size(TextSize::XSmall.rems(cx))
8191 .child(h_flex().children(ui::render_modifiers(
8192 &accept_keystroke.modifiers,
8193 PlatformStyle::platform(),
8194 Some(modifiers_color),
8195 Some(IconSize::XSmall.rems().into()),
8196 true,
8197 )))
8198 .when(is_platform_style_mac, |parent| {
8199 parent.child(accept_keystroke.key.clone())
8200 })
8201 .when(!is_platform_style_mac, |parent| {
8202 parent.child(
8203 Key::new(
8204 util::capitalize(&accept_keystroke.key),
8205 Some(Color::Default),
8206 )
8207 .size(Some(IconSize::XSmall.rems().into())),
8208 )
8209 })
8210 .into_any()
8211 .into()
8212 }
8213
8214 fn render_edit_prediction_line_popover(
8215 &self,
8216 label: impl Into<SharedString>,
8217 icon: Option<IconName>,
8218 window: &mut Window,
8219 cx: &App,
8220 ) -> Option<Stateful<Div>> {
8221 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8222
8223 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8224 let has_keybind = keybind.is_some();
8225
8226 let result = h_flex()
8227 .id("ep-line-popover")
8228 .py_0p5()
8229 .pl_1()
8230 .pr(padding_right)
8231 .gap_1()
8232 .rounded_md()
8233 .border_1()
8234 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8235 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8236 .shadow_sm()
8237 .when(!has_keybind, |el| {
8238 let status_colors = cx.theme().status();
8239
8240 el.bg(status_colors.error_background)
8241 .border_color(status_colors.error.opacity(0.6))
8242 .pl_2()
8243 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8244 .cursor_default()
8245 .hoverable_tooltip(move |_window, cx| {
8246 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8247 })
8248 })
8249 .children(keybind)
8250 .child(
8251 Label::new(label)
8252 .size(LabelSize::Small)
8253 .when(!has_keybind, |el| {
8254 el.color(cx.theme().status().error.into()).strikethrough()
8255 }),
8256 )
8257 .when(!has_keybind, |el| {
8258 el.child(
8259 h_flex().ml_1().child(
8260 Icon::new(IconName::Info)
8261 .size(IconSize::Small)
8262 .color(cx.theme().status().error.into()),
8263 ),
8264 )
8265 })
8266 .when_some(icon, |element, icon| {
8267 element.child(
8268 div()
8269 .mt(px(1.5))
8270 .child(Icon::new(icon).size(IconSize::Small)),
8271 )
8272 });
8273
8274 Some(result)
8275 }
8276
8277 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8278 let accent_color = cx.theme().colors().text_accent;
8279 let editor_bg_color = cx.theme().colors().editor_background;
8280 editor_bg_color.blend(accent_color.opacity(0.1))
8281 }
8282
8283 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8284 let accent_color = cx.theme().colors().text_accent;
8285 let editor_bg_color = cx.theme().colors().editor_background;
8286 editor_bg_color.blend(accent_color.opacity(0.6))
8287 }
8288
8289 fn render_edit_prediction_cursor_popover(
8290 &self,
8291 min_width: Pixels,
8292 max_width: Pixels,
8293 cursor_point: Point,
8294 style: &EditorStyle,
8295 accept_keystroke: Option<&gpui::Keystroke>,
8296 _window: &Window,
8297 cx: &mut Context<Editor>,
8298 ) -> Option<AnyElement> {
8299 let provider = self.edit_prediction_provider.as_ref()?;
8300
8301 if provider.provider.needs_terms_acceptance(cx) {
8302 return Some(
8303 h_flex()
8304 .min_w(min_width)
8305 .flex_1()
8306 .px_2()
8307 .py_1()
8308 .gap_3()
8309 .elevation_2(cx)
8310 .hover(|style| style.bg(cx.theme().colors().element_hover))
8311 .id("accept-terms")
8312 .cursor_pointer()
8313 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8314 .on_click(cx.listener(|this, _event, window, cx| {
8315 cx.stop_propagation();
8316 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8317 window.dispatch_action(
8318 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8319 cx,
8320 );
8321 }))
8322 .child(
8323 h_flex()
8324 .flex_1()
8325 .gap_2()
8326 .child(Icon::new(IconName::ZedPredict))
8327 .child(Label::new("Accept Terms of Service"))
8328 .child(div().w_full())
8329 .child(
8330 Icon::new(IconName::ArrowUpRight)
8331 .color(Color::Muted)
8332 .size(IconSize::Small),
8333 )
8334 .into_any_element(),
8335 )
8336 .into_any(),
8337 );
8338 }
8339
8340 let is_refreshing = provider.provider.is_refreshing(cx);
8341
8342 fn pending_completion_container() -> Div {
8343 h_flex()
8344 .h_full()
8345 .flex_1()
8346 .gap_2()
8347 .child(Icon::new(IconName::ZedPredict))
8348 }
8349
8350 let completion = match &self.active_inline_completion {
8351 Some(prediction) => {
8352 if !self.has_visible_completions_menu() {
8353 const RADIUS: Pixels = px(6.);
8354 const BORDER_WIDTH: Pixels = px(1.);
8355
8356 return Some(
8357 h_flex()
8358 .elevation_2(cx)
8359 .border(BORDER_WIDTH)
8360 .border_color(cx.theme().colors().border)
8361 .when(accept_keystroke.is_none(), |el| {
8362 el.border_color(cx.theme().status().error)
8363 })
8364 .rounded(RADIUS)
8365 .rounded_tl(px(0.))
8366 .overflow_hidden()
8367 .child(div().px_1p5().child(match &prediction.completion {
8368 InlineCompletion::Move { target, snapshot } => {
8369 use text::ToPoint as _;
8370 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8371 {
8372 Icon::new(IconName::ZedPredictDown)
8373 } else {
8374 Icon::new(IconName::ZedPredictUp)
8375 }
8376 }
8377 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8378 }))
8379 .child(
8380 h_flex()
8381 .gap_1()
8382 .py_1()
8383 .px_2()
8384 .rounded_r(RADIUS - BORDER_WIDTH)
8385 .border_l_1()
8386 .border_color(cx.theme().colors().border)
8387 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8388 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8389 el.child(
8390 Label::new("Hold")
8391 .size(LabelSize::Small)
8392 .when(accept_keystroke.is_none(), |el| {
8393 el.strikethrough()
8394 })
8395 .line_height_style(LineHeightStyle::UiLabel),
8396 )
8397 })
8398 .id("edit_prediction_cursor_popover_keybind")
8399 .when(accept_keystroke.is_none(), |el| {
8400 let status_colors = cx.theme().status();
8401
8402 el.bg(status_colors.error_background)
8403 .border_color(status_colors.error.opacity(0.6))
8404 .child(Icon::new(IconName::Info).color(Color::Error))
8405 .cursor_default()
8406 .hoverable_tooltip(move |_window, cx| {
8407 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8408 .into()
8409 })
8410 })
8411 .when_some(
8412 accept_keystroke.as_ref(),
8413 |el, accept_keystroke| {
8414 el.child(h_flex().children(ui::render_modifiers(
8415 &accept_keystroke.modifiers,
8416 PlatformStyle::platform(),
8417 Some(Color::Default),
8418 Some(IconSize::XSmall.rems().into()),
8419 false,
8420 )))
8421 },
8422 ),
8423 )
8424 .into_any(),
8425 );
8426 }
8427
8428 self.render_edit_prediction_cursor_popover_preview(
8429 prediction,
8430 cursor_point,
8431 style,
8432 cx,
8433 )?
8434 }
8435
8436 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8437 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8438 stale_completion,
8439 cursor_point,
8440 style,
8441 cx,
8442 )?,
8443
8444 None => {
8445 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8446 }
8447 },
8448
8449 None => pending_completion_container().child(Label::new("No Prediction")),
8450 };
8451
8452 let completion = if is_refreshing {
8453 completion
8454 .with_animation(
8455 "loading-completion",
8456 Animation::new(Duration::from_secs(2))
8457 .repeat()
8458 .with_easing(pulsating_between(0.4, 0.8)),
8459 |label, delta| label.opacity(delta),
8460 )
8461 .into_any_element()
8462 } else {
8463 completion.into_any_element()
8464 };
8465
8466 let has_completion = self.active_inline_completion.is_some();
8467
8468 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8469 Some(
8470 h_flex()
8471 .min_w(min_width)
8472 .max_w(max_width)
8473 .flex_1()
8474 .elevation_2(cx)
8475 .border_color(cx.theme().colors().border)
8476 .child(
8477 div()
8478 .flex_1()
8479 .py_1()
8480 .px_2()
8481 .overflow_hidden()
8482 .child(completion),
8483 )
8484 .when_some(accept_keystroke, |el, accept_keystroke| {
8485 if !accept_keystroke.modifiers.modified() {
8486 return el;
8487 }
8488
8489 el.child(
8490 h_flex()
8491 .h_full()
8492 .border_l_1()
8493 .rounded_r_lg()
8494 .border_color(cx.theme().colors().border)
8495 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8496 .gap_1()
8497 .py_1()
8498 .px_2()
8499 .child(
8500 h_flex()
8501 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8502 .when(is_platform_style_mac, |parent| parent.gap_1())
8503 .child(h_flex().children(ui::render_modifiers(
8504 &accept_keystroke.modifiers,
8505 PlatformStyle::platform(),
8506 Some(if !has_completion {
8507 Color::Muted
8508 } else {
8509 Color::Default
8510 }),
8511 None,
8512 false,
8513 ))),
8514 )
8515 .child(Label::new("Preview").into_any_element())
8516 .opacity(if has_completion { 1.0 } else { 0.4 }),
8517 )
8518 })
8519 .into_any(),
8520 )
8521 }
8522
8523 fn render_edit_prediction_cursor_popover_preview(
8524 &self,
8525 completion: &InlineCompletionState,
8526 cursor_point: Point,
8527 style: &EditorStyle,
8528 cx: &mut Context<Editor>,
8529 ) -> Option<Div> {
8530 use text::ToPoint as _;
8531
8532 fn render_relative_row_jump(
8533 prefix: impl Into<String>,
8534 current_row: u32,
8535 target_row: u32,
8536 ) -> Div {
8537 let (row_diff, arrow) = if target_row < current_row {
8538 (current_row - target_row, IconName::ArrowUp)
8539 } else {
8540 (target_row - current_row, IconName::ArrowDown)
8541 };
8542
8543 h_flex()
8544 .child(
8545 Label::new(format!("{}{}", prefix.into(), row_diff))
8546 .color(Color::Muted)
8547 .size(LabelSize::Small),
8548 )
8549 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8550 }
8551
8552 match &completion.completion {
8553 InlineCompletion::Move {
8554 target, snapshot, ..
8555 } => Some(
8556 h_flex()
8557 .px_2()
8558 .gap_2()
8559 .flex_1()
8560 .child(
8561 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8562 Icon::new(IconName::ZedPredictDown)
8563 } else {
8564 Icon::new(IconName::ZedPredictUp)
8565 },
8566 )
8567 .child(Label::new("Jump to Edit")),
8568 ),
8569
8570 InlineCompletion::Edit {
8571 edits,
8572 edit_preview,
8573 snapshot,
8574 display_mode: _,
8575 } => {
8576 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8577
8578 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8579 &snapshot,
8580 &edits,
8581 edit_preview.as_ref()?,
8582 true,
8583 cx,
8584 )
8585 .first_line_preview();
8586
8587 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8588 .with_default_highlights(&style.text, highlighted_edits.highlights);
8589
8590 let preview = h_flex()
8591 .gap_1()
8592 .min_w_16()
8593 .child(styled_text)
8594 .when(has_more_lines, |parent| parent.child("…"));
8595
8596 let left = if first_edit_row != cursor_point.row {
8597 render_relative_row_jump("", cursor_point.row, first_edit_row)
8598 .into_any_element()
8599 } else {
8600 Icon::new(IconName::ZedPredict).into_any_element()
8601 };
8602
8603 Some(
8604 h_flex()
8605 .h_full()
8606 .flex_1()
8607 .gap_2()
8608 .pr_1()
8609 .overflow_x_hidden()
8610 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8611 .child(left)
8612 .child(preview),
8613 )
8614 }
8615 }
8616 }
8617
8618 pub fn render_context_menu(
8619 &self,
8620 style: &EditorStyle,
8621 max_height_in_lines: u32,
8622 window: &mut Window,
8623 cx: &mut Context<Editor>,
8624 ) -> Option<AnyElement> {
8625 let menu = self.context_menu.borrow();
8626 let menu = menu.as_ref()?;
8627 if !menu.visible() {
8628 return None;
8629 };
8630 Some(menu.render(style, max_height_in_lines, window, cx))
8631 }
8632
8633 fn render_context_menu_aside(
8634 &mut self,
8635 max_size: Size<Pixels>,
8636 window: &mut Window,
8637 cx: &mut Context<Editor>,
8638 ) -> Option<AnyElement> {
8639 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8640 if menu.visible() {
8641 menu.render_aside(self, max_size, window, cx)
8642 } else {
8643 None
8644 }
8645 })
8646 }
8647
8648 fn hide_context_menu(
8649 &mut self,
8650 window: &mut Window,
8651 cx: &mut Context<Self>,
8652 ) -> Option<CodeContextMenu> {
8653 cx.notify();
8654 self.completion_tasks.clear();
8655 let context_menu = self.context_menu.borrow_mut().take();
8656 self.stale_inline_completion_in_menu.take();
8657 self.update_visible_inline_completion(window, cx);
8658 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8659 if let Some(completion_provider) = &self.completion_provider {
8660 completion_provider.selection_changed(None, window, cx);
8661 }
8662 }
8663 context_menu
8664 }
8665
8666 fn show_snippet_choices(
8667 &mut self,
8668 choices: &Vec<String>,
8669 selection: Range<Anchor>,
8670 cx: &mut Context<Self>,
8671 ) {
8672 if selection.start.buffer_id.is_none() {
8673 return;
8674 }
8675 let buffer_id = selection.start.buffer_id.unwrap();
8676 let buffer = self.buffer().read(cx).buffer(buffer_id);
8677 let id = post_inc(&mut self.next_completion_id);
8678 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8679
8680 if let Some(buffer) = buffer {
8681 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8682 CompletionsMenu::new_snippet_choices(
8683 id,
8684 true,
8685 choices,
8686 selection,
8687 buffer,
8688 snippet_sort_order,
8689 ),
8690 ));
8691 }
8692 }
8693
8694 pub fn insert_snippet(
8695 &mut self,
8696 insertion_ranges: &[Range<usize>],
8697 snippet: Snippet,
8698 window: &mut Window,
8699 cx: &mut Context<Self>,
8700 ) -> Result<()> {
8701 struct Tabstop<T> {
8702 is_end_tabstop: bool,
8703 ranges: Vec<Range<T>>,
8704 choices: Option<Vec<String>>,
8705 }
8706
8707 let tabstops = self.buffer.update(cx, |buffer, cx| {
8708 let snippet_text: Arc<str> = snippet.text.clone().into();
8709 let edits = insertion_ranges
8710 .iter()
8711 .cloned()
8712 .map(|range| (range, snippet_text.clone()));
8713 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8714
8715 let snapshot = &*buffer.read(cx);
8716 let snippet = &snippet;
8717 snippet
8718 .tabstops
8719 .iter()
8720 .map(|tabstop| {
8721 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8722 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8723 });
8724 let mut tabstop_ranges = tabstop
8725 .ranges
8726 .iter()
8727 .flat_map(|tabstop_range| {
8728 let mut delta = 0_isize;
8729 insertion_ranges.iter().map(move |insertion_range| {
8730 let insertion_start = insertion_range.start as isize + delta;
8731 delta +=
8732 snippet.text.len() as isize - insertion_range.len() as isize;
8733
8734 let start = ((insertion_start + tabstop_range.start) as usize)
8735 .min(snapshot.len());
8736 let end = ((insertion_start + tabstop_range.end) as usize)
8737 .min(snapshot.len());
8738 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8739 })
8740 })
8741 .collect::<Vec<_>>();
8742 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8743
8744 Tabstop {
8745 is_end_tabstop,
8746 ranges: tabstop_ranges,
8747 choices: tabstop.choices.clone(),
8748 }
8749 })
8750 .collect::<Vec<_>>()
8751 });
8752 if let Some(tabstop) = tabstops.first() {
8753 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8754 s.select_ranges(tabstop.ranges.iter().cloned());
8755 });
8756
8757 if let Some(choices) = &tabstop.choices {
8758 if let Some(selection) = tabstop.ranges.first() {
8759 self.show_snippet_choices(choices, selection.clone(), cx)
8760 }
8761 }
8762
8763 // If we're already at the last tabstop and it's at the end of the snippet,
8764 // we're done, we don't need to keep the state around.
8765 if !tabstop.is_end_tabstop {
8766 let choices = tabstops
8767 .iter()
8768 .map(|tabstop| tabstop.choices.clone())
8769 .collect();
8770
8771 let ranges = tabstops
8772 .into_iter()
8773 .map(|tabstop| tabstop.ranges)
8774 .collect::<Vec<_>>();
8775
8776 self.snippet_stack.push(SnippetState {
8777 active_index: 0,
8778 ranges,
8779 choices,
8780 });
8781 }
8782
8783 // Check whether the just-entered snippet ends with an auto-closable bracket.
8784 if self.autoclose_regions.is_empty() {
8785 let snapshot = self.buffer.read(cx).snapshot(cx);
8786 for selection in &mut self.selections.all::<Point>(cx) {
8787 let selection_head = selection.head();
8788 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8789 continue;
8790 };
8791
8792 let mut bracket_pair = None;
8793 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8794 let prev_chars = snapshot
8795 .reversed_chars_at(selection_head)
8796 .collect::<String>();
8797 for (pair, enabled) in scope.brackets() {
8798 if enabled
8799 && pair.close
8800 && prev_chars.starts_with(pair.start.as_str())
8801 && next_chars.starts_with(pair.end.as_str())
8802 {
8803 bracket_pair = Some(pair.clone());
8804 break;
8805 }
8806 }
8807 if let Some(pair) = bracket_pair {
8808 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8809 let autoclose_enabled =
8810 self.use_autoclose && snapshot_settings.use_autoclose;
8811 if autoclose_enabled {
8812 let start = snapshot.anchor_after(selection_head);
8813 let end = snapshot.anchor_after(selection_head);
8814 self.autoclose_regions.push(AutocloseRegion {
8815 selection_id: selection.id,
8816 range: start..end,
8817 pair,
8818 });
8819 }
8820 }
8821 }
8822 }
8823 }
8824 Ok(())
8825 }
8826
8827 pub fn move_to_next_snippet_tabstop(
8828 &mut self,
8829 window: &mut Window,
8830 cx: &mut Context<Self>,
8831 ) -> bool {
8832 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8833 }
8834
8835 pub fn move_to_prev_snippet_tabstop(
8836 &mut self,
8837 window: &mut Window,
8838 cx: &mut Context<Self>,
8839 ) -> bool {
8840 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8841 }
8842
8843 pub fn move_to_snippet_tabstop(
8844 &mut self,
8845 bias: Bias,
8846 window: &mut Window,
8847 cx: &mut Context<Self>,
8848 ) -> bool {
8849 if let Some(mut snippet) = self.snippet_stack.pop() {
8850 match bias {
8851 Bias::Left => {
8852 if snippet.active_index > 0 {
8853 snippet.active_index -= 1;
8854 } else {
8855 self.snippet_stack.push(snippet);
8856 return false;
8857 }
8858 }
8859 Bias::Right => {
8860 if snippet.active_index + 1 < snippet.ranges.len() {
8861 snippet.active_index += 1;
8862 } else {
8863 self.snippet_stack.push(snippet);
8864 return false;
8865 }
8866 }
8867 }
8868 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8869 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8870 s.select_anchor_ranges(current_ranges.iter().cloned())
8871 });
8872
8873 if let Some(choices) = &snippet.choices[snippet.active_index] {
8874 if let Some(selection) = current_ranges.first() {
8875 self.show_snippet_choices(&choices, selection.clone(), cx);
8876 }
8877 }
8878
8879 // If snippet state is not at the last tabstop, push it back on the stack
8880 if snippet.active_index + 1 < snippet.ranges.len() {
8881 self.snippet_stack.push(snippet);
8882 }
8883 return true;
8884 }
8885 }
8886
8887 false
8888 }
8889
8890 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8891 self.transact(window, cx, |this, window, cx| {
8892 this.select_all(&SelectAll, window, cx);
8893 this.insert("", window, cx);
8894 });
8895 }
8896
8897 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8898 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8899 self.transact(window, cx, |this, window, cx| {
8900 this.select_autoclose_pair(window, cx);
8901 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8902 if !this.linked_edit_ranges.is_empty() {
8903 let selections = this.selections.all::<MultiBufferPoint>(cx);
8904 let snapshot = this.buffer.read(cx).snapshot(cx);
8905
8906 for selection in selections.iter() {
8907 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8908 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8909 if selection_start.buffer_id != selection_end.buffer_id {
8910 continue;
8911 }
8912 if let Some(ranges) =
8913 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8914 {
8915 for (buffer, entries) in ranges {
8916 linked_ranges.entry(buffer).or_default().extend(entries);
8917 }
8918 }
8919 }
8920 }
8921
8922 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8923 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8924 for selection in &mut selections {
8925 if selection.is_empty() {
8926 let old_head = selection.head();
8927 let mut new_head =
8928 movement::left(&display_map, old_head.to_display_point(&display_map))
8929 .to_point(&display_map);
8930 if let Some((buffer, line_buffer_range)) = display_map
8931 .buffer_snapshot
8932 .buffer_line_for_row(MultiBufferRow(old_head.row))
8933 {
8934 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8935 let indent_len = match indent_size.kind {
8936 IndentKind::Space => {
8937 buffer.settings_at(line_buffer_range.start, cx).tab_size
8938 }
8939 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8940 };
8941 if old_head.column <= indent_size.len && old_head.column > 0 {
8942 let indent_len = indent_len.get();
8943 new_head = cmp::min(
8944 new_head,
8945 MultiBufferPoint::new(
8946 old_head.row,
8947 ((old_head.column - 1) / indent_len) * indent_len,
8948 ),
8949 );
8950 }
8951 }
8952
8953 selection.set_head(new_head, SelectionGoal::None);
8954 }
8955 }
8956
8957 this.signature_help_state.set_backspace_pressed(true);
8958 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8959 s.select(selections)
8960 });
8961 this.insert("", window, cx);
8962 let empty_str: Arc<str> = Arc::from("");
8963 for (buffer, edits) in linked_ranges {
8964 let snapshot = buffer.read(cx).snapshot();
8965 use text::ToPoint as TP;
8966
8967 let edits = edits
8968 .into_iter()
8969 .map(|range| {
8970 let end_point = TP::to_point(&range.end, &snapshot);
8971 let mut start_point = TP::to_point(&range.start, &snapshot);
8972
8973 if end_point == start_point {
8974 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
8975 .saturating_sub(1);
8976 start_point =
8977 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
8978 };
8979
8980 (start_point..end_point, empty_str.clone())
8981 })
8982 .sorted_by_key(|(range, _)| range.start)
8983 .collect::<Vec<_>>();
8984 buffer.update(cx, |this, cx| {
8985 this.edit(edits, None, cx);
8986 })
8987 }
8988 this.refresh_inline_completion(true, false, window, cx);
8989 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
8990 });
8991 }
8992
8993 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
8994 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8995 self.transact(window, cx, |this, window, cx| {
8996 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8997 s.move_with(|map, selection| {
8998 if selection.is_empty() {
8999 let cursor = movement::right(map, selection.head());
9000 selection.end = cursor;
9001 selection.reversed = true;
9002 selection.goal = SelectionGoal::None;
9003 }
9004 })
9005 });
9006 this.insert("", window, cx);
9007 this.refresh_inline_completion(true, false, window, cx);
9008 });
9009 }
9010
9011 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9012 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9013 if self.move_to_prev_snippet_tabstop(window, cx) {
9014 return;
9015 }
9016 self.outdent(&Outdent, window, cx);
9017 }
9018
9019 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9020 if self.move_to_next_snippet_tabstop(window, cx) {
9021 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9022 return;
9023 }
9024 if self.read_only(cx) {
9025 return;
9026 }
9027 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9028 let mut selections = self.selections.all_adjusted(cx);
9029 let buffer = self.buffer.read(cx);
9030 let snapshot = buffer.snapshot(cx);
9031 let rows_iter = selections.iter().map(|s| s.head().row);
9032 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9033
9034 let has_some_cursor_in_whitespace = selections
9035 .iter()
9036 .filter(|selection| selection.is_empty())
9037 .any(|selection| {
9038 let cursor = selection.head();
9039 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9040 cursor.column < current_indent.len
9041 });
9042
9043 let mut edits = Vec::new();
9044 let mut prev_edited_row = 0;
9045 let mut row_delta = 0;
9046 for selection in &mut selections {
9047 if selection.start.row != prev_edited_row {
9048 row_delta = 0;
9049 }
9050 prev_edited_row = selection.end.row;
9051
9052 // If the selection is non-empty, then increase the indentation of the selected lines.
9053 if !selection.is_empty() {
9054 row_delta =
9055 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9056 continue;
9057 }
9058
9059 let cursor = selection.head();
9060 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9061 if let Some(suggested_indent) =
9062 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9063 {
9064 // Don't do anything if already at suggested indent
9065 // and there is any other cursor which is not
9066 if has_some_cursor_in_whitespace
9067 && cursor.column == current_indent.len
9068 && current_indent.len == suggested_indent.len
9069 {
9070 continue;
9071 }
9072
9073 // Adjust line and move cursor to suggested indent
9074 // if cursor is not at suggested indent
9075 if cursor.column < suggested_indent.len
9076 && cursor.column <= current_indent.len
9077 && current_indent.len <= suggested_indent.len
9078 {
9079 selection.start = Point::new(cursor.row, suggested_indent.len);
9080 selection.end = selection.start;
9081 if row_delta == 0 {
9082 edits.extend(Buffer::edit_for_indent_size_adjustment(
9083 cursor.row,
9084 current_indent,
9085 suggested_indent,
9086 ));
9087 row_delta = suggested_indent.len - current_indent.len;
9088 }
9089 continue;
9090 }
9091
9092 // If current indent is more than suggested indent
9093 // only move cursor to current indent and skip indent
9094 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9095 selection.start = Point::new(cursor.row, current_indent.len);
9096 selection.end = selection.start;
9097 continue;
9098 }
9099 }
9100
9101 // Otherwise, insert a hard or soft tab.
9102 let settings = buffer.language_settings_at(cursor, cx);
9103 let tab_size = if settings.hard_tabs {
9104 IndentSize::tab()
9105 } else {
9106 let tab_size = settings.tab_size.get();
9107 let indent_remainder = snapshot
9108 .text_for_range(Point::new(cursor.row, 0)..cursor)
9109 .flat_map(str::chars)
9110 .fold(row_delta % tab_size, |counter: u32, c| {
9111 if c == '\t' {
9112 0
9113 } else {
9114 (counter + 1) % tab_size
9115 }
9116 });
9117
9118 let chars_to_next_tab_stop = tab_size - indent_remainder;
9119 IndentSize::spaces(chars_to_next_tab_stop)
9120 };
9121 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9122 selection.end = selection.start;
9123 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9124 row_delta += tab_size.len;
9125 }
9126
9127 self.transact(window, cx, |this, window, cx| {
9128 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9129 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9130 s.select(selections)
9131 });
9132 this.refresh_inline_completion(true, false, window, cx);
9133 });
9134 }
9135
9136 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9137 if self.read_only(cx) {
9138 return;
9139 }
9140 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9141 let mut selections = self.selections.all::<Point>(cx);
9142 let mut prev_edited_row = 0;
9143 let mut row_delta = 0;
9144 let mut edits = Vec::new();
9145 let buffer = self.buffer.read(cx);
9146 let snapshot = buffer.snapshot(cx);
9147 for selection in &mut selections {
9148 if selection.start.row != prev_edited_row {
9149 row_delta = 0;
9150 }
9151 prev_edited_row = selection.end.row;
9152
9153 row_delta =
9154 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9155 }
9156
9157 self.transact(window, cx, |this, window, cx| {
9158 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9159 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9160 s.select(selections)
9161 });
9162 });
9163 }
9164
9165 fn indent_selection(
9166 buffer: &MultiBuffer,
9167 snapshot: &MultiBufferSnapshot,
9168 selection: &mut Selection<Point>,
9169 edits: &mut Vec<(Range<Point>, String)>,
9170 delta_for_start_row: u32,
9171 cx: &App,
9172 ) -> u32 {
9173 let settings = buffer.language_settings_at(selection.start, cx);
9174 let tab_size = settings.tab_size.get();
9175 let indent_kind = if settings.hard_tabs {
9176 IndentKind::Tab
9177 } else {
9178 IndentKind::Space
9179 };
9180 let mut start_row = selection.start.row;
9181 let mut end_row = selection.end.row + 1;
9182
9183 // If a selection ends at the beginning of a line, don't indent
9184 // that last line.
9185 if selection.end.column == 0 && selection.end.row > selection.start.row {
9186 end_row -= 1;
9187 }
9188
9189 // Avoid re-indenting a row that has already been indented by a
9190 // previous selection, but still update this selection's column
9191 // to reflect that indentation.
9192 if delta_for_start_row > 0 {
9193 start_row += 1;
9194 selection.start.column += delta_for_start_row;
9195 if selection.end.row == selection.start.row {
9196 selection.end.column += delta_for_start_row;
9197 }
9198 }
9199
9200 let mut delta_for_end_row = 0;
9201 let has_multiple_rows = start_row + 1 != end_row;
9202 for row in start_row..end_row {
9203 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9204 let indent_delta = match (current_indent.kind, indent_kind) {
9205 (IndentKind::Space, IndentKind::Space) => {
9206 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9207 IndentSize::spaces(columns_to_next_tab_stop)
9208 }
9209 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9210 (_, IndentKind::Tab) => IndentSize::tab(),
9211 };
9212
9213 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9214 0
9215 } else {
9216 selection.start.column
9217 };
9218 let row_start = Point::new(row, start);
9219 edits.push((
9220 row_start..row_start,
9221 indent_delta.chars().collect::<String>(),
9222 ));
9223
9224 // Update this selection's endpoints to reflect the indentation.
9225 if row == selection.start.row {
9226 selection.start.column += indent_delta.len;
9227 }
9228 if row == selection.end.row {
9229 selection.end.column += indent_delta.len;
9230 delta_for_end_row = indent_delta.len;
9231 }
9232 }
9233
9234 if selection.start.row == selection.end.row {
9235 delta_for_start_row + delta_for_end_row
9236 } else {
9237 delta_for_end_row
9238 }
9239 }
9240
9241 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9242 if self.read_only(cx) {
9243 return;
9244 }
9245 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9246 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9247 let selections = self.selections.all::<Point>(cx);
9248 let mut deletion_ranges = Vec::new();
9249 let mut last_outdent = None;
9250 {
9251 let buffer = self.buffer.read(cx);
9252 let snapshot = buffer.snapshot(cx);
9253 for selection in &selections {
9254 let settings = buffer.language_settings_at(selection.start, cx);
9255 let tab_size = settings.tab_size.get();
9256 let mut rows = selection.spanned_rows(false, &display_map);
9257
9258 // Avoid re-outdenting a row that has already been outdented by a
9259 // previous selection.
9260 if let Some(last_row) = last_outdent {
9261 if last_row == rows.start {
9262 rows.start = rows.start.next_row();
9263 }
9264 }
9265 let has_multiple_rows = rows.len() > 1;
9266 for row in rows.iter_rows() {
9267 let indent_size = snapshot.indent_size_for_line(row);
9268 if indent_size.len > 0 {
9269 let deletion_len = match indent_size.kind {
9270 IndentKind::Space => {
9271 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9272 if columns_to_prev_tab_stop == 0 {
9273 tab_size
9274 } else {
9275 columns_to_prev_tab_stop
9276 }
9277 }
9278 IndentKind::Tab => 1,
9279 };
9280 let start = if has_multiple_rows
9281 || deletion_len > selection.start.column
9282 || indent_size.len < selection.start.column
9283 {
9284 0
9285 } else {
9286 selection.start.column - deletion_len
9287 };
9288 deletion_ranges.push(
9289 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9290 );
9291 last_outdent = Some(row);
9292 }
9293 }
9294 }
9295 }
9296
9297 self.transact(window, cx, |this, window, cx| {
9298 this.buffer.update(cx, |buffer, cx| {
9299 let empty_str: Arc<str> = Arc::default();
9300 buffer.edit(
9301 deletion_ranges
9302 .into_iter()
9303 .map(|range| (range, empty_str.clone())),
9304 None,
9305 cx,
9306 );
9307 });
9308 let selections = this.selections.all::<usize>(cx);
9309 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9310 s.select(selections)
9311 });
9312 });
9313 }
9314
9315 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9316 if self.read_only(cx) {
9317 return;
9318 }
9319 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9320 let selections = self
9321 .selections
9322 .all::<usize>(cx)
9323 .into_iter()
9324 .map(|s| s.range());
9325
9326 self.transact(window, cx, |this, window, cx| {
9327 this.buffer.update(cx, |buffer, cx| {
9328 buffer.autoindent_ranges(selections, cx);
9329 });
9330 let selections = this.selections.all::<usize>(cx);
9331 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9332 s.select(selections)
9333 });
9334 });
9335 }
9336
9337 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9338 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9339 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9340 let selections = self.selections.all::<Point>(cx);
9341
9342 let mut new_cursors = Vec::new();
9343 let mut edit_ranges = Vec::new();
9344 let mut selections = selections.iter().peekable();
9345 while let Some(selection) = selections.next() {
9346 let mut rows = selection.spanned_rows(false, &display_map);
9347 let goal_display_column = selection.head().to_display_point(&display_map).column();
9348
9349 // Accumulate contiguous regions of rows that we want to delete.
9350 while let Some(next_selection) = selections.peek() {
9351 let next_rows = next_selection.spanned_rows(false, &display_map);
9352 if next_rows.start <= rows.end {
9353 rows.end = next_rows.end;
9354 selections.next().unwrap();
9355 } else {
9356 break;
9357 }
9358 }
9359
9360 let buffer = &display_map.buffer_snapshot;
9361 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9362 let edit_end;
9363 let cursor_buffer_row;
9364 if buffer.max_point().row >= rows.end.0 {
9365 // If there's a line after the range, delete the \n from the end of the row range
9366 // and position the cursor on the next line.
9367 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9368 cursor_buffer_row = rows.end;
9369 } else {
9370 // If there isn't a line after the range, delete the \n from the line before the
9371 // start of the row range and position the cursor there.
9372 edit_start = edit_start.saturating_sub(1);
9373 edit_end = buffer.len();
9374 cursor_buffer_row = rows.start.previous_row();
9375 }
9376
9377 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9378 *cursor.column_mut() =
9379 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9380
9381 new_cursors.push((
9382 selection.id,
9383 buffer.anchor_after(cursor.to_point(&display_map)),
9384 ));
9385 edit_ranges.push(edit_start..edit_end);
9386 }
9387
9388 self.transact(window, cx, |this, window, cx| {
9389 let buffer = this.buffer.update(cx, |buffer, cx| {
9390 let empty_str: Arc<str> = Arc::default();
9391 buffer.edit(
9392 edit_ranges
9393 .into_iter()
9394 .map(|range| (range, empty_str.clone())),
9395 None,
9396 cx,
9397 );
9398 buffer.snapshot(cx)
9399 });
9400 let new_selections = new_cursors
9401 .into_iter()
9402 .map(|(id, cursor)| {
9403 let cursor = cursor.to_point(&buffer);
9404 Selection {
9405 id,
9406 start: cursor,
9407 end: cursor,
9408 reversed: false,
9409 goal: SelectionGoal::None,
9410 }
9411 })
9412 .collect();
9413
9414 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9415 s.select(new_selections);
9416 });
9417 });
9418 }
9419
9420 pub fn join_lines_impl(
9421 &mut self,
9422 insert_whitespace: bool,
9423 window: &mut Window,
9424 cx: &mut Context<Self>,
9425 ) {
9426 if self.read_only(cx) {
9427 return;
9428 }
9429 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9430 for selection in self.selections.all::<Point>(cx) {
9431 let start = MultiBufferRow(selection.start.row);
9432 // Treat single line selections as if they include the next line. Otherwise this action
9433 // would do nothing for single line selections individual cursors.
9434 let end = if selection.start.row == selection.end.row {
9435 MultiBufferRow(selection.start.row + 1)
9436 } else {
9437 MultiBufferRow(selection.end.row)
9438 };
9439
9440 if let Some(last_row_range) = row_ranges.last_mut() {
9441 if start <= last_row_range.end {
9442 last_row_range.end = end;
9443 continue;
9444 }
9445 }
9446 row_ranges.push(start..end);
9447 }
9448
9449 let snapshot = self.buffer.read(cx).snapshot(cx);
9450 let mut cursor_positions = Vec::new();
9451 for row_range in &row_ranges {
9452 let anchor = snapshot.anchor_before(Point::new(
9453 row_range.end.previous_row().0,
9454 snapshot.line_len(row_range.end.previous_row()),
9455 ));
9456 cursor_positions.push(anchor..anchor);
9457 }
9458
9459 self.transact(window, cx, |this, window, cx| {
9460 for row_range in row_ranges.into_iter().rev() {
9461 for row in row_range.iter_rows().rev() {
9462 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9463 let next_line_row = row.next_row();
9464 let indent = snapshot.indent_size_for_line(next_line_row);
9465 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9466
9467 let replace =
9468 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9469 " "
9470 } else {
9471 ""
9472 };
9473
9474 this.buffer.update(cx, |buffer, cx| {
9475 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9476 });
9477 }
9478 }
9479
9480 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9481 s.select_anchor_ranges(cursor_positions)
9482 });
9483 });
9484 }
9485
9486 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9487 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9488 self.join_lines_impl(true, window, cx);
9489 }
9490
9491 pub fn sort_lines_case_sensitive(
9492 &mut self,
9493 _: &SortLinesCaseSensitive,
9494 window: &mut Window,
9495 cx: &mut Context<Self>,
9496 ) {
9497 self.manipulate_lines(window, cx, |lines| lines.sort())
9498 }
9499
9500 pub fn sort_lines_case_insensitive(
9501 &mut self,
9502 _: &SortLinesCaseInsensitive,
9503 window: &mut Window,
9504 cx: &mut Context<Self>,
9505 ) {
9506 self.manipulate_lines(window, cx, |lines| {
9507 lines.sort_by_key(|line| line.to_lowercase())
9508 })
9509 }
9510
9511 pub fn unique_lines_case_insensitive(
9512 &mut self,
9513 _: &UniqueLinesCaseInsensitive,
9514 window: &mut Window,
9515 cx: &mut Context<Self>,
9516 ) {
9517 self.manipulate_lines(window, cx, |lines| {
9518 let mut seen = HashSet::default();
9519 lines.retain(|line| seen.insert(line.to_lowercase()));
9520 })
9521 }
9522
9523 pub fn unique_lines_case_sensitive(
9524 &mut self,
9525 _: &UniqueLinesCaseSensitive,
9526 window: &mut Window,
9527 cx: &mut Context<Self>,
9528 ) {
9529 self.manipulate_lines(window, cx, |lines| {
9530 let mut seen = HashSet::default();
9531 lines.retain(|line| seen.insert(*line));
9532 })
9533 }
9534
9535 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9536 let Some(project) = self.project.clone() else {
9537 return;
9538 };
9539 self.reload(project, window, cx)
9540 .detach_and_notify_err(window, cx);
9541 }
9542
9543 pub fn restore_file(
9544 &mut self,
9545 _: &::git::RestoreFile,
9546 window: &mut Window,
9547 cx: &mut Context<Self>,
9548 ) {
9549 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9550 let mut buffer_ids = HashSet::default();
9551 let snapshot = self.buffer().read(cx).snapshot(cx);
9552 for selection in self.selections.all::<usize>(cx) {
9553 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9554 }
9555
9556 let buffer = self.buffer().read(cx);
9557 let ranges = buffer_ids
9558 .into_iter()
9559 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9560 .collect::<Vec<_>>();
9561
9562 self.restore_hunks_in_ranges(ranges, window, cx);
9563 }
9564
9565 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9566 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9567 let selections = self
9568 .selections
9569 .all(cx)
9570 .into_iter()
9571 .map(|s| s.range())
9572 .collect();
9573 self.restore_hunks_in_ranges(selections, window, cx);
9574 }
9575
9576 pub fn restore_hunks_in_ranges(
9577 &mut self,
9578 ranges: Vec<Range<Point>>,
9579 window: &mut Window,
9580 cx: &mut Context<Editor>,
9581 ) {
9582 let mut revert_changes = HashMap::default();
9583 let chunk_by = self
9584 .snapshot(window, cx)
9585 .hunks_for_ranges(ranges)
9586 .into_iter()
9587 .chunk_by(|hunk| hunk.buffer_id);
9588 for (buffer_id, hunks) in &chunk_by {
9589 let hunks = hunks.collect::<Vec<_>>();
9590 for hunk in &hunks {
9591 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9592 }
9593 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9594 }
9595 drop(chunk_by);
9596 if !revert_changes.is_empty() {
9597 self.transact(window, cx, |editor, window, cx| {
9598 editor.restore(revert_changes, window, cx);
9599 });
9600 }
9601 }
9602
9603 pub fn open_active_item_in_terminal(
9604 &mut self,
9605 _: &OpenInTerminal,
9606 window: &mut Window,
9607 cx: &mut Context<Self>,
9608 ) {
9609 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9610 let project_path = buffer.read(cx).project_path(cx)?;
9611 let project = self.project.as_ref()?.read(cx);
9612 let entry = project.entry_for_path(&project_path, cx)?;
9613 let parent = match &entry.canonical_path {
9614 Some(canonical_path) => canonical_path.to_path_buf(),
9615 None => project.absolute_path(&project_path, cx)?,
9616 }
9617 .parent()?
9618 .to_path_buf();
9619 Some(parent)
9620 }) {
9621 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9622 }
9623 }
9624
9625 fn set_breakpoint_context_menu(
9626 &mut self,
9627 display_row: DisplayRow,
9628 position: Option<Anchor>,
9629 clicked_point: gpui::Point<Pixels>,
9630 window: &mut Window,
9631 cx: &mut Context<Self>,
9632 ) {
9633 if !cx.has_flag::<DebuggerFeatureFlag>() {
9634 return;
9635 }
9636 let source = self
9637 .buffer
9638 .read(cx)
9639 .snapshot(cx)
9640 .anchor_before(Point::new(display_row.0, 0u32));
9641
9642 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9643
9644 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9645 self,
9646 source,
9647 clicked_point,
9648 context_menu,
9649 window,
9650 cx,
9651 );
9652 }
9653
9654 fn add_edit_breakpoint_block(
9655 &mut self,
9656 anchor: Anchor,
9657 breakpoint: &Breakpoint,
9658 edit_action: BreakpointPromptEditAction,
9659 window: &mut Window,
9660 cx: &mut Context<Self>,
9661 ) {
9662 let weak_editor = cx.weak_entity();
9663 let bp_prompt = cx.new(|cx| {
9664 BreakpointPromptEditor::new(
9665 weak_editor,
9666 anchor,
9667 breakpoint.clone(),
9668 edit_action,
9669 window,
9670 cx,
9671 )
9672 });
9673
9674 let height = bp_prompt.update(cx, |this, cx| {
9675 this.prompt
9676 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9677 });
9678 let cloned_prompt = bp_prompt.clone();
9679 let blocks = vec![BlockProperties {
9680 style: BlockStyle::Sticky,
9681 placement: BlockPlacement::Above(anchor),
9682 height: Some(height),
9683 render: Arc::new(move |cx| {
9684 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9685 cloned_prompt.clone().into_any_element()
9686 }),
9687 priority: 0,
9688 render_in_minimap: true,
9689 }];
9690
9691 let focus_handle = bp_prompt.focus_handle(cx);
9692 window.focus(&focus_handle);
9693
9694 let block_ids = self.insert_blocks(blocks, None, cx);
9695 bp_prompt.update(cx, |prompt, _| {
9696 prompt.add_block_ids(block_ids);
9697 });
9698 }
9699
9700 pub(crate) fn breakpoint_at_row(
9701 &self,
9702 row: u32,
9703 window: &mut Window,
9704 cx: &mut Context<Self>,
9705 ) -> Option<(Anchor, Breakpoint)> {
9706 let snapshot = self.snapshot(window, cx);
9707 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9708
9709 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9710 }
9711
9712 pub(crate) fn breakpoint_at_anchor(
9713 &self,
9714 breakpoint_position: Anchor,
9715 snapshot: &EditorSnapshot,
9716 cx: &mut Context<Self>,
9717 ) -> Option<(Anchor, Breakpoint)> {
9718 let project = self.project.clone()?;
9719
9720 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9721 snapshot
9722 .buffer_snapshot
9723 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9724 })?;
9725
9726 let enclosing_excerpt = breakpoint_position.excerpt_id;
9727 let buffer = project.read_with(cx, |project, cx| project.buffer_for_id(buffer_id, cx))?;
9728 let buffer_snapshot = buffer.read(cx).snapshot();
9729
9730 let row = buffer_snapshot
9731 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9732 .row;
9733
9734 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9735 let anchor_end = snapshot
9736 .buffer_snapshot
9737 .anchor_after(Point::new(row, line_len));
9738
9739 let bp = self
9740 .breakpoint_store
9741 .as_ref()?
9742 .read_with(cx, |breakpoint_store, cx| {
9743 breakpoint_store
9744 .breakpoints(
9745 &buffer,
9746 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9747 &buffer_snapshot,
9748 cx,
9749 )
9750 .next()
9751 .and_then(|(bp, _)| {
9752 let breakpoint_row = buffer_snapshot
9753 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9754 .row;
9755
9756 if breakpoint_row == row {
9757 snapshot
9758 .buffer_snapshot
9759 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9760 .map(|position| (position, bp.bp.clone()))
9761 } else {
9762 None
9763 }
9764 })
9765 });
9766 bp
9767 }
9768
9769 pub fn edit_log_breakpoint(
9770 &mut self,
9771 _: &EditLogBreakpoint,
9772 window: &mut Window,
9773 cx: &mut Context<Self>,
9774 ) {
9775 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9776 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9777 message: None,
9778 state: BreakpointState::Enabled,
9779 condition: None,
9780 hit_condition: None,
9781 });
9782
9783 self.add_edit_breakpoint_block(
9784 anchor,
9785 &breakpoint,
9786 BreakpointPromptEditAction::Log,
9787 window,
9788 cx,
9789 );
9790 }
9791 }
9792
9793 fn breakpoints_at_cursors(
9794 &self,
9795 window: &mut Window,
9796 cx: &mut Context<Self>,
9797 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9798 let snapshot = self.snapshot(window, cx);
9799 let cursors = self
9800 .selections
9801 .disjoint_anchors()
9802 .into_iter()
9803 .map(|selection| {
9804 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9805
9806 let breakpoint_position = self
9807 .breakpoint_at_row(cursor_position.row, window, cx)
9808 .map(|bp| bp.0)
9809 .unwrap_or_else(|| {
9810 snapshot
9811 .display_snapshot
9812 .buffer_snapshot
9813 .anchor_after(Point::new(cursor_position.row, 0))
9814 });
9815
9816 let breakpoint = self
9817 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9818 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9819
9820 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9821 })
9822 // 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.
9823 .collect::<HashMap<Anchor, _>>();
9824
9825 cursors.into_iter().collect()
9826 }
9827
9828 pub fn enable_breakpoint(
9829 &mut self,
9830 _: &crate::actions::EnableBreakpoint,
9831 window: &mut Window,
9832 cx: &mut Context<Self>,
9833 ) {
9834 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9835 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9836 continue;
9837 };
9838 self.edit_breakpoint_at_anchor(
9839 anchor,
9840 breakpoint,
9841 BreakpointEditAction::InvertState,
9842 cx,
9843 );
9844 }
9845 }
9846
9847 pub fn disable_breakpoint(
9848 &mut self,
9849 _: &crate::actions::DisableBreakpoint,
9850 window: &mut Window,
9851 cx: &mut Context<Self>,
9852 ) {
9853 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9854 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9855 continue;
9856 };
9857 self.edit_breakpoint_at_anchor(
9858 anchor,
9859 breakpoint,
9860 BreakpointEditAction::InvertState,
9861 cx,
9862 );
9863 }
9864 }
9865
9866 pub fn toggle_breakpoint(
9867 &mut self,
9868 _: &crate::actions::ToggleBreakpoint,
9869 window: &mut Window,
9870 cx: &mut Context<Self>,
9871 ) {
9872 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9873 if let Some(breakpoint) = breakpoint {
9874 self.edit_breakpoint_at_anchor(
9875 anchor,
9876 breakpoint,
9877 BreakpointEditAction::Toggle,
9878 cx,
9879 );
9880 } else {
9881 self.edit_breakpoint_at_anchor(
9882 anchor,
9883 Breakpoint::new_standard(),
9884 BreakpointEditAction::Toggle,
9885 cx,
9886 );
9887 }
9888 }
9889 }
9890
9891 pub fn edit_breakpoint_at_anchor(
9892 &mut self,
9893 breakpoint_position: Anchor,
9894 breakpoint: Breakpoint,
9895 edit_action: BreakpointEditAction,
9896 cx: &mut Context<Self>,
9897 ) {
9898 let Some(breakpoint_store) = &self.breakpoint_store else {
9899 return;
9900 };
9901
9902 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9903 if breakpoint_position == Anchor::min() {
9904 self.buffer()
9905 .read(cx)
9906 .excerpt_buffer_ids()
9907 .into_iter()
9908 .next()
9909 } else {
9910 None
9911 }
9912 }) else {
9913 return;
9914 };
9915
9916 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9917 return;
9918 };
9919
9920 breakpoint_store.update(cx, |breakpoint_store, cx| {
9921 breakpoint_store.toggle_breakpoint(
9922 buffer,
9923 BreakpointWithPosition {
9924 position: breakpoint_position.text_anchor,
9925 bp: breakpoint,
9926 },
9927 edit_action,
9928 cx,
9929 );
9930 });
9931
9932 cx.notify();
9933 }
9934
9935 #[cfg(any(test, feature = "test-support"))]
9936 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9937 self.breakpoint_store.clone()
9938 }
9939
9940 pub fn prepare_restore_change(
9941 &self,
9942 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9943 hunk: &MultiBufferDiffHunk,
9944 cx: &mut App,
9945 ) -> Option<()> {
9946 if hunk.is_created_file() {
9947 return None;
9948 }
9949 let buffer = self.buffer.read(cx);
9950 let diff = buffer.diff_for(hunk.buffer_id)?;
9951 let buffer = buffer.buffer(hunk.buffer_id)?;
9952 let buffer = buffer.read(cx);
9953 let original_text = diff
9954 .read(cx)
9955 .base_text()
9956 .as_rope()
9957 .slice(hunk.diff_base_byte_range.clone());
9958 let buffer_snapshot = buffer.snapshot();
9959 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
9960 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
9961 probe
9962 .0
9963 .start
9964 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
9965 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
9966 }) {
9967 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
9968 Some(())
9969 } else {
9970 None
9971 }
9972 }
9973
9974 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
9975 self.manipulate_lines(window, cx, |lines| lines.reverse())
9976 }
9977
9978 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
9979 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
9980 }
9981
9982 fn manipulate_lines<Fn>(
9983 &mut self,
9984 window: &mut Window,
9985 cx: &mut Context<Self>,
9986 mut callback: Fn,
9987 ) where
9988 Fn: FnMut(&mut Vec<&str>),
9989 {
9990 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9991
9992 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9993 let buffer = self.buffer.read(cx).snapshot(cx);
9994
9995 let mut edits = Vec::new();
9996
9997 let selections = self.selections.all::<Point>(cx);
9998 let mut selections = selections.iter().peekable();
9999 let mut contiguous_row_selections = Vec::new();
10000 let mut new_selections = Vec::new();
10001 let mut added_lines = 0;
10002 let mut removed_lines = 0;
10003
10004 while let Some(selection) = selections.next() {
10005 let (start_row, end_row) = consume_contiguous_rows(
10006 &mut contiguous_row_selections,
10007 selection,
10008 &display_map,
10009 &mut selections,
10010 );
10011
10012 let start_point = Point::new(start_row.0, 0);
10013 let end_point = Point::new(
10014 end_row.previous_row().0,
10015 buffer.line_len(end_row.previous_row()),
10016 );
10017 let text = buffer
10018 .text_for_range(start_point..end_point)
10019 .collect::<String>();
10020
10021 let mut lines = text.split('\n').collect_vec();
10022
10023 let lines_before = lines.len();
10024 callback(&mut lines);
10025 let lines_after = lines.len();
10026
10027 edits.push((start_point..end_point, lines.join("\n")));
10028
10029 // Selections must change based on added and removed line count
10030 let start_row =
10031 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10032 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10033 new_selections.push(Selection {
10034 id: selection.id,
10035 start: start_row,
10036 end: end_row,
10037 goal: SelectionGoal::None,
10038 reversed: selection.reversed,
10039 });
10040
10041 if lines_after > lines_before {
10042 added_lines += lines_after - lines_before;
10043 } else if lines_before > lines_after {
10044 removed_lines += lines_before - lines_after;
10045 }
10046 }
10047
10048 self.transact(window, cx, |this, window, cx| {
10049 let buffer = this.buffer.update(cx, |buffer, cx| {
10050 buffer.edit(edits, None, cx);
10051 buffer.snapshot(cx)
10052 });
10053
10054 // Recalculate offsets on newly edited buffer
10055 let new_selections = new_selections
10056 .iter()
10057 .map(|s| {
10058 let start_point = Point::new(s.start.0, 0);
10059 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10060 Selection {
10061 id: s.id,
10062 start: buffer.point_to_offset(start_point),
10063 end: buffer.point_to_offset(end_point),
10064 goal: s.goal,
10065 reversed: s.reversed,
10066 }
10067 })
10068 .collect();
10069
10070 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10071 s.select(new_selections);
10072 });
10073
10074 this.request_autoscroll(Autoscroll::fit(), cx);
10075 });
10076 }
10077
10078 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10079 self.manipulate_text(window, cx, |text| {
10080 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10081 if has_upper_case_characters {
10082 text.to_lowercase()
10083 } else {
10084 text.to_uppercase()
10085 }
10086 })
10087 }
10088
10089 pub fn convert_to_upper_case(
10090 &mut self,
10091 _: &ConvertToUpperCase,
10092 window: &mut Window,
10093 cx: &mut Context<Self>,
10094 ) {
10095 self.manipulate_text(window, cx, |text| text.to_uppercase())
10096 }
10097
10098 pub fn convert_to_lower_case(
10099 &mut self,
10100 _: &ConvertToLowerCase,
10101 window: &mut Window,
10102 cx: &mut Context<Self>,
10103 ) {
10104 self.manipulate_text(window, cx, |text| text.to_lowercase())
10105 }
10106
10107 pub fn convert_to_title_case(
10108 &mut self,
10109 _: &ConvertToTitleCase,
10110 window: &mut Window,
10111 cx: &mut Context<Self>,
10112 ) {
10113 self.manipulate_text(window, cx, |text| {
10114 text.split('\n')
10115 .map(|line| line.to_case(Case::Title))
10116 .join("\n")
10117 })
10118 }
10119
10120 pub fn convert_to_snake_case(
10121 &mut self,
10122 _: &ConvertToSnakeCase,
10123 window: &mut Window,
10124 cx: &mut Context<Self>,
10125 ) {
10126 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10127 }
10128
10129 pub fn convert_to_kebab_case(
10130 &mut self,
10131 _: &ConvertToKebabCase,
10132 window: &mut Window,
10133 cx: &mut Context<Self>,
10134 ) {
10135 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10136 }
10137
10138 pub fn convert_to_upper_camel_case(
10139 &mut self,
10140 _: &ConvertToUpperCamelCase,
10141 window: &mut Window,
10142 cx: &mut Context<Self>,
10143 ) {
10144 self.manipulate_text(window, cx, |text| {
10145 text.split('\n')
10146 .map(|line| line.to_case(Case::UpperCamel))
10147 .join("\n")
10148 })
10149 }
10150
10151 pub fn convert_to_lower_camel_case(
10152 &mut self,
10153 _: &ConvertToLowerCamelCase,
10154 window: &mut Window,
10155 cx: &mut Context<Self>,
10156 ) {
10157 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10158 }
10159
10160 pub fn convert_to_opposite_case(
10161 &mut self,
10162 _: &ConvertToOppositeCase,
10163 window: &mut Window,
10164 cx: &mut Context<Self>,
10165 ) {
10166 self.manipulate_text(window, cx, |text| {
10167 text.chars()
10168 .fold(String::with_capacity(text.len()), |mut t, c| {
10169 if c.is_uppercase() {
10170 t.extend(c.to_lowercase());
10171 } else {
10172 t.extend(c.to_uppercase());
10173 }
10174 t
10175 })
10176 })
10177 }
10178
10179 pub fn convert_to_rot13(
10180 &mut self,
10181 _: &ConvertToRot13,
10182 window: &mut Window,
10183 cx: &mut Context<Self>,
10184 ) {
10185 self.manipulate_text(window, cx, |text| {
10186 text.chars()
10187 .map(|c| match c {
10188 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10189 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10190 _ => c,
10191 })
10192 .collect()
10193 })
10194 }
10195
10196 pub fn convert_to_rot47(
10197 &mut self,
10198 _: &ConvertToRot47,
10199 window: &mut Window,
10200 cx: &mut Context<Self>,
10201 ) {
10202 self.manipulate_text(window, cx, |text| {
10203 text.chars()
10204 .map(|c| {
10205 let code_point = c as u32;
10206 if code_point >= 33 && code_point <= 126 {
10207 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10208 }
10209 c
10210 })
10211 .collect()
10212 })
10213 }
10214
10215 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10216 where
10217 Fn: FnMut(&str) -> String,
10218 {
10219 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10220 let buffer = self.buffer.read(cx).snapshot(cx);
10221
10222 let mut new_selections = Vec::new();
10223 let mut edits = Vec::new();
10224 let mut selection_adjustment = 0i32;
10225
10226 for selection in self.selections.all::<usize>(cx) {
10227 let selection_is_empty = selection.is_empty();
10228
10229 let (start, end) = if selection_is_empty {
10230 let word_range = movement::surrounding_word(
10231 &display_map,
10232 selection.start.to_display_point(&display_map),
10233 );
10234 let start = word_range.start.to_offset(&display_map, Bias::Left);
10235 let end = word_range.end.to_offset(&display_map, Bias::Left);
10236 (start, end)
10237 } else {
10238 (selection.start, selection.end)
10239 };
10240
10241 let text = buffer.text_for_range(start..end).collect::<String>();
10242 let old_length = text.len() as i32;
10243 let text = callback(&text);
10244
10245 new_selections.push(Selection {
10246 start: (start as i32 - selection_adjustment) as usize,
10247 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10248 goal: SelectionGoal::None,
10249 ..selection
10250 });
10251
10252 selection_adjustment += old_length - text.len() as i32;
10253
10254 edits.push((start..end, text));
10255 }
10256
10257 self.transact(window, cx, |this, window, cx| {
10258 this.buffer.update(cx, |buffer, cx| {
10259 buffer.edit(edits, None, cx);
10260 });
10261
10262 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10263 s.select(new_selections);
10264 });
10265
10266 this.request_autoscroll(Autoscroll::fit(), cx);
10267 });
10268 }
10269
10270 pub fn duplicate(
10271 &mut self,
10272 upwards: bool,
10273 whole_lines: bool,
10274 window: &mut Window,
10275 cx: &mut Context<Self>,
10276 ) {
10277 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10278
10279 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10280 let buffer = &display_map.buffer_snapshot;
10281 let selections = self.selections.all::<Point>(cx);
10282
10283 let mut edits = Vec::new();
10284 let mut selections_iter = selections.iter().peekable();
10285 while let Some(selection) = selections_iter.next() {
10286 let mut rows = selection.spanned_rows(false, &display_map);
10287 // duplicate line-wise
10288 if whole_lines || selection.start == selection.end {
10289 // Avoid duplicating the same lines twice.
10290 while let Some(next_selection) = selections_iter.peek() {
10291 let next_rows = next_selection.spanned_rows(false, &display_map);
10292 if next_rows.start < rows.end {
10293 rows.end = next_rows.end;
10294 selections_iter.next().unwrap();
10295 } else {
10296 break;
10297 }
10298 }
10299
10300 // Copy the text from the selected row region and splice it either at the start
10301 // or end of the region.
10302 let start = Point::new(rows.start.0, 0);
10303 let end = Point::new(
10304 rows.end.previous_row().0,
10305 buffer.line_len(rows.end.previous_row()),
10306 );
10307 let text = buffer
10308 .text_for_range(start..end)
10309 .chain(Some("\n"))
10310 .collect::<String>();
10311 let insert_location = if upwards {
10312 Point::new(rows.end.0, 0)
10313 } else {
10314 start
10315 };
10316 edits.push((insert_location..insert_location, text));
10317 } else {
10318 // duplicate character-wise
10319 let start = selection.start;
10320 let end = selection.end;
10321 let text = buffer.text_for_range(start..end).collect::<String>();
10322 edits.push((selection.end..selection.end, text));
10323 }
10324 }
10325
10326 self.transact(window, cx, |this, _, cx| {
10327 this.buffer.update(cx, |buffer, cx| {
10328 buffer.edit(edits, None, cx);
10329 });
10330
10331 this.request_autoscroll(Autoscroll::fit(), cx);
10332 });
10333 }
10334
10335 pub fn duplicate_line_up(
10336 &mut self,
10337 _: &DuplicateLineUp,
10338 window: &mut Window,
10339 cx: &mut Context<Self>,
10340 ) {
10341 self.duplicate(true, true, window, cx);
10342 }
10343
10344 pub fn duplicate_line_down(
10345 &mut self,
10346 _: &DuplicateLineDown,
10347 window: &mut Window,
10348 cx: &mut Context<Self>,
10349 ) {
10350 self.duplicate(false, true, window, cx);
10351 }
10352
10353 pub fn duplicate_selection(
10354 &mut self,
10355 _: &DuplicateSelection,
10356 window: &mut Window,
10357 cx: &mut Context<Self>,
10358 ) {
10359 self.duplicate(false, false, window, cx);
10360 }
10361
10362 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10363 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10364
10365 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10366 let buffer = self.buffer.read(cx).snapshot(cx);
10367
10368 let mut edits = Vec::new();
10369 let mut unfold_ranges = Vec::new();
10370 let mut refold_creases = Vec::new();
10371
10372 let selections = self.selections.all::<Point>(cx);
10373 let mut selections = selections.iter().peekable();
10374 let mut contiguous_row_selections = Vec::new();
10375 let mut new_selections = Vec::new();
10376
10377 while let Some(selection) = selections.next() {
10378 // Find all the selections that span a contiguous row range
10379 let (start_row, end_row) = consume_contiguous_rows(
10380 &mut contiguous_row_selections,
10381 selection,
10382 &display_map,
10383 &mut selections,
10384 );
10385
10386 // Move the text spanned by the row range to be before the line preceding the row range
10387 if start_row.0 > 0 {
10388 let range_to_move = Point::new(
10389 start_row.previous_row().0,
10390 buffer.line_len(start_row.previous_row()),
10391 )
10392 ..Point::new(
10393 end_row.previous_row().0,
10394 buffer.line_len(end_row.previous_row()),
10395 );
10396 let insertion_point = display_map
10397 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10398 .0;
10399
10400 // Don't move lines across excerpts
10401 if buffer
10402 .excerpt_containing(insertion_point..range_to_move.end)
10403 .is_some()
10404 {
10405 let text = buffer
10406 .text_for_range(range_to_move.clone())
10407 .flat_map(|s| s.chars())
10408 .skip(1)
10409 .chain(['\n'])
10410 .collect::<String>();
10411
10412 edits.push((
10413 buffer.anchor_after(range_to_move.start)
10414 ..buffer.anchor_before(range_to_move.end),
10415 String::new(),
10416 ));
10417 let insertion_anchor = buffer.anchor_after(insertion_point);
10418 edits.push((insertion_anchor..insertion_anchor, text));
10419
10420 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10421
10422 // Move selections up
10423 new_selections.extend(contiguous_row_selections.drain(..).map(
10424 |mut selection| {
10425 selection.start.row -= row_delta;
10426 selection.end.row -= row_delta;
10427 selection
10428 },
10429 ));
10430
10431 // Move folds up
10432 unfold_ranges.push(range_to_move.clone());
10433 for fold in display_map.folds_in_range(
10434 buffer.anchor_before(range_to_move.start)
10435 ..buffer.anchor_after(range_to_move.end),
10436 ) {
10437 let mut start = fold.range.start.to_point(&buffer);
10438 let mut end = fold.range.end.to_point(&buffer);
10439 start.row -= row_delta;
10440 end.row -= row_delta;
10441 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10442 }
10443 }
10444 }
10445
10446 // If we didn't move line(s), preserve the existing selections
10447 new_selections.append(&mut contiguous_row_selections);
10448 }
10449
10450 self.transact(window, cx, |this, window, cx| {
10451 this.unfold_ranges(&unfold_ranges, true, true, cx);
10452 this.buffer.update(cx, |buffer, cx| {
10453 for (range, text) in edits {
10454 buffer.edit([(range, text)], None, cx);
10455 }
10456 });
10457 this.fold_creases(refold_creases, true, window, cx);
10458 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10459 s.select(new_selections);
10460 })
10461 });
10462 }
10463
10464 pub fn move_line_down(
10465 &mut self,
10466 _: &MoveLineDown,
10467 window: &mut Window,
10468 cx: &mut Context<Self>,
10469 ) {
10470 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10471
10472 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10473 let buffer = self.buffer.read(cx).snapshot(cx);
10474
10475 let mut edits = Vec::new();
10476 let mut unfold_ranges = Vec::new();
10477 let mut refold_creases = Vec::new();
10478
10479 let selections = self.selections.all::<Point>(cx);
10480 let mut selections = selections.iter().peekable();
10481 let mut contiguous_row_selections = Vec::new();
10482 let mut new_selections = Vec::new();
10483
10484 while let Some(selection) = selections.next() {
10485 // Find all the selections that span a contiguous row range
10486 let (start_row, end_row) = consume_contiguous_rows(
10487 &mut contiguous_row_selections,
10488 selection,
10489 &display_map,
10490 &mut selections,
10491 );
10492
10493 // Move the text spanned by the row range to be after the last line of the row range
10494 if end_row.0 <= buffer.max_point().row {
10495 let range_to_move =
10496 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10497 let insertion_point = display_map
10498 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10499 .0;
10500
10501 // Don't move lines across excerpt boundaries
10502 if buffer
10503 .excerpt_containing(range_to_move.start..insertion_point)
10504 .is_some()
10505 {
10506 let mut text = String::from("\n");
10507 text.extend(buffer.text_for_range(range_to_move.clone()));
10508 text.pop(); // Drop trailing newline
10509 edits.push((
10510 buffer.anchor_after(range_to_move.start)
10511 ..buffer.anchor_before(range_to_move.end),
10512 String::new(),
10513 ));
10514 let insertion_anchor = buffer.anchor_after(insertion_point);
10515 edits.push((insertion_anchor..insertion_anchor, text));
10516
10517 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10518
10519 // Move selections down
10520 new_selections.extend(contiguous_row_selections.drain(..).map(
10521 |mut selection| {
10522 selection.start.row += row_delta;
10523 selection.end.row += row_delta;
10524 selection
10525 },
10526 ));
10527
10528 // Move folds down
10529 unfold_ranges.push(range_to_move.clone());
10530 for fold in display_map.folds_in_range(
10531 buffer.anchor_before(range_to_move.start)
10532 ..buffer.anchor_after(range_to_move.end),
10533 ) {
10534 let mut start = fold.range.start.to_point(&buffer);
10535 let mut end = fold.range.end.to_point(&buffer);
10536 start.row += row_delta;
10537 end.row += row_delta;
10538 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10539 }
10540 }
10541 }
10542
10543 // If we didn't move line(s), preserve the existing selections
10544 new_selections.append(&mut contiguous_row_selections);
10545 }
10546
10547 self.transact(window, cx, |this, window, cx| {
10548 this.unfold_ranges(&unfold_ranges, true, true, cx);
10549 this.buffer.update(cx, |buffer, cx| {
10550 for (range, text) in edits {
10551 buffer.edit([(range, text)], None, cx);
10552 }
10553 });
10554 this.fold_creases(refold_creases, true, window, cx);
10555 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10556 s.select(new_selections)
10557 });
10558 });
10559 }
10560
10561 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10562 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10563 let text_layout_details = &self.text_layout_details(window);
10564 self.transact(window, cx, |this, window, cx| {
10565 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10566 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10567 s.move_with(|display_map, selection| {
10568 if !selection.is_empty() {
10569 return;
10570 }
10571
10572 let mut head = selection.head();
10573 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10574 if head.column() == display_map.line_len(head.row()) {
10575 transpose_offset = display_map
10576 .buffer_snapshot
10577 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10578 }
10579
10580 if transpose_offset == 0 {
10581 return;
10582 }
10583
10584 *head.column_mut() += 1;
10585 head = display_map.clip_point(head, Bias::Right);
10586 let goal = SelectionGoal::HorizontalPosition(
10587 display_map
10588 .x_for_display_point(head, text_layout_details)
10589 .into(),
10590 );
10591 selection.collapse_to(head, goal);
10592
10593 let transpose_start = display_map
10594 .buffer_snapshot
10595 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10596 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10597 let transpose_end = display_map
10598 .buffer_snapshot
10599 .clip_offset(transpose_offset + 1, Bias::Right);
10600 if let Some(ch) =
10601 display_map.buffer_snapshot.chars_at(transpose_start).next()
10602 {
10603 edits.push((transpose_start..transpose_offset, String::new()));
10604 edits.push((transpose_end..transpose_end, ch.to_string()));
10605 }
10606 }
10607 });
10608 edits
10609 });
10610 this.buffer
10611 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10612 let selections = this.selections.all::<usize>(cx);
10613 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10614 s.select(selections);
10615 });
10616 });
10617 }
10618
10619 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10620 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10621 self.rewrap_impl(RewrapOptions::default(), cx)
10622 }
10623
10624 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10625 let buffer = self.buffer.read(cx).snapshot(cx);
10626 let selections = self.selections.all::<Point>(cx);
10627 let mut selections = selections.iter().peekable();
10628
10629 let mut edits = Vec::new();
10630 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10631
10632 while let Some(selection) = selections.next() {
10633 let mut start_row = selection.start.row;
10634 let mut end_row = selection.end.row;
10635
10636 // Skip selections that overlap with a range that has already been rewrapped.
10637 let selection_range = start_row..end_row;
10638 if rewrapped_row_ranges
10639 .iter()
10640 .any(|range| range.overlaps(&selection_range))
10641 {
10642 continue;
10643 }
10644
10645 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10646
10647 // Since not all lines in the selection may be at the same indent
10648 // level, choose the indent size that is the most common between all
10649 // of the lines.
10650 //
10651 // If there is a tie, we use the deepest indent.
10652 let (indent_size, indent_end) = {
10653 let mut indent_size_occurrences = HashMap::default();
10654 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10655
10656 for row in start_row..=end_row {
10657 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10658 rows_by_indent_size.entry(indent).or_default().push(row);
10659 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10660 }
10661
10662 let indent_size = indent_size_occurrences
10663 .into_iter()
10664 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10665 .map(|(indent, _)| indent)
10666 .unwrap_or_default();
10667 let row = rows_by_indent_size[&indent_size][0];
10668 let indent_end = Point::new(row, indent_size.len);
10669
10670 (indent_size, indent_end)
10671 };
10672
10673 let mut line_prefix = indent_size.chars().collect::<String>();
10674
10675 let mut inside_comment = false;
10676 if let Some(comment_prefix) =
10677 buffer
10678 .language_scope_at(selection.head())
10679 .and_then(|language| {
10680 language
10681 .line_comment_prefixes()
10682 .iter()
10683 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10684 .cloned()
10685 })
10686 {
10687 line_prefix.push_str(&comment_prefix);
10688 inside_comment = true;
10689 }
10690
10691 let language_settings = buffer.language_settings_at(selection.head(), cx);
10692 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10693 RewrapBehavior::InComments => inside_comment,
10694 RewrapBehavior::InSelections => !selection.is_empty(),
10695 RewrapBehavior::Anywhere => true,
10696 };
10697
10698 let should_rewrap = options.override_language_settings
10699 || allow_rewrap_based_on_language
10700 || self.hard_wrap.is_some();
10701 if !should_rewrap {
10702 continue;
10703 }
10704
10705 if selection.is_empty() {
10706 'expand_upwards: while start_row > 0 {
10707 let prev_row = start_row - 1;
10708 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10709 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10710 {
10711 start_row = prev_row;
10712 } else {
10713 break 'expand_upwards;
10714 }
10715 }
10716
10717 'expand_downwards: while end_row < buffer.max_point().row {
10718 let next_row = end_row + 1;
10719 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10720 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10721 {
10722 end_row = next_row;
10723 } else {
10724 break 'expand_downwards;
10725 }
10726 }
10727 }
10728
10729 let start = Point::new(start_row, 0);
10730 let start_offset = start.to_offset(&buffer);
10731 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10732 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10733 let Some(lines_without_prefixes) = selection_text
10734 .lines()
10735 .map(|line| {
10736 line.strip_prefix(&line_prefix)
10737 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10738 .with_context(|| {
10739 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10740 })
10741 })
10742 .collect::<Result<Vec<_>, _>>()
10743 .log_err()
10744 else {
10745 continue;
10746 };
10747
10748 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10749 buffer
10750 .language_settings_at(Point::new(start_row, 0), cx)
10751 .preferred_line_length as usize
10752 });
10753 let wrapped_text = wrap_with_prefix(
10754 line_prefix,
10755 lines_without_prefixes.join("\n"),
10756 wrap_column,
10757 tab_size,
10758 options.preserve_existing_whitespace,
10759 );
10760
10761 // TODO: should always use char-based diff while still supporting cursor behavior that
10762 // matches vim.
10763 let mut diff_options = DiffOptions::default();
10764 if options.override_language_settings {
10765 diff_options.max_word_diff_len = 0;
10766 diff_options.max_word_diff_line_count = 0;
10767 } else {
10768 diff_options.max_word_diff_len = usize::MAX;
10769 diff_options.max_word_diff_line_count = usize::MAX;
10770 }
10771
10772 for (old_range, new_text) in
10773 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10774 {
10775 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10776 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10777 edits.push((edit_start..edit_end, new_text));
10778 }
10779
10780 rewrapped_row_ranges.push(start_row..=end_row);
10781 }
10782
10783 self.buffer
10784 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10785 }
10786
10787 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10788 let mut text = String::new();
10789 let buffer = self.buffer.read(cx).snapshot(cx);
10790 let mut selections = self.selections.all::<Point>(cx);
10791 let mut clipboard_selections = Vec::with_capacity(selections.len());
10792 {
10793 let max_point = buffer.max_point();
10794 let mut is_first = true;
10795 for selection in &mut selections {
10796 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10797 if is_entire_line {
10798 selection.start = Point::new(selection.start.row, 0);
10799 if !selection.is_empty() && selection.end.column == 0 {
10800 selection.end = cmp::min(max_point, selection.end);
10801 } else {
10802 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10803 }
10804 selection.goal = SelectionGoal::None;
10805 }
10806 if is_first {
10807 is_first = false;
10808 } else {
10809 text += "\n";
10810 }
10811 let mut len = 0;
10812 for chunk in buffer.text_for_range(selection.start..selection.end) {
10813 text.push_str(chunk);
10814 len += chunk.len();
10815 }
10816 clipboard_selections.push(ClipboardSelection {
10817 len,
10818 is_entire_line,
10819 first_line_indent: buffer
10820 .indent_size_for_line(MultiBufferRow(selection.start.row))
10821 .len,
10822 });
10823 }
10824 }
10825
10826 self.transact(window, cx, |this, window, cx| {
10827 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10828 s.select(selections);
10829 });
10830 this.insert("", window, cx);
10831 });
10832 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10833 }
10834
10835 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10836 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10837 let item = self.cut_common(window, cx);
10838 cx.write_to_clipboard(item);
10839 }
10840
10841 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10842 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10843 self.change_selections(None, window, cx, |s| {
10844 s.move_with(|snapshot, sel| {
10845 if sel.is_empty() {
10846 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10847 }
10848 });
10849 });
10850 let item = self.cut_common(window, cx);
10851 cx.set_global(KillRing(item))
10852 }
10853
10854 pub fn kill_ring_yank(
10855 &mut self,
10856 _: &KillRingYank,
10857 window: &mut Window,
10858 cx: &mut Context<Self>,
10859 ) {
10860 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10861 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10862 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10863 (kill_ring.text().to_string(), kill_ring.metadata_json())
10864 } else {
10865 return;
10866 }
10867 } else {
10868 return;
10869 };
10870 self.do_paste(&text, metadata, false, window, cx);
10871 }
10872
10873 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10874 self.do_copy(true, cx);
10875 }
10876
10877 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10878 self.do_copy(false, cx);
10879 }
10880
10881 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10882 let selections = self.selections.all::<Point>(cx);
10883 let buffer = self.buffer.read(cx).read(cx);
10884 let mut text = String::new();
10885
10886 let mut clipboard_selections = Vec::with_capacity(selections.len());
10887 {
10888 let max_point = buffer.max_point();
10889 let mut is_first = true;
10890 for selection in &selections {
10891 let mut start = selection.start;
10892 let mut end = selection.end;
10893 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10894 if is_entire_line {
10895 start = Point::new(start.row, 0);
10896 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10897 }
10898
10899 let mut trimmed_selections = Vec::new();
10900 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10901 let row = MultiBufferRow(start.row);
10902 let first_indent = buffer.indent_size_for_line(row);
10903 if first_indent.len == 0 || start.column > first_indent.len {
10904 trimmed_selections.push(start..end);
10905 } else {
10906 trimmed_selections.push(
10907 Point::new(row.0, first_indent.len)
10908 ..Point::new(row.0, buffer.line_len(row)),
10909 );
10910 for row in start.row + 1..=end.row {
10911 let mut line_len = buffer.line_len(MultiBufferRow(row));
10912 if row == end.row {
10913 line_len = end.column;
10914 }
10915 if line_len == 0 {
10916 trimmed_selections
10917 .push(Point::new(row, 0)..Point::new(row, line_len));
10918 continue;
10919 }
10920 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10921 if row_indent_size.len >= first_indent.len {
10922 trimmed_selections.push(
10923 Point::new(row, first_indent.len)..Point::new(row, line_len),
10924 );
10925 } else {
10926 trimmed_selections.clear();
10927 trimmed_selections.push(start..end);
10928 break;
10929 }
10930 }
10931 }
10932 } else {
10933 trimmed_selections.push(start..end);
10934 }
10935
10936 for trimmed_range in trimmed_selections {
10937 if is_first {
10938 is_first = false;
10939 } else {
10940 text += "\n";
10941 }
10942 let mut len = 0;
10943 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10944 text.push_str(chunk);
10945 len += chunk.len();
10946 }
10947 clipboard_selections.push(ClipboardSelection {
10948 len,
10949 is_entire_line,
10950 first_line_indent: buffer
10951 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10952 .len,
10953 });
10954 }
10955 }
10956 }
10957
10958 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
10959 text,
10960 clipboard_selections,
10961 ));
10962 }
10963
10964 pub fn do_paste(
10965 &mut self,
10966 text: &String,
10967 clipboard_selections: Option<Vec<ClipboardSelection>>,
10968 handle_entire_lines: bool,
10969 window: &mut Window,
10970 cx: &mut Context<Self>,
10971 ) {
10972 if self.read_only(cx) {
10973 return;
10974 }
10975
10976 let clipboard_text = Cow::Borrowed(text);
10977
10978 self.transact(window, cx, |this, window, cx| {
10979 if let Some(mut clipboard_selections) = clipboard_selections {
10980 let old_selections = this.selections.all::<usize>(cx);
10981 let all_selections_were_entire_line =
10982 clipboard_selections.iter().all(|s| s.is_entire_line);
10983 let first_selection_indent_column =
10984 clipboard_selections.first().map(|s| s.first_line_indent);
10985 if clipboard_selections.len() != old_selections.len() {
10986 clipboard_selections.drain(..);
10987 }
10988 let cursor_offset = this.selections.last::<usize>(cx).head();
10989 let mut auto_indent_on_paste = true;
10990
10991 this.buffer.update(cx, |buffer, cx| {
10992 let snapshot = buffer.read(cx);
10993 auto_indent_on_paste = snapshot
10994 .language_settings_at(cursor_offset, cx)
10995 .auto_indent_on_paste;
10996
10997 let mut start_offset = 0;
10998 let mut edits = Vec::new();
10999 let mut original_indent_columns = Vec::new();
11000 for (ix, selection) in old_selections.iter().enumerate() {
11001 let to_insert;
11002 let entire_line;
11003 let original_indent_column;
11004 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11005 let end_offset = start_offset + clipboard_selection.len;
11006 to_insert = &clipboard_text[start_offset..end_offset];
11007 entire_line = clipboard_selection.is_entire_line;
11008 start_offset = end_offset + 1;
11009 original_indent_column = Some(clipboard_selection.first_line_indent);
11010 } else {
11011 to_insert = clipboard_text.as_str();
11012 entire_line = all_selections_were_entire_line;
11013 original_indent_column = first_selection_indent_column
11014 }
11015
11016 // If the corresponding selection was empty when this slice of the
11017 // clipboard text was written, then the entire line containing the
11018 // selection was copied. If this selection is also currently empty,
11019 // then paste the line before the current line of the buffer.
11020 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11021 let column = selection.start.to_point(&snapshot).column as usize;
11022 let line_start = selection.start - column;
11023 line_start..line_start
11024 } else {
11025 selection.range()
11026 };
11027
11028 edits.push((range, to_insert));
11029 original_indent_columns.push(original_indent_column);
11030 }
11031 drop(snapshot);
11032
11033 buffer.edit(
11034 edits,
11035 if auto_indent_on_paste {
11036 Some(AutoindentMode::Block {
11037 original_indent_columns,
11038 })
11039 } else {
11040 None
11041 },
11042 cx,
11043 );
11044 });
11045
11046 let selections = this.selections.all::<usize>(cx);
11047 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11048 s.select(selections)
11049 });
11050 } else {
11051 this.insert(&clipboard_text, window, cx);
11052 }
11053 });
11054 }
11055
11056 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11057 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11058 if let Some(item) = cx.read_from_clipboard() {
11059 let entries = item.entries();
11060
11061 match entries.first() {
11062 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11063 // of all the pasted entries.
11064 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11065 .do_paste(
11066 clipboard_string.text(),
11067 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11068 true,
11069 window,
11070 cx,
11071 ),
11072 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11073 }
11074 }
11075 }
11076
11077 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11078 if self.read_only(cx) {
11079 return;
11080 }
11081
11082 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11083
11084 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11085 if let Some((selections, _)) =
11086 self.selection_history.transaction(transaction_id).cloned()
11087 {
11088 self.change_selections(None, window, cx, |s| {
11089 s.select_anchors(selections.to_vec());
11090 });
11091 } else {
11092 log::error!(
11093 "No entry in selection_history found for undo. \
11094 This may correspond to a bug where undo does not update the selection. \
11095 If this is occurring, please add details to \
11096 https://github.com/zed-industries/zed/issues/22692"
11097 );
11098 }
11099 self.request_autoscroll(Autoscroll::fit(), cx);
11100 self.unmark_text(window, cx);
11101 self.refresh_inline_completion(true, false, window, cx);
11102 cx.emit(EditorEvent::Edited { transaction_id });
11103 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11104 }
11105 }
11106
11107 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11108 if self.read_only(cx) {
11109 return;
11110 }
11111
11112 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11113
11114 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11115 if let Some((_, Some(selections))) =
11116 self.selection_history.transaction(transaction_id).cloned()
11117 {
11118 self.change_selections(None, window, cx, |s| {
11119 s.select_anchors(selections.to_vec());
11120 });
11121 } else {
11122 log::error!(
11123 "No entry in selection_history found for redo. \
11124 This may correspond to a bug where undo does not update the selection. \
11125 If this is occurring, please add details to \
11126 https://github.com/zed-industries/zed/issues/22692"
11127 );
11128 }
11129 self.request_autoscroll(Autoscroll::fit(), cx);
11130 self.unmark_text(window, cx);
11131 self.refresh_inline_completion(true, false, window, cx);
11132 cx.emit(EditorEvent::Edited { transaction_id });
11133 }
11134 }
11135
11136 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11137 self.buffer
11138 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11139 }
11140
11141 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11142 self.buffer
11143 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11144 }
11145
11146 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11147 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11148 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11149 s.move_with(|map, selection| {
11150 let cursor = if selection.is_empty() {
11151 movement::left(map, selection.start)
11152 } else {
11153 selection.start
11154 };
11155 selection.collapse_to(cursor, SelectionGoal::None);
11156 });
11157 })
11158 }
11159
11160 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11161 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11162 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11163 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11164 })
11165 }
11166
11167 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11168 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11169 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11170 s.move_with(|map, selection| {
11171 let cursor = if selection.is_empty() {
11172 movement::right(map, selection.end)
11173 } else {
11174 selection.end
11175 };
11176 selection.collapse_to(cursor, SelectionGoal::None)
11177 });
11178 })
11179 }
11180
11181 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11182 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11183 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11184 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11185 })
11186 }
11187
11188 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11189 if self.take_rename(true, window, cx).is_some() {
11190 return;
11191 }
11192
11193 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11194 cx.propagate();
11195 return;
11196 }
11197
11198 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11199
11200 let text_layout_details = &self.text_layout_details(window);
11201 let selection_count = self.selections.count();
11202 let first_selection = self.selections.first_anchor();
11203
11204 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11205 s.move_with(|map, selection| {
11206 if !selection.is_empty() {
11207 selection.goal = SelectionGoal::None;
11208 }
11209 let (cursor, goal) = movement::up(
11210 map,
11211 selection.start,
11212 selection.goal,
11213 false,
11214 text_layout_details,
11215 );
11216 selection.collapse_to(cursor, goal);
11217 });
11218 });
11219
11220 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11221 {
11222 cx.propagate();
11223 }
11224 }
11225
11226 pub fn move_up_by_lines(
11227 &mut self,
11228 action: &MoveUpByLines,
11229 window: &mut Window,
11230 cx: &mut Context<Self>,
11231 ) {
11232 if self.take_rename(true, window, cx).is_some() {
11233 return;
11234 }
11235
11236 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11237 cx.propagate();
11238 return;
11239 }
11240
11241 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11242
11243 let text_layout_details = &self.text_layout_details(window);
11244
11245 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11246 s.move_with(|map, selection| {
11247 if !selection.is_empty() {
11248 selection.goal = SelectionGoal::None;
11249 }
11250 let (cursor, goal) = movement::up_by_rows(
11251 map,
11252 selection.start,
11253 action.lines,
11254 selection.goal,
11255 false,
11256 text_layout_details,
11257 );
11258 selection.collapse_to(cursor, goal);
11259 });
11260 })
11261 }
11262
11263 pub fn move_down_by_lines(
11264 &mut self,
11265 action: &MoveDownByLines,
11266 window: &mut Window,
11267 cx: &mut Context<Self>,
11268 ) {
11269 if self.take_rename(true, window, cx).is_some() {
11270 return;
11271 }
11272
11273 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11274 cx.propagate();
11275 return;
11276 }
11277
11278 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11279
11280 let text_layout_details = &self.text_layout_details(window);
11281
11282 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11283 s.move_with(|map, selection| {
11284 if !selection.is_empty() {
11285 selection.goal = SelectionGoal::None;
11286 }
11287 let (cursor, goal) = movement::down_by_rows(
11288 map,
11289 selection.start,
11290 action.lines,
11291 selection.goal,
11292 false,
11293 text_layout_details,
11294 );
11295 selection.collapse_to(cursor, goal);
11296 });
11297 })
11298 }
11299
11300 pub fn select_down_by_lines(
11301 &mut self,
11302 action: &SelectDownByLines,
11303 window: &mut Window,
11304 cx: &mut Context<Self>,
11305 ) {
11306 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11307 let text_layout_details = &self.text_layout_details(window);
11308 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11309 s.move_heads_with(|map, head, goal| {
11310 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11311 })
11312 })
11313 }
11314
11315 pub fn select_up_by_lines(
11316 &mut self,
11317 action: &SelectUpByLines,
11318 window: &mut Window,
11319 cx: &mut Context<Self>,
11320 ) {
11321 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11322 let text_layout_details = &self.text_layout_details(window);
11323 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11324 s.move_heads_with(|map, head, goal| {
11325 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11326 })
11327 })
11328 }
11329
11330 pub fn select_page_up(
11331 &mut self,
11332 _: &SelectPageUp,
11333 window: &mut Window,
11334 cx: &mut Context<Self>,
11335 ) {
11336 let Some(row_count) = self.visible_row_count() else {
11337 return;
11338 };
11339
11340 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11341
11342 let text_layout_details = &self.text_layout_details(window);
11343
11344 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11345 s.move_heads_with(|map, head, goal| {
11346 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11347 })
11348 })
11349 }
11350
11351 pub fn move_page_up(
11352 &mut self,
11353 action: &MovePageUp,
11354 window: &mut Window,
11355 cx: &mut Context<Self>,
11356 ) {
11357 if self.take_rename(true, window, cx).is_some() {
11358 return;
11359 }
11360
11361 if self
11362 .context_menu
11363 .borrow_mut()
11364 .as_mut()
11365 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11366 .unwrap_or(false)
11367 {
11368 return;
11369 }
11370
11371 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11372 cx.propagate();
11373 return;
11374 }
11375
11376 let Some(row_count) = self.visible_row_count() else {
11377 return;
11378 };
11379
11380 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11381
11382 let autoscroll = if action.center_cursor {
11383 Autoscroll::center()
11384 } else {
11385 Autoscroll::fit()
11386 };
11387
11388 let text_layout_details = &self.text_layout_details(window);
11389
11390 self.change_selections(Some(autoscroll), window, cx, |s| {
11391 s.move_with(|map, selection| {
11392 if !selection.is_empty() {
11393 selection.goal = SelectionGoal::None;
11394 }
11395 let (cursor, goal) = movement::up_by_rows(
11396 map,
11397 selection.end,
11398 row_count,
11399 selection.goal,
11400 false,
11401 text_layout_details,
11402 );
11403 selection.collapse_to(cursor, goal);
11404 });
11405 });
11406 }
11407
11408 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11409 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11410 let text_layout_details = &self.text_layout_details(window);
11411 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11412 s.move_heads_with(|map, head, goal| {
11413 movement::up(map, head, goal, false, text_layout_details)
11414 })
11415 })
11416 }
11417
11418 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11419 self.take_rename(true, window, cx);
11420
11421 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11422 cx.propagate();
11423 return;
11424 }
11425
11426 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11427
11428 let text_layout_details = &self.text_layout_details(window);
11429 let selection_count = self.selections.count();
11430 let first_selection = self.selections.first_anchor();
11431
11432 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11433 s.move_with(|map, selection| {
11434 if !selection.is_empty() {
11435 selection.goal = SelectionGoal::None;
11436 }
11437 let (cursor, goal) = movement::down(
11438 map,
11439 selection.end,
11440 selection.goal,
11441 false,
11442 text_layout_details,
11443 );
11444 selection.collapse_to(cursor, goal);
11445 });
11446 });
11447
11448 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11449 {
11450 cx.propagate();
11451 }
11452 }
11453
11454 pub fn select_page_down(
11455 &mut self,
11456 _: &SelectPageDown,
11457 window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 let Some(row_count) = self.visible_row_count() else {
11461 return;
11462 };
11463
11464 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11465
11466 let text_layout_details = &self.text_layout_details(window);
11467
11468 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11469 s.move_heads_with(|map, head, goal| {
11470 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11471 })
11472 })
11473 }
11474
11475 pub fn move_page_down(
11476 &mut self,
11477 action: &MovePageDown,
11478 window: &mut Window,
11479 cx: &mut Context<Self>,
11480 ) {
11481 if self.take_rename(true, window, cx).is_some() {
11482 return;
11483 }
11484
11485 if self
11486 .context_menu
11487 .borrow_mut()
11488 .as_mut()
11489 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11490 .unwrap_or(false)
11491 {
11492 return;
11493 }
11494
11495 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11496 cx.propagate();
11497 return;
11498 }
11499
11500 let Some(row_count) = self.visible_row_count() else {
11501 return;
11502 };
11503
11504 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11505
11506 let autoscroll = if action.center_cursor {
11507 Autoscroll::center()
11508 } else {
11509 Autoscroll::fit()
11510 };
11511
11512 let text_layout_details = &self.text_layout_details(window);
11513 self.change_selections(Some(autoscroll), window, cx, |s| {
11514 s.move_with(|map, selection| {
11515 if !selection.is_empty() {
11516 selection.goal = SelectionGoal::None;
11517 }
11518 let (cursor, goal) = movement::down_by_rows(
11519 map,
11520 selection.end,
11521 row_count,
11522 selection.goal,
11523 false,
11524 text_layout_details,
11525 );
11526 selection.collapse_to(cursor, goal);
11527 });
11528 });
11529 }
11530
11531 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11532 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11533 let text_layout_details = &self.text_layout_details(window);
11534 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11535 s.move_heads_with(|map, head, goal| {
11536 movement::down(map, head, goal, false, text_layout_details)
11537 })
11538 });
11539 }
11540
11541 pub fn context_menu_first(
11542 &mut self,
11543 _: &ContextMenuFirst,
11544 window: &mut Window,
11545 cx: &mut Context<Self>,
11546 ) {
11547 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11548 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11549 }
11550 }
11551
11552 pub fn context_menu_prev(
11553 &mut self,
11554 _: &ContextMenuPrevious,
11555 window: &mut Window,
11556 cx: &mut Context<Self>,
11557 ) {
11558 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11559 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11560 }
11561 }
11562
11563 pub fn context_menu_next(
11564 &mut self,
11565 _: &ContextMenuNext,
11566 window: &mut Window,
11567 cx: &mut Context<Self>,
11568 ) {
11569 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11570 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11571 }
11572 }
11573
11574 pub fn context_menu_last(
11575 &mut self,
11576 _: &ContextMenuLast,
11577 window: &mut Window,
11578 cx: &mut Context<Self>,
11579 ) {
11580 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11581 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11582 }
11583 }
11584
11585 pub fn move_to_previous_word_start(
11586 &mut self,
11587 _: &MoveToPreviousWordStart,
11588 window: &mut Window,
11589 cx: &mut Context<Self>,
11590 ) {
11591 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11592 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11593 s.move_cursors_with(|map, head, _| {
11594 (
11595 movement::previous_word_start(map, head),
11596 SelectionGoal::None,
11597 )
11598 });
11599 })
11600 }
11601
11602 pub fn move_to_previous_subword_start(
11603 &mut self,
11604 _: &MoveToPreviousSubwordStart,
11605 window: &mut Window,
11606 cx: &mut Context<Self>,
11607 ) {
11608 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11609 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11610 s.move_cursors_with(|map, head, _| {
11611 (
11612 movement::previous_subword_start(map, head),
11613 SelectionGoal::None,
11614 )
11615 });
11616 })
11617 }
11618
11619 pub fn select_to_previous_word_start(
11620 &mut self,
11621 _: &SelectToPreviousWordStart,
11622 window: &mut Window,
11623 cx: &mut Context<Self>,
11624 ) {
11625 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11626 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11627 s.move_heads_with(|map, head, _| {
11628 (
11629 movement::previous_word_start(map, head),
11630 SelectionGoal::None,
11631 )
11632 });
11633 })
11634 }
11635
11636 pub fn select_to_previous_subword_start(
11637 &mut self,
11638 _: &SelectToPreviousSubwordStart,
11639 window: &mut Window,
11640 cx: &mut Context<Self>,
11641 ) {
11642 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11643 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11644 s.move_heads_with(|map, head, _| {
11645 (
11646 movement::previous_subword_start(map, head),
11647 SelectionGoal::None,
11648 )
11649 });
11650 })
11651 }
11652
11653 pub fn delete_to_previous_word_start(
11654 &mut self,
11655 action: &DeleteToPreviousWordStart,
11656 window: &mut Window,
11657 cx: &mut Context<Self>,
11658 ) {
11659 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11660 self.transact(window, cx, |this, window, cx| {
11661 this.select_autoclose_pair(window, cx);
11662 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11663 s.move_with(|map, selection| {
11664 if selection.is_empty() {
11665 let cursor = if action.ignore_newlines {
11666 movement::previous_word_start(map, selection.head())
11667 } else {
11668 movement::previous_word_start_or_newline(map, selection.head())
11669 };
11670 selection.set_head(cursor, SelectionGoal::None);
11671 }
11672 });
11673 });
11674 this.insert("", window, cx);
11675 });
11676 }
11677
11678 pub fn delete_to_previous_subword_start(
11679 &mut self,
11680 _: &DeleteToPreviousSubwordStart,
11681 window: &mut Window,
11682 cx: &mut Context<Self>,
11683 ) {
11684 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11685 self.transact(window, cx, |this, window, cx| {
11686 this.select_autoclose_pair(window, cx);
11687 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11688 s.move_with(|map, selection| {
11689 if selection.is_empty() {
11690 let cursor = movement::previous_subword_start(map, selection.head());
11691 selection.set_head(cursor, SelectionGoal::None);
11692 }
11693 });
11694 });
11695 this.insert("", window, cx);
11696 });
11697 }
11698
11699 pub fn move_to_next_word_end(
11700 &mut self,
11701 _: &MoveToNextWordEnd,
11702 window: &mut Window,
11703 cx: &mut Context<Self>,
11704 ) {
11705 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11706 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11707 s.move_cursors_with(|map, head, _| {
11708 (movement::next_word_end(map, head), SelectionGoal::None)
11709 });
11710 })
11711 }
11712
11713 pub fn move_to_next_subword_end(
11714 &mut self,
11715 _: &MoveToNextSubwordEnd,
11716 window: &mut Window,
11717 cx: &mut Context<Self>,
11718 ) {
11719 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11720 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11721 s.move_cursors_with(|map, head, _| {
11722 (movement::next_subword_end(map, head), SelectionGoal::None)
11723 });
11724 })
11725 }
11726
11727 pub fn select_to_next_word_end(
11728 &mut self,
11729 _: &SelectToNextWordEnd,
11730 window: &mut Window,
11731 cx: &mut Context<Self>,
11732 ) {
11733 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11734 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11735 s.move_heads_with(|map, head, _| {
11736 (movement::next_word_end(map, head), SelectionGoal::None)
11737 });
11738 })
11739 }
11740
11741 pub fn select_to_next_subword_end(
11742 &mut self,
11743 _: &SelectToNextSubwordEnd,
11744 window: &mut Window,
11745 cx: &mut Context<Self>,
11746 ) {
11747 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11748 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11749 s.move_heads_with(|map, head, _| {
11750 (movement::next_subword_end(map, head), SelectionGoal::None)
11751 });
11752 })
11753 }
11754
11755 pub fn delete_to_next_word_end(
11756 &mut self,
11757 action: &DeleteToNextWordEnd,
11758 window: &mut Window,
11759 cx: &mut Context<Self>,
11760 ) {
11761 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11762 self.transact(window, cx, |this, window, cx| {
11763 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11764 s.move_with(|map, selection| {
11765 if selection.is_empty() {
11766 let cursor = if action.ignore_newlines {
11767 movement::next_word_end(map, selection.head())
11768 } else {
11769 movement::next_word_end_or_newline(map, selection.head())
11770 };
11771 selection.set_head(cursor, SelectionGoal::None);
11772 }
11773 });
11774 });
11775 this.insert("", window, cx);
11776 });
11777 }
11778
11779 pub fn delete_to_next_subword_end(
11780 &mut self,
11781 _: &DeleteToNextSubwordEnd,
11782 window: &mut Window,
11783 cx: &mut Context<Self>,
11784 ) {
11785 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11786 self.transact(window, cx, |this, window, cx| {
11787 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11788 s.move_with(|map, selection| {
11789 if selection.is_empty() {
11790 let cursor = movement::next_subword_end(map, selection.head());
11791 selection.set_head(cursor, SelectionGoal::None);
11792 }
11793 });
11794 });
11795 this.insert("", window, cx);
11796 });
11797 }
11798
11799 pub fn move_to_beginning_of_line(
11800 &mut self,
11801 action: &MoveToBeginningOfLine,
11802 window: &mut Window,
11803 cx: &mut Context<Self>,
11804 ) {
11805 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11806 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11807 s.move_cursors_with(|map, head, _| {
11808 (
11809 movement::indented_line_beginning(
11810 map,
11811 head,
11812 action.stop_at_soft_wraps,
11813 action.stop_at_indent,
11814 ),
11815 SelectionGoal::None,
11816 )
11817 });
11818 })
11819 }
11820
11821 pub fn select_to_beginning_of_line(
11822 &mut self,
11823 action: &SelectToBeginningOfLine,
11824 window: &mut Window,
11825 cx: &mut Context<Self>,
11826 ) {
11827 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11828 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11829 s.move_heads_with(|map, head, _| {
11830 (
11831 movement::indented_line_beginning(
11832 map,
11833 head,
11834 action.stop_at_soft_wraps,
11835 action.stop_at_indent,
11836 ),
11837 SelectionGoal::None,
11838 )
11839 });
11840 });
11841 }
11842
11843 pub fn delete_to_beginning_of_line(
11844 &mut self,
11845 action: &DeleteToBeginningOfLine,
11846 window: &mut Window,
11847 cx: &mut Context<Self>,
11848 ) {
11849 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11850 self.transact(window, cx, |this, window, cx| {
11851 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11852 s.move_with(|_, selection| {
11853 selection.reversed = true;
11854 });
11855 });
11856
11857 this.select_to_beginning_of_line(
11858 &SelectToBeginningOfLine {
11859 stop_at_soft_wraps: false,
11860 stop_at_indent: action.stop_at_indent,
11861 },
11862 window,
11863 cx,
11864 );
11865 this.backspace(&Backspace, window, cx);
11866 });
11867 }
11868
11869 pub fn move_to_end_of_line(
11870 &mut self,
11871 action: &MoveToEndOfLine,
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_cursors_with(|map, head, _| {
11878 (
11879 movement::line_end(map, head, action.stop_at_soft_wraps),
11880 SelectionGoal::None,
11881 )
11882 });
11883 })
11884 }
11885
11886 pub fn select_to_end_of_line(
11887 &mut self,
11888 action: &SelectToEndOfLine,
11889 window: &mut Window,
11890 cx: &mut Context<Self>,
11891 ) {
11892 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11893 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11894 s.move_heads_with(|map, head, _| {
11895 (
11896 movement::line_end(map, head, action.stop_at_soft_wraps),
11897 SelectionGoal::None,
11898 )
11899 });
11900 })
11901 }
11902
11903 pub fn delete_to_end_of_line(
11904 &mut self,
11905 _: &DeleteToEndOfLine,
11906 window: &mut Window,
11907 cx: &mut Context<Self>,
11908 ) {
11909 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11910 self.transact(window, cx, |this, window, cx| {
11911 this.select_to_end_of_line(
11912 &SelectToEndOfLine {
11913 stop_at_soft_wraps: false,
11914 },
11915 window,
11916 cx,
11917 );
11918 this.delete(&Delete, window, cx);
11919 });
11920 }
11921
11922 pub fn cut_to_end_of_line(
11923 &mut self,
11924 _: &CutToEndOfLine,
11925 window: &mut Window,
11926 cx: &mut Context<Self>,
11927 ) {
11928 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11929 self.transact(window, cx, |this, window, cx| {
11930 this.select_to_end_of_line(
11931 &SelectToEndOfLine {
11932 stop_at_soft_wraps: false,
11933 },
11934 window,
11935 cx,
11936 );
11937 this.cut(&Cut, window, cx);
11938 });
11939 }
11940
11941 pub fn move_to_start_of_paragraph(
11942 &mut self,
11943 _: &MoveToStartOfParagraph,
11944 window: &mut Window,
11945 cx: &mut Context<Self>,
11946 ) {
11947 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11948 cx.propagate();
11949 return;
11950 }
11951 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11952 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11953 s.move_with(|map, selection| {
11954 selection.collapse_to(
11955 movement::start_of_paragraph(map, selection.head(), 1),
11956 SelectionGoal::None,
11957 )
11958 });
11959 })
11960 }
11961
11962 pub fn move_to_end_of_paragraph(
11963 &mut self,
11964 _: &MoveToEndOfParagraph,
11965 window: &mut Window,
11966 cx: &mut Context<Self>,
11967 ) {
11968 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11969 cx.propagate();
11970 return;
11971 }
11972 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11973 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11974 s.move_with(|map, selection| {
11975 selection.collapse_to(
11976 movement::end_of_paragraph(map, selection.head(), 1),
11977 SelectionGoal::None,
11978 )
11979 });
11980 })
11981 }
11982
11983 pub fn select_to_start_of_paragraph(
11984 &mut self,
11985 _: &SelectToStartOfParagraph,
11986 window: &mut Window,
11987 cx: &mut Context<Self>,
11988 ) {
11989 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11990 cx.propagate();
11991 return;
11992 }
11993 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11994 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11995 s.move_heads_with(|map, head, _| {
11996 (
11997 movement::start_of_paragraph(map, head, 1),
11998 SelectionGoal::None,
11999 )
12000 });
12001 })
12002 }
12003
12004 pub fn select_to_end_of_paragraph(
12005 &mut self,
12006 _: &SelectToEndOfParagraph,
12007 window: &mut Window,
12008 cx: &mut Context<Self>,
12009 ) {
12010 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12011 cx.propagate();
12012 return;
12013 }
12014 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12015 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12016 s.move_heads_with(|map, head, _| {
12017 (
12018 movement::end_of_paragraph(map, head, 1),
12019 SelectionGoal::None,
12020 )
12021 });
12022 })
12023 }
12024
12025 pub fn move_to_start_of_excerpt(
12026 &mut self,
12027 _: &MoveToStartOfExcerpt,
12028 window: &mut Window,
12029 cx: &mut Context<Self>,
12030 ) {
12031 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12032 cx.propagate();
12033 return;
12034 }
12035 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12036 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12037 s.move_with(|map, selection| {
12038 selection.collapse_to(
12039 movement::start_of_excerpt(
12040 map,
12041 selection.head(),
12042 workspace::searchable::Direction::Prev,
12043 ),
12044 SelectionGoal::None,
12045 )
12046 });
12047 })
12048 }
12049
12050 pub fn move_to_start_of_next_excerpt(
12051 &mut self,
12052 _: &MoveToStartOfNextExcerpt,
12053 window: &mut Window,
12054 cx: &mut Context<Self>,
12055 ) {
12056 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12057 cx.propagate();
12058 return;
12059 }
12060
12061 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12062 s.move_with(|map, selection| {
12063 selection.collapse_to(
12064 movement::start_of_excerpt(
12065 map,
12066 selection.head(),
12067 workspace::searchable::Direction::Next,
12068 ),
12069 SelectionGoal::None,
12070 )
12071 });
12072 })
12073 }
12074
12075 pub fn move_to_end_of_excerpt(
12076 &mut self,
12077 _: &MoveToEndOfExcerpt,
12078 window: &mut Window,
12079 cx: &mut Context<Self>,
12080 ) {
12081 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12082 cx.propagate();
12083 return;
12084 }
12085 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12086 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12087 s.move_with(|map, selection| {
12088 selection.collapse_to(
12089 movement::end_of_excerpt(
12090 map,
12091 selection.head(),
12092 workspace::searchable::Direction::Next,
12093 ),
12094 SelectionGoal::None,
12095 )
12096 });
12097 })
12098 }
12099
12100 pub fn move_to_end_of_previous_excerpt(
12101 &mut self,
12102 _: &MoveToEndOfPreviousExcerpt,
12103 window: &mut Window,
12104 cx: &mut Context<Self>,
12105 ) {
12106 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12107 cx.propagate();
12108 return;
12109 }
12110 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12111 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12112 s.move_with(|map, selection| {
12113 selection.collapse_to(
12114 movement::end_of_excerpt(
12115 map,
12116 selection.head(),
12117 workspace::searchable::Direction::Prev,
12118 ),
12119 SelectionGoal::None,
12120 )
12121 });
12122 })
12123 }
12124
12125 pub fn select_to_start_of_excerpt(
12126 &mut self,
12127 _: &SelectToStartOfExcerpt,
12128 window: &mut Window,
12129 cx: &mut Context<Self>,
12130 ) {
12131 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12132 cx.propagate();
12133 return;
12134 }
12135 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12136 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12137 s.move_heads_with(|map, head, _| {
12138 (
12139 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12140 SelectionGoal::None,
12141 )
12142 });
12143 })
12144 }
12145
12146 pub fn select_to_start_of_next_excerpt(
12147 &mut self,
12148 _: &SelectToStartOfNextExcerpt,
12149 window: &mut Window,
12150 cx: &mut Context<Self>,
12151 ) {
12152 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12153 cx.propagate();
12154 return;
12155 }
12156 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12157 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12158 s.move_heads_with(|map, head, _| {
12159 (
12160 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12161 SelectionGoal::None,
12162 )
12163 });
12164 })
12165 }
12166
12167 pub fn select_to_end_of_excerpt(
12168 &mut self,
12169 _: &SelectToEndOfExcerpt,
12170 window: &mut Window,
12171 cx: &mut Context<Self>,
12172 ) {
12173 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12174 cx.propagate();
12175 return;
12176 }
12177 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12178 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12179 s.move_heads_with(|map, head, _| {
12180 (
12181 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12182 SelectionGoal::None,
12183 )
12184 });
12185 })
12186 }
12187
12188 pub fn select_to_end_of_previous_excerpt(
12189 &mut self,
12190 _: &SelectToEndOfPreviousExcerpt,
12191 window: &mut Window,
12192 cx: &mut Context<Self>,
12193 ) {
12194 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12195 cx.propagate();
12196 return;
12197 }
12198 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12199 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12200 s.move_heads_with(|map, head, _| {
12201 (
12202 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12203 SelectionGoal::None,
12204 )
12205 });
12206 })
12207 }
12208
12209 pub fn move_to_beginning(
12210 &mut self,
12211 _: &MoveToBeginning,
12212 window: &mut Window,
12213 cx: &mut Context<Self>,
12214 ) {
12215 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12216 cx.propagate();
12217 return;
12218 }
12219 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12220 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12221 s.select_ranges(vec![0..0]);
12222 });
12223 }
12224
12225 pub fn select_to_beginning(
12226 &mut self,
12227 _: &SelectToBeginning,
12228 window: &mut Window,
12229 cx: &mut Context<Self>,
12230 ) {
12231 let mut selection = self.selections.last::<Point>(cx);
12232 selection.set_head(Point::zero(), SelectionGoal::None);
12233 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12234 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12235 s.select(vec![selection]);
12236 });
12237 }
12238
12239 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12240 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12241 cx.propagate();
12242 return;
12243 }
12244 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12245 let cursor = self.buffer.read(cx).read(cx).len();
12246 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12247 s.select_ranges(vec![cursor..cursor])
12248 });
12249 }
12250
12251 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12252 self.nav_history = nav_history;
12253 }
12254
12255 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12256 self.nav_history.as_ref()
12257 }
12258
12259 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12260 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12261 }
12262
12263 fn push_to_nav_history(
12264 &mut self,
12265 cursor_anchor: Anchor,
12266 new_position: Option<Point>,
12267 is_deactivate: bool,
12268 cx: &mut Context<Self>,
12269 ) {
12270 if let Some(nav_history) = self.nav_history.as_mut() {
12271 let buffer = self.buffer.read(cx).read(cx);
12272 let cursor_position = cursor_anchor.to_point(&buffer);
12273 let scroll_state = self.scroll_manager.anchor();
12274 let scroll_top_row = scroll_state.top_row(&buffer);
12275 drop(buffer);
12276
12277 if let Some(new_position) = new_position {
12278 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12279 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12280 return;
12281 }
12282 }
12283
12284 nav_history.push(
12285 Some(NavigationData {
12286 cursor_anchor,
12287 cursor_position,
12288 scroll_anchor: scroll_state,
12289 scroll_top_row,
12290 }),
12291 cx,
12292 );
12293 cx.emit(EditorEvent::PushedToNavHistory {
12294 anchor: cursor_anchor,
12295 is_deactivate,
12296 })
12297 }
12298 }
12299
12300 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12301 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12302 let buffer = self.buffer.read(cx).snapshot(cx);
12303 let mut selection = self.selections.first::<usize>(cx);
12304 selection.set_head(buffer.len(), SelectionGoal::None);
12305 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12306 s.select(vec![selection]);
12307 });
12308 }
12309
12310 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12311 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12312 let end = self.buffer.read(cx).read(cx).len();
12313 self.change_selections(None, window, cx, |s| {
12314 s.select_ranges(vec![0..end]);
12315 });
12316 }
12317
12318 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12319 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12320 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12321 let mut selections = self.selections.all::<Point>(cx);
12322 let max_point = display_map.buffer_snapshot.max_point();
12323 for selection in &mut selections {
12324 let rows = selection.spanned_rows(true, &display_map);
12325 selection.start = Point::new(rows.start.0, 0);
12326 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12327 selection.reversed = false;
12328 }
12329 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12330 s.select(selections);
12331 });
12332 }
12333
12334 pub fn split_selection_into_lines(
12335 &mut self,
12336 _: &SplitSelectionIntoLines,
12337 window: &mut Window,
12338 cx: &mut Context<Self>,
12339 ) {
12340 let selections = self
12341 .selections
12342 .all::<Point>(cx)
12343 .into_iter()
12344 .map(|selection| selection.start..selection.end)
12345 .collect::<Vec<_>>();
12346 self.unfold_ranges(&selections, true, true, cx);
12347
12348 let mut new_selection_ranges = Vec::new();
12349 {
12350 let buffer = self.buffer.read(cx).read(cx);
12351 for selection in selections {
12352 for row in selection.start.row..selection.end.row {
12353 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12354 new_selection_ranges.push(cursor..cursor);
12355 }
12356
12357 let is_multiline_selection = selection.start.row != selection.end.row;
12358 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12359 // so this action feels more ergonomic when paired with other selection operations
12360 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12361 if !should_skip_last {
12362 new_selection_ranges.push(selection.end..selection.end);
12363 }
12364 }
12365 }
12366 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12367 s.select_ranges(new_selection_ranges);
12368 });
12369 }
12370
12371 pub fn add_selection_above(
12372 &mut self,
12373 _: &AddSelectionAbove,
12374 window: &mut Window,
12375 cx: &mut Context<Self>,
12376 ) {
12377 self.add_selection(true, window, cx);
12378 }
12379
12380 pub fn add_selection_below(
12381 &mut self,
12382 _: &AddSelectionBelow,
12383 window: &mut Window,
12384 cx: &mut Context<Self>,
12385 ) {
12386 self.add_selection(false, window, cx);
12387 }
12388
12389 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12390 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12391
12392 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12393 let mut selections = self.selections.all::<Point>(cx);
12394 let text_layout_details = self.text_layout_details(window);
12395 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12396 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12397 let range = oldest_selection.display_range(&display_map).sorted();
12398
12399 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12400 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12401 let positions = start_x.min(end_x)..start_x.max(end_x);
12402
12403 selections.clear();
12404 let mut stack = Vec::new();
12405 for row in range.start.row().0..=range.end.row().0 {
12406 if let Some(selection) = self.selections.build_columnar_selection(
12407 &display_map,
12408 DisplayRow(row),
12409 &positions,
12410 oldest_selection.reversed,
12411 &text_layout_details,
12412 ) {
12413 stack.push(selection.id);
12414 selections.push(selection);
12415 }
12416 }
12417
12418 if above {
12419 stack.reverse();
12420 }
12421
12422 AddSelectionsState { above, stack }
12423 });
12424
12425 let last_added_selection = *state.stack.last().unwrap();
12426 let mut new_selections = Vec::new();
12427 if above == state.above {
12428 let end_row = if above {
12429 DisplayRow(0)
12430 } else {
12431 display_map.max_point().row()
12432 };
12433
12434 'outer: for selection in selections {
12435 if selection.id == last_added_selection {
12436 let range = selection.display_range(&display_map).sorted();
12437 debug_assert_eq!(range.start.row(), range.end.row());
12438 let mut row = range.start.row();
12439 let positions =
12440 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12441 px(start)..px(end)
12442 } else {
12443 let start_x =
12444 display_map.x_for_display_point(range.start, &text_layout_details);
12445 let end_x =
12446 display_map.x_for_display_point(range.end, &text_layout_details);
12447 start_x.min(end_x)..start_x.max(end_x)
12448 };
12449
12450 while row != end_row {
12451 if above {
12452 row.0 -= 1;
12453 } else {
12454 row.0 += 1;
12455 }
12456
12457 if let Some(new_selection) = self.selections.build_columnar_selection(
12458 &display_map,
12459 row,
12460 &positions,
12461 selection.reversed,
12462 &text_layout_details,
12463 ) {
12464 state.stack.push(new_selection.id);
12465 if above {
12466 new_selections.push(new_selection);
12467 new_selections.push(selection);
12468 } else {
12469 new_selections.push(selection);
12470 new_selections.push(new_selection);
12471 }
12472
12473 continue 'outer;
12474 }
12475 }
12476 }
12477
12478 new_selections.push(selection);
12479 }
12480 } else {
12481 new_selections = selections;
12482 new_selections.retain(|s| s.id != last_added_selection);
12483 state.stack.pop();
12484 }
12485
12486 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12487 s.select(new_selections);
12488 });
12489 if state.stack.len() > 1 {
12490 self.add_selections_state = Some(state);
12491 }
12492 }
12493
12494 fn select_match_ranges(
12495 &mut self,
12496 range: Range<usize>,
12497 reversed: bool,
12498 replace_newest: bool,
12499 auto_scroll: Option<Autoscroll>,
12500 window: &mut Window,
12501 cx: &mut Context<Editor>,
12502 ) {
12503 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12504 self.change_selections(auto_scroll, window, cx, |s| {
12505 if replace_newest {
12506 s.delete(s.newest_anchor().id);
12507 }
12508 if reversed {
12509 s.insert_range(range.end..range.start);
12510 } else {
12511 s.insert_range(range);
12512 }
12513 });
12514 }
12515
12516 pub fn select_next_match_internal(
12517 &mut self,
12518 display_map: &DisplaySnapshot,
12519 replace_newest: bool,
12520 autoscroll: Option<Autoscroll>,
12521 window: &mut Window,
12522 cx: &mut Context<Self>,
12523 ) -> Result<()> {
12524 let buffer = &display_map.buffer_snapshot;
12525 let mut selections = self.selections.all::<usize>(cx);
12526 if let Some(mut select_next_state) = self.select_next_state.take() {
12527 let query = &select_next_state.query;
12528 if !select_next_state.done {
12529 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12530 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12531 let mut next_selected_range = None;
12532
12533 let bytes_after_last_selection =
12534 buffer.bytes_in_range(last_selection.end..buffer.len());
12535 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12536 let query_matches = query
12537 .stream_find_iter(bytes_after_last_selection)
12538 .map(|result| (last_selection.end, result))
12539 .chain(
12540 query
12541 .stream_find_iter(bytes_before_first_selection)
12542 .map(|result| (0, result)),
12543 );
12544
12545 for (start_offset, query_match) in query_matches {
12546 let query_match = query_match.unwrap(); // can only fail due to I/O
12547 let offset_range =
12548 start_offset + query_match.start()..start_offset + query_match.end();
12549 let display_range = offset_range.start.to_display_point(display_map)
12550 ..offset_range.end.to_display_point(display_map);
12551
12552 if !select_next_state.wordwise
12553 || (!movement::is_inside_word(display_map, display_range.start)
12554 && !movement::is_inside_word(display_map, display_range.end))
12555 {
12556 // TODO: This is n^2, because we might check all the selections
12557 if !selections
12558 .iter()
12559 .any(|selection| selection.range().overlaps(&offset_range))
12560 {
12561 next_selected_range = Some(offset_range);
12562 break;
12563 }
12564 }
12565 }
12566
12567 if let Some(next_selected_range) = next_selected_range {
12568 self.select_match_ranges(
12569 next_selected_range,
12570 last_selection.reversed,
12571 replace_newest,
12572 autoscroll,
12573 window,
12574 cx,
12575 );
12576 } else {
12577 select_next_state.done = true;
12578 }
12579 }
12580
12581 self.select_next_state = Some(select_next_state);
12582 } else {
12583 let mut only_carets = true;
12584 let mut same_text_selected = true;
12585 let mut selected_text = None;
12586
12587 let mut selections_iter = selections.iter().peekable();
12588 while let Some(selection) = selections_iter.next() {
12589 if selection.start != selection.end {
12590 only_carets = false;
12591 }
12592
12593 if same_text_selected {
12594 if selected_text.is_none() {
12595 selected_text =
12596 Some(buffer.text_for_range(selection.range()).collect::<String>());
12597 }
12598
12599 if let Some(next_selection) = selections_iter.peek() {
12600 if next_selection.range().len() == selection.range().len() {
12601 let next_selected_text = buffer
12602 .text_for_range(next_selection.range())
12603 .collect::<String>();
12604 if Some(next_selected_text) != selected_text {
12605 same_text_selected = false;
12606 selected_text = None;
12607 }
12608 } else {
12609 same_text_selected = false;
12610 selected_text = None;
12611 }
12612 }
12613 }
12614 }
12615
12616 if only_carets {
12617 for selection in &mut selections {
12618 let word_range = movement::surrounding_word(
12619 display_map,
12620 selection.start.to_display_point(display_map),
12621 );
12622 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12623 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12624 selection.goal = SelectionGoal::None;
12625 selection.reversed = false;
12626 self.select_match_ranges(
12627 selection.start..selection.end,
12628 selection.reversed,
12629 replace_newest,
12630 autoscroll,
12631 window,
12632 cx,
12633 );
12634 }
12635
12636 if selections.len() == 1 {
12637 let selection = selections
12638 .last()
12639 .expect("ensured that there's only one selection");
12640 let query = buffer
12641 .text_for_range(selection.start..selection.end)
12642 .collect::<String>();
12643 let is_empty = query.is_empty();
12644 let select_state = SelectNextState {
12645 query: AhoCorasick::new(&[query])?,
12646 wordwise: true,
12647 done: is_empty,
12648 };
12649 self.select_next_state = Some(select_state);
12650 } else {
12651 self.select_next_state = None;
12652 }
12653 } else if let Some(selected_text) = selected_text {
12654 self.select_next_state = Some(SelectNextState {
12655 query: AhoCorasick::new(&[selected_text])?,
12656 wordwise: false,
12657 done: false,
12658 });
12659 self.select_next_match_internal(
12660 display_map,
12661 replace_newest,
12662 autoscroll,
12663 window,
12664 cx,
12665 )?;
12666 }
12667 }
12668 Ok(())
12669 }
12670
12671 pub fn select_all_matches(
12672 &mut self,
12673 _action: &SelectAllMatches,
12674 window: &mut Window,
12675 cx: &mut Context<Self>,
12676 ) -> Result<()> {
12677 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12678
12679 self.push_to_selection_history();
12680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12681
12682 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12683 let Some(select_next_state) = self.select_next_state.as_mut() else {
12684 return Ok(());
12685 };
12686 if select_next_state.done {
12687 return Ok(());
12688 }
12689
12690 let mut new_selections = Vec::new();
12691
12692 let reversed = self.selections.oldest::<usize>(cx).reversed;
12693 let buffer = &display_map.buffer_snapshot;
12694 let query_matches = select_next_state
12695 .query
12696 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12697
12698 for query_match in query_matches.into_iter() {
12699 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12700 let offset_range = if reversed {
12701 query_match.end()..query_match.start()
12702 } else {
12703 query_match.start()..query_match.end()
12704 };
12705 let display_range = offset_range.start.to_display_point(&display_map)
12706 ..offset_range.end.to_display_point(&display_map);
12707
12708 if !select_next_state.wordwise
12709 || (!movement::is_inside_word(&display_map, display_range.start)
12710 && !movement::is_inside_word(&display_map, display_range.end))
12711 {
12712 new_selections.push(offset_range.start..offset_range.end);
12713 }
12714 }
12715
12716 select_next_state.done = true;
12717 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12718 self.change_selections(None, window, cx, |selections| {
12719 selections.select_ranges(new_selections)
12720 });
12721
12722 Ok(())
12723 }
12724
12725 pub fn select_next(
12726 &mut self,
12727 action: &SelectNext,
12728 window: &mut Window,
12729 cx: &mut Context<Self>,
12730 ) -> Result<()> {
12731 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12732 self.push_to_selection_history();
12733 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12734 self.select_next_match_internal(
12735 &display_map,
12736 action.replace_newest,
12737 Some(Autoscroll::newest()),
12738 window,
12739 cx,
12740 )?;
12741 Ok(())
12742 }
12743
12744 pub fn select_previous(
12745 &mut self,
12746 action: &SelectPrevious,
12747 window: &mut Window,
12748 cx: &mut Context<Self>,
12749 ) -> Result<()> {
12750 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12751 self.push_to_selection_history();
12752 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12753 let buffer = &display_map.buffer_snapshot;
12754 let mut selections = self.selections.all::<usize>(cx);
12755 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12756 let query = &select_prev_state.query;
12757 if !select_prev_state.done {
12758 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12759 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12760 let mut next_selected_range = None;
12761 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12762 let bytes_before_last_selection =
12763 buffer.reversed_bytes_in_range(0..last_selection.start);
12764 let bytes_after_first_selection =
12765 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12766 let query_matches = query
12767 .stream_find_iter(bytes_before_last_selection)
12768 .map(|result| (last_selection.start, result))
12769 .chain(
12770 query
12771 .stream_find_iter(bytes_after_first_selection)
12772 .map(|result| (buffer.len(), result)),
12773 );
12774 for (end_offset, query_match) in query_matches {
12775 let query_match = query_match.unwrap(); // can only fail due to I/O
12776 let offset_range =
12777 end_offset - query_match.end()..end_offset - query_match.start();
12778 let display_range = offset_range.start.to_display_point(&display_map)
12779 ..offset_range.end.to_display_point(&display_map);
12780
12781 if !select_prev_state.wordwise
12782 || (!movement::is_inside_word(&display_map, display_range.start)
12783 && !movement::is_inside_word(&display_map, display_range.end))
12784 {
12785 next_selected_range = Some(offset_range);
12786 break;
12787 }
12788 }
12789
12790 if let Some(next_selected_range) = next_selected_range {
12791 self.select_match_ranges(
12792 next_selected_range,
12793 last_selection.reversed,
12794 action.replace_newest,
12795 Some(Autoscroll::newest()),
12796 window,
12797 cx,
12798 );
12799 } else {
12800 select_prev_state.done = true;
12801 }
12802 }
12803
12804 self.select_prev_state = Some(select_prev_state);
12805 } else {
12806 let mut only_carets = true;
12807 let mut same_text_selected = true;
12808 let mut selected_text = None;
12809
12810 let mut selections_iter = selections.iter().peekable();
12811 while let Some(selection) = selections_iter.next() {
12812 if selection.start != selection.end {
12813 only_carets = false;
12814 }
12815
12816 if same_text_selected {
12817 if selected_text.is_none() {
12818 selected_text =
12819 Some(buffer.text_for_range(selection.range()).collect::<String>());
12820 }
12821
12822 if let Some(next_selection) = selections_iter.peek() {
12823 if next_selection.range().len() == selection.range().len() {
12824 let next_selected_text = buffer
12825 .text_for_range(next_selection.range())
12826 .collect::<String>();
12827 if Some(next_selected_text) != selected_text {
12828 same_text_selected = false;
12829 selected_text = None;
12830 }
12831 } else {
12832 same_text_selected = false;
12833 selected_text = None;
12834 }
12835 }
12836 }
12837 }
12838
12839 if only_carets {
12840 for selection in &mut selections {
12841 let word_range = movement::surrounding_word(
12842 &display_map,
12843 selection.start.to_display_point(&display_map),
12844 );
12845 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12846 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12847 selection.goal = SelectionGoal::None;
12848 selection.reversed = false;
12849 self.select_match_ranges(
12850 selection.start..selection.end,
12851 selection.reversed,
12852 action.replace_newest,
12853 Some(Autoscroll::newest()),
12854 window,
12855 cx,
12856 );
12857 }
12858 if selections.len() == 1 {
12859 let selection = selections
12860 .last()
12861 .expect("ensured that there's only one selection");
12862 let query = buffer
12863 .text_for_range(selection.start..selection.end)
12864 .collect::<String>();
12865 let is_empty = query.is_empty();
12866 let select_state = SelectNextState {
12867 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12868 wordwise: true,
12869 done: is_empty,
12870 };
12871 self.select_prev_state = Some(select_state);
12872 } else {
12873 self.select_prev_state = None;
12874 }
12875 } else if let Some(selected_text) = selected_text {
12876 self.select_prev_state = Some(SelectNextState {
12877 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12878 wordwise: false,
12879 done: false,
12880 });
12881 self.select_previous(action, window, cx)?;
12882 }
12883 }
12884 Ok(())
12885 }
12886
12887 pub fn find_next_match(
12888 &mut self,
12889 _: &FindNextMatch,
12890 window: &mut Window,
12891 cx: &mut Context<Self>,
12892 ) -> Result<()> {
12893 let selections = self.selections.disjoint_anchors();
12894 match selections.first() {
12895 Some(first) if selections.len() >= 2 => {
12896 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12897 s.select_ranges([first.range()]);
12898 });
12899 }
12900 _ => self.select_next(
12901 &SelectNext {
12902 replace_newest: true,
12903 },
12904 window,
12905 cx,
12906 )?,
12907 }
12908 Ok(())
12909 }
12910
12911 pub fn find_previous_match(
12912 &mut self,
12913 _: &FindPreviousMatch,
12914 window: &mut Window,
12915 cx: &mut Context<Self>,
12916 ) -> Result<()> {
12917 let selections = self.selections.disjoint_anchors();
12918 match selections.last() {
12919 Some(last) if selections.len() >= 2 => {
12920 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12921 s.select_ranges([last.range()]);
12922 });
12923 }
12924 _ => self.select_previous(
12925 &SelectPrevious {
12926 replace_newest: true,
12927 },
12928 window,
12929 cx,
12930 )?,
12931 }
12932 Ok(())
12933 }
12934
12935 pub fn toggle_comments(
12936 &mut self,
12937 action: &ToggleComments,
12938 window: &mut Window,
12939 cx: &mut Context<Self>,
12940 ) {
12941 if self.read_only(cx) {
12942 return;
12943 }
12944 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12945 let text_layout_details = &self.text_layout_details(window);
12946 self.transact(window, cx, |this, window, cx| {
12947 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12948 let mut edits = Vec::new();
12949 let mut selection_edit_ranges = Vec::new();
12950 let mut last_toggled_row = None;
12951 let snapshot = this.buffer.read(cx).read(cx);
12952 let empty_str: Arc<str> = Arc::default();
12953 let mut suffixes_inserted = Vec::new();
12954 let ignore_indent = action.ignore_indent;
12955
12956 fn comment_prefix_range(
12957 snapshot: &MultiBufferSnapshot,
12958 row: MultiBufferRow,
12959 comment_prefix: &str,
12960 comment_prefix_whitespace: &str,
12961 ignore_indent: bool,
12962 ) -> Range<Point> {
12963 let indent_size = if ignore_indent {
12964 0
12965 } else {
12966 snapshot.indent_size_for_line(row).len
12967 };
12968
12969 let start = Point::new(row.0, indent_size);
12970
12971 let mut line_bytes = snapshot
12972 .bytes_in_range(start..snapshot.max_point())
12973 .flatten()
12974 .copied();
12975
12976 // If this line currently begins with the line comment prefix, then record
12977 // the range containing the prefix.
12978 if line_bytes
12979 .by_ref()
12980 .take(comment_prefix.len())
12981 .eq(comment_prefix.bytes())
12982 {
12983 // Include any whitespace that matches the comment prefix.
12984 let matching_whitespace_len = line_bytes
12985 .zip(comment_prefix_whitespace.bytes())
12986 .take_while(|(a, b)| a == b)
12987 .count() as u32;
12988 let end = Point::new(
12989 start.row,
12990 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
12991 );
12992 start..end
12993 } else {
12994 start..start
12995 }
12996 }
12997
12998 fn comment_suffix_range(
12999 snapshot: &MultiBufferSnapshot,
13000 row: MultiBufferRow,
13001 comment_suffix: &str,
13002 comment_suffix_has_leading_space: bool,
13003 ) -> Range<Point> {
13004 let end = Point::new(row.0, snapshot.line_len(row));
13005 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13006
13007 let mut line_end_bytes = snapshot
13008 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13009 .flatten()
13010 .copied();
13011
13012 let leading_space_len = if suffix_start_column > 0
13013 && line_end_bytes.next() == Some(b' ')
13014 && comment_suffix_has_leading_space
13015 {
13016 1
13017 } else {
13018 0
13019 };
13020
13021 // If this line currently begins with the line comment prefix, then record
13022 // the range containing the prefix.
13023 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13024 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13025 start..end
13026 } else {
13027 end..end
13028 }
13029 }
13030
13031 // TODO: Handle selections that cross excerpts
13032 for selection in &mut selections {
13033 let start_column = snapshot
13034 .indent_size_for_line(MultiBufferRow(selection.start.row))
13035 .len;
13036 let language = if let Some(language) =
13037 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13038 {
13039 language
13040 } else {
13041 continue;
13042 };
13043
13044 selection_edit_ranges.clear();
13045
13046 // If multiple selections contain a given row, avoid processing that
13047 // row more than once.
13048 let mut start_row = MultiBufferRow(selection.start.row);
13049 if last_toggled_row == Some(start_row) {
13050 start_row = start_row.next_row();
13051 }
13052 let end_row =
13053 if selection.end.row > selection.start.row && selection.end.column == 0 {
13054 MultiBufferRow(selection.end.row - 1)
13055 } else {
13056 MultiBufferRow(selection.end.row)
13057 };
13058 last_toggled_row = Some(end_row);
13059
13060 if start_row > end_row {
13061 continue;
13062 }
13063
13064 // If the language has line comments, toggle those.
13065 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13066
13067 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13068 if ignore_indent {
13069 full_comment_prefixes = full_comment_prefixes
13070 .into_iter()
13071 .map(|s| Arc::from(s.trim_end()))
13072 .collect();
13073 }
13074
13075 if !full_comment_prefixes.is_empty() {
13076 let first_prefix = full_comment_prefixes
13077 .first()
13078 .expect("prefixes is non-empty");
13079 let prefix_trimmed_lengths = full_comment_prefixes
13080 .iter()
13081 .map(|p| p.trim_end_matches(' ').len())
13082 .collect::<SmallVec<[usize; 4]>>();
13083
13084 let mut all_selection_lines_are_comments = true;
13085
13086 for row in start_row.0..=end_row.0 {
13087 let row = MultiBufferRow(row);
13088 if start_row < end_row && snapshot.is_line_blank(row) {
13089 continue;
13090 }
13091
13092 let prefix_range = full_comment_prefixes
13093 .iter()
13094 .zip(prefix_trimmed_lengths.iter().copied())
13095 .map(|(prefix, trimmed_prefix_len)| {
13096 comment_prefix_range(
13097 snapshot.deref(),
13098 row,
13099 &prefix[..trimmed_prefix_len],
13100 &prefix[trimmed_prefix_len..],
13101 ignore_indent,
13102 )
13103 })
13104 .max_by_key(|range| range.end.column - range.start.column)
13105 .expect("prefixes is non-empty");
13106
13107 if prefix_range.is_empty() {
13108 all_selection_lines_are_comments = false;
13109 }
13110
13111 selection_edit_ranges.push(prefix_range);
13112 }
13113
13114 if all_selection_lines_are_comments {
13115 edits.extend(
13116 selection_edit_ranges
13117 .iter()
13118 .cloned()
13119 .map(|range| (range, empty_str.clone())),
13120 );
13121 } else {
13122 let min_column = selection_edit_ranges
13123 .iter()
13124 .map(|range| range.start.column)
13125 .min()
13126 .unwrap_or(0);
13127 edits.extend(selection_edit_ranges.iter().map(|range| {
13128 let position = Point::new(range.start.row, min_column);
13129 (position..position, first_prefix.clone())
13130 }));
13131 }
13132 } else if let Some((full_comment_prefix, comment_suffix)) =
13133 language.block_comment_delimiters()
13134 {
13135 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13136 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13137 let prefix_range = comment_prefix_range(
13138 snapshot.deref(),
13139 start_row,
13140 comment_prefix,
13141 comment_prefix_whitespace,
13142 ignore_indent,
13143 );
13144 let suffix_range = comment_suffix_range(
13145 snapshot.deref(),
13146 end_row,
13147 comment_suffix.trim_start_matches(' '),
13148 comment_suffix.starts_with(' '),
13149 );
13150
13151 if prefix_range.is_empty() || suffix_range.is_empty() {
13152 edits.push((
13153 prefix_range.start..prefix_range.start,
13154 full_comment_prefix.clone(),
13155 ));
13156 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13157 suffixes_inserted.push((end_row, comment_suffix.len()));
13158 } else {
13159 edits.push((prefix_range, empty_str.clone()));
13160 edits.push((suffix_range, empty_str.clone()));
13161 }
13162 } else {
13163 continue;
13164 }
13165 }
13166
13167 drop(snapshot);
13168 this.buffer.update(cx, |buffer, cx| {
13169 buffer.edit(edits, None, cx);
13170 });
13171
13172 // Adjust selections so that they end before any comment suffixes that
13173 // were inserted.
13174 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13175 let mut selections = this.selections.all::<Point>(cx);
13176 let snapshot = this.buffer.read(cx).read(cx);
13177 for selection in &mut selections {
13178 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13179 match row.cmp(&MultiBufferRow(selection.end.row)) {
13180 Ordering::Less => {
13181 suffixes_inserted.next();
13182 continue;
13183 }
13184 Ordering::Greater => break,
13185 Ordering::Equal => {
13186 if selection.end.column == snapshot.line_len(row) {
13187 if selection.is_empty() {
13188 selection.start.column -= suffix_len as u32;
13189 }
13190 selection.end.column -= suffix_len as u32;
13191 }
13192 break;
13193 }
13194 }
13195 }
13196 }
13197
13198 drop(snapshot);
13199 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13200 s.select(selections)
13201 });
13202
13203 let selections = this.selections.all::<Point>(cx);
13204 let selections_on_single_row = selections.windows(2).all(|selections| {
13205 selections[0].start.row == selections[1].start.row
13206 && selections[0].end.row == selections[1].end.row
13207 && selections[0].start.row == selections[0].end.row
13208 });
13209 let selections_selecting = selections
13210 .iter()
13211 .any(|selection| selection.start != selection.end);
13212 let advance_downwards = action.advance_downwards
13213 && selections_on_single_row
13214 && !selections_selecting
13215 && !matches!(this.mode, EditorMode::SingleLine { .. });
13216
13217 if advance_downwards {
13218 let snapshot = this.buffer.read(cx).snapshot(cx);
13219
13220 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13221 s.move_cursors_with(|display_snapshot, display_point, _| {
13222 let mut point = display_point.to_point(display_snapshot);
13223 point.row += 1;
13224 point = snapshot.clip_point(point, Bias::Left);
13225 let display_point = point.to_display_point(display_snapshot);
13226 let goal = SelectionGoal::HorizontalPosition(
13227 display_snapshot
13228 .x_for_display_point(display_point, text_layout_details)
13229 .into(),
13230 );
13231 (display_point, goal)
13232 })
13233 });
13234 }
13235 });
13236 }
13237
13238 pub fn select_enclosing_symbol(
13239 &mut self,
13240 _: &SelectEnclosingSymbol,
13241 window: &mut Window,
13242 cx: &mut Context<Self>,
13243 ) {
13244 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13245
13246 let buffer = self.buffer.read(cx).snapshot(cx);
13247 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13248
13249 fn update_selection(
13250 selection: &Selection<usize>,
13251 buffer_snap: &MultiBufferSnapshot,
13252 ) -> Option<Selection<usize>> {
13253 let cursor = selection.head();
13254 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13255 for symbol in symbols.iter().rev() {
13256 let start = symbol.range.start.to_offset(buffer_snap);
13257 let end = symbol.range.end.to_offset(buffer_snap);
13258 let new_range = start..end;
13259 if start < selection.start || end > selection.end {
13260 return Some(Selection {
13261 id: selection.id,
13262 start: new_range.start,
13263 end: new_range.end,
13264 goal: SelectionGoal::None,
13265 reversed: selection.reversed,
13266 });
13267 }
13268 }
13269 None
13270 }
13271
13272 let mut selected_larger_symbol = false;
13273 let new_selections = old_selections
13274 .iter()
13275 .map(|selection| match update_selection(selection, &buffer) {
13276 Some(new_selection) => {
13277 if new_selection.range() != selection.range() {
13278 selected_larger_symbol = true;
13279 }
13280 new_selection
13281 }
13282 None => selection.clone(),
13283 })
13284 .collect::<Vec<_>>();
13285
13286 if selected_larger_symbol {
13287 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13288 s.select(new_selections);
13289 });
13290 }
13291 }
13292
13293 pub fn select_larger_syntax_node(
13294 &mut self,
13295 _: &SelectLargerSyntaxNode,
13296 window: &mut Window,
13297 cx: &mut Context<Self>,
13298 ) {
13299 let Some(visible_row_count) = self.visible_row_count() else {
13300 return;
13301 };
13302 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13303 if old_selections.is_empty() {
13304 return;
13305 }
13306
13307 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13308
13309 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13310 let buffer = self.buffer.read(cx).snapshot(cx);
13311
13312 let mut selected_larger_node = false;
13313 let mut new_selections = old_selections
13314 .iter()
13315 .map(|selection| {
13316 let old_range = selection.start..selection.end;
13317
13318 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13319 // manually select word at selection
13320 if ["string_content", "inline"].contains(&node.kind()) {
13321 let word_range = {
13322 let display_point = buffer
13323 .offset_to_point(old_range.start)
13324 .to_display_point(&display_map);
13325 let Range { start, end } =
13326 movement::surrounding_word(&display_map, display_point);
13327 start.to_point(&display_map).to_offset(&buffer)
13328 ..end.to_point(&display_map).to_offset(&buffer)
13329 };
13330 // ignore if word is already selected
13331 if !word_range.is_empty() && old_range != word_range {
13332 let last_word_range = {
13333 let display_point = buffer
13334 .offset_to_point(old_range.end)
13335 .to_display_point(&display_map);
13336 let Range { start, end } =
13337 movement::surrounding_word(&display_map, display_point);
13338 start.to_point(&display_map).to_offset(&buffer)
13339 ..end.to_point(&display_map).to_offset(&buffer)
13340 };
13341 // only select word if start and end point belongs to same word
13342 if word_range == last_word_range {
13343 selected_larger_node = true;
13344 return Selection {
13345 id: selection.id,
13346 start: word_range.start,
13347 end: word_range.end,
13348 goal: SelectionGoal::None,
13349 reversed: selection.reversed,
13350 };
13351 }
13352 }
13353 }
13354 }
13355
13356 let mut new_range = old_range.clone();
13357 while let Some((_node, containing_range)) =
13358 buffer.syntax_ancestor(new_range.clone())
13359 {
13360 new_range = match containing_range {
13361 MultiOrSingleBufferOffsetRange::Single(_) => break,
13362 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13363 };
13364 if !display_map.intersects_fold(new_range.start)
13365 && !display_map.intersects_fold(new_range.end)
13366 {
13367 break;
13368 }
13369 }
13370
13371 selected_larger_node |= new_range != old_range;
13372 Selection {
13373 id: selection.id,
13374 start: new_range.start,
13375 end: new_range.end,
13376 goal: SelectionGoal::None,
13377 reversed: selection.reversed,
13378 }
13379 })
13380 .collect::<Vec<_>>();
13381
13382 if !selected_larger_node {
13383 return; // don't put this call in the history
13384 }
13385
13386 // scroll based on transformation done to the last selection created by the user
13387 let (last_old, last_new) = old_selections
13388 .last()
13389 .zip(new_selections.last().cloned())
13390 .expect("old_selections isn't empty");
13391
13392 // revert selection
13393 let is_selection_reversed = {
13394 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13395 new_selections.last_mut().expect("checked above").reversed =
13396 should_newest_selection_be_reversed;
13397 should_newest_selection_be_reversed
13398 };
13399
13400 if selected_larger_node {
13401 self.select_syntax_node_history.disable_clearing = true;
13402 self.change_selections(None, window, cx, |s| {
13403 s.select(new_selections.clone());
13404 });
13405 self.select_syntax_node_history.disable_clearing = false;
13406 }
13407
13408 let start_row = last_new.start.to_display_point(&display_map).row().0;
13409 let end_row = last_new.end.to_display_point(&display_map).row().0;
13410 let selection_height = end_row - start_row + 1;
13411 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13412
13413 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13414 let scroll_behavior = if fits_on_the_screen {
13415 self.request_autoscroll(Autoscroll::fit(), cx);
13416 SelectSyntaxNodeScrollBehavior::FitSelection
13417 } else if is_selection_reversed {
13418 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13419 SelectSyntaxNodeScrollBehavior::CursorTop
13420 } else {
13421 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13422 SelectSyntaxNodeScrollBehavior::CursorBottom
13423 };
13424
13425 self.select_syntax_node_history.push((
13426 old_selections,
13427 scroll_behavior,
13428 is_selection_reversed,
13429 ));
13430 }
13431
13432 pub fn select_smaller_syntax_node(
13433 &mut self,
13434 _: &SelectSmallerSyntaxNode,
13435 window: &mut Window,
13436 cx: &mut Context<Self>,
13437 ) {
13438 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13439
13440 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13441 self.select_syntax_node_history.pop()
13442 {
13443 if let Some(selection) = selections.last_mut() {
13444 selection.reversed = is_selection_reversed;
13445 }
13446
13447 self.select_syntax_node_history.disable_clearing = true;
13448 self.change_selections(None, window, cx, |s| {
13449 s.select(selections.to_vec());
13450 });
13451 self.select_syntax_node_history.disable_clearing = false;
13452
13453 match scroll_behavior {
13454 SelectSyntaxNodeScrollBehavior::CursorTop => {
13455 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13456 }
13457 SelectSyntaxNodeScrollBehavior::FitSelection => {
13458 self.request_autoscroll(Autoscroll::fit(), cx);
13459 }
13460 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13461 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13462 }
13463 }
13464 }
13465 }
13466
13467 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13468 if !EditorSettings::get_global(cx).gutter.runnables {
13469 self.clear_tasks();
13470 return Task::ready(());
13471 }
13472 let project = self.project.as_ref().map(Entity::downgrade);
13473 let task_sources = self.lsp_task_sources(cx);
13474 cx.spawn_in(window, async move |editor, cx| {
13475 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13476 let Some(project) = project.and_then(|p| p.upgrade()) else {
13477 return;
13478 };
13479 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13480 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13481 }) else {
13482 return;
13483 };
13484
13485 let hide_runnables = project
13486 .update(cx, |project, cx| {
13487 // Do not display any test indicators in non-dev server remote projects.
13488 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13489 })
13490 .unwrap_or(true);
13491 if hide_runnables {
13492 return;
13493 }
13494 let new_rows =
13495 cx.background_spawn({
13496 let snapshot = display_snapshot.clone();
13497 async move {
13498 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13499 }
13500 })
13501 .await;
13502 let Ok(lsp_tasks) =
13503 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13504 else {
13505 return;
13506 };
13507 let lsp_tasks = lsp_tasks.await;
13508
13509 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13510 lsp_tasks
13511 .into_iter()
13512 .flat_map(|(kind, tasks)| {
13513 tasks.into_iter().filter_map(move |(location, task)| {
13514 Some((kind.clone(), location?, task))
13515 })
13516 })
13517 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13518 let buffer = location.target.buffer;
13519 let buffer_snapshot = buffer.read(cx).snapshot();
13520 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13521 |(excerpt_id, snapshot, _)| {
13522 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13523 display_snapshot
13524 .buffer_snapshot
13525 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13526 } else {
13527 None
13528 }
13529 },
13530 );
13531 if let Some(offset) = offset {
13532 let task_buffer_range =
13533 location.target.range.to_point(&buffer_snapshot);
13534 let context_buffer_range =
13535 task_buffer_range.to_offset(&buffer_snapshot);
13536 let context_range = BufferOffset(context_buffer_range.start)
13537 ..BufferOffset(context_buffer_range.end);
13538
13539 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13540 .or_insert_with(|| RunnableTasks {
13541 templates: Vec::new(),
13542 offset,
13543 column: task_buffer_range.start.column,
13544 extra_variables: HashMap::default(),
13545 context_range,
13546 })
13547 .templates
13548 .push((kind, task.original_task().clone()));
13549 }
13550
13551 acc
13552 })
13553 }) else {
13554 return;
13555 };
13556
13557 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13558 editor
13559 .update(cx, |editor, _| {
13560 editor.clear_tasks();
13561 for (key, mut value) in rows {
13562 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13563 value.templates.extend(lsp_tasks.templates);
13564 }
13565
13566 editor.insert_tasks(key, value);
13567 }
13568 for (key, value) in lsp_tasks_by_rows {
13569 editor.insert_tasks(key, value);
13570 }
13571 })
13572 .ok();
13573 })
13574 }
13575 fn fetch_runnable_ranges(
13576 snapshot: &DisplaySnapshot,
13577 range: Range<Anchor>,
13578 ) -> Vec<language::RunnableRange> {
13579 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13580 }
13581
13582 fn runnable_rows(
13583 project: Entity<Project>,
13584 snapshot: DisplaySnapshot,
13585 runnable_ranges: Vec<RunnableRange>,
13586 mut cx: AsyncWindowContext,
13587 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13588 runnable_ranges
13589 .into_iter()
13590 .filter_map(|mut runnable| {
13591 let tasks = cx
13592 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13593 .ok()?;
13594 if tasks.is_empty() {
13595 return None;
13596 }
13597
13598 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13599
13600 let row = snapshot
13601 .buffer_snapshot
13602 .buffer_line_for_row(MultiBufferRow(point.row))?
13603 .1
13604 .start
13605 .row;
13606
13607 let context_range =
13608 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13609 Some((
13610 (runnable.buffer_id, row),
13611 RunnableTasks {
13612 templates: tasks,
13613 offset: snapshot
13614 .buffer_snapshot
13615 .anchor_before(runnable.run_range.start),
13616 context_range,
13617 column: point.column,
13618 extra_variables: runnable.extra_captures,
13619 },
13620 ))
13621 })
13622 .collect()
13623 }
13624
13625 fn templates_with_tags(
13626 project: &Entity<Project>,
13627 runnable: &mut Runnable,
13628 cx: &mut App,
13629 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13630 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13631 let (worktree_id, file) = project
13632 .buffer_for_id(runnable.buffer, cx)
13633 .and_then(|buffer| buffer.read(cx).file())
13634 .map(|file| (file.worktree_id(cx), file.clone()))
13635 .unzip();
13636
13637 (
13638 project.task_store().read(cx).task_inventory().cloned(),
13639 worktree_id,
13640 file,
13641 )
13642 });
13643
13644 let mut templates_with_tags = mem::take(&mut runnable.tags)
13645 .into_iter()
13646 .flat_map(|RunnableTag(tag)| {
13647 inventory
13648 .as_ref()
13649 .into_iter()
13650 .flat_map(|inventory| {
13651 inventory.read(cx).list_tasks(
13652 file.clone(),
13653 Some(runnable.language.clone()),
13654 worktree_id,
13655 cx,
13656 )
13657 })
13658 .filter(move |(_, template)| {
13659 template.tags.iter().any(|source_tag| source_tag == &tag)
13660 })
13661 })
13662 .sorted_by_key(|(kind, _)| kind.to_owned())
13663 .collect::<Vec<_>>();
13664 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13665 // Strongest source wins; if we have worktree tag binding, prefer that to
13666 // global and language bindings;
13667 // if we have a global binding, prefer that to language binding.
13668 let first_mismatch = templates_with_tags
13669 .iter()
13670 .position(|(tag_source, _)| tag_source != leading_tag_source);
13671 if let Some(index) = first_mismatch {
13672 templates_with_tags.truncate(index);
13673 }
13674 }
13675
13676 templates_with_tags
13677 }
13678
13679 pub fn move_to_enclosing_bracket(
13680 &mut self,
13681 _: &MoveToEnclosingBracket,
13682 window: &mut Window,
13683 cx: &mut Context<Self>,
13684 ) {
13685 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13686 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13687 s.move_offsets_with(|snapshot, selection| {
13688 let Some(enclosing_bracket_ranges) =
13689 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13690 else {
13691 return;
13692 };
13693
13694 let mut best_length = usize::MAX;
13695 let mut best_inside = false;
13696 let mut best_in_bracket_range = false;
13697 let mut best_destination = None;
13698 for (open, close) in enclosing_bracket_ranges {
13699 let close = close.to_inclusive();
13700 let length = close.end() - open.start;
13701 let inside = selection.start >= open.end && selection.end <= *close.start();
13702 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13703 || close.contains(&selection.head());
13704
13705 // If best is next to a bracket and current isn't, skip
13706 if !in_bracket_range && best_in_bracket_range {
13707 continue;
13708 }
13709
13710 // Prefer smaller lengths unless best is inside and current isn't
13711 if length > best_length && (best_inside || !inside) {
13712 continue;
13713 }
13714
13715 best_length = length;
13716 best_inside = inside;
13717 best_in_bracket_range = in_bracket_range;
13718 best_destination = Some(
13719 if close.contains(&selection.start) && close.contains(&selection.end) {
13720 if inside { open.end } else { open.start }
13721 } else if inside {
13722 *close.start()
13723 } else {
13724 *close.end()
13725 },
13726 );
13727 }
13728
13729 if let Some(destination) = best_destination {
13730 selection.collapse_to(destination, SelectionGoal::None);
13731 }
13732 })
13733 });
13734 }
13735
13736 pub fn undo_selection(
13737 &mut self,
13738 _: &UndoSelection,
13739 window: &mut Window,
13740 cx: &mut Context<Self>,
13741 ) {
13742 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13743 self.end_selection(window, cx);
13744 self.selection_history.mode = SelectionHistoryMode::Undoing;
13745 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13746 self.change_selections(None, window, cx, |s| {
13747 s.select_anchors(entry.selections.to_vec())
13748 });
13749 self.select_next_state = entry.select_next_state;
13750 self.select_prev_state = entry.select_prev_state;
13751 self.add_selections_state = entry.add_selections_state;
13752 self.request_autoscroll(Autoscroll::newest(), cx);
13753 }
13754 self.selection_history.mode = SelectionHistoryMode::Normal;
13755 }
13756
13757 pub fn redo_selection(
13758 &mut self,
13759 _: &RedoSelection,
13760 window: &mut Window,
13761 cx: &mut Context<Self>,
13762 ) {
13763 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13764 self.end_selection(window, cx);
13765 self.selection_history.mode = SelectionHistoryMode::Redoing;
13766 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13767 self.change_selections(None, window, cx, |s| {
13768 s.select_anchors(entry.selections.to_vec())
13769 });
13770 self.select_next_state = entry.select_next_state;
13771 self.select_prev_state = entry.select_prev_state;
13772 self.add_selections_state = entry.add_selections_state;
13773 self.request_autoscroll(Autoscroll::newest(), cx);
13774 }
13775 self.selection_history.mode = SelectionHistoryMode::Normal;
13776 }
13777
13778 pub fn expand_excerpts(
13779 &mut self,
13780 action: &ExpandExcerpts,
13781 _: &mut Window,
13782 cx: &mut Context<Self>,
13783 ) {
13784 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13785 }
13786
13787 pub fn expand_excerpts_down(
13788 &mut self,
13789 action: &ExpandExcerptsDown,
13790 _: &mut Window,
13791 cx: &mut Context<Self>,
13792 ) {
13793 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13794 }
13795
13796 pub fn expand_excerpts_up(
13797 &mut self,
13798 action: &ExpandExcerptsUp,
13799 _: &mut Window,
13800 cx: &mut Context<Self>,
13801 ) {
13802 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13803 }
13804
13805 pub fn expand_excerpts_for_direction(
13806 &mut self,
13807 lines: u32,
13808 direction: ExpandExcerptDirection,
13809
13810 cx: &mut Context<Self>,
13811 ) {
13812 let selections = self.selections.disjoint_anchors();
13813
13814 let lines = if lines == 0 {
13815 EditorSettings::get_global(cx).expand_excerpt_lines
13816 } else {
13817 lines
13818 };
13819
13820 self.buffer.update(cx, |buffer, cx| {
13821 let snapshot = buffer.snapshot(cx);
13822 let mut excerpt_ids = selections
13823 .iter()
13824 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13825 .collect::<Vec<_>>();
13826 excerpt_ids.sort();
13827 excerpt_ids.dedup();
13828 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13829 })
13830 }
13831
13832 pub fn expand_excerpt(
13833 &mut self,
13834 excerpt: ExcerptId,
13835 direction: ExpandExcerptDirection,
13836 window: &mut Window,
13837 cx: &mut Context<Self>,
13838 ) {
13839 let current_scroll_position = self.scroll_position(cx);
13840 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13841 let mut should_scroll_up = false;
13842
13843 if direction == ExpandExcerptDirection::Down {
13844 let multi_buffer = self.buffer.read(cx);
13845 let snapshot = multi_buffer.snapshot(cx);
13846 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13847 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13848 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13849 let buffer_snapshot = buffer.read(cx).snapshot();
13850 let excerpt_end_row =
13851 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13852 let last_row = buffer_snapshot.max_point().row;
13853 let lines_below = last_row.saturating_sub(excerpt_end_row);
13854 should_scroll_up = lines_below >= lines_to_expand;
13855 }
13856 }
13857 }
13858 }
13859
13860 self.buffer.update(cx, |buffer, cx| {
13861 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13862 });
13863
13864 if should_scroll_up {
13865 let new_scroll_position =
13866 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13867 self.set_scroll_position(new_scroll_position, window, cx);
13868 }
13869 }
13870
13871 pub fn go_to_singleton_buffer_point(
13872 &mut self,
13873 point: Point,
13874 window: &mut Window,
13875 cx: &mut Context<Self>,
13876 ) {
13877 self.go_to_singleton_buffer_range(point..point, window, cx);
13878 }
13879
13880 pub fn go_to_singleton_buffer_range(
13881 &mut self,
13882 range: Range<Point>,
13883 window: &mut Window,
13884 cx: &mut Context<Self>,
13885 ) {
13886 let multibuffer = self.buffer().read(cx);
13887 let Some(buffer) = multibuffer.as_singleton() else {
13888 return;
13889 };
13890 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13891 return;
13892 };
13893 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13894 return;
13895 };
13896 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13897 s.select_anchor_ranges([start..end])
13898 });
13899 }
13900
13901 pub fn go_to_diagnostic(
13902 &mut self,
13903 _: &GoToDiagnostic,
13904 window: &mut Window,
13905 cx: &mut Context<Self>,
13906 ) {
13907 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13908 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13909 }
13910
13911 pub fn go_to_prev_diagnostic(
13912 &mut self,
13913 _: &GoToPreviousDiagnostic,
13914 window: &mut Window,
13915 cx: &mut Context<Self>,
13916 ) {
13917 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13918 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13919 }
13920
13921 pub fn go_to_diagnostic_impl(
13922 &mut self,
13923 direction: Direction,
13924 window: &mut Window,
13925 cx: &mut Context<Self>,
13926 ) {
13927 let buffer = self.buffer.read(cx).snapshot(cx);
13928 let selection = self.selections.newest::<usize>(cx);
13929
13930 let mut active_group_id = None;
13931 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13932 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13933 active_group_id = Some(active_group.group_id);
13934 }
13935 }
13936
13937 fn filtered(
13938 snapshot: EditorSnapshot,
13939 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13940 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13941 diagnostics
13942 .filter(|entry| entry.range.start != entry.range.end)
13943 .filter(|entry| !entry.diagnostic.is_unnecessary)
13944 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13945 }
13946
13947 let snapshot = self.snapshot(window, cx);
13948 let before = filtered(
13949 snapshot.clone(),
13950 buffer
13951 .diagnostics_in_range(0..selection.start)
13952 .filter(|entry| entry.range.start <= selection.start),
13953 );
13954 let after = filtered(
13955 snapshot,
13956 buffer
13957 .diagnostics_in_range(selection.start..buffer.len())
13958 .filter(|entry| entry.range.start >= selection.start),
13959 );
13960
13961 let mut found: Option<DiagnosticEntry<usize>> = None;
13962 if direction == Direction::Prev {
13963 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
13964 {
13965 for diagnostic in prev_diagnostics.into_iter().rev() {
13966 if diagnostic.range.start != selection.start
13967 || active_group_id
13968 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
13969 {
13970 found = Some(diagnostic);
13971 break 'outer;
13972 }
13973 }
13974 }
13975 } else {
13976 for diagnostic in after.chain(before) {
13977 if diagnostic.range.start != selection.start
13978 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
13979 {
13980 found = Some(diagnostic);
13981 break;
13982 }
13983 }
13984 }
13985 let Some(next_diagnostic) = found else {
13986 return;
13987 };
13988
13989 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
13990 return;
13991 };
13992 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13993 s.select_ranges(vec![
13994 next_diagnostic.range.start..next_diagnostic.range.start,
13995 ])
13996 });
13997 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
13998 self.refresh_inline_completion(false, true, window, cx);
13999 }
14000
14001 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14002 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14003 let snapshot = self.snapshot(window, cx);
14004 let selection = self.selections.newest::<Point>(cx);
14005 self.go_to_hunk_before_or_after_position(
14006 &snapshot,
14007 selection.head(),
14008 Direction::Next,
14009 window,
14010 cx,
14011 );
14012 }
14013
14014 pub fn go_to_hunk_before_or_after_position(
14015 &mut self,
14016 snapshot: &EditorSnapshot,
14017 position: Point,
14018 direction: Direction,
14019 window: &mut Window,
14020 cx: &mut Context<Editor>,
14021 ) {
14022 let row = if direction == Direction::Next {
14023 self.hunk_after_position(snapshot, position)
14024 .map(|hunk| hunk.row_range.start)
14025 } else {
14026 self.hunk_before_position(snapshot, position)
14027 };
14028
14029 if let Some(row) = row {
14030 let destination = Point::new(row.0, 0);
14031 let autoscroll = Autoscroll::center();
14032
14033 self.unfold_ranges(&[destination..destination], false, false, cx);
14034 self.change_selections(Some(autoscroll), window, cx, |s| {
14035 s.select_ranges([destination..destination]);
14036 });
14037 }
14038 }
14039
14040 fn hunk_after_position(
14041 &mut self,
14042 snapshot: &EditorSnapshot,
14043 position: Point,
14044 ) -> Option<MultiBufferDiffHunk> {
14045 snapshot
14046 .buffer_snapshot
14047 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14048 .find(|hunk| hunk.row_range.start.0 > position.row)
14049 .or_else(|| {
14050 snapshot
14051 .buffer_snapshot
14052 .diff_hunks_in_range(Point::zero()..position)
14053 .find(|hunk| hunk.row_range.end.0 < position.row)
14054 })
14055 }
14056
14057 fn go_to_prev_hunk(
14058 &mut self,
14059 _: &GoToPreviousHunk,
14060 window: &mut Window,
14061 cx: &mut Context<Self>,
14062 ) {
14063 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14064 let snapshot = self.snapshot(window, cx);
14065 let selection = self.selections.newest::<Point>(cx);
14066 self.go_to_hunk_before_or_after_position(
14067 &snapshot,
14068 selection.head(),
14069 Direction::Prev,
14070 window,
14071 cx,
14072 );
14073 }
14074
14075 fn hunk_before_position(
14076 &mut self,
14077 snapshot: &EditorSnapshot,
14078 position: Point,
14079 ) -> Option<MultiBufferRow> {
14080 snapshot
14081 .buffer_snapshot
14082 .diff_hunk_before(position)
14083 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14084 }
14085
14086 fn go_to_next_change(
14087 &mut self,
14088 _: &GoToNextChange,
14089 window: &mut Window,
14090 cx: &mut Context<Self>,
14091 ) {
14092 if let Some(selections) = self
14093 .change_list
14094 .next_change(1, Direction::Next)
14095 .map(|s| s.to_vec())
14096 {
14097 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14098 let map = s.display_map();
14099 s.select_display_ranges(selections.iter().map(|a| {
14100 let point = a.to_display_point(&map);
14101 point..point
14102 }))
14103 })
14104 }
14105 }
14106
14107 fn go_to_previous_change(
14108 &mut self,
14109 _: &GoToPreviousChange,
14110 window: &mut Window,
14111 cx: &mut Context<Self>,
14112 ) {
14113 if let Some(selections) = self
14114 .change_list
14115 .next_change(1, Direction::Prev)
14116 .map(|s| s.to_vec())
14117 {
14118 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14119 let map = s.display_map();
14120 s.select_display_ranges(selections.iter().map(|a| {
14121 let point = a.to_display_point(&map);
14122 point..point
14123 }))
14124 })
14125 }
14126 }
14127
14128 fn go_to_line<T: 'static>(
14129 &mut self,
14130 position: Anchor,
14131 highlight_color: Option<Hsla>,
14132 window: &mut Window,
14133 cx: &mut Context<Self>,
14134 ) {
14135 let snapshot = self.snapshot(window, cx).display_snapshot;
14136 let position = position.to_point(&snapshot.buffer_snapshot);
14137 let start = snapshot
14138 .buffer_snapshot
14139 .clip_point(Point::new(position.row, 0), Bias::Left);
14140 let end = start + Point::new(1, 0);
14141 let start = snapshot.buffer_snapshot.anchor_before(start);
14142 let end = snapshot.buffer_snapshot.anchor_before(end);
14143
14144 self.highlight_rows::<T>(
14145 start..end,
14146 highlight_color
14147 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14148 Default::default(),
14149 cx,
14150 );
14151
14152 if self.buffer.read(cx).is_singleton() {
14153 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14154 }
14155 }
14156
14157 pub fn go_to_definition(
14158 &mut self,
14159 _: &GoToDefinition,
14160 window: &mut Window,
14161 cx: &mut Context<Self>,
14162 ) -> Task<Result<Navigated>> {
14163 let definition =
14164 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14165 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14166 cx.spawn_in(window, async move |editor, cx| {
14167 if definition.await? == Navigated::Yes {
14168 return Ok(Navigated::Yes);
14169 }
14170 match fallback_strategy {
14171 GoToDefinitionFallback::None => Ok(Navigated::No),
14172 GoToDefinitionFallback::FindAllReferences => {
14173 match editor.update_in(cx, |editor, window, cx| {
14174 editor.find_all_references(&FindAllReferences, window, cx)
14175 })? {
14176 Some(references) => references.await,
14177 None => Ok(Navigated::No),
14178 }
14179 }
14180 }
14181 })
14182 }
14183
14184 pub fn go_to_declaration(
14185 &mut self,
14186 _: &GoToDeclaration,
14187 window: &mut Window,
14188 cx: &mut Context<Self>,
14189 ) -> Task<Result<Navigated>> {
14190 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14191 }
14192
14193 pub fn go_to_declaration_split(
14194 &mut self,
14195 _: &GoToDeclaration,
14196 window: &mut Window,
14197 cx: &mut Context<Self>,
14198 ) -> Task<Result<Navigated>> {
14199 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14200 }
14201
14202 pub fn go_to_implementation(
14203 &mut self,
14204 _: &GoToImplementation,
14205 window: &mut Window,
14206 cx: &mut Context<Self>,
14207 ) -> Task<Result<Navigated>> {
14208 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14209 }
14210
14211 pub fn go_to_implementation_split(
14212 &mut self,
14213 _: &GoToImplementationSplit,
14214 window: &mut Window,
14215 cx: &mut Context<Self>,
14216 ) -> Task<Result<Navigated>> {
14217 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14218 }
14219
14220 pub fn go_to_type_definition(
14221 &mut self,
14222 _: &GoToTypeDefinition,
14223 window: &mut Window,
14224 cx: &mut Context<Self>,
14225 ) -> Task<Result<Navigated>> {
14226 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14227 }
14228
14229 pub fn go_to_definition_split(
14230 &mut self,
14231 _: &GoToDefinitionSplit,
14232 window: &mut Window,
14233 cx: &mut Context<Self>,
14234 ) -> Task<Result<Navigated>> {
14235 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14236 }
14237
14238 pub fn go_to_type_definition_split(
14239 &mut self,
14240 _: &GoToTypeDefinitionSplit,
14241 window: &mut Window,
14242 cx: &mut Context<Self>,
14243 ) -> Task<Result<Navigated>> {
14244 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14245 }
14246
14247 fn go_to_definition_of_kind(
14248 &mut self,
14249 kind: GotoDefinitionKind,
14250 split: bool,
14251 window: &mut Window,
14252 cx: &mut Context<Self>,
14253 ) -> Task<Result<Navigated>> {
14254 let Some(provider) = self.semantics_provider.clone() else {
14255 return Task::ready(Ok(Navigated::No));
14256 };
14257 let head = self.selections.newest::<usize>(cx).head();
14258 let buffer = self.buffer.read(cx);
14259 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14260 text_anchor
14261 } else {
14262 return Task::ready(Ok(Navigated::No));
14263 };
14264
14265 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14266 return Task::ready(Ok(Navigated::No));
14267 };
14268
14269 cx.spawn_in(window, async move |editor, cx| {
14270 let definitions = definitions.await?;
14271 let navigated = editor
14272 .update_in(cx, |editor, window, cx| {
14273 editor.navigate_to_hover_links(
14274 Some(kind),
14275 definitions
14276 .into_iter()
14277 .filter(|location| {
14278 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14279 })
14280 .map(HoverLink::Text)
14281 .collect::<Vec<_>>(),
14282 split,
14283 window,
14284 cx,
14285 )
14286 })?
14287 .await?;
14288 anyhow::Ok(navigated)
14289 })
14290 }
14291
14292 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14293 let selection = self.selections.newest_anchor();
14294 let head = selection.head();
14295 let tail = selection.tail();
14296
14297 let Some((buffer, start_position)) =
14298 self.buffer.read(cx).text_anchor_for_position(head, cx)
14299 else {
14300 return;
14301 };
14302
14303 let end_position = if head != tail {
14304 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14305 return;
14306 };
14307 Some(pos)
14308 } else {
14309 None
14310 };
14311
14312 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14313 let url = if let Some(end_pos) = end_position {
14314 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14315 } else {
14316 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14317 };
14318
14319 if let Some(url) = url {
14320 editor.update(cx, |_, cx| {
14321 cx.open_url(&url);
14322 })
14323 } else {
14324 Ok(())
14325 }
14326 });
14327
14328 url_finder.detach();
14329 }
14330
14331 pub fn open_selected_filename(
14332 &mut self,
14333 _: &OpenSelectedFilename,
14334 window: &mut Window,
14335 cx: &mut Context<Self>,
14336 ) {
14337 let Some(workspace) = self.workspace() else {
14338 return;
14339 };
14340
14341 let position = self.selections.newest_anchor().head();
14342
14343 let Some((buffer, buffer_position)) =
14344 self.buffer.read(cx).text_anchor_for_position(position, cx)
14345 else {
14346 return;
14347 };
14348
14349 let project = self.project.clone();
14350
14351 cx.spawn_in(window, async move |_, cx| {
14352 let result = find_file(&buffer, project, buffer_position, cx).await;
14353
14354 if let Some((_, path)) = result {
14355 workspace
14356 .update_in(cx, |workspace, window, cx| {
14357 workspace.open_resolved_path(path, window, cx)
14358 })?
14359 .await?;
14360 }
14361 anyhow::Ok(())
14362 })
14363 .detach();
14364 }
14365
14366 pub(crate) fn navigate_to_hover_links(
14367 &mut self,
14368 kind: Option<GotoDefinitionKind>,
14369 mut definitions: Vec<HoverLink>,
14370 split: bool,
14371 window: &mut Window,
14372 cx: &mut Context<Editor>,
14373 ) -> Task<Result<Navigated>> {
14374 // If there is one definition, just open it directly
14375 if definitions.len() == 1 {
14376 let definition = definitions.pop().unwrap();
14377
14378 enum TargetTaskResult {
14379 Location(Option<Location>),
14380 AlreadyNavigated,
14381 }
14382
14383 let target_task = match definition {
14384 HoverLink::Text(link) => {
14385 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14386 }
14387 HoverLink::InlayHint(lsp_location, server_id) => {
14388 let computation =
14389 self.compute_target_location(lsp_location, server_id, window, cx);
14390 cx.background_spawn(async move {
14391 let location = computation.await?;
14392 Ok(TargetTaskResult::Location(location))
14393 })
14394 }
14395 HoverLink::Url(url) => {
14396 cx.open_url(&url);
14397 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14398 }
14399 HoverLink::File(path) => {
14400 if let Some(workspace) = self.workspace() {
14401 cx.spawn_in(window, async move |_, cx| {
14402 workspace
14403 .update_in(cx, |workspace, window, cx| {
14404 workspace.open_resolved_path(path, window, cx)
14405 })?
14406 .await
14407 .map(|_| TargetTaskResult::AlreadyNavigated)
14408 })
14409 } else {
14410 Task::ready(Ok(TargetTaskResult::Location(None)))
14411 }
14412 }
14413 };
14414 cx.spawn_in(window, async move |editor, cx| {
14415 let target = match target_task.await.context("target resolution task")? {
14416 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14417 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14418 TargetTaskResult::Location(Some(target)) => target,
14419 };
14420
14421 editor.update_in(cx, |editor, window, cx| {
14422 let Some(workspace) = editor.workspace() else {
14423 return Navigated::No;
14424 };
14425 let pane = workspace.read(cx).active_pane().clone();
14426
14427 let range = target.range.to_point(target.buffer.read(cx));
14428 let range = editor.range_for_match(&range);
14429 let range = collapse_multiline_range(range);
14430
14431 if !split
14432 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14433 {
14434 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14435 } else {
14436 window.defer(cx, move |window, cx| {
14437 let target_editor: Entity<Self> =
14438 workspace.update(cx, |workspace, cx| {
14439 let pane = if split {
14440 workspace.adjacent_pane(window, cx)
14441 } else {
14442 workspace.active_pane().clone()
14443 };
14444
14445 workspace.open_project_item(
14446 pane,
14447 target.buffer.clone(),
14448 true,
14449 true,
14450 window,
14451 cx,
14452 )
14453 });
14454 target_editor.update(cx, |target_editor, cx| {
14455 // When selecting a definition in a different buffer, disable the nav history
14456 // to avoid creating a history entry at the previous cursor location.
14457 pane.update(cx, |pane, _| pane.disable_history());
14458 target_editor.go_to_singleton_buffer_range(range, window, cx);
14459 pane.update(cx, |pane, _| pane.enable_history());
14460 });
14461 });
14462 }
14463 Navigated::Yes
14464 })
14465 })
14466 } else if !definitions.is_empty() {
14467 cx.spawn_in(window, async move |editor, cx| {
14468 let (title, location_tasks, workspace) = editor
14469 .update_in(cx, |editor, window, cx| {
14470 let tab_kind = match kind {
14471 Some(GotoDefinitionKind::Implementation) => "Implementations",
14472 _ => "Definitions",
14473 };
14474 let title = definitions
14475 .iter()
14476 .find_map(|definition| match definition {
14477 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14478 let buffer = origin.buffer.read(cx);
14479 format!(
14480 "{} for {}",
14481 tab_kind,
14482 buffer
14483 .text_for_range(origin.range.clone())
14484 .collect::<String>()
14485 )
14486 }),
14487 HoverLink::InlayHint(_, _) => None,
14488 HoverLink::Url(_) => None,
14489 HoverLink::File(_) => None,
14490 })
14491 .unwrap_or(tab_kind.to_string());
14492 let location_tasks = definitions
14493 .into_iter()
14494 .map(|definition| match definition {
14495 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14496 HoverLink::InlayHint(lsp_location, server_id) => editor
14497 .compute_target_location(lsp_location, server_id, window, cx),
14498 HoverLink::Url(_) => Task::ready(Ok(None)),
14499 HoverLink::File(_) => Task::ready(Ok(None)),
14500 })
14501 .collect::<Vec<_>>();
14502 (title, location_tasks, editor.workspace().clone())
14503 })
14504 .context("location tasks preparation")?;
14505
14506 let locations = future::join_all(location_tasks)
14507 .await
14508 .into_iter()
14509 .filter_map(|location| location.transpose())
14510 .collect::<Result<_>>()
14511 .context("location tasks")?;
14512
14513 let Some(workspace) = workspace else {
14514 return Ok(Navigated::No);
14515 };
14516 let opened = workspace
14517 .update_in(cx, |workspace, window, cx| {
14518 Self::open_locations_in_multibuffer(
14519 workspace,
14520 locations,
14521 title,
14522 split,
14523 MultibufferSelectionMode::First,
14524 window,
14525 cx,
14526 )
14527 })
14528 .ok();
14529
14530 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14531 })
14532 } else {
14533 Task::ready(Ok(Navigated::No))
14534 }
14535 }
14536
14537 fn compute_target_location(
14538 &self,
14539 lsp_location: lsp::Location,
14540 server_id: LanguageServerId,
14541 window: &mut Window,
14542 cx: &mut Context<Self>,
14543 ) -> Task<anyhow::Result<Option<Location>>> {
14544 let Some(project) = self.project.clone() else {
14545 return Task::ready(Ok(None));
14546 };
14547
14548 cx.spawn_in(window, async move |editor, cx| {
14549 let location_task = editor.update(cx, |_, cx| {
14550 project.update(cx, |project, cx| {
14551 let language_server_name = project
14552 .language_server_statuses(cx)
14553 .find(|(id, _)| server_id == *id)
14554 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14555 language_server_name.map(|language_server_name| {
14556 project.open_local_buffer_via_lsp(
14557 lsp_location.uri.clone(),
14558 server_id,
14559 language_server_name,
14560 cx,
14561 )
14562 })
14563 })
14564 })?;
14565 let location = match location_task {
14566 Some(task) => Some({
14567 let target_buffer_handle = task.await.context("open local buffer")?;
14568 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14569 let target_start = target_buffer
14570 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14571 let target_end = target_buffer
14572 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14573 target_buffer.anchor_after(target_start)
14574 ..target_buffer.anchor_before(target_end)
14575 })?;
14576 Location {
14577 buffer: target_buffer_handle,
14578 range,
14579 }
14580 }),
14581 None => None,
14582 };
14583 Ok(location)
14584 })
14585 }
14586
14587 pub fn find_all_references(
14588 &mut self,
14589 _: &FindAllReferences,
14590 window: &mut Window,
14591 cx: &mut Context<Self>,
14592 ) -> Option<Task<Result<Navigated>>> {
14593 let selection = self.selections.newest::<usize>(cx);
14594 let multi_buffer = self.buffer.read(cx);
14595 let head = selection.head();
14596
14597 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14598 let head_anchor = multi_buffer_snapshot.anchor_at(
14599 head,
14600 if head < selection.tail() {
14601 Bias::Right
14602 } else {
14603 Bias::Left
14604 },
14605 );
14606
14607 match self
14608 .find_all_references_task_sources
14609 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14610 {
14611 Ok(_) => {
14612 log::info!(
14613 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14614 );
14615 return None;
14616 }
14617 Err(i) => {
14618 self.find_all_references_task_sources.insert(i, head_anchor);
14619 }
14620 }
14621
14622 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14623 let workspace = self.workspace()?;
14624 let project = workspace.read(cx).project().clone();
14625 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14626 Some(cx.spawn_in(window, async move |editor, cx| {
14627 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14628 if let Ok(i) = editor
14629 .find_all_references_task_sources
14630 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14631 {
14632 editor.find_all_references_task_sources.remove(i);
14633 }
14634 });
14635
14636 let locations = references.await?;
14637 if locations.is_empty() {
14638 return anyhow::Ok(Navigated::No);
14639 }
14640
14641 workspace.update_in(cx, |workspace, window, cx| {
14642 let title = locations
14643 .first()
14644 .as_ref()
14645 .map(|location| {
14646 let buffer = location.buffer.read(cx);
14647 format!(
14648 "References to `{}`",
14649 buffer
14650 .text_for_range(location.range.clone())
14651 .collect::<String>()
14652 )
14653 })
14654 .unwrap();
14655 Self::open_locations_in_multibuffer(
14656 workspace,
14657 locations,
14658 title,
14659 false,
14660 MultibufferSelectionMode::First,
14661 window,
14662 cx,
14663 );
14664 Navigated::Yes
14665 })
14666 }))
14667 }
14668
14669 /// Opens a multibuffer with the given project locations in it
14670 pub fn open_locations_in_multibuffer(
14671 workspace: &mut Workspace,
14672 mut locations: Vec<Location>,
14673 title: String,
14674 split: bool,
14675 multibuffer_selection_mode: MultibufferSelectionMode,
14676 window: &mut Window,
14677 cx: &mut Context<Workspace>,
14678 ) {
14679 // If there are multiple definitions, open them in a multibuffer
14680 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14681 let mut locations = locations.into_iter().peekable();
14682 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14683 let capability = workspace.project().read(cx).capability();
14684
14685 let excerpt_buffer = cx.new(|cx| {
14686 let mut multibuffer = MultiBuffer::new(capability);
14687 while let Some(location) = locations.next() {
14688 let buffer = location.buffer.read(cx);
14689 let mut ranges_for_buffer = Vec::new();
14690 let range = location.range.to_point(buffer);
14691 ranges_for_buffer.push(range.clone());
14692
14693 while let Some(next_location) = locations.peek() {
14694 if next_location.buffer == location.buffer {
14695 ranges_for_buffer.push(next_location.range.to_point(buffer));
14696 locations.next();
14697 } else {
14698 break;
14699 }
14700 }
14701
14702 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14703 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14704 PathKey::for_buffer(&location.buffer, cx),
14705 location.buffer.clone(),
14706 ranges_for_buffer,
14707 DEFAULT_MULTIBUFFER_CONTEXT,
14708 cx,
14709 );
14710 ranges.extend(new_ranges)
14711 }
14712
14713 multibuffer.with_title(title)
14714 });
14715
14716 let editor = cx.new(|cx| {
14717 Editor::for_multibuffer(
14718 excerpt_buffer,
14719 Some(workspace.project().clone()),
14720 window,
14721 cx,
14722 )
14723 });
14724 editor.update(cx, |editor, cx| {
14725 match multibuffer_selection_mode {
14726 MultibufferSelectionMode::First => {
14727 if let Some(first_range) = ranges.first() {
14728 editor.change_selections(None, window, cx, |selections| {
14729 selections.clear_disjoint();
14730 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14731 });
14732 }
14733 editor.highlight_background::<Self>(
14734 &ranges,
14735 |theme| theme.editor_highlighted_line_background,
14736 cx,
14737 );
14738 }
14739 MultibufferSelectionMode::All => {
14740 editor.change_selections(None, window, cx, |selections| {
14741 selections.clear_disjoint();
14742 selections.select_anchor_ranges(ranges);
14743 });
14744 }
14745 }
14746 editor.register_buffers_with_language_servers(cx);
14747 });
14748
14749 let item = Box::new(editor);
14750 let item_id = item.item_id();
14751
14752 if split {
14753 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14754 } else {
14755 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14756 let (preview_item_id, preview_item_idx) =
14757 workspace.active_pane().update(cx, |pane, _| {
14758 (pane.preview_item_id(), pane.preview_item_idx())
14759 });
14760
14761 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14762
14763 if let Some(preview_item_id) = preview_item_id {
14764 workspace.active_pane().update(cx, |pane, cx| {
14765 pane.remove_item(preview_item_id, false, false, window, cx);
14766 });
14767 }
14768 } else {
14769 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14770 }
14771 }
14772 workspace.active_pane().update(cx, |pane, cx| {
14773 pane.set_preview_item_id(Some(item_id), cx);
14774 });
14775 }
14776
14777 pub fn rename(
14778 &mut self,
14779 _: &Rename,
14780 window: &mut Window,
14781 cx: &mut Context<Self>,
14782 ) -> Option<Task<Result<()>>> {
14783 use language::ToOffset as _;
14784
14785 let provider = self.semantics_provider.clone()?;
14786 let selection = self.selections.newest_anchor().clone();
14787 let (cursor_buffer, cursor_buffer_position) = self
14788 .buffer
14789 .read(cx)
14790 .text_anchor_for_position(selection.head(), cx)?;
14791 let (tail_buffer, cursor_buffer_position_end) = self
14792 .buffer
14793 .read(cx)
14794 .text_anchor_for_position(selection.tail(), cx)?;
14795 if tail_buffer != cursor_buffer {
14796 return None;
14797 }
14798
14799 let snapshot = cursor_buffer.read(cx).snapshot();
14800 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14801 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14802 let prepare_rename = provider
14803 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14804 .unwrap_or_else(|| Task::ready(Ok(None)));
14805 drop(snapshot);
14806
14807 Some(cx.spawn_in(window, async move |this, cx| {
14808 let rename_range = if let Some(range) = prepare_rename.await? {
14809 Some(range)
14810 } else {
14811 this.update(cx, |this, cx| {
14812 let buffer = this.buffer.read(cx).snapshot(cx);
14813 let mut buffer_highlights = this
14814 .document_highlights_for_position(selection.head(), &buffer)
14815 .filter(|highlight| {
14816 highlight.start.excerpt_id == selection.head().excerpt_id
14817 && highlight.end.excerpt_id == selection.head().excerpt_id
14818 });
14819 buffer_highlights
14820 .next()
14821 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14822 })?
14823 };
14824 if let Some(rename_range) = rename_range {
14825 this.update_in(cx, |this, window, cx| {
14826 let snapshot = cursor_buffer.read(cx).snapshot();
14827 let rename_buffer_range = rename_range.to_offset(&snapshot);
14828 let cursor_offset_in_rename_range =
14829 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14830 let cursor_offset_in_rename_range_end =
14831 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14832
14833 this.take_rename(false, window, cx);
14834 let buffer = this.buffer.read(cx).read(cx);
14835 let cursor_offset = selection.head().to_offset(&buffer);
14836 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14837 let rename_end = rename_start + rename_buffer_range.len();
14838 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14839 let mut old_highlight_id = None;
14840 let old_name: Arc<str> = buffer
14841 .chunks(rename_start..rename_end, true)
14842 .map(|chunk| {
14843 if old_highlight_id.is_none() {
14844 old_highlight_id = chunk.syntax_highlight_id;
14845 }
14846 chunk.text
14847 })
14848 .collect::<String>()
14849 .into();
14850
14851 drop(buffer);
14852
14853 // Position the selection in the rename editor so that it matches the current selection.
14854 this.show_local_selections = false;
14855 let rename_editor = cx.new(|cx| {
14856 let mut editor = Editor::single_line(window, cx);
14857 editor.buffer.update(cx, |buffer, cx| {
14858 buffer.edit([(0..0, old_name.clone())], None, cx)
14859 });
14860 let rename_selection_range = match cursor_offset_in_rename_range
14861 .cmp(&cursor_offset_in_rename_range_end)
14862 {
14863 Ordering::Equal => {
14864 editor.select_all(&SelectAll, window, cx);
14865 return editor;
14866 }
14867 Ordering::Less => {
14868 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14869 }
14870 Ordering::Greater => {
14871 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14872 }
14873 };
14874 if rename_selection_range.end > old_name.len() {
14875 editor.select_all(&SelectAll, window, cx);
14876 } else {
14877 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14878 s.select_ranges([rename_selection_range]);
14879 });
14880 }
14881 editor
14882 });
14883 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14884 if e == &EditorEvent::Focused {
14885 cx.emit(EditorEvent::FocusedIn)
14886 }
14887 })
14888 .detach();
14889
14890 let write_highlights =
14891 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14892 let read_highlights =
14893 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14894 let ranges = write_highlights
14895 .iter()
14896 .flat_map(|(_, ranges)| ranges.iter())
14897 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14898 .cloned()
14899 .collect();
14900
14901 this.highlight_text::<Rename>(
14902 ranges,
14903 HighlightStyle {
14904 fade_out: Some(0.6),
14905 ..Default::default()
14906 },
14907 cx,
14908 );
14909 let rename_focus_handle = rename_editor.focus_handle(cx);
14910 window.focus(&rename_focus_handle);
14911 let block_id = this.insert_blocks(
14912 [BlockProperties {
14913 style: BlockStyle::Flex,
14914 placement: BlockPlacement::Below(range.start),
14915 height: Some(1),
14916 render: Arc::new({
14917 let rename_editor = rename_editor.clone();
14918 move |cx: &mut BlockContext| {
14919 let mut text_style = cx.editor_style.text.clone();
14920 if let Some(highlight_style) = old_highlight_id
14921 .and_then(|h| h.style(&cx.editor_style.syntax))
14922 {
14923 text_style = text_style.highlight(highlight_style);
14924 }
14925 div()
14926 .block_mouse_down()
14927 .pl(cx.anchor_x)
14928 .child(EditorElement::new(
14929 &rename_editor,
14930 EditorStyle {
14931 background: cx.theme().system().transparent,
14932 local_player: cx.editor_style.local_player,
14933 text: text_style,
14934 scrollbar_width: cx.editor_style.scrollbar_width,
14935 syntax: cx.editor_style.syntax.clone(),
14936 status: cx.editor_style.status.clone(),
14937 inlay_hints_style: HighlightStyle {
14938 font_weight: Some(FontWeight::BOLD),
14939 ..make_inlay_hints_style(cx.app)
14940 },
14941 inline_completion_styles: make_suggestion_styles(
14942 cx.app,
14943 ),
14944 ..EditorStyle::default()
14945 },
14946 ))
14947 .into_any_element()
14948 }
14949 }),
14950 priority: 0,
14951 render_in_minimap: true,
14952 }],
14953 Some(Autoscroll::fit()),
14954 cx,
14955 )[0];
14956 this.pending_rename = Some(RenameState {
14957 range,
14958 old_name,
14959 editor: rename_editor,
14960 block_id,
14961 });
14962 })?;
14963 }
14964
14965 Ok(())
14966 }))
14967 }
14968
14969 pub fn confirm_rename(
14970 &mut self,
14971 _: &ConfirmRename,
14972 window: &mut Window,
14973 cx: &mut Context<Self>,
14974 ) -> Option<Task<Result<()>>> {
14975 let rename = self.take_rename(false, window, cx)?;
14976 let workspace = self.workspace()?.downgrade();
14977 let (buffer, start) = self
14978 .buffer
14979 .read(cx)
14980 .text_anchor_for_position(rename.range.start, cx)?;
14981 let (end_buffer, _) = self
14982 .buffer
14983 .read(cx)
14984 .text_anchor_for_position(rename.range.end, cx)?;
14985 if buffer != end_buffer {
14986 return None;
14987 }
14988
14989 let old_name = rename.old_name;
14990 let new_name = rename.editor.read(cx).text(cx);
14991
14992 let rename = self.semantics_provider.as_ref()?.perform_rename(
14993 &buffer,
14994 start,
14995 new_name.clone(),
14996 cx,
14997 )?;
14998
14999 Some(cx.spawn_in(window, async move |editor, cx| {
15000 let project_transaction = rename.await?;
15001 Self::open_project_transaction(
15002 &editor,
15003 workspace,
15004 project_transaction,
15005 format!("Rename: {} → {}", old_name, new_name),
15006 cx,
15007 )
15008 .await?;
15009
15010 editor.update(cx, |editor, cx| {
15011 editor.refresh_document_highlights(cx);
15012 })?;
15013 Ok(())
15014 }))
15015 }
15016
15017 fn take_rename(
15018 &mut self,
15019 moving_cursor: bool,
15020 window: &mut Window,
15021 cx: &mut Context<Self>,
15022 ) -> Option<RenameState> {
15023 let rename = self.pending_rename.take()?;
15024 if rename.editor.focus_handle(cx).is_focused(window) {
15025 window.focus(&self.focus_handle);
15026 }
15027
15028 self.remove_blocks(
15029 [rename.block_id].into_iter().collect(),
15030 Some(Autoscroll::fit()),
15031 cx,
15032 );
15033 self.clear_highlights::<Rename>(cx);
15034 self.show_local_selections = true;
15035
15036 if moving_cursor {
15037 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15038 editor.selections.newest::<usize>(cx).head()
15039 });
15040
15041 // Update the selection to match the position of the selection inside
15042 // the rename editor.
15043 let snapshot = self.buffer.read(cx).read(cx);
15044 let rename_range = rename.range.to_offset(&snapshot);
15045 let cursor_in_editor = snapshot
15046 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15047 .min(rename_range.end);
15048 drop(snapshot);
15049
15050 self.change_selections(None, window, cx, |s| {
15051 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15052 });
15053 } else {
15054 self.refresh_document_highlights(cx);
15055 }
15056
15057 Some(rename)
15058 }
15059
15060 pub fn pending_rename(&self) -> Option<&RenameState> {
15061 self.pending_rename.as_ref()
15062 }
15063
15064 fn format(
15065 &mut self,
15066 _: &Format,
15067 window: &mut Window,
15068 cx: &mut Context<Self>,
15069 ) -> Option<Task<Result<()>>> {
15070 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15071
15072 let project = match &self.project {
15073 Some(project) => project.clone(),
15074 None => return None,
15075 };
15076
15077 Some(self.perform_format(
15078 project,
15079 FormatTrigger::Manual,
15080 FormatTarget::Buffers,
15081 window,
15082 cx,
15083 ))
15084 }
15085
15086 fn format_selections(
15087 &mut self,
15088 _: &FormatSelections,
15089 window: &mut Window,
15090 cx: &mut Context<Self>,
15091 ) -> Option<Task<Result<()>>> {
15092 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15093
15094 let project = match &self.project {
15095 Some(project) => project.clone(),
15096 None => return None,
15097 };
15098
15099 let ranges = self
15100 .selections
15101 .all_adjusted(cx)
15102 .into_iter()
15103 .map(|selection| selection.range())
15104 .collect_vec();
15105
15106 Some(self.perform_format(
15107 project,
15108 FormatTrigger::Manual,
15109 FormatTarget::Ranges(ranges),
15110 window,
15111 cx,
15112 ))
15113 }
15114
15115 fn perform_format(
15116 &mut self,
15117 project: Entity<Project>,
15118 trigger: FormatTrigger,
15119 target: FormatTarget,
15120 window: &mut Window,
15121 cx: &mut Context<Self>,
15122 ) -> Task<Result<()>> {
15123 let buffer = self.buffer.clone();
15124 let (buffers, target) = match target {
15125 FormatTarget::Buffers => {
15126 let mut buffers = buffer.read(cx).all_buffers();
15127 if trigger == FormatTrigger::Save {
15128 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15129 }
15130 (buffers, LspFormatTarget::Buffers)
15131 }
15132 FormatTarget::Ranges(selection_ranges) => {
15133 let multi_buffer = buffer.read(cx);
15134 let snapshot = multi_buffer.read(cx);
15135 let mut buffers = HashSet::default();
15136 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15137 BTreeMap::new();
15138 for selection_range in selection_ranges {
15139 for (buffer, buffer_range, _) in
15140 snapshot.range_to_buffer_ranges(selection_range)
15141 {
15142 let buffer_id = buffer.remote_id();
15143 let start = buffer.anchor_before(buffer_range.start);
15144 let end = buffer.anchor_after(buffer_range.end);
15145 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15146 buffer_id_to_ranges
15147 .entry(buffer_id)
15148 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15149 .or_insert_with(|| vec![start..end]);
15150 }
15151 }
15152 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15153 }
15154 };
15155
15156 let transaction_id_prev = buffer.read_with(cx, |b, cx| b.last_transaction_id(cx));
15157 let selections_prev = transaction_id_prev
15158 .and_then(|transaction_id_prev| {
15159 // default to selections as they were after the last edit, if we have them,
15160 // instead of how they are now.
15161 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15162 // will take you back to where you made the last edit, instead of staying where you scrolled
15163 self.selection_history
15164 .transaction(transaction_id_prev)
15165 .map(|t| t.0.clone())
15166 })
15167 .unwrap_or_else(|| {
15168 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15169 self.selections.disjoint_anchors()
15170 });
15171
15172 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15173 let format = project.update(cx, |project, cx| {
15174 project.format(buffers, target, true, trigger, cx)
15175 });
15176
15177 cx.spawn_in(window, async move |editor, cx| {
15178 let transaction = futures::select_biased! {
15179 transaction = format.log_err().fuse() => transaction,
15180 () = timeout => {
15181 log::warn!("timed out waiting for formatting");
15182 None
15183 }
15184 };
15185
15186 buffer
15187 .update(cx, |buffer, cx| {
15188 if let Some(transaction) = transaction {
15189 if !buffer.is_singleton() {
15190 buffer.push_transaction(&transaction.0, cx);
15191 }
15192 }
15193 cx.notify();
15194 })
15195 .ok();
15196
15197 if let Some(transaction_id_now) =
15198 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15199 {
15200 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15201 if has_new_transaction {
15202 _ = editor.update(cx, |editor, _| {
15203 editor
15204 .selection_history
15205 .insert_transaction(transaction_id_now, selections_prev);
15206 });
15207 }
15208 }
15209
15210 Ok(())
15211 })
15212 }
15213
15214 fn organize_imports(
15215 &mut self,
15216 _: &OrganizeImports,
15217 window: &mut Window,
15218 cx: &mut Context<Self>,
15219 ) -> Option<Task<Result<()>>> {
15220 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15221 let project = match &self.project {
15222 Some(project) => project.clone(),
15223 None => return None,
15224 };
15225 Some(self.perform_code_action_kind(
15226 project,
15227 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15228 window,
15229 cx,
15230 ))
15231 }
15232
15233 fn perform_code_action_kind(
15234 &mut self,
15235 project: Entity<Project>,
15236 kind: CodeActionKind,
15237 window: &mut Window,
15238 cx: &mut Context<Self>,
15239 ) -> Task<Result<()>> {
15240 let buffer = self.buffer.clone();
15241 let buffers = buffer.read(cx).all_buffers();
15242 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15243 let apply_action = project.update(cx, |project, cx| {
15244 project.apply_code_action_kind(buffers, kind, true, cx)
15245 });
15246 cx.spawn_in(window, async move |_, cx| {
15247 let transaction = futures::select_biased! {
15248 () = timeout => {
15249 log::warn!("timed out waiting for executing code action");
15250 None
15251 }
15252 transaction = apply_action.log_err().fuse() => transaction,
15253 };
15254 buffer
15255 .update(cx, |buffer, cx| {
15256 // check if we need this
15257 if let Some(transaction) = transaction {
15258 if !buffer.is_singleton() {
15259 buffer.push_transaction(&transaction.0, cx);
15260 }
15261 }
15262 cx.notify();
15263 })
15264 .ok();
15265 Ok(())
15266 })
15267 }
15268
15269 fn restart_language_server(
15270 &mut self,
15271 _: &RestartLanguageServer,
15272 _: &mut Window,
15273 cx: &mut Context<Self>,
15274 ) {
15275 if let Some(project) = self.project.clone() {
15276 self.buffer.update(cx, |multi_buffer, cx| {
15277 project.update(cx, |project, cx| {
15278 project.restart_language_servers_for_buffers(
15279 multi_buffer.all_buffers().into_iter().collect(),
15280 cx,
15281 );
15282 });
15283 })
15284 }
15285 }
15286
15287 fn stop_language_server(
15288 &mut self,
15289 _: &StopLanguageServer,
15290 _: &mut Window,
15291 cx: &mut Context<Self>,
15292 ) {
15293 if let Some(project) = self.project.clone() {
15294 self.buffer.update(cx, |multi_buffer, cx| {
15295 project.update(cx, |project, cx| {
15296 project.stop_language_servers_for_buffers(
15297 multi_buffer.all_buffers().into_iter().collect(),
15298 cx,
15299 );
15300 cx.emit(project::Event::RefreshInlayHints);
15301 });
15302 });
15303 }
15304 }
15305
15306 fn cancel_language_server_work(
15307 workspace: &mut Workspace,
15308 _: &actions::CancelLanguageServerWork,
15309 _: &mut Window,
15310 cx: &mut Context<Workspace>,
15311 ) {
15312 let project = workspace.project();
15313 let buffers = workspace
15314 .active_item(cx)
15315 .and_then(|item| item.act_as::<Editor>(cx))
15316 .map_or(HashSet::default(), |editor| {
15317 editor.read(cx).buffer.read(cx).all_buffers()
15318 });
15319 project.update(cx, |project, cx| {
15320 project.cancel_language_server_work_for_buffers(buffers, cx);
15321 });
15322 }
15323
15324 fn show_character_palette(
15325 &mut self,
15326 _: &ShowCharacterPalette,
15327 window: &mut Window,
15328 _: &mut Context<Self>,
15329 ) {
15330 window.show_character_palette();
15331 }
15332
15333 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15334 if self.mode.is_minimap() {
15335 return;
15336 }
15337
15338 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15339 let buffer = self.buffer.read(cx).snapshot(cx);
15340 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15341 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15342 let is_valid = buffer
15343 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15344 .any(|entry| {
15345 entry.diagnostic.is_primary
15346 && !entry.range.is_empty()
15347 && entry.range.start == primary_range_start
15348 && entry.diagnostic.message == active_diagnostics.active_message
15349 });
15350
15351 if !is_valid {
15352 self.dismiss_diagnostics(cx);
15353 }
15354 }
15355 }
15356
15357 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15358 match &self.active_diagnostics {
15359 ActiveDiagnostic::Group(group) => Some(group),
15360 _ => None,
15361 }
15362 }
15363
15364 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15365 self.dismiss_diagnostics(cx);
15366 self.active_diagnostics = ActiveDiagnostic::All;
15367 }
15368
15369 fn activate_diagnostics(
15370 &mut self,
15371 buffer_id: BufferId,
15372 diagnostic: DiagnosticEntry<usize>,
15373 window: &mut Window,
15374 cx: &mut Context<Self>,
15375 ) {
15376 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15377 return;
15378 }
15379 self.dismiss_diagnostics(cx);
15380 let snapshot = self.snapshot(window, cx);
15381 let buffer = self.buffer.read(cx).snapshot(cx);
15382 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15383 return;
15384 };
15385
15386 let diagnostic_group = buffer
15387 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15388 .collect::<Vec<_>>();
15389
15390 let blocks =
15391 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15392
15393 let blocks = self.display_map.update(cx, |display_map, cx| {
15394 display_map.insert_blocks(blocks, cx).into_iter().collect()
15395 });
15396 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15397 active_range: buffer.anchor_before(diagnostic.range.start)
15398 ..buffer.anchor_after(diagnostic.range.end),
15399 active_message: diagnostic.diagnostic.message.clone(),
15400 group_id: diagnostic.diagnostic.group_id,
15401 blocks,
15402 });
15403 cx.notify();
15404 }
15405
15406 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15407 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15408 return;
15409 };
15410
15411 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15412 if let ActiveDiagnostic::Group(group) = prev {
15413 self.display_map.update(cx, |display_map, cx| {
15414 display_map.remove_blocks(group.blocks, cx);
15415 });
15416 cx.notify();
15417 }
15418 }
15419
15420 /// Disable inline diagnostics rendering for this editor.
15421 pub fn disable_inline_diagnostics(&mut self) {
15422 self.inline_diagnostics_enabled = false;
15423 self.inline_diagnostics_update = Task::ready(());
15424 self.inline_diagnostics.clear();
15425 }
15426
15427 pub fn diagnostics_enabled(&self) -> bool {
15428 self.mode.is_full()
15429 }
15430
15431 pub fn inline_diagnostics_enabled(&self) -> bool {
15432 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15433 }
15434
15435 pub fn show_inline_diagnostics(&self) -> bool {
15436 self.show_inline_diagnostics
15437 }
15438
15439 pub fn toggle_inline_diagnostics(
15440 &mut self,
15441 _: &ToggleInlineDiagnostics,
15442 window: &mut Window,
15443 cx: &mut Context<Editor>,
15444 ) {
15445 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15446 self.refresh_inline_diagnostics(false, window, cx);
15447 }
15448
15449 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15450 self.diagnostics_max_severity = severity;
15451 self.display_map.update(cx, |display_map, _| {
15452 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15453 });
15454 }
15455
15456 pub fn toggle_diagnostics(
15457 &mut self,
15458 _: &ToggleDiagnostics,
15459 window: &mut Window,
15460 cx: &mut Context<Editor>,
15461 ) {
15462 if !self.diagnostics_enabled() {
15463 return;
15464 }
15465
15466 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15467 EditorSettings::get_global(cx)
15468 .diagnostics_max_severity
15469 .filter(|severity| severity != &DiagnosticSeverity::Off)
15470 .unwrap_or(DiagnosticSeverity::Hint)
15471 } else {
15472 DiagnosticSeverity::Off
15473 };
15474 self.set_max_diagnostics_severity(new_severity, cx);
15475 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15476 self.active_diagnostics = ActiveDiagnostic::None;
15477 self.inline_diagnostics_update = Task::ready(());
15478 self.inline_diagnostics.clear();
15479 } else {
15480 self.refresh_inline_diagnostics(false, window, cx);
15481 }
15482
15483 cx.notify();
15484 }
15485
15486 pub fn toggle_minimap(
15487 &mut self,
15488 _: &ToggleMinimap,
15489 window: &mut Window,
15490 cx: &mut Context<Editor>,
15491 ) {
15492 if self.supports_minimap(cx) {
15493 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15494 }
15495 }
15496
15497 fn refresh_inline_diagnostics(
15498 &mut self,
15499 debounce: bool,
15500 window: &mut Window,
15501 cx: &mut Context<Self>,
15502 ) {
15503 let max_severity = ProjectSettings::get_global(cx)
15504 .diagnostics
15505 .inline
15506 .max_severity
15507 .unwrap_or(self.diagnostics_max_severity);
15508
15509 if !self.inline_diagnostics_enabled()
15510 || !self.show_inline_diagnostics
15511 || max_severity == DiagnosticSeverity::Off
15512 {
15513 self.inline_diagnostics_update = Task::ready(());
15514 self.inline_diagnostics.clear();
15515 return;
15516 }
15517
15518 let debounce_ms = ProjectSettings::get_global(cx)
15519 .diagnostics
15520 .inline
15521 .update_debounce_ms;
15522 let debounce = if debounce && debounce_ms > 0 {
15523 Some(Duration::from_millis(debounce_ms))
15524 } else {
15525 None
15526 };
15527 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15528 let editor = editor.upgrade().unwrap();
15529
15530 if let Some(debounce) = debounce {
15531 cx.background_executor().timer(debounce).await;
15532 }
15533 let Some(snapshot) = editor
15534 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15535 .ok()
15536 else {
15537 return;
15538 };
15539
15540 let new_inline_diagnostics = cx
15541 .background_spawn(async move {
15542 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15543 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15544 let message = diagnostic_entry
15545 .diagnostic
15546 .message
15547 .split_once('\n')
15548 .map(|(line, _)| line)
15549 .map(SharedString::new)
15550 .unwrap_or_else(|| {
15551 SharedString::from(diagnostic_entry.diagnostic.message)
15552 });
15553 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15554 let (Ok(i) | Err(i)) = inline_diagnostics
15555 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15556 inline_diagnostics.insert(
15557 i,
15558 (
15559 start_anchor,
15560 InlineDiagnostic {
15561 message,
15562 group_id: diagnostic_entry.diagnostic.group_id,
15563 start: diagnostic_entry.range.start.to_point(&snapshot),
15564 is_primary: diagnostic_entry.diagnostic.is_primary,
15565 severity: diagnostic_entry.diagnostic.severity,
15566 },
15567 ),
15568 );
15569 }
15570 inline_diagnostics
15571 })
15572 .await;
15573
15574 editor
15575 .update(cx, |editor, cx| {
15576 editor.inline_diagnostics = new_inline_diagnostics;
15577 cx.notify();
15578 })
15579 .ok();
15580 });
15581 }
15582
15583 pub fn set_selections_from_remote(
15584 &mut self,
15585 selections: Vec<Selection<Anchor>>,
15586 pending_selection: Option<Selection<Anchor>>,
15587 window: &mut Window,
15588 cx: &mut Context<Self>,
15589 ) {
15590 let old_cursor_position = self.selections.newest_anchor().head();
15591 self.selections.change_with(cx, |s| {
15592 s.select_anchors(selections);
15593 if let Some(pending_selection) = pending_selection {
15594 s.set_pending(pending_selection, SelectMode::Character);
15595 } else {
15596 s.clear_pending();
15597 }
15598 });
15599 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15600 }
15601
15602 fn push_to_selection_history(&mut self) {
15603 self.selection_history.push(SelectionHistoryEntry {
15604 selections: self.selections.disjoint_anchors(),
15605 select_next_state: self.select_next_state.clone(),
15606 select_prev_state: self.select_prev_state.clone(),
15607 add_selections_state: self.add_selections_state.clone(),
15608 });
15609 }
15610
15611 pub fn transact(
15612 &mut self,
15613 window: &mut Window,
15614 cx: &mut Context<Self>,
15615 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15616 ) -> Option<TransactionId> {
15617 self.start_transaction_at(Instant::now(), window, cx);
15618 update(self, window, cx);
15619 self.end_transaction_at(Instant::now(), cx)
15620 }
15621
15622 pub fn start_transaction_at(
15623 &mut self,
15624 now: Instant,
15625 window: &mut Window,
15626 cx: &mut Context<Self>,
15627 ) {
15628 self.end_selection(window, cx);
15629 if let Some(tx_id) = self
15630 .buffer
15631 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15632 {
15633 self.selection_history
15634 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15635 cx.emit(EditorEvent::TransactionBegun {
15636 transaction_id: tx_id,
15637 })
15638 }
15639 }
15640
15641 pub fn end_transaction_at(
15642 &mut self,
15643 now: Instant,
15644 cx: &mut Context<Self>,
15645 ) -> Option<TransactionId> {
15646 if let Some(transaction_id) = self
15647 .buffer
15648 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15649 {
15650 if let Some((_, end_selections)) =
15651 self.selection_history.transaction_mut(transaction_id)
15652 {
15653 *end_selections = Some(self.selections.disjoint_anchors());
15654 } else {
15655 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15656 }
15657
15658 cx.emit(EditorEvent::Edited { transaction_id });
15659 Some(transaction_id)
15660 } else {
15661 None
15662 }
15663 }
15664
15665 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15666 if self.selection_mark_mode {
15667 self.change_selections(None, window, cx, |s| {
15668 s.move_with(|_, sel| {
15669 sel.collapse_to(sel.head(), SelectionGoal::None);
15670 });
15671 })
15672 }
15673 self.selection_mark_mode = true;
15674 cx.notify();
15675 }
15676
15677 pub fn swap_selection_ends(
15678 &mut self,
15679 _: &actions::SwapSelectionEnds,
15680 window: &mut Window,
15681 cx: &mut Context<Self>,
15682 ) {
15683 self.change_selections(None, window, cx, |s| {
15684 s.move_with(|_, sel| {
15685 if sel.start != sel.end {
15686 sel.reversed = !sel.reversed
15687 }
15688 });
15689 });
15690 self.request_autoscroll(Autoscroll::newest(), cx);
15691 cx.notify();
15692 }
15693
15694 pub fn toggle_fold(
15695 &mut self,
15696 _: &actions::ToggleFold,
15697 window: &mut Window,
15698 cx: &mut Context<Self>,
15699 ) {
15700 if self.is_singleton(cx) {
15701 let selection = self.selections.newest::<Point>(cx);
15702
15703 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15704 let range = if selection.is_empty() {
15705 let point = selection.head().to_display_point(&display_map);
15706 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15707 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15708 .to_point(&display_map);
15709 start..end
15710 } else {
15711 selection.range()
15712 };
15713 if display_map.folds_in_range(range).next().is_some() {
15714 self.unfold_lines(&Default::default(), window, cx)
15715 } else {
15716 self.fold(&Default::default(), window, cx)
15717 }
15718 } else {
15719 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15720 let buffer_ids: HashSet<_> = self
15721 .selections
15722 .disjoint_anchor_ranges()
15723 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15724 .collect();
15725
15726 let should_unfold = buffer_ids
15727 .iter()
15728 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15729
15730 for buffer_id in buffer_ids {
15731 if should_unfold {
15732 self.unfold_buffer(buffer_id, cx);
15733 } else {
15734 self.fold_buffer(buffer_id, cx);
15735 }
15736 }
15737 }
15738 }
15739
15740 pub fn toggle_fold_recursive(
15741 &mut self,
15742 _: &actions::ToggleFoldRecursive,
15743 window: &mut Window,
15744 cx: &mut Context<Self>,
15745 ) {
15746 let selection = self.selections.newest::<Point>(cx);
15747
15748 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15749 let range = if selection.is_empty() {
15750 let point = selection.head().to_display_point(&display_map);
15751 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15752 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15753 .to_point(&display_map);
15754 start..end
15755 } else {
15756 selection.range()
15757 };
15758 if display_map.folds_in_range(range).next().is_some() {
15759 self.unfold_recursive(&Default::default(), window, cx)
15760 } else {
15761 self.fold_recursive(&Default::default(), window, cx)
15762 }
15763 }
15764
15765 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15766 if self.is_singleton(cx) {
15767 let mut to_fold = Vec::new();
15768 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15769 let selections = self.selections.all_adjusted(cx);
15770
15771 for selection in selections {
15772 let range = selection.range().sorted();
15773 let buffer_start_row = range.start.row;
15774
15775 if range.start.row != range.end.row {
15776 let mut found = false;
15777 let mut row = range.start.row;
15778 while row <= range.end.row {
15779 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15780 {
15781 found = true;
15782 row = crease.range().end.row + 1;
15783 to_fold.push(crease);
15784 } else {
15785 row += 1
15786 }
15787 }
15788 if found {
15789 continue;
15790 }
15791 }
15792
15793 for row in (0..=range.start.row).rev() {
15794 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15795 if crease.range().end.row >= buffer_start_row {
15796 to_fold.push(crease);
15797 if row <= range.start.row {
15798 break;
15799 }
15800 }
15801 }
15802 }
15803 }
15804
15805 self.fold_creases(to_fold, true, window, cx);
15806 } else {
15807 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15808 let buffer_ids = self
15809 .selections
15810 .disjoint_anchor_ranges()
15811 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15812 .collect::<HashSet<_>>();
15813 for buffer_id in buffer_ids {
15814 self.fold_buffer(buffer_id, cx);
15815 }
15816 }
15817 }
15818
15819 fn fold_at_level(
15820 &mut self,
15821 fold_at: &FoldAtLevel,
15822 window: &mut Window,
15823 cx: &mut Context<Self>,
15824 ) {
15825 if !self.buffer.read(cx).is_singleton() {
15826 return;
15827 }
15828
15829 let fold_at_level = fold_at.0;
15830 let snapshot = self.buffer.read(cx).snapshot(cx);
15831 let mut to_fold = Vec::new();
15832 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15833
15834 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15835 while start_row < end_row {
15836 match self
15837 .snapshot(window, cx)
15838 .crease_for_buffer_row(MultiBufferRow(start_row))
15839 {
15840 Some(crease) => {
15841 let nested_start_row = crease.range().start.row + 1;
15842 let nested_end_row = crease.range().end.row;
15843
15844 if current_level < fold_at_level {
15845 stack.push((nested_start_row, nested_end_row, current_level + 1));
15846 } else if current_level == fold_at_level {
15847 to_fold.push(crease);
15848 }
15849
15850 start_row = nested_end_row + 1;
15851 }
15852 None => start_row += 1,
15853 }
15854 }
15855 }
15856
15857 self.fold_creases(to_fold, true, window, cx);
15858 }
15859
15860 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15861 if self.buffer.read(cx).is_singleton() {
15862 let mut fold_ranges = Vec::new();
15863 let snapshot = self.buffer.read(cx).snapshot(cx);
15864
15865 for row in 0..snapshot.max_row().0 {
15866 if let Some(foldable_range) = self
15867 .snapshot(window, cx)
15868 .crease_for_buffer_row(MultiBufferRow(row))
15869 {
15870 fold_ranges.push(foldable_range);
15871 }
15872 }
15873
15874 self.fold_creases(fold_ranges, true, window, cx);
15875 } else {
15876 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15877 editor
15878 .update_in(cx, |editor, _, cx| {
15879 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15880 editor.fold_buffer(buffer_id, cx);
15881 }
15882 })
15883 .ok();
15884 });
15885 }
15886 }
15887
15888 pub fn fold_function_bodies(
15889 &mut self,
15890 _: &actions::FoldFunctionBodies,
15891 window: &mut Window,
15892 cx: &mut Context<Self>,
15893 ) {
15894 let snapshot = self.buffer.read(cx).snapshot(cx);
15895
15896 let ranges = snapshot
15897 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15898 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15899 .collect::<Vec<_>>();
15900
15901 let creases = ranges
15902 .into_iter()
15903 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15904 .collect();
15905
15906 self.fold_creases(creases, true, window, cx);
15907 }
15908
15909 pub fn fold_recursive(
15910 &mut self,
15911 _: &actions::FoldRecursive,
15912 window: &mut Window,
15913 cx: &mut Context<Self>,
15914 ) {
15915 let mut to_fold = Vec::new();
15916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15917 let selections = self.selections.all_adjusted(cx);
15918
15919 for selection in selections {
15920 let range = selection.range().sorted();
15921 let buffer_start_row = range.start.row;
15922
15923 if range.start.row != range.end.row {
15924 let mut found = false;
15925 for row in range.start.row..=range.end.row {
15926 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15927 found = true;
15928 to_fold.push(crease);
15929 }
15930 }
15931 if found {
15932 continue;
15933 }
15934 }
15935
15936 for row in (0..=range.start.row).rev() {
15937 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15938 if crease.range().end.row >= buffer_start_row {
15939 to_fold.push(crease);
15940 } else {
15941 break;
15942 }
15943 }
15944 }
15945 }
15946
15947 self.fold_creases(to_fold, true, window, cx);
15948 }
15949
15950 pub fn fold_at(
15951 &mut self,
15952 buffer_row: MultiBufferRow,
15953 window: &mut Window,
15954 cx: &mut Context<Self>,
15955 ) {
15956 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15957
15958 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
15959 let autoscroll = self
15960 .selections
15961 .all::<Point>(cx)
15962 .iter()
15963 .any(|selection| crease.range().overlaps(&selection.range()));
15964
15965 self.fold_creases(vec![crease], autoscroll, window, cx);
15966 }
15967 }
15968
15969 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
15970 if self.is_singleton(cx) {
15971 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15972 let buffer = &display_map.buffer_snapshot;
15973 let selections = self.selections.all::<Point>(cx);
15974 let ranges = selections
15975 .iter()
15976 .map(|s| {
15977 let range = s.display_range(&display_map).sorted();
15978 let mut start = range.start.to_point(&display_map);
15979 let mut end = range.end.to_point(&display_map);
15980 start.column = 0;
15981 end.column = buffer.line_len(MultiBufferRow(end.row));
15982 start..end
15983 })
15984 .collect::<Vec<_>>();
15985
15986 self.unfold_ranges(&ranges, true, true, cx);
15987 } else {
15988 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15989 let buffer_ids = self
15990 .selections
15991 .disjoint_anchor_ranges()
15992 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15993 .collect::<HashSet<_>>();
15994 for buffer_id in buffer_ids {
15995 self.unfold_buffer(buffer_id, cx);
15996 }
15997 }
15998 }
15999
16000 pub fn unfold_recursive(
16001 &mut self,
16002 _: &UnfoldRecursive,
16003 _window: &mut Window,
16004 cx: &mut Context<Self>,
16005 ) {
16006 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16007 let selections = self.selections.all::<Point>(cx);
16008 let ranges = selections
16009 .iter()
16010 .map(|s| {
16011 let mut range = s.display_range(&display_map).sorted();
16012 *range.start.column_mut() = 0;
16013 *range.end.column_mut() = display_map.line_len(range.end.row());
16014 let start = range.start.to_point(&display_map);
16015 let end = range.end.to_point(&display_map);
16016 start..end
16017 })
16018 .collect::<Vec<_>>();
16019
16020 self.unfold_ranges(&ranges, true, true, cx);
16021 }
16022
16023 pub fn unfold_at(
16024 &mut self,
16025 buffer_row: MultiBufferRow,
16026 _window: &mut Window,
16027 cx: &mut Context<Self>,
16028 ) {
16029 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16030
16031 let intersection_range = Point::new(buffer_row.0, 0)
16032 ..Point::new(
16033 buffer_row.0,
16034 display_map.buffer_snapshot.line_len(buffer_row),
16035 );
16036
16037 let autoscroll = self
16038 .selections
16039 .all::<Point>(cx)
16040 .iter()
16041 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16042
16043 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16044 }
16045
16046 pub fn unfold_all(
16047 &mut self,
16048 _: &actions::UnfoldAll,
16049 _window: &mut Window,
16050 cx: &mut Context<Self>,
16051 ) {
16052 if self.buffer.read(cx).is_singleton() {
16053 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16054 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16055 } else {
16056 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16057 editor
16058 .update(cx, |editor, cx| {
16059 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16060 editor.unfold_buffer(buffer_id, cx);
16061 }
16062 })
16063 .ok();
16064 });
16065 }
16066 }
16067
16068 pub fn fold_selected_ranges(
16069 &mut self,
16070 _: &FoldSelectedRanges,
16071 window: &mut Window,
16072 cx: &mut Context<Self>,
16073 ) {
16074 let selections = self.selections.all_adjusted(cx);
16075 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16076 let ranges = selections
16077 .into_iter()
16078 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16079 .collect::<Vec<_>>();
16080 self.fold_creases(ranges, true, window, cx);
16081 }
16082
16083 pub fn fold_ranges<T: ToOffset + Clone>(
16084 &mut self,
16085 ranges: Vec<Range<T>>,
16086 auto_scroll: bool,
16087 window: &mut Window,
16088 cx: &mut Context<Self>,
16089 ) {
16090 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16091 let ranges = ranges
16092 .into_iter()
16093 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16094 .collect::<Vec<_>>();
16095 self.fold_creases(ranges, auto_scroll, window, cx);
16096 }
16097
16098 pub fn fold_creases<T: ToOffset + Clone>(
16099 &mut self,
16100 creases: Vec<Crease<T>>,
16101 auto_scroll: bool,
16102 _window: &mut Window,
16103 cx: &mut Context<Self>,
16104 ) {
16105 if creases.is_empty() {
16106 return;
16107 }
16108
16109 let mut buffers_affected = HashSet::default();
16110 let multi_buffer = self.buffer().read(cx);
16111 for crease in &creases {
16112 if let Some((_, buffer, _)) =
16113 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16114 {
16115 buffers_affected.insert(buffer.read(cx).remote_id());
16116 };
16117 }
16118
16119 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16120
16121 if auto_scroll {
16122 self.request_autoscroll(Autoscroll::fit(), cx);
16123 }
16124
16125 cx.notify();
16126
16127 self.scrollbar_marker_state.dirty = true;
16128 self.folds_did_change(cx);
16129 }
16130
16131 /// Removes any folds whose ranges intersect any of the given ranges.
16132 pub fn unfold_ranges<T: ToOffset + Clone>(
16133 &mut self,
16134 ranges: &[Range<T>],
16135 inclusive: bool,
16136 auto_scroll: bool,
16137 cx: &mut Context<Self>,
16138 ) {
16139 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16140 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16141 });
16142 self.folds_did_change(cx);
16143 }
16144
16145 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16146 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16147 return;
16148 }
16149 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16150 self.display_map.update(cx, |display_map, cx| {
16151 display_map.fold_buffers([buffer_id], cx)
16152 });
16153 cx.emit(EditorEvent::BufferFoldToggled {
16154 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16155 folded: true,
16156 });
16157 cx.notify();
16158 }
16159
16160 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16161 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16162 return;
16163 }
16164 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16165 self.display_map.update(cx, |display_map, cx| {
16166 display_map.unfold_buffers([buffer_id], cx);
16167 });
16168 cx.emit(EditorEvent::BufferFoldToggled {
16169 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16170 folded: false,
16171 });
16172 cx.notify();
16173 }
16174
16175 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16176 self.display_map.read(cx).is_buffer_folded(buffer)
16177 }
16178
16179 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16180 self.display_map.read(cx).folded_buffers()
16181 }
16182
16183 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16184 self.display_map.update(cx, |display_map, cx| {
16185 display_map.disable_header_for_buffer(buffer_id, cx);
16186 });
16187 cx.notify();
16188 }
16189
16190 /// Removes any folds with the given ranges.
16191 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16192 &mut self,
16193 ranges: &[Range<T>],
16194 type_id: TypeId,
16195 auto_scroll: bool,
16196 cx: &mut Context<Self>,
16197 ) {
16198 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16199 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16200 });
16201 self.folds_did_change(cx);
16202 }
16203
16204 fn remove_folds_with<T: ToOffset + Clone>(
16205 &mut self,
16206 ranges: &[Range<T>],
16207 auto_scroll: bool,
16208 cx: &mut Context<Self>,
16209 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16210 ) {
16211 if ranges.is_empty() {
16212 return;
16213 }
16214
16215 let mut buffers_affected = HashSet::default();
16216 let multi_buffer = self.buffer().read(cx);
16217 for range in ranges {
16218 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16219 buffers_affected.insert(buffer.read(cx).remote_id());
16220 };
16221 }
16222
16223 self.display_map.update(cx, update);
16224
16225 if auto_scroll {
16226 self.request_autoscroll(Autoscroll::fit(), cx);
16227 }
16228
16229 cx.notify();
16230 self.scrollbar_marker_state.dirty = true;
16231 self.active_indent_guides_state.dirty = true;
16232 }
16233
16234 pub fn update_fold_widths(
16235 &mut self,
16236 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16237 cx: &mut Context<Self>,
16238 ) -> bool {
16239 self.display_map
16240 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16241 }
16242
16243 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16244 self.display_map.read(cx).fold_placeholder.clone()
16245 }
16246
16247 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16248 self.buffer.update(cx, |buffer, cx| {
16249 buffer.set_all_diff_hunks_expanded(cx);
16250 });
16251 }
16252
16253 pub fn expand_all_diff_hunks(
16254 &mut self,
16255 _: &ExpandAllDiffHunks,
16256 _window: &mut Window,
16257 cx: &mut Context<Self>,
16258 ) {
16259 self.buffer.update(cx, |buffer, cx| {
16260 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16261 });
16262 }
16263
16264 pub fn toggle_selected_diff_hunks(
16265 &mut self,
16266 _: &ToggleSelectedDiffHunks,
16267 _window: &mut Window,
16268 cx: &mut Context<Self>,
16269 ) {
16270 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16271 self.toggle_diff_hunks_in_ranges(ranges, cx);
16272 }
16273
16274 pub fn diff_hunks_in_ranges<'a>(
16275 &'a self,
16276 ranges: &'a [Range<Anchor>],
16277 buffer: &'a MultiBufferSnapshot,
16278 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16279 ranges.iter().flat_map(move |range| {
16280 let end_excerpt_id = range.end.excerpt_id;
16281 let range = range.to_point(buffer);
16282 let mut peek_end = range.end;
16283 if range.end.row < buffer.max_row().0 {
16284 peek_end = Point::new(range.end.row + 1, 0);
16285 }
16286 buffer
16287 .diff_hunks_in_range(range.start..peek_end)
16288 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16289 })
16290 }
16291
16292 pub fn has_stageable_diff_hunks_in_ranges(
16293 &self,
16294 ranges: &[Range<Anchor>],
16295 snapshot: &MultiBufferSnapshot,
16296 ) -> bool {
16297 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16298 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16299 }
16300
16301 pub fn toggle_staged_selected_diff_hunks(
16302 &mut self,
16303 _: &::git::ToggleStaged,
16304 _: &mut Window,
16305 cx: &mut Context<Self>,
16306 ) {
16307 let snapshot = self.buffer.read(cx).snapshot(cx);
16308 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16309 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16310 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16311 }
16312
16313 pub fn set_render_diff_hunk_controls(
16314 &mut self,
16315 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16316 cx: &mut Context<Self>,
16317 ) {
16318 self.render_diff_hunk_controls = render_diff_hunk_controls;
16319 cx.notify();
16320 }
16321
16322 pub fn stage_and_next(
16323 &mut self,
16324 _: &::git::StageAndNext,
16325 window: &mut Window,
16326 cx: &mut Context<Self>,
16327 ) {
16328 self.do_stage_or_unstage_and_next(true, window, cx);
16329 }
16330
16331 pub fn unstage_and_next(
16332 &mut self,
16333 _: &::git::UnstageAndNext,
16334 window: &mut Window,
16335 cx: &mut Context<Self>,
16336 ) {
16337 self.do_stage_or_unstage_and_next(false, window, cx);
16338 }
16339
16340 pub fn stage_or_unstage_diff_hunks(
16341 &mut self,
16342 stage: bool,
16343 ranges: Vec<Range<Anchor>>,
16344 cx: &mut Context<Self>,
16345 ) {
16346 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16347 cx.spawn(async move |this, cx| {
16348 task.await?;
16349 this.update(cx, |this, cx| {
16350 let snapshot = this.buffer.read(cx).snapshot(cx);
16351 let chunk_by = this
16352 .diff_hunks_in_ranges(&ranges, &snapshot)
16353 .chunk_by(|hunk| hunk.buffer_id);
16354 for (buffer_id, hunks) in &chunk_by {
16355 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16356 }
16357 })
16358 })
16359 .detach_and_log_err(cx);
16360 }
16361
16362 fn save_buffers_for_ranges_if_needed(
16363 &mut self,
16364 ranges: &[Range<Anchor>],
16365 cx: &mut Context<Editor>,
16366 ) -> Task<Result<()>> {
16367 let multibuffer = self.buffer.read(cx);
16368 let snapshot = multibuffer.read(cx);
16369 let buffer_ids: HashSet<_> = ranges
16370 .iter()
16371 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16372 .collect();
16373 drop(snapshot);
16374
16375 let mut buffers = HashSet::default();
16376 for buffer_id in buffer_ids {
16377 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16378 let buffer = buffer_entity.read(cx);
16379 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16380 {
16381 buffers.insert(buffer_entity);
16382 }
16383 }
16384 }
16385
16386 if let Some(project) = &self.project {
16387 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16388 } else {
16389 Task::ready(Ok(()))
16390 }
16391 }
16392
16393 fn do_stage_or_unstage_and_next(
16394 &mut self,
16395 stage: bool,
16396 window: &mut Window,
16397 cx: &mut Context<Self>,
16398 ) {
16399 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16400
16401 if ranges.iter().any(|range| range.start != range.end) {
16402 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16403 return;
16404 }
16405
16406 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16407 let snapshot = self.snapshot(window, cx);
16408 let position = self.selections.newest::<Point>(cx).head();
16409 let mut row = snapshot
16410 .buffer_snapshot
16411 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16412 .find(|hunk| hunk.row_range.start.0 > position.row)
16413 .map(|hunk| hunk.row_range.start);
16414
16415 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16416 // Outside of the project diff editor, wrap around to the beginning.
16417 if !all_diff_hunks_expanded {
16418 row = row.or_else(|| {
16419 snapshot
16420 .buffer_snapshot
16421 .diff_hunks_in_range(Point::zero()..position)
16422 .find(|hunk| hunk.row_range.end.0 < position.row)
16423 .map(|hunk| hunk.row_range.start)
16424 });
16425 }
16426
16427 if let Some(row) = row {
16428 let destination = Point::new(row.0, 0);
16429 let autoscroll = Autoscroll::center();
16430
16431 self.unfold_ranges(&[destination..destination], false, false, cx);
16432 self.change_selections(Some(autoscroll), window, cx, |s| {
16433 s.select_ranges([destination..destination]);
16434 });
16435 }
16436 }
16437
16438 fn do_stage_or_unstage(
16439 &self,
16440 stage: bool,
16441 buffer_id: BufferId,
16442 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16443 cx: &mut App,
16444 ) -> Option<()> {
16445 let project = self.project.as_ref()?;
16446 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16447 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16448 let buffer_snapshot = buffer.read(cx).snapshot();
16449 let file_exists = buffer_snapshot
16450 .file()
16451 .is_some_and(|file| file.disk_state().exists());
16452 diff.update(cx, |diff, cx| {
16453 diff.stage_or_unstage_hunks(
16454 stage,
16455 &hunks
16456 .map(|hunk| buffer_diff::DiffHunk {
16457 buffer_range: hunk.buffer_range,
16458 diff_base_byte_range: hunk.diff_base_byte_range,
16459 secondary_status: hunk.secondary_status,
16460 range: Point::zero()..Point::zero(), // unused
16461 })
16462 .collect::<Vec<_>>(),
16463 &buffer_snapshot,
16464 file_exists,
16465 cx,
16466 )
16467 });
16468 None
16469 }
16470
16471 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16472 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16473 self.buffer
16474 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16475 }
16476
16477 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16478 self.buffer.update(cx, |buffer, cx| {
16479 let ranges = vec![Anchor::min()..Anchor::max()];
16480 if !buffer.all_diff_hunks_expanded()
16481 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16482 {
16483 buffer.collapse_diff_hunks(ranges, cx);
16484 true
16485 } else {
16486 false
16487 }
16488 })
16489 }
16490
16491 fn toggle_diff_hunks_in_ranges(
16492 &mut self,
16493 ranges: Vec<Range<Anchor>>,
16494 cx: &mut Context<Editor>,
16495 ) {
16496 self.buffer.update(cx, |buffer, cx| {
16497 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16498 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16499 })
16500 }
16501
16502 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16503 self.buffer.update(cx, |buffer, cx| {
16504 let snapshot = buffer.snapshot(cx);
16505 let excerpt_id = range.end.excerpt_id;
16506 let point_range = range.to_point(&snapshot);
16507 let expand = !buffer.single_hunk_is_expanded(range, cx);
16508 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16509 })
16510 }
16511
16512 pub(crate) fn apply_all_diff_hunks(
16513 &mut self,
16514 _: &ApplyAllDiffHunks,
16515 window: &mut Window,
16516 cx: &mut Context<Self>,
16517 ) {
16518 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16519
16520 let buffers = self.buffer.read(cx).all_buffers();
16521 for branch_buffer in buffers {
16522 branch_buffer.update(cx, |branch_buffer, cx| {
16523 branch_buffer.merge_into_base(Vec::new(), cx);
16524 });
16525 }
16526
16527 if let Some(project) = self.project.clone() {
16528 self.save(true, project, window, cx).detach_and_log_err(cx);
16529 }
16530 }
16531
16532 pub(crate) fn apply_selected_diff_hunks(
16533 &mut self,
16534 _: &ApplyDiffHunk,
16535 window: &mut Window,
16536 cx: &mut Context<Self>,
16537 ) {
16538 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16539 let snapshot = self.snapshot(window, cx);
16540 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16541 let mut ranges_by_buffer = HashMap::default();
16542 self.transact(window, cx, |editor, _window, cx| {
16543 for hunk in hunks {
16544 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16545 ranges_by_buffer
16546 .entry(buffer.clone())
16547 .or_insert_with(Vec::new)
16548 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16549 }
16550 }
16551
16552 for (buffer, ranges) in ranges_by_buffer {
16553 buffer.update(cx, |buffer, cx| {
16554 buffer.merge_into_base(ranges, cx);
16555 });
16556 }
16557 });
16558
16559 if let Some(project) = self.project.clone() {
16560 self.save(true, project, window, cx).detach_and_log_err(cx);
16561 }
16562 }
16563
16564 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16565 if hovered != self.gutter_hovered {
16566 self.gutter_hovered = hovered;
16567 cx.notify();
16568 }
16569 }
16570
16571 pub fn insert_blocks(
16572 &mut self,
16573 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16574 autoscroll: Option<Autoscroll>,
16575 cx: &mut Context<Self>,
16576 ) -> Vec<CustomBlockId> {
16577 let blocks = self
16578 .display_map
16579 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16580 if let Some(autoscroll) = autoscroll {
16581 self.request_autoscroll(autoscroll, cx);
16582 }
16583 cx.notify();
16584 blocks
16585 }
16586
16587 pub fn resize_blocks(
16588 &mut self,
16589 heights: HashMap<CustomBlockId, u32>,
16590 autoscroll: Option<Autoscroll>,
16591 cx: &mut Context<Self>,
16592 ) {
16593 self.display_map
16594 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16595 if let Some(autoscroll) = autoscroll {
16596 self.request_autoscroll(autoscroll, cx);
16597 }
16598 cx.notify();
16599 }
16600
16601 pub fn replace_blocks(
16602 &mut self,
16603 renderers: HashMap<CustomBlockId, RenderBlock>,
16604 autoscroll: Option<Autoscroll>,
16605 cx: &mut Context<Self>,
16606 ) {
16607 self.display_map
16608 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16609 if let Some(autoscroll) = autoscroll {
16610 self.request_autoscroll(autoscroll, cx);
16611 }
16612 cx.notify();
16613 }
16614
16615 pub fn remove_blocks(
16616 &mut self,
16617 block_ids: HashSet<CustomBlockId>,
16618 autoscroll: Option<Autoscroll>,
16619 cx: &mut Context<Self>,
16620 ) {
16621 self.display_map.update(cx, |display_map, cx| {
16622 display_map.remove_blocks(block_ids, cx)
16623 });
16624 if let Some(autoscroll) = autoscroll {
16625 self.request_autoscroll(autoscroll, cx);
16626 }
16627 cx.notify();
16628 }
16629
16630 pub fn row_for_block(
16631 &self,
16632 block_id: CustomBlockId,
16633 cx: &mut Context<Self>,
16634 ) -> Option<DisplayRow> {
16635 self.display_map
16636 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16637 }
16638
16639 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16640 self.focused_block = Some(focused_block);
16641 }
16642
16643 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16644 self.focused_block.take()
16645 }
16646
16647 pub fn insert_creases(
16648 &mut self,
16649 creases: impl IntoIterator<Item = Crease<Anchor>>,
16650 cx: &mut Context<Self>,
16651 ) -> Vec<CreaseId> {
16652 self.display_map
16653 .update(cx, |map, cx| map.insert_creases(creases, cx))
16654 }
16655
16656 pub fn remove_creases(
16657 &mut self,
16658 ids: impl IntoIterator<Item = CreaseId>,
16659 cx: &mut Context<Self>,
16660 ) -> Vec<(CreaseId, Range<Anchor>)> {
16661 self.display_map
16662 .update(cx, |map, cx| map.remove_creases(ids, cx))
16663 }
16664
16665 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16666 self.display_map
16667 .update(cx, |map, cx| map.snapshot(cx))
16668 .longest_row()
16669 }
16670
16671 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16672 self.display_map
16673 .update(cx, |map, cx| map.snapshot(cx))
16674 .max_point()
16675 }
16676
16677 pub fn text(&self, cx: &App) -> String {
16678 self.buffer.read(cx).read(cx).text()
16679 }
16680
16681 pub fn is_empty(&self, cx: &App) -> bool {
16682 self.buffer.read(cx).read(cx).is_empty()
16683 }
16684
16685 pub fn text_option(&self, cx: &App) -> Option<String> {
16686 let text = self.text(cx);
16687 let text = text.trim();
16688
16689 if text.is_empty() {
16690 return None;
16691 }
16692
16693 Some(text.to_string())
16694 }
16695
16696 pub fn set_text(
16697 &mut self,
16698 text: impl Into<Arc<str>>,
16699 window: &mut Window,
16700 cx: &mut Context<Self>,
16701 ) {
16702 self.transact(window, cx, |this, _, cx| {
16703 this.buffer
16704 .read(cx)
16705 .as_singleton()
16706 .expect("you can only call set_text on editors for singleton buffers")
16707 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16708 });
16709 }
16710
16711 pub fn display_text(&self, cx: &mut App) -> String {
16712 self.display_map
16713 .update(cx, |map, cx| map.snapshot(cx))
16714 .text()
16715 }
16716
16717 fn create_minimap(
16718 &self,
16719 minimap_settings: MinimapSettings,
16720 window: &mut Window,
16721 cx: &mut Context<Self>,
16722 ) -> Option<Entity<Self>> {
16723 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16724 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16725 }
16726
16727 fn initialize_new_minimap(
16728 &self,
16729 minimap_settings: MinimapSettings,
16730 window: &mut Window,
16731 cx: &mut Context<Self>,
16732 ) -> Entity<Self> {
16733 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16734
16735 let mut minimap = Editor::new_internal(
16736 EditorMode::Minimap {
16737 parent: cx.weak_entity(),
16738 },
16739 self.buffer.clone(),
16740 self.project.clone(),
16741 Some(self.display_map.clone()),
16742 window,
16743 cx,
16744 );
16745 minimap.scroll_manager.clone_state(&self.scroll_manager);
16746 minimap.set_text_style_refinement(TextStyleRefinement {
16747 font_size: Some(MINIMAP_FONT_SIZE),
16748 font_weight: Some(MINIMAP_FONT_WEIGHT),
16749 ..Default::default()
16750 });
16751 minimap.update_minimap_configuration(minimap_settings, cx);
16752 cx.new(|_| minimap)
16753 }
16754
16755 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16756 let current_line_highlight = minimap_settings
16757 .current_line_highlight
16758 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16759 self.set_current_line_highlight(Some(current_line_highlight));
16760 }
16761
16762 pub fn minimap(&self) -> Option<&Entity<Self>> {
16763 self.minimap
16764 .as_ref()
16765 .filter(|_| self.minimap_visibility.visible())
16766 }
16767
16768 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16769 let mut wrap_guides = smallvec![];
16770
16771 if self.show_wrap_guides == Some(false) {
16772 return wrap_guides;
16773 }
16774
16775 let settings = self.buffer.read(cx).language_settings(cx);
16776 if settings.show_wrap_guides {
16777 match self.soft_wrap_mode(cx) {
16778 SoftWrap::Column(soft_wrap) => {
16779 wrap_guides.push((soft_wrap as usize, true));
16780 }
16781 SoftWrap::Bounded(soft_wrap) => {
16782 wrap_guides.push((soft_wrap as usize, true));
16783 }
16784 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16785 }
16786 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16787 }
16788
16789 wrap_guides
16790 }
16791
16792 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16793 let settings = self.buffer.read(cx).language_settings(cx);
16794 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16795 match mode {
16796 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16797 SoftWrap::None
16798 }
16799 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16800 language_settings::SoftWrap::PreferredLineLength => {
16801 SoftWrap::Column(settings.preferred_line_length)
16802 }
16803 language_settings::SoftWrap::Bounded => {
16804 SoftWrap::Bounded(settings.preferred_line_length)
16805 }
16806 }
16807 }
16808
16809 pub fn set_soft_wrap_mode(
16810 &mut self,
16811 mode: language_settings::SoftWrap,
16812
16813 cx: &mut Context<Self>,
16814 ) {
16815 self.soft_wrap_mode_override = Some(mode);
16816 cx.notify();
16817 }
16818
16819 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16820 self.hard_wrap = hard_wrap;
16821 cx.notify();
16822 }
16823
16824 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16825 self.text_style_refinement = Some(style);
16826 }
16827
16828 /// called by the Element so we know what style we were most recently rendered with.
16829 pub(crate) fn set_style(
16830 &mut self,
16831 style: EditorStyle,
16832 window: &mut Window,
16833 cx: &mut Context<Self>,
16834 ) {
16835 // We intentionally do not inform the display map about the minimap style
16836 // so that wrapping is not recalculated and stays consistent for the editor
16837 // and its linked minimap.
16838 if !self.mode.is_minimap() {
16839 let rem_size = window.rem_size();
16840 self.display_map.update(cx, |map, cx| {
16841 map.set_font(
16842 style.text.font(),
16843 style.text.font_size.to_pixels(rem_size),
16844 cx,
16845 )
16846 });
16847 }
16848 self.style = Some(style);
16849 }
16850
16851 pub fn style(&self) -> Option<&EditorStyle> {
16852 self.style.as_ref()
16853 }
16854
16855 // Called by the element. This method is not designed to be called outside of the editor
16856 // element's layout code because it does not notify when rewrapping is computed synchronously.
16857 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16858 self.display_map
16859 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16860 }
16861
16862 pub fn set_soft_wrap(&mut self) {
16863 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16864 }
16865
16866 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16867 if self.soft_wrap_mode_override.is_some() {
16868 self.soft_wrap_mode_override.take();
16869 } else {
16870 let soft_wrap = match self.soft_wrap_mode(cx) {
16871 SoftWrap::GitDiff => return,
16872 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16873 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16874 language_settings::SoftWrap::None
16875 }
16876 };
16877 self.soft_wrap_mode_override = Some(soft_wrap);
16878 }
16879 cx.notify();
16880 }
16881
16882 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16883 let Some(workspace) = self.workspace() else {
16884 return;
16885 };
16886 let fs = workspace.read(cx).app_state().fs.clone();
16887 let current_show = TabBarSettings::get_global(cx).show;
16888 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16889 setting.show = Some(!current_show);
16890 });
16891 }
16892
16893 pub fn toggle_indent_guides(
16894 &mut self,
16895 _: &ToggleIndentGuides,
16896 _: &mut Window,
16897 cx: &mut Context<Self>,
16898 ) {
16899 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16900 self.buffer
16901 .read(cx)
16902 .language_settings(cx)
16903 .indent_guides
16904 .enabled
16905 });
16906 self.show_indent_guides = Some(!currently_enabled);
16907 cx.notify();
16908 }
16909
16910 fn should_show_indent_guides(&self) -> Option<bool> {
16911 self.show_indent_guides
16912 }
16913
16914 pub fn toggle_line_numbers(
16915 &mut self,
16916 _: &ToggleLineNumbers,
16917 _: &mut Window,
16918 cx: &mut Context<Self>,
16919 ) {
16920 let mut editor_settings = EditorSettings::get_global(cx).clone();
16921 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16922 EditorSettings::override_global(editor_settings, cx);
16923 }
16924
16925 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16926 if let Some(show_line_numbers) = self.show_line_numbers {
16927 return show_line_numbers;
16928 }
16929 EditorSettings::get_global(cx).gutter.line_numbers
16930 }
16931
16932 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16933 self.use_relative_line_numbers
16934 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16935 }
16936
16937 pub fn toggle_relative_line_numbers(
16938 &mut self,
16939 _: &ToggleRelativeLineNumbers,
16940 _: &mut Window,
16941 cx: &mut Context<Self>,
16942 ) {
16943 let is_relative = self.should_use_relative_line_numbers(cx);
16944 self.set_relative_line_number(Some(!is_relative), cx)
16945 }
16946
16947 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16948 self.use_relative_line_numbers = is_relative;
16949 cx.notify();
16950 }
16951
16952 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16953 self.show_gutter = show_gutter;
16954 cx.notify();
16955 }
16956
16957 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
16958 self.show_scrollbars = show_scrollbars;
16959 cx.notify();
16960 }
16961
16962 pub fn set_minimap_visibility(
16963 &mut self,
16964 minimap_visibility: MinimapVisibility,
16965 window: &mut Window,
16966 cx: &mut Context<Self>,
16967 ) {
16968 if self.minimap_visibility != minimap_visibility {
16969 if minimap_visibility.visible() && self.minimap.is_none() {
16970 let minimap_settings = EditorSettings::get_global(cx).minimap;
16971 self.minimap =
16972 self.create_minimap(minimap_settings.with_show_override(), window, cx);
16973 }
16974 self.minimap_visibility = minimap_visibility;
16975 cx.notify();
16976 }
16977 }
16978
16979 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
16980 self.set_show_scrollbars(false, cx);
16981 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
16982 }
16983
16984 /// Normally the text in full mode and auto height editors is padded on the
16985 /// left side by roughly half a character width for improved hit testing.
16986 ///
16987 /// Use this method to disable this for cases where this is not wanted (e.g.
16988 /// if you want to align the editor text with some other text above or below)
16989 /// or if you want to add this padding to single-line editors.
16990 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
16991 self.offset_content = offset_content;
16992 cx.notify();
16993 }
16994
16995 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
16996 self.show_line_numbers = Some(show_line_numbers);
16997 cx.notify();
16998 }
16999
17000 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17001 self.disable_expand_excerpt_buttons = true;
17002 cx.notify();
17003 }
17004
17005 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17006 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17007 cx.notify();
17008 }
17009
17010 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17011 self.show_code_actions = Some(show_code_actions);
17012 cx.notify();
17013 }
17014
17015 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17016 self.show_runnables = Some(show_runnables);
17017 cx.notify();
17018 }
17019
17020 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17021 self.show_breakpoints = Some(show_breakpoints);
17022 cx.notify();
17023 }
17024
17025 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17026 if self.display_map.read(cx).masked != masked {
17027 self.display_map.update(cx, |map, _| map.masked = masked);
17028 }
17029 cx.notify()
17030 }
17031
17032 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17033 self.show_wrap_guides = Some(show_wrap_guides);
17034 cx.notify();
17035 }
17036
17037 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17038 self.show_indent_guides = Some(show_indent_guides);
17039 cx.notify();
17040 }
17041
17042 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17043 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17044 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17045 if let Some(dir) = file.abs_path(cx).parent() {
17046 return Some(dir.to_owned());
17047 }
17048 }
17049
17050 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17051 return Some(project_path.path.to_path_buf());
17052 }
17053 }
17054
17055 None
17056 }
17057
17058 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17059 self.active_excerpt(cx)?
17060 .1
17061 .read(cx)
17062 .file()
17063 .and_then(|f| f.as_local())
17064 }
17065
17066 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17067 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17068 let buffer = buffer.read(cx);
17069 if let Some(project_path) = buffer.project_path(cx) {
17070 let project = self.project.as_ref()?.read(cx);
17071 project.absolute_path(&project_path, cx)
17072 } else {
17073 buffer
17074 .file()
17075 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17076 }
17077 })
17078 }
17079
17080 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17081 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17082 let project_path = buffer.read(cx).project_path(cx)?;
17083 let project = self.project.as_ref()?.read(cx);
17084 let entry = project.entry_for_path(&project_path, cx)?;
17085 let path = entry.path.to_path_buf();
17086 Some(path)
17087 })
17088 }
17089
17090 pub fn reveal_in_finder(
17091 &mut self,
17092 _: &RevealInFileManager,
17093 _window: &mut Window,
17094 cx: &mut Context<Self>,
17095 ) {
17096 if let Some(target) = self.target_file(cx) {
17097 cx.reveal_path(&target.abs_path(cx));
17098 }
17099 }
17100
17101 pub fn copy_path(
17102 &mut self,
17103 _: &zed_actions::workspace::CopyPath,
17104 _window: &mut Window,
17105 cx: &mut Context<Self>,
17106 ) {
17107 if let Some(path) = self.target_file_abs_path(cx) {
17108 if let Some(path) = path.to_str() {
17109 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17110 }
17111 }
17112 }
17113
17114 pub fn copy_relative_path(
17115 &mut self,
17116 _: &zed_actions::workspace::CopyRelativePath,
17117 _window: &mut Window,
17118 cx: &mut Context<Self>,
17119 ) {
17120 if let Some(path) = self.target_file_path(cx) {
17121 if let Some(path) = path.to_str() {
17122 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17123 }
17124 }
17125 }
17126
17127 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17128 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17129 buffer.read(cx).project_path(cx)
17130 } else {
17131 None
17132 }
17133 }
17134
17135 // Returns true if the editor handled a go-to-line request
17136 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17137 maybe!({
17138 let breakpoint_store = self.breakpoint_store.as_ref()?;
17139
17140 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17141 else {
17142 self.clear_row_highlights::<ActiveDebugLine>();
17143 return None;
17144 };
17145
17146 let position = active_stack_frame.position;
17147 let buffer_id = position.buffer_id?;
17148 let snapshot = self
17149 .project
17150 .as_ref()?
17151 .read(cx)
17152 .buffer_for_id(buffer_id, cx)?
17153 .read(cx)
17154 .snapshot();
17155
17156 let mut handled = false;
17157 for (id, ExcerptRange { context, .. }) in
17158 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17159 {
17160 if context.start.cmp(&position, &snapshot).is_ge()
17161 || context.end.cmp(&position, &snapshot).is_lt()
17162 {
17163 continue;
17164 }
17165 let snapshot = self.buffer.read(cx).snapshot(cx);
17166 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17167
17168 handled = true;
17169 self.clear_row_highlights::<ActiveDebugLine>();
17170
17171 self.go_to_line::<ActiveDebugLine>(
17172 multibuffer_anchor,
17173 Some(cx.theme().colors().editor_debugger_active_line_background),
17174 window,
17175 cx,
17176 );
17177
17178 cx.notify();
17179 }
17180
17181 handled.then_some(())
17182 })
17183 .is_some()
17184 }
17185
17186 pub fn copy_file_name_without_extension(
17187 &mut self,
17188 _: &CopyFileNameWithoutExtension,
17189 _: &mut Window,
17190 cx: &mut Context<Self>,
17191 ) {
17192 if let Some(file) = self.target_file(cx) {
17193 if let Some(file_stem) = file.path().file_stem() {
17194 if let Some(name) = file_stem.to_str() {
17195 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17196 }
17197 }
17198 }
17199 }
17200
17201 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17202 if let Some(file) = self.target_file(cx) {
17203 if let Some(file_name) = file.path().file_name() {
17204 if let Some(name) = file_name.to_str() {
17205 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17206 }
17207 }
17208 }
17209 }
17210
17211 pub fn toggle_git_blame(
17212 &mut self,
17213 _: &::git::Blame,
17214 window: &mut Window,
17215 cx: &mut Context<Self>,
17216 ) {
17217 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17218
17219 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17220 self.start_git_blame(true, window, cx);
17221 }
17222
17223 cx.notify();
17224 }
17225
17226 pub fn toggle_git_blame_inline(
17227 &mut self,
17228 _: &ToggleGitBlameInline,
17229 window: &mut Window,
17230 cx: &mut Context<Self>,
17231 ) {
17232 self.toggle_git_blame_inline_internal(true, window, cx);
17233 cx.notify();
17234 }
17235
17236 pub fn open_git_blame_commit(
17237 &mut self,
17238 _: &OpenGitBlameCommit,
17239 window: &mut Window,
17240 cx: &mut Context<Self>,
17241 ) {
17242 self.open_git_blame_commit_internal(window, cx);
17243 }
17244
17245 fn open_git_blame_commit_internal(
17246 &mut self,
17247 window: &mut Window,
17248 cx: &mut Context<Self>,
17249 ) -> Option<()> {
17250 let blame = self.blame.as_ref()?;
17251 let snapshot = self.snapshot(window, cx);
17252 let cursor = self.selections.newest::<Point>(cx).head();
17253 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17254 let blame_entry = blame
17255 .update(cx, |blame, cx| {
17256 blame
17257 .blame_for_rows(
17258 &[RowInfo {
17259 buffer_id: Some(buffer.remote_id()),
17260 buffer_row: Some(point.row),
17261 ..Default::default()
17262 }],
17263 cx,
17264 )
17265 .next()
17266 })
17267 .flatten()?;
17268 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17269 let repo = blame.read(cx).repository(cx)?;
17270 let workspace = self.workspace()?.downgrade();
17271 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17272 None
17273 }
17274
17275 pub fn git_blame_inline_enabled(&self) -> bool {
17276 self.git_blame_inline_enabled
17277 }
17278
17279 pub fn toggle_selection_menu(
17280 &mut self,
17281 _: &ToggleSelectionMenu,
17282 _: &mut Window,
17283 cx: &mut Context<Self>,
17284 ) {
17285 self.show_selection_menu = self
17286 .show_selection_menu
17287 .map(|show_selections_menu| !show_selections_menu)
17288 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17289
17290 cx.notify();
17291 }
17292
17293 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17294 self.show_selection_menu
17295 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17296 }
17297
17298 fn start_git_blame(
17299 &mut self,
17300 user_triggered: bool,
17301 window: &mut Window,
17302 cx: &mut Context<Self>,
17303 ) {
17304 if let Some(project) = self.project.as_ref() {
17305 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17306 return;
17307 };
17308
17309 if buffer.read(cx).file().is_none() {
17310 return;
17311 }
17312
17313 let focused = self.focus_handle(cx).contains_focused(window, cx);
17314
17315 let project = project.clone();
17316 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17317 self.blame_subscription =
17318 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17319 self.blame = Some(blame);
17320 }
17321 }
17322
17323 fn toggle_git_blame_inline_internal(
17324 &mut self,
17325 user_triggered: bool,
17326 window: &mut Window,
17327 cx: &mut Context<Self>,
17328 ) {
17329 if self.git_blame_inline_enabled {
17330 self.git_blame_inline_enabled = false;
17331 self.show_git_blame_inline = false;
17332 self.show_git_blame_inline_delay_task.take();
17333 } else {
17334 self.git_blame_inline_enabled = true;
17335 self.start_git_blame_inline(user_triggered, window, cx);
17336 }
17337
17338 cx.notify();
17339 }
17340
17341 fn start_git_blame_inline(
17342 &mut self,
17343 user_triggered: bool,
17344 window: &mut Window,
17345 cx: &mut Context<Self>,
17346 ) {
17347 self.start_git_blame(user_triggered, window, cx);
17348
17349 if ProjectSettings::get_global(cx)
17350 .git
17351 .inline_blame_delay()
17352 .is_some()
17353 {
17354 self.start_inline_blame_timer(window, cx);
17355 } else {
17356 self.show_git_blame_inline = true
17357 }
17358 }
17359
17360 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17361 self.blame.as_ref()
17362 }
17363
17364 pub fn show_git_blame_gutter(&self) -> bool {
17365 self.show_git_blame_gutter
17366 }
17367
17368 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17369 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17370 }
17371
17372 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17373 self.show_git_blame_inline
17374 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17375 && !self.newest_selection_head_on_empty_line(cx)
17376 && self.has_blame_entries(cx)
17377 }
17378
17379 fn has_blame_entries(&self, cx: &App) -> bool {
17380 self.blame()
17381 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17382 }
17383
17384 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17385 let cursor_anchor = self.selections.newest_anchor().head();
17386
17387 let snapshot = self.buffer.read(cx).snapshot(cx);
17388 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17389
17390 snapshot.line_len(buffer_row) == 0
17391 }
17392
17393 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17394 let buffer_and_selection = maybe!({
17395 let selection = self.selections.newest::<Point>(cx);
17396 let selection_range = selection.range();
17397
17398 let multi_buffer = self.buffer().read(cx);
17399 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17400 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17401
17402 let (buffer, range, _) = if selection.reversed {
17403 buffer_ranges.first()
17404 } else {
17405 buffer_ranges.last()
17406 }?;
17407
17408 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17409 ..text::ToPoint::to_point(&range.end, &buffer).row;
17410 Some((
17411 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17412 selection,
17413 ))
17414 });
17415
17416 let Some((buffer, selection)) = buffer_and_selection else {
17417 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17418 };
17419
17420 let Some(project) = self.project.as_ref() else {
17421 return Task::ready(Err(anyhow!("editor does not have project")));
17422 };
17423
17424 project.update(cx, |project, cx| {
17425 project.get_permalink_to_line(&buffer, selection, cx)
17426 })
17427 }
17428
17429 pub fn copy_permalink_to_line(
17430 &mut self,
17431 _: &CopyPermalinkToLine,
17432 window: &mut Window,
17433 cx: &mut Context<Self>,
17434 ) {
17435 let permalink_task = self.get_permalink_to_line(cx);
17436 let workspace = self.workspace();
17437
17438 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17439 Ok(permalink) => {
17440 cx.update(|_, cx| {
17441 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17442 })
17443 .ok();
17444 }
17445 Err(err) => {
17446 let message = format!("Failed to copy permalink: {err}");
17447
17448 anyhow::Result::<()>::Err(err).log_err();
17449
17450 if let Some(workspace) = workspace {
17451 workspace
17452 .update_in(cx, |workspace, _, cx| {
17453 struct CopyPermalinkToLine;
17454
17455 workspace.show_toast(
17456 Toast::new(
17457 NotificationId::unique::<CopyPermalinkToLine>(),
17458 message,
17459 ),
17460 cx,
17461 )
17462 })
17463 .ok();
17464 }
17465 }
17466 })
17467 .detach();
17468 }
17469
17470 pub fn copy_file_location(
17471 &mut self,
17472 _: &CopyFileLocation,
17473 _: &mut Window,
17474 cx: &mut Context<Self>,
17475 ) {
17476 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17477 if let Some(file) = self.target_file(cx) {
17478 if let Some(path) = file.path().to_str() {
17479 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17480 }
17481 }
17482 }
17483
17484 pub fn open_permalink_to_line(
17485 &mut self,
17486 _: &OpenPermalinkToLine,
17487 window: &mut Window,
17488 cx: &mut Context<Self>,
17489 ) {
17490 let permalink_task = self.get_permalink_to_line(cx);
17491 let workspace = self.workspace();
17492
17493 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17494 Ok(permalink) => {
17495 cx.update(|_, cx| {
17496 cx.open_url(permalink.as_ref());
17497 })
17498 .ok();
17499 }
17500 Err(err) => {
17501 let message = format!("Failed to open permalink: {err}");
17502
17503 anyhow::Result::<()>::Err(err).log_err();
17504
17505 if let Some(workspace) = workspace {
17506 workspace
17507 .update(cx, |workspace, cx| {
17508 struct OpenPermalinkToLine;
17509
17510 workspace.show_toast(
17511 Toast::new(
17512 NotificationId::unique::<OpenPermalinkToLine>(),
17513 message,
17514 ),
17515 cx,
17516 )
17517 })
17518 .ok();
17519 }
17520 }
17521 })
17522 .detach();
17523 }
17524
17525 pub fn insert_uuid_v4(
17526 &mut self,
17527 _: &InsertUuidV4,
17528 window: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) {
17531 self.insert_uuid(UuidVersion::V4, window, cx);
17532 }
17533
17534 pub fn insert_uuid_v7(
17535 &mut self,
17536 _: &InsertUuidV7,
17537 window: &mut Window,
17538 cx: &mut Context<Self>,
17539 ) {
17540 self.insert_uuid(UuidVersion::V7, window, cx);
17541 }
17542
17543 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17544 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17545 self.transact(window, cx, |this, window, cx| {
17546 let edits = this
17547 .selections
17548 .all::<Point>(cx)
17549 .into_iter()
17550 .map(|selection| {
17551 let uuid = match version {
17552 UuidVersion::V4 => uuid::Uuid::new_v4(),
17553 UuidVersion::V7 => uuid::Uuid::now_v7(),
17554 };
17555
17556 (selection.range(), uuid.to_string())
17557 });
17558 this.edit(edits, cx);
17559 this.refresh_inline_completion(true, false, window, cx);
17560 });
17561 }
17562
17563 pub fn open_selections_in_multibuffer(
17564 &mut self,
17565 _: &OpenSelectionsInMultibuffer,
17566 window: &mut Window,
17567 cx: &mut Context<Self>,
17568 ) {
17569 let multibuffer = self.buffer.read(cx);
17570
17571 let Some(buffer) = multibuffer.as_singleton() else {
17572 return;
17573 };
17574
17575 let Some(workspace) = self.workspace() else {
17576 return;
17577 };
17578
17579 let locations = self
17580 .selections
17581 .disjoint_anchors()
17582 .iter()
17583 .map(|selection| {
17584 let range = if selection.reversed {
17585 selection.end.text_anchor..selection.start.text_anchor
17586 } else {
17587 selection.start.text_anchor..selection.end.text_anchor
17588 };
17589 Location {
17590 buffer: buffer.clone(),
17591 range,
17592 }
17593 })
17594 .collect::<Vec<_>>();
17595
17596 let title = multibuffer.title(cx).to_string();
17597
17598 cx.spawn_in(window, async move |_, cx| {
17599 workspace.update_in(cx, |workspace, window, cx| {
17600 Self::open_locations_in_multibuffer(
17601 workspace,
17602 locations,
17603 format!("Selections for '{title}'"),
17604 false,
17605 MultibufferSelectionMode::All,
17606 window,
17607 cx,
17608 );
17609 })
17610 })
17611 .detach();
17612 }
17613
17614 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17615 /// last highlight added will be used.
17616 ///
17617 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17618 pub fn highlight_rows<T: 'static>(
17619 &mut self,
17620 range: Range<Anchor>,
17621 color: Hsla,
17622 options: RowHighlightOptions,
17623 cx: &mut Context<Self>,
17624 ) {
17625 let snapshot = self.buffer().read(cx).snapshot(cx);
17626 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17627 let ix = row_highlights.binary_search_by(|highlight| {
17628 Ordering::Equal
17629 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17630 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17631 });
17632
17633 if let Err(mut ix) = ix {
17634 let index = post_inc(&mut self.highlight_order);
17635
17636 // If this range intersects with the preceding highlight, then merge it with
17637 // the preceding highlight. Otherwise insert a new highlight.
17638 let mut merged = false;
17639 if ix > 0 {
17640 let prev_highlight = &mut row_highlights[ix - 1];
17641 if prev_highlight
17642 .range
17643 .end
17644 .cmp(&range.start, &snapshot)
17645 .is_ge()
17646 {
17647 ix -= 1;
17648 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17649 prev_highlight.range.end = range.end;
17650 }
17651 merged = true;
17652 prev_highlight.index = index;
17653 prev_highlight.color = color;
17654 prev_highlight.options = options;
17655 }
17656 }
17657
17658 if !merged {
17659 row_highlights.insert(
17660 ix,
17661 RowHighlight {
17662 range: range.clone(),
17663 index,
17664 color,
17665 options,
17666 type_id: TypeId::of::<T>(),
17667 },
17668 );
17669 }
17670
17671 // If any of the following highlights intersect with this one, merge them.
17672 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17673 let highlight = &row_highlights[ix];
17674 if next_highlight
17675 .range
17676 .start
17677 .cmp(&highlight.range.end, &snapshot)
17678 .is_le()
17679 {
17680 if next_highlight
17681 .range
17682 .end
17683 .cmp(&highlight.range.end, &snapshot)
17684 .is_gt()
17685 {
17686 row_highlights[ix].range.end = next_highlight.range.end;
17687 }
17688 row_highlights.remove(ix + 1);
17689 } else {
17690 break;
17691 }
17692 }
17693 }
17694 }
17695
17696 /// Remove any highlighted row ranges of the given type that intersect the
17697 /// given ranges.
17698 pub fn remove_highlighted_rows<T: 'static>(
17699 &mut self,
17700 ranges_to_remove: Vec<Range<Anchor>>,
17701 cx: &mut Context<Self>,
17702 ) {
17703 let snapshot = self.buffer().read(cx).snapshot(cx);
17704 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17705 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17706 row_highlights.retain(|highlight| {
17707 while let Some(range_to_remove) = ranges_to_remove.peek() {
17708 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17709 Ordering::Less | Ordering::Equal => {
17710 ranges_to_remove.next();
17711 }
17712 Ordering::Greater => {
17713 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17714 Ordering::Less | Ordering::Equal => {
17715 return false;
17716 }
17717 Ordering::Greater => break,
17718 }
17719 }
17720 }
17721 }
17722
17723 true
17724 })
17725 }
17726
17727 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17728 pub fn clear_row_highlights<T: 'static>(&mut self) {
17729 self.highlighted_rows.remove(&TypeId::of::<T>());
17730 }
17731
17732 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17733 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17734 self.highlighted_rows
17735 .get(&TypeId::of::<T>())
17736 .map_or(&[] as &[_], |vec| vec.as_slice())
17737 .iter()
17738 .map(|highlight| (highlight.range.clone(), highlight.color))
17739 }
17740
17741 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17742 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17743 /// Allows to ignore certain kinds of highlights.
17744 pub fn highlighted_display_rows(
17745 &self,
17746 window: &mut Window,
17747 cx: &mut App,
17748 ) -> BTreeMap<DisplayRow, LineHighlight> {
17749 let snapshot = self.snapshot(window, cx);
17750 let mut used_highlight_orders = HashMap::default();
17751 self.highlighted_rows
17752 .iter()
17753 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17754 .fold(
17755 BTreeMap::<DisplayRow, LineHighlight>::new(),
17756 |mut unique_rows, highlight| {
17757 let start = highlight.range.start.to_display_point(&snapshot);
17758 let end = highlight.range.end.to_display_point(&snapshot);
17759 let start_row = start.row().0;
17760 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17761 && end.column() == 0
17762 {
17763 end.row().0.saturating_sub(1)
17764 } else {
17765 end.row().0
17766 };
17767 for row in start_row..=end_row {
17768 let used_index =
17769 used_highlight_orders.entry(row).or_insert(highlight.index);
17770 if highlight.index >= *used_index {
17771 *used_index = highlight.index;
17772 unique_rows.insert(
17773 DisplayRow(row),
17774 LineHighlight {
17775 include_gutter: highlight.options.include_gutter,
17776 border: None,
17777 background: highlight.color.into(),
17778 type_id: Some(highlight.type_id),
17779 },
17780 );
17781 }
17782 }
17783 unique_rows
17784 },
17785 )
17786 }
17787
17788 pub fn highlighted_display_row_for_autoscroll(
17789 &self,
17790 snapshot: &DisplaySnapshot,
17791 ) -> Option<DisplayRow> {
17792 self.highlighted_rows
17793 .values()
17794 .flat_map(|highlighted_rows| highlighted_rows.iter())
17795 .filter_map(|highlight| {
17796 if highlight.options.autoscroll {
17797 Some(highlight.range.start.to_display_point(snapshot).row())
17798 } else {
17799 None
17800 }
17801 })
17802 .min()
17803 }
17804
17805 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17806 self.highlight_background::<SearchWithinRange>(
17807 ranges,
17808 |colors| colors.editor_document_highlight_read_background,
17809 cx,
17810 )
17811 }
17812
17813 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17814 self.breadcrumb_header = Some(new_header);
17815 }
17816
17817 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17818 self.clear_background_highlights::<SearchWithinRange>(cx);
17819 }
17820
17821 pub fn highlight_background<T: 'static>(
17822 &mut self,
17823 ranges: &[Range<Anchor>],
17824 color_fetcher: fn(&ThemeColors) -> Hsla,
17825 cx: &mut Context<Self>,
17826 ) {
17827 self.background_highlights
17828 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17829 self.scrollbar_marker_state.dirty = true;
17830 cx.notify();
17831 }
17832
17833 pub fn clear_background_highlights<T: 'static>(
17834 &mut self,
17835 cx: &mut Context<Self>,
17836 ) -> Option<BackgroundHighlight> {
17837 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17838 if !text_highlights.1.is_empty() {
17839 self.scrollbar_marker_state.dirty = true;
17840 cx.notify();
17841 }
17842 Some(text_highlights)
17843 }
17844
17845 pub fn highlight_gutter<T: 'static>(
17846 &mut self,
17847 ranges: &[Range<Anchor>],
17848 color_fetcher: fn(&App) -> Hsla,
17849 cx: &mut Context<Self>,
17850 ) {
17851 self.gutter_highlights
17852 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17853 cx.notify();
17854 }
17855
17856 pub fn clear_gutter_highlights<T: 'static>(
17857 &mut self,
17858 cx: &mut Context<Self>,
17859 ) -> Option<GutterHighlight> {
17860 cx.notify();
17861 self.gutter_highlights.remove(&TypeId::of::<T>())
17862 }
17863
17864 #[cfg(feature = "test-support")]
17865 pub fn all_text_background_highlights(
17866 &self,
17867 window: &mut Window,
17868 cx: &mut Context<Self>,
17869 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17870 let snapshot = self.snapshot(window, cx);
17871 let buffer = &snapshot.buffer_snapshot;
17872 let start = buffer.anchor_before(0);
17873 let end = buffer.anchor_after(buffer.len());
17874 let theme = cx.theme().colors();
17875 self.background_highlights_in_range(start..end, &snapshot, theme)
17876 }
17877
17878 #[cfg(feature = "test-support")]
17879 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17880 let snapshot = self.buffer().read(cx).snapshot(cx);
17881
17882 let highlights = self
17883 .background_highlights
17884 .get(&TypeId::of::<items::BufferSearchHighlights>());
17885
17886 if let Some((_color, ranges)) = highlights {
17887 ranges
17888 .iter()
17889 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17890 .collect_vec()
17891 } else {
17892 vec![]
17893 }
17894 }
17895
17896 fn document_highlights_for_position<'a>(
17897 &'a self,
17898 position: Anchor,
17899 buffer: &'a MultiBufferSnapshot,
17900 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17901 let read_highlights = self
17902 .background_highlights
17903 .get(&TypeId::of::<DocumentHighlightRead>())
17904 .map(|h| &h.1);
17905 let write_highlights = self
17906 .background_highlights
17907 .get(&TypeId::of::<DocumentHighlightWrite>())
17908 .map(|h| &h.1);
17909 let left_position = position.bias_left(buffer);
17910 let right_position = position.bias_right(buffer);
17911 read_highlights
17912 .into_iter()
17913 .chain(write_highlights)
17914 .flat_map(move |ranges| {
17915 let start_ix = match ranges.binary_search_by(|probe| {
17916 let cmp = probe.end.cmp(&left_position, buffer);
17917 if cmp.is_ge() {
17918 Ordering::Greater
17919 } else {
17920 Ordering::Less
17921 }
17922 }) {
17923 Ok(i) | Err(i) => i,
17924 };
17925
17926 ranges[start_ix..]
17927 .iter()
17928 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17929 })
17930 }
17931
17932 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17933 self.background_highlights
17934 .get(&TypeId::of::<T>())
17935 .map_or(false, |(_, highlights)| !highlights.is_empty())
17936 }
17937
17938 pub fn background_highlights_in_range(
17939 &self,
17940 search_range: Range<Anchor>,
17941 display_snapshot: &DisplaySnapshot,
17942 theme: &ThemeColors,
17943 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17944 let mut results = Vec::new();
17945 for (color_fetcher, ranges) in self.background_highlights.values() {
17946 let color = color_fetcher(theme);
17947 let start_ix = match ranges.binary_search_by(|probe| {
17948 let cmp = probe
17949 .end
17950 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17951 if cmp.is_gt() {
17952 Ordering::Greater
17953 } else {
17954 Ordering::Less
17955 }
17956 }) {
17957 Ok(i) | Err(i) => i,
17958 };
17959 for range in &ranges[start_ix..] {
17960 if range
17961 .start
17962 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
17963 .is_ge()
17964 {
17965 break;
17966 }
17967
17968 let start = range.start.to_display_point(display_snapshot);
17969 let end = range.end.to_display_point(display_snapshot);
17970 results.push((start..end, color))
17971 }
17972 }
17973 results
17974 }
17975
17976 pub fn background_highlight_row_ranges<T: 'static>(
17977 &self,
17978 search_range: Range<Anchor>,
17979 display_snapshot: &DisplaySnapshot,
17980 count: usize,
17981 ) -> Vec<RangeInclusive<DisplayPoint>> {
17982 let mut results = Vec::new();
17983 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
17984 return vec![];
17985 };
17986
17987 let start_ix = match ranges.binary_search_by(|probe| {
17988 let cmp = probe
17989 .end
17990 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
17991 if cmp.is_gt() {
17992 Ordering::Greater
17993 } else {
17994 Ordering::Less
17995 }
17996 }) {
17997 Ok(i) | Err(i) => i,
17998 };
17999 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18000 if let (Some(start_display), Some(end_display)) = (start, end) {
18001 results.push(
18002 start_display.to_display_point(display_snapshot)
18003 ..=end_display.to_display_point(display_snapshot),
18004 );
18005 }
18006 };
18007 let mut start_row: Option<Point> = None;
18008 let mut end_row: Option<Point> = None;
18009 if ranges.len() > count {
18010 return Vec::new();
18011 }
18012 for range in &ranges[start_ix..] {
18013 if range
18014 .start
18015 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18016 .is_ge()
18017 {
18018 break;
18019 }
18020 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18021 if let Some(current_row) = &end_row {
18022 if end.row == current_row.row {
18023 continue;
18024 }
18025 }
18026 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18027 if start_row.is_none() {
18028 assert_eq!(end_row, None);
18029 start_row = Some(start);
18030 end_row = Some(end);
18031 continue;
18032 }
18033 if let Some(current_end) = end_row.as_mut() {
18034 if start.row > current_end.row + 1 {
18035 push_region(start_row, end_row);
18036 start_row = Some(start);
18037 end_row = Some(end);
18038 } else {
18039 // Merge two hunks.
18040 *current_end = end;
18041 }
18042 } else {
18043 unreachable!();
18044 }
18045 }
18046 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18047 push_region(start_row, end_row);
18048 results
18049 }
18050
18051 pub fn gutter_highlights_in_range(
18052 &self,
18053 search_range: Range<Anchor>,
18054 display_snapshot: &DisplaySnapshot,
18055 cx: &App,
18056 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18057 let mut results = Vec::new();
18058 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18059 let color = color_fetcher(cx);
18060 let start_ix = match ranges.binary_search_by(|probe| {
18061 let cmp = probe
18062 .end
18063 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18064 if cmp.is_gt() {
18065 Ordering::Greater
18066 } else {
18067 Ordering::Less
18068 }
18069 }) {
18070 Ok(i) | Err(i) => i,
18071 };
18072 for range in &ranges[start_ix..] {
18073 if range
18074 .start
18075 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18076 .is_ge()
18077 {
18078 break;
18079 }
18080
18081 let start = range.start.to_display_point(display_snapshot);
18082 let end = range.end.to_display_point(display_snapshot);
18083 results.push((start..end, color))
18084 }
18085 }
18086 results
18087 }
18088
18089 /// Get the text ranges corresponding to the redaction query
18090 pub fn redacted_ranges(
18091 &self,
18092 search_range: Range<Anchor>,
18093 display_snapshot: &DisplaySnapshot,
18094 cx: &App,
18095 ) -> Vec<Range<DisplayPoint>> {
18096 display_snapshot
18097 .buffer_snapshot
18098 .redacted_ranges(search_range, |file| {
18099 if let Some(file) = file {
18100 file.is_private()
18101 && EditorSettings::get(
18102 Some(SettingsLocation {
18103 worktree_id: file.worktree_id(cx),
18104 path: file.path().as_ref(),
18105 }),
18106 cx,
18107 )
18108 .redact_private_values
18109 } else {
18110 false
18111 }
18112 })
18113 .map(|range| {
18114 range.start.to_display_point(display_snapshot)
18115 ..range.end.to_display_point(display_snapshot)
18116 })
18117 .collect()
18118 }
18119
18120 pub fn highlight_text<T: 'static>(
18121 &mut self,
18122 ranges: Vec<Range<Anchor>>,
18123 style: HighlightStyle,
18124 cx: &mut Context<Self>,
18125 ) {
18126 self.display_map.update(cx, |map, _| {
18127 map.highlight_text(TypeId::of::<T>(), ranges, style)
18128 });
18129 cx.notify();
18130 }
18131
18132 pub(crate) fn highlight_inlays<T: 'static>(
18133 &mut self,
18134 highlights: Vec<InlayHighlight>,
18135 style: HighlightStyle,
18136 cx: &mut Context<Self>,
18137 ) {
18138 self.display_map.update(cx, |map, _| {
18139 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18140 });
18141 cx.notify();
18142 }
18143
18144 pub fn text_highlights<'a, T: 'static>(
18145 &'a self,
18146 cx: &'a App,
18147 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18148 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18149 }
18150
18151 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18152 let cleared = self
18153 .display_map
18154 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18155 if cleared {
18156 cx.notify();
18157 }
18158 }
18159
18160 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18161 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18162 && self.focus_handle.is_focused(window)
18163 }
18164
18165 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18166 self.show_cursor_when_unfocused = is_enabled;
18167 cx.notify();
18168 }
18169
18170 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18171 cx.notify();
18172 }
18173
18174 fn on_debug_session_event(
18175 &mut self,
18176 _session: Entity<Session>,
18177 event: &SessionEvent,
18178 cx: &mut Context<Self>,
18179 ) {
18180 match event {
18181 SessionEvent::InvalidateInlineValue => {
18182 self.refresh_inline_values(cx);
18183 }
18184 _ => {}
18185 }
18186 }
18187
18188 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18189 let Some(project) = self.project.clone() else {
18190 return;
18191 };
18192
18193 if !self.inline_value_cache.enabled {
18194 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18195 self.splice_inlays(&inlays, Vec::new(), cx);
18196 return;
18197 }
18198
18199 let current_execution_position = self
18200 .highlighted_rows
18201 .get(&TypeId::of::<ActiveDebugLine>())
18202 .and_then(|lines| lines.last().map(|line| line.range.start));
18203
18204 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18205 let inline_values = editor
18206 .update(cx, |editor, cx| {
18207 let Some(current_execution_position) = current_execution_position else {
18208 return Some(Task::ready(Ok(Vec::new())));
18209 };
18210
18211 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18212 let snapshot = buffer.snapshot(cx);
18213
18214 let excerpt = snapshot.excerpt_containing(
18215 current_execution_position..current_execution_position,
18216 )?;
18217
18218 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18219 })?;
18220
18221 let range =
18222 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18223
18224 project.inline_values(buffer, range, cx)
18225 })
18226 .ok()
18227 .flatten()?
18228 .await
18229 .context("refreshing debugger inlays")
18230 .log_err()?;
18231
18232 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18233
18234 for (buffer_id, inline_value) in inline_values
18235 .into_iter()
18236 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18237 {
18238 buffer_inline_values
18239 .entry(buffer_id)
18240 .or_default()
18241 .push(inline_value);
18242 }
18243
18244 editor
18245 .update(cx, |editor, cx| {
18246 let snapshot = editor.buffer.read(cx).snapshot(cx);
18247 let mut new_inlays = Vec::default();
18248
18249 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18250 let buffer_id = buffer_snapshot.remote_id();
18251 buffer_inline_values
18252 .get(&buffer_id)
18253 .into_iter()
18254 .flatten()
18255 .for_each(|hint| {
18256 let inlay = Inlay::debugger_hint(
18257 post_inc(&mut editor.next_inlay_id),
18258 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18259 hint.text(),
18260 );
18261
18262 new_inlays.push(inlay);
18263 });
18264 }
18265
18266 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18267 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18268
18269 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18270 })
18271 .ok()?;
18272 Some(())
18273 });
18274 }
18275
18276 fn on_buffer_event(
18277 &mut self,
18278 multibuffer: &Entity<MultiBuffer>,
18279 event: &multi_buffer::Event,
18280 window: &mut Window,
18281 cx: &mut Context<Self>,
18282 ) {
18283 match event {
18284 multi_buffer::Event::Edited {
18285 singleton_buffer_edited,
18286 edited_buffer: buffer_edited,
18287 } => {
18288 self.scrollbar_marker_state.dirty = true;
18289 self.active_indent_guides_state.dirty = true;
18290 self.refresh_active_diagnostics(cx);
18291 self.refresh_code_actions(window, cx);
18292 self.refresh_selected_text_highlights(true, window, cx);
18293 refresh_matching_bracket_highlights(self, window, cx);
18294 if self.has_active_inline_completion() {
18295 self.update_visible_inline_completion(window, cx);
18296 }
18297 if let Some(buffer) = buffer_edited {
18298 let buffer_id = buffer.read(cx).remote_id();
18299 if !self.registered_buffers.contains_key(&buffer_id) {
18300 if let Some(project) = self.project.as_ref() {
18301 project.update(cx, |project, cx| {
18302 self.registered_buffers.insert(
18303 buffer_id,
18304 project.register_buffer_with_language_servers(&buffer, cx),
18305 );
18306 })
18307 }
18308 }
18309 }
18310 cx.emit(EditorEvent::BufferEdited);
18311 cx.emit(SearchEvent::MatchesInvalidated);
18312 if *singleton_buffer_edited {
18313 if let Some(project) = &self.project {
18314 #[allow(clippy::mutable_key_type)]
18315 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18316 multibuffer
18317 .all_buffers()
18318 .into_iter()
18319 .filter_map(|buffer| {
18320 buffer.update(cx, |buffer, cx| {
18321 let language = buffer.language()?;
18322 let should_discard = project.update(cx, |project, cx| {
18323 project.is_local()
18324 && !project.has_language_servers_for(buffer, cx)
18325 });
18326 should_discard.not().then_some(language.clone())
18327 })
18328 })
18329 .collect::<HashSet<_>>()
18330 });
18331 if !languages_affected.is_empty() {
18332 self.refresh_inlay_hints(
18333 InlayHintRefreshReason::BufferEdited(languages_affected),
18334 cx,
18335 );
18336 }
18337 }
18338 }
18339
18340 let Some(project) = &self.project else { return };
18341 let (telemetry, is_via_ssh) = {
18342 let project = project.read(cx);
18343 let telemetry = project.client().telemetry().clone();
18344 let is_via_ssh = project.is_via_ssh();
18345 (telemetry, is_via_ssh)
18346 };
18347 refresh_linked_ranges(self, window, cx);
18348 telemetry.log_edit_event("editor", is_via_ssh);
18349 }
18350 multi_buffer::Event::ExcerptsAdded {
18351 buffer,
18352 predecessor,
18353 excerpts,
18354 } => {
18355 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18356 let buffer_id = buffer.read(cx).remote_id();
18357 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18358 if let Some(project) = &self.project {
18359 update_uncommitted_diff_for_buffer(
18360 cx.entity(),
18361 project,
18362 [buffer.clone()],
18363 self.buffer.clone(),
18364 cx,
18365 )
18366 .detach();
18367 }
18368 }
18369 cx.emit(EditorEvent::ExcerptsAdded {
18370 buffer: buffer.clone(),
18371 predecessor: *predecessor,
18372 excerpts: excerpts.clone(),
18373 });
18374 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18375 }
18376 multi_buffer::Event::ExcerptsRemoved {
18377 ids,
18378 removed_buffer_ids,
18379 } => {
18380 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18381 let buffer = self.buffer.read(cx);
18382 self.registered_buffers
18383 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18384 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18385 cx.emit(EditorEvent::ExcerptsRemoved {
18386 ids: ids.clone(),
18387 removed_buffer_ids: removed_buffer_ids.clone(),
18388 })
18389 }
18390 multi_buffer::Event::ExcerptsEdited {
18391 excerpt_ids,
18392 buffer_ids,
18393 } => {
18394 self.display_map.update(cx, |map, cx| {
18395 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18396 });
18397 cx.emit(EditorEvent::ExcerptsEdited {
18398 ids: excerpt_ids.clone(),
18399 })
18400 }
18401 multi_buffer::Event::ExcerptsExpanded { ids } => {
18402 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18403 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18404 }
18405 multi_buffer::Event::Reparsed(buffer_id) => {
18406 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18407 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18408
18409 cx.emit(EditorEvent::Reparsed(*buffer_id));
18410 }
18411 multi_buffer::Event::DiffHunksToggled => {
18412 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18413 }
18414 multi_buffer::Event::LanguageChanged(buffer_id) => {
18415 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18416 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18417 cx.emit(EditorEvent::Reparsed(*buffer_id));
18418 cx.notify();
18419 }
18420 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18421 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18422 multi_buffer::Event::FileHandleChanged
18423 | multi_buffer::Event::Reloaded
18424 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18425 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18426 multi_buffer::Event::DiagnosticsUpdated => {
18427 self.refresh_active_diagnostics(cx);
18428 self.refresh_inline_diagnostics(true, window, cx);
18429 self.scrollbar_marker_state.dirty = true;
18430 cx.notify();
18431 }
18432 _ => {}
18433 };
18434 }
18435
18436 pub fn start_temporary_diff_override(&mut self) {
18437 self.load_diff_task.take();
18438 self.temporary_diff_override = true;
18439 }
18440
18441 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18442 self.temporary_diff_override = false;
18443 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18444 self.buffer.update(cx, |buffer, cx| {
18445 buffer.set_all_diff_hunks_collapsed(cx);
18446 });
18447
18448 if let Some(project) = self.project.clone() {
18449 self.load_diff_task = Some(
18450 update_uncommitted_diff_for_buffer(
18451 cx.entity(),
18452 &project,
18453 self.buffer.read(cx).all_buffers(),
18454 self.buffer.clone(),
18455 cx,
18456 )
18457 .shared(),
18458 );
18459 }
18460 }
18461
18462 fn on_display_map_changed(
18463 &mut self,
18464 _: Entity<DisplayMap>,
18465 _: &mut Window,
18466 cx: &mut Context<Self>,
18467 ) {
18468 cx.notify();
18469 }
18470
18471 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18472 let new_severity = if self.diagnostics_enabled() {
18473 EditorSettings::get_global(cx)
18474 .diagnostics_max_severity
18475 .unwrap_or(DiagnosticSeverity::Hint)
18476 } else {
18477 DiagnosticSeverity::Off
18478 };
18479 self.set_max_diagnostics_severity(new_severity, cx);
18480 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18481 self.update_edit_prediction_settings(cx);
18482 self.refresh_inline_completion(true, false, window, cx);
18483 self.refresh_inlay_hints(
18484 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18485 self.selections.newest_anchor().head(),
18486 &self.buffer.read(cx).snapshot(cx),
18487 cx,
18488 )),
18489 cx,
18490 );
18491
18492 let old_cursor_shape = self.cursor_shape;
18493
18494 {
18495 let editor_settings = EditorSettings::get_global(cx);
18496 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18497 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18498 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18499 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18500 }
18501
18502 if old_cursor_shape != self.cursor_shape {
18503 cx.emit(EditorEvent::CursorShapeChanged);
18504 }
18505
18506 let project_settings = ProjectSettings::get_global(cx);
18507 self.serialize_dirty_buffers =
18508 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18509
18510 if self.mode.is_full() {
18511 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18512 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18513 if self.show_inline_diagnostics != show_inline_diagnostics {
18514 self.show_inline_diagnostics = show_inline_diagnostics;
18515 self.refresh_inline_diagnostics(false, window, cx);
18516 }
18517
18518 if self.git_blame_inline_enabled != inline_blame_enabled {
18519 self.toggle_git_blame_inline_internal(false, window, cx);
18520 }
18521
18522 let minimap_settings = EditorSettings::get_global(cx).minimap;
18523 if self.minimap_visibility.visible() != minimap_settings.minimap_enabled() {
18524 self.set_minimap_visibility(
18525 self.minimap_visibility.toggle_visibility(),
18526 window,
18527 cx,
18528 );
18529 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18530 minimap_entity.update(cx, |minimap_editor, cx| {
18531 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18532 })
18533 }
18534 }
18535
18536 cx.notify();
18537 }
18538
18539 pub fn set_searchable(&mut self, searchable: bool) {
18540 self.searchable = searchable;
18541 }
18542
18543 pub fn searchable(&self) -> bool {
18544 self.searchable
18545 }
18546
18547 fn open_proposed_changes_editor(
18548 &mut self,
18549 _: &OpenProposedChangesEditor,
18550 window: &mut Window,
18551 cx: &mut Context<Self>,
18552 ) {
18553 let Some(workspace) = self.workspace() else {
18554 cx.propagate();
18555 return;
18556 };
18557
18558 let selections = self.selections.all::<usize>(cx);
18559 let multi_buffer = self.buffer.read(cx);
18560 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18561 let mut new_selections_by_buffer = HashMap::default();
18562 for selection in selections {
18563 for (buffer, range, _) in
18564 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18565 {
18566 let mut range = range.to_point(buffer);
18567 range.start.column = 0;
18568 range.end.column = buffer.line_len(range.end.row);
18569 new_selections_by_buffer
18570 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18571 .or_insert(Vec::new())
18572 .push(range)
18573 }
18574 }
18575
18576 let proposed_changes_buffers = new_selections_by_buffer
18577 .into_iter()
18578 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18579 .collect::<Vec<_>>();
18580 let proposed_changes_editor = cx.new(|cx| {
18581 ProposedChangesEditor::new(
18582 "Proposed changes",
18583 proposed_changes_buffers,
18584 self.project.clone(),
18585 window,
18586 cx,
18587 )
18588 });
18589
18590 window.defer(cx, move |window, cx| {
18591 workspace.update(cx, |workspace, cx| {
18592 workspace.active_pane().update(cx, |pane, cx| {
18593 pane.add_item(
18594 Box::new(proposed_changes_editor),
18595 true,
18596 true,
18597 None,
18598 window,
18599 cx,
18600 );
18601 });
18602 });
18603 });
18604 }
18605
18606 pub fn open_excerpts_in_split(
18607 &mut self,
18608 _: &OpenExcerptsSplit,
18609 window: &mut Window,
18610 cx: &mut Context<Self>,
18611 ) {
18612 self.open_excerpts_common(None, true, window, cx)
18613 }
18614
18615 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18616 self.open_excerpts_common(None, false, window, cx)
18617 }
18618
18619 fn open_excerpts_common(
18620 &mut self,
18621 jump_data: Option<JumpData>,
18622 split: bool,
18623 window: &mut Window,
18624 cx: &mut Context<Self>,
18625 ) {
18626 let Some(workspace) = self.workspace() else {
18627 cx.propagate();
18628 return;
18629 };
18630
18631 if self.buffer.read(cx).is_singleton() {
18632 cx.propagate();
18633 return;
18634 }
18635
18636 let mut new_selections_by_buffer = HashMap::default();
18637 match &jump_data {
18638 Some(JumpData::MultiBufferPoint {
18639 excerpt_id,
18640 position,
18641 anchor,
18642 line_offset_from_top,
18643 }) => {
18644 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18645 if let Some(buffer) = multi_buffer_snapshot
18646 .buffer_id_for_excerpt(*excerpt_id)
18647 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18648 {
18649 let buffer_snapshot = buffer.read(cx).snapshot();
18650 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18651 language::ToPoint::to_point(anchor, &buffer_snapshot)
18652 } else {
18653 buffer_snapshot.clip_point(*position, Bias::Left)
18654 };
18655 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18656 new_selections_by_buffer.insert(
18657 buffer,
18658 (
18659 vec![jump_to_offset..jump_to_offset],
18660 Some(*line_offset_from_top),
18661 ),
18662 );
18663 }
18664 }
18665 Some(JumpData::MultiBufferRow {
18666 row,
18667 line_offset_from_top,
18668 }) => {
18669 let point = MultiBufferPoint::new(row.0, 0);
18670 if let Some((buffer, buffer_point, _)) =
18671 self.buffer.read(cx).point_to_buffer_point(point, cx)
18672 {
18673 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18674 new_selections_by_buffer
18675 .entry(buffer)
18676 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18677 .0
18678 .push(buffer_offset..buffer_offset)
18679 }
18680 }
18681 None => {
18682 let selections = self.selections.all::<usize>(cx);
18683 let multi_buffer = self.buffer.read(cx);
18684 for selection in selections {
18685 for (snapshot, range, _, anchor) in multi_buffer
18686 .snapshot(cx)
18687 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18688 {
18689 if let Some(anchor) = anchor {
18690 // selection is in a deleted hunk
18691 let Some(buffer_id) = anchor.buffer_id else {
18692 continue;
18693 };
18694 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18695 continue;
18696 };
18697 let offset = text::ToOffset::to_offset(
18698 &anchor.text_anchor,
18699 &buffer_handle.read(cx).snapshot(),
18700 );
18701 let range = offset..offset;
18702 new_selections_by_buffer
18703 .entry(buffer_handle)
18704 .or_insert((Vec::new(), None))
18705 .0
18706 .push(range)
18707 } else {
18708 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18709 else {
18710 continue;
18711 };
18712 new_selections_by_buffer
18713 .entry(buffer_handle)
18714 .or_insert((Vec::new(), None))
18715 .0
18716 .push(range)
18717 }
18718 }
18719 }
18720 }
18721 }
18722
18723 new_selections_by_buffer
18724 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18725
18726 if new_selections_by_buffer.is_empty() {
18727 return;
18728 }
18729
18730 // We defer the pane interaction because we ourselves are a workspace item
18731 // and activating a new item causes the pane to call a method on us reentrantly,
18732 // which panics if we're on the stack.
18733 window.defer(cx, move |window, cx| {
18734 workspace.update(cx, |workspace, cx| {
18735 let pane = if split {
18736 workspace.adjacent_pane(window, cx)
18737 } else {
18738 workspace.active_pane().clone()
18739 };
18740
18741 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18742 let editor = buffer
18743 .read(cx)
18744 .file()
18745 .is_none()
18746 .then(|| {
18747 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18748 // so `workspace.open_project_item` will never find them, always opening a new editor.
18749 // Instead, we try to activate the existing editor in the pane first.
18750 let (editor, pane_item_index) =
18751 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18752 let editor = item.downcast::<Editor>()?;
18753 let singleton_buffer =
18754 editor.read(cx).buffer().read(cx).as_singleton()?;
18755 if singleton_buffer == buffer {
18756 Some((editor, i))
18757 } else {
18758 None
18759 }
18760 })?;
18761 pane.update(cx, |pane, cx| {
18762 pane.activate_item(pane_item_index, true, true, window, cx)
18763 });
18764 Some(editor)
18765 })
18766 .flatten()
18767 .unwrap_or_else(|| {
18768 workspace.open_project_item::<Self>(
18769 pane.clone(),
18770 buffer,
18771 true,
18772 true,
18773 window,
18774 cx,
18775 )
18776 });
18777
18778 editor.update(cx, |editor, cx| {
18779 let autoscroll = match scroll_offset {
18780 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18781 None => Autoscroll::newest(),
18782 };
18783 let nav_history = editor.nav_history.take();
18784 editor.change_selections(Some(autoscroll), window, cx, |s| {
18785 s.select_ranges(ranges);
18786 });
18787 editor.nav_history = nav_history;
18788 });
18789 }
18790 })
18791 });
18792 }
18793
18794 // For now, don't allow opening excerpts in buffers that aren't backed by
18795 // regular project files.
18796 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18797 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18798 }
18799
18800 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18801 let snapshot = self.buffer.read(cx).read(cx);
18802 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18803 Some(
18804 ranges
18805 .iter()
18806 .map(move |range| {
18807 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18808 })
18809 .collect(),
18810 )
18811 }
18812
18813 fn selection_replacement_ranges(
18814 &self,
18815 range: Range<OffsetUtf16>,
18816 cx: &mut App,
18817 ) -> Vec<Range<OffsetUtf16>> {
18818 let selections = self.selections.all::<OffsetUtf16>(cx);
18819 let newest_selection = selections
18820 .iter()
18821 .max_by_key(|selection| selection.id)
18822 .unwrap();
18823 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18824 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18825 let snapshot = self.buffer.read(cx).read(cx);
18826 selections
18827 .into_iter()
18828 .map(|mut selection| {
18829 selection.start.0 =
18830 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18831 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18832 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18833 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18834 })
18835 .collect()
18836 }
18837
18838 fn report_editor_event(
18839 &self,
18840 event_type: &'static str,
18841 file_extension: Option<String>,
18842 cx: &App,
18843 ) {
18844 if cfg!(any(test, feature = "test-support")) {
18845 return;
18846 }
18847
18848 let Some(project) = &self.project else { return };
18849
18850 // If None, we are in a file without an extension
18851 let file = self
18852 .buffer
18853 .read(cx)
18854 .as_singleton()
18855 .and_then(|b| b.read(cx).file());
18856 let file_extension = file_extension.or(file
18857 .as_ref()
18858 .and_then(|file| Path::new(file.file_name(cx)).extension())
18859 .and_then(|e| e.to_str())
18860 .map(|a| a.to_string()));
18861
18862 let vim_mode = vim_enabled(cx);
18863
18864 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18865 let copilot_enabled = edit_predictions_provider
18866 == language::language_settings::EditPredictionProvider::Copilot;
18867 let copilot_enabled_for_language = self
18868 .buffer
18869 .read(cx)
18870 .language_settings(cx)
18871 .show_edit_predictions;
18872
18873 let project = project.read(cx);
18874 telemetry::event!(
18875 event_type,
18876 file_extension,
18877 vim_mode,
18878 copilot_enabled,
18879 copilot_enabled_for_language,
18880 edit_predictions_provider,
18881 is_via_ssh = project.is_via_ssh(),
18882 );
18883 }
18884
18885 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18886 /// with each line being an array of {text, highlight} objects.
18887 fn copy_highlight_json(
18888 &mut self,
18889 _: &CopyHighlightJson,
18890 window: &mut Window,
18891 cx: &mut Context<Self>,
18892 ) {
18893 #[derive(Serialize)]
18894 struct Chunk<'a> {
18895 text: String,
18896 highlight: Option<&'a str>,
18897 }
18898
18899 let snapshot = self.buffer.read(cx).snapshot(cx);
18900 let range = self
18901 .selected_text_range(false, window, cx)
18902 .and_then(|selection| {
18903 if selection.range.is_empty() {
18904 None
18905 } else {
18906 Some(selection.range)
18907 }
18908 })
18909 .unwrap_or_else(|| 0..snapshot.len());
18910
18911 let chunks = snapshot.chunks(range, true);
18912 let mut lines = Vec::new();
18913 let mut line: VecDeque<Chunk> = VecDeque::new();
18914
18915 let Some(style) = self.style.as_ref() else {
18916 return;
18917 };
18918
18919 for chunk in chunks {
18920 let highlight = chunk
18921 .syntax_highlight_id
18922 .and_then(|id| id.name(&style.syntax));
18923 let mut chunk_lines = chunk.text.split('\n').peekable();
18924 while let Some(text) = chunk_lines.next() {
18925 let mut merged_with_last_token = false;
18926 if let Some(last_token) = line.back_mut() {
18927 if last_token.highlight == highlight {
18928 last_token.text.push_str(text);
18929 merged_with_last_token = true;
18930 }
18931 }
18932
18933 if !merged_with_last_token {
18934 line.push_back(Chunk {
18935 text: text.into(),
18936 highlight,
18937 });
18938 }
18939
18940 if chunk_lines.peek().is_some() {
18941 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18942 line.pop_front();
18943 }
18944 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18945 line.pop_back();
18946 }
18947
18948 lines.push(mem::take(&mut line));
18949 }
18950 }
18951 }
18952
18953 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
18954 return;
18955 };
18956 cx.write_to_clipboard(ClipboardItem::new_string(lines));
18957 }
18958
18959 pub fn open_context_menu(
18960 &mut self,
18961 _: &OpenContextMenu,
18962 window: &mut Window,
18963 cx: &mut Context<Self>,
18964 ) {
18965 self.request_autoscroll(Autoscroll::newest(), cx);
18966 let position = self.selections.newest_display(cx).start;
18967 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
18968 }
18969
18970 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
18971 &self.inlay_hint_cache
18972 }
18973
18974 pub fn replay_insert_event(
18975 &mut self,
18976 text: &str,
18977 relative_utf16_range: Option<Range<isize>>,
18978 window: &mut Window,
18979 cx: &mut Context<Self>,
18980 ) {
18981 if !self.input_enabled {
18982 cx.emit(EditorEvent::InputIgnored { text: text.into() });
18983 return;
18984 }
18985 if let Some(relative_utf16_range) = relative_utf16_range {
18986 let selections = self.selections.all::<OffsetUtf16>(cx);
18987 self.change_selections(None, window, cx, |s| {
18988 let new_ranges = selections.into_iter().map(|range| {
18989 let start = OffsetUtf16(
18990 range
18991 .head()
18992 .0
18993 .saturating_add_signed(relative_utf16_range.start),
18994 );
18995 let end = OffsetUtf16(
18996 range
18997 .head()
18998 .0
18999 .saturating_add_signed(relative_utf16_range.end),
19000 );
19001 start..end
19002 });
19003 s.select_ranges(new_ranges);
19004 });
19005 }
19006
19007 self.handle_input(text, window, cx);
19008 }
19009
19010 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19011 let Some(provider) = self.semantics_provider.as_ref() else {
19012 return false;
19013 };
19014
19015 let mut supports = false;
19016 self.buffer().update(cx, |this, cx| {
19017 this.for_each_buffer(|buffer| {
19018 supports |= provider.supports_inlay_hints(buffer, cx);
19019 });
19020 });
19021
19022 supports
19023 }
19024
19025 pub fn is_focused(&self, window: &Window) -> bool {
19026 self.focus_handle.is_focused(window)
19027 }
19028
19029 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19030 cx.emit(EditorEvent::Focused);
19031
19032 if let Some(descendant) = self
19033 .last_focused_descendant
19034 .take()
19035 .and_then(|descendant| descendant.upgrade())
19036 {
19037 window.focus(&descendant);
19038 } else {
19039 if let Some(blame) = self.blame.as_ref() {
19040 blame.update(cx, GitBlame::focus)
19041 }
19042
19043 self.blink_manager.update(cx, BlinkManager::enable);
19044 self.show_cursor_names(window, cx);
19045 self.buffer.update(cx, |buffer, cx| {
19046 buffer.finalize_last_transaction(cx);
19047 if self.leader_id.is_none() {
19048 buffer.set_active_selections(
19049 &self.selections.disjoint_anchors(),
19050 self.selections.line_mode,
19051 self.cursor_shape,
19052 cx,
19053 );
19054 }
19055 });
19056 }
19057 }
19058
19059 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19060 cx.emit(EditorEvent::FocusedIn)
19061 }
19062
19063 fn handle_focus_out(
19064 &mut self,
19065 event: FocusOutEvent,
19066 _window: &mut Window,
19067 cx: &mut Context<Self>,
19068 ) {
19069 if event.blurred != self.focus_handle {
19070 self.last_focused_descendant = Some(event.blurred);
19071 }
19072 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19073 }
19074
19075 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19076 self.blink_manager.update(cx, BlinkManager::disable);
19077 self.buffer
19078 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19079
19080 if let Some(blame) = self.blame.as_ref() {
19081 blame.update(cx, GitBlame::blur)
19082 }
19083 if !self.hover_state.focused(window, cx) {
19084 hide_hover(self, cx);
19085 }
19086 if !self
19087 .context_menu
19088 .borrow()
19089 .as_ref()
19090 .is_some_and(|context_menu| context_menu.focused(window, cx))
19091 {
19092 self.hide_context_menu(window, cx);
19093 }
19094 self.discard_inline_completion(false, cx);
19095 cx.emit(EditorEvent::Blurred);
19096 cx.notify();
19097 }
19098
19099 pub fn register_action<A: Action>(
19100 &mut self,
19101 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19102 ) -> Subscription {
19103 let id = self.next_editor_action_id.post_inc();
19104 let listener = Arc::new(listener);
19105 self.editor_actions.borrow_mut().insert(
19106 id,
19107 Box::new(move |window, _| {
19108 let listener = listener.clone();
19109 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19110 let action = action.downcast_ref().unwrap();
19111 if phase == DispatchPhase::Bubble {
19112 listener(action, window, cx)
19113 }
19114 })
19115 }),
19116 );
19117
19118 let editor_actions = self.editor_actions.clone();
19119 Subscription::new(move || {
19120 editor_actions.borrow_mut().remove(&id);
19121 })
19122 }
19123
19124 pub fn file_header_size(&self) -> u32 {
19125 FILE_HEADER_HEIGHT
19126 }
19127
19128 pub fn restore(
19129 &mut self,
19130 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19131 window: &mut Window,
19132 cx: &mut Context<Self>,
19133 ) {
19134 let workspace = self.workspace();
19135 let project = self.project.as_ref();
19136 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19137 let mut tasks = Vec::new();
19138 for (buffer_id, changes) in revert_changes {
19139 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19140 buffer.update(cx, |buffer, cx| {
19141 buffer.edit(
19142 changes
19143 .into_iter()
19144 .map(|(range, text)| (range, text.to_string())),
19145 None,
19146 cx,
19147 );
19148 });
19149
19150 if let Some(project) =
19151 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19152 {
19153 project.update(cx, |project, cx| {
19154 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19155 })
19156 }
19157 }
19158 }
19159 tasks
19160 });
19161 cx.spawn_in(window, async move |_, cx| {
19162 for (buffer, task) in save_tasks {
19163 let result = task.await;
19164 if result.is_err() {
19165 let Some(path) = buffer
19166 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19167 .ok()
19168 else {
19169 continue;
19170 };
19171 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19172 let Some(task) = cx
19173 .update_window_entity(&workspace, |workspace, window, cx| {
19174 workspace
19175 .open_path_preview(path, None, false, false, false, window, cx)
19176 })
19177 .ok()
19178 else {
19179 continue;
19180 };
19181 task.await.log_err();
19182 }
19183 }
19184 }
19185 })
19186 .detach();
19187 self.change_selections(None, window, cx, |selections| selections.refresh());
19188 }
19189
19190 pub fn to_pixel_point(
19191 &self,
19192 source: multi_buffer::Anchor,
19193 editor_snapshot: &EditorSnapshot,
19194 window: &mut Window,
19195 ) -> Option<gpui::Point<Pixels>> {
19196 let source_point = source.to_display_point(editor_snapshot);
19197 self.display_to_pixel_point(source_point, editor_snapshot, window)
19198 }
19199
19200 pub fn display_to_pixel_point(
19201 &self,
19202 source: DisplayPoint,
19203 editor_snapshot: &EditorSnapshot,
19204 window: &mut Window,
19205 ) -> Option<gpui::Point<Pixels>> {
19206 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19207 let text_layout_details = self.text_layout_details(window);
19208 let scroll_top = text_layout_details
19209 .scroll_anchor
19210 .scroll_position(editor_snapshot)
19211 .y;
19212
19213 if source.row().as_f32() < scroll_top.floor() {
19214 return None;
19215 }
19216 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19217 let source_y = line_height * (source.row().as_f32() - scroll_top);
19218 Some(gpui::Point::new(source_x, source_y))
19219 }
19220
19221 pub fn has_visible_completions_menu(&self) -> bool {
19222 !self.edit_prediction_preview_is_active()
19223 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19224 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19225 })
19226 }
19227
19228 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19229 if self.mode.is_minimap() {
19230 return;
19231 }
19232 self.addons
19233 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19234 }
19235
19236 pub fn unregister_addon<T: Addon>(&mut self) {
19237 self.addons.remove(&std::any::TypeId::of::<T>());
19238 }
19239
19240 pub fn addon<T: Addon>(&self) -> Option<&T> {
19241 let type_id = std::any::TypeId::of::<T>();
19242 self.addons
19243 .get(&type_id)
19244 .and_then(|item| item.to_any().downcast_ref::<T>())
19245 }
19246
19247 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19248 let type_id = std::any::TypeId::of::<T>();
19249 self.addons
19250 .get_mut(&type_id)
19251 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19252 }
19253
19254 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19255 let text_layout_details = self.text_layout_details(window);
19256 let style = &text_layout_details.editor_style;
19257 let font_id = window.text_system().resolve_font(&style.text.font());
19258 let font_size = style.text.font_size.to_pixels(window.rem_size());
19259 let line_height = style.text.line_height_in_pixels(window.rem_size());
19260 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19261
19262 gpui::Size::new(em_width, line_height)
19263 }
19264
19265 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19266 self.load_diff_task.clone()
19267 }
19268
19269 fn read_metadata_from_db(
19270 &mut self,
19271 item_id: u64,
19272 workspace_id: WorkspaceId,
19273 window: &mut Window,
19274 cx: &mut Context<Editor>,
19275 ) {
19276 if self.is_singleton(cx)
19277 && !self.mode.is_minimap()
19278 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19279 {
19280 let buffer_snapshot = OnceCell::new();
19281
19282 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19283 if !folds.is_empty() {
19284 let snapshot =
19285 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19286 self.fold_ranges(
19287 folds
19288 .into_iter()
19289 .map(|(start, end)| {
19290 snapshot.clip_offset(start, Bias::Left)
19291 ..snapshot.clip_offset(end, Bias::Right)
19292 })
19293 .collect(),
19294 false,
19295 window,
19296 cx,
19297 );
19298 }
19299 }
19300
19301 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19302 if !selections.is_empty() {
19303 let snapshot =
19304 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19305 self.change_selections(None, window, cx, |s| {
19306 s.select_ranges(selections.into_iter().map(|(start, end)| {
19307 snapshot.clip_offset(start, Bias::Left)
19308 ..snapshot.clip_offset(end, Bias::Right)
19309 }));
19310 });
19311 }
19312 };
19313 }
19314
19315 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19316 }
19317}
19318
19319fn vim_enabled(cx: &App) -> bool {
19320 cx.global::<SettingsStore>()
19321 .raw_user_settings()
19322 .get("vim_mode")
19323 == Some(&serde_json::Value::Bool(true))
19324}
19325
19326// Consider user intent and default settings
19327fn choose_completion_range(
19328 completion: &Completion,
19329 intent: CompletionIntent,
19330 buffer: &Entity<Buffer>,
19331 cx: &mut Context<Editor>,
19332) -> Range<usize> {
19333 fn should_replace(
19334 completion: &Completion,
19335 insert_range: &Range<text::Anchor>,
19336 intent: CompletionIntent,
19337 completion_mode_setting: LspInsertMode,
19338 buffer: &Buffer,
19339 ) -> bool {
19340 // specific actions take precedence over settings
19341 match intent {
19342 CompletionIntent::CompleteWithInsert => return false,
19343 CompletionIntent::CompleteWithReplace => return true,
19344 CompletionIntent::Complete | CompletionIntent::Compose => {}
19345 }
19346
19347 match completion_mode_setting {
19348 LspInsertMode::Insert => false,
19349 LspInsertMode::Replace => true,
19350 LspInsertMode::ReplaceSubsequence => {
19351 let mut text_to_replace = buffer.chars_for_range(
19352 buffer.anchor_before(completion.replace_range.start)
19353 ..buffer.anchor_after(completion.replace_range.end),
19354 );
19355 let mut completion_text = completion.new_text.chars();
19356
19357 // is `text_to_replace` a subsequence of `completion_text`
19358 text_to_replace
19359 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19360 }
19361 LspInsertMode::ReplaceSuffix => {
19362 let range_after_cursor = insert_range.end..completion.replace_range.end;
19363
19364 let text_after_cursor = buffer
19365 .text_for_range(
19366 buffer.anchor_before(range_after_cursor.start)
19367 ..buffer.anchor_after(range_after_cursor.end),
19368 )
19369 .collect::<String>();
19370 completion.new_text.ends_with(&text_after_cursor)
19371 }
19372 }
19373 }
19374
19375 let buffer = buffer.read(cx);
19376
19377 if let CompletionSource::Lsp {
19378 insert_range: Some(insert_range),
19379 ..
19380 } = &completion.source
19381 {
19382 let completion_mode_setting =
19383 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19384 .completions
19385 .lsp_insert_mode;
19386
19387 if !should_replace(
19388 completion,
19389 &insert_range,
19390 intent,
19391 completion_mode_setting,
19392 buffer,
19393 ) {
19394 return insert_range.to_offset(buffer);
19395 }
19396 }
19397
19398 completion.replace_range.to_offset(buffer)
19399}
19400
19401fn insert_extra_newline_brackets(
19402 buffer: &MultiBufferSnapshot,
19403 range: Range<usize>,
19404 language: &language::LanguageScope,
19405) -> bool {
19406 let leading_whitespace_len = buffer
19407 .reversed_chars_at(range.start)
19408 .take_while(|c| c.is_whitespace() && *c != '\n')
19409 .map(|c| c.len_utf8())
19410 .sum::<usize>();
19411 let trailing_whitespace_len = buffer
19412 .chars_at(range.end)
19413 .take_while(|c| c.is_whitespace() && *c != '\n')
19414 .map(|c| c.len_utf8())
19415 .sum::<usize>();
19416 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19417
19418 language.brackets().any(|(pair, enabled)| {
19419 let pair_start = pair.start.trim_end();
19420 let pair_end = pair.end.trim_start();
19421
19422 enabled
19423 && pair.newline
19424 && buffer.contains_str_at(range.end, pair_end)
19425 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19426 })
19427}
19428
19429fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19430 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19431 [(buffer, range, _)] => (*buffer, range.clone()),
19432 _ => return false,
19433 };
19434 let pair = {
19435 let mut result: Option<BracketMatch> = None;
19436
19437 for pair in buffer
19438 .all_bracket_ranges(range.clone())
19439 .filter(move |pair| {
19440 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19441 })
19442 {
19443 let len = pair.close_range.end - pair.open_range.start;
19444
19445 if let Some(existing) = &result {
19446 let existing_len = existing.close_range.end - existing.open_range.start;
19447 if len > existing_len {
19448 continue;
19449 }
19450 }
19451
19452 result = Some(pair);
19453 }
19454
19455 result
19456 };
19457 let Some(pair) = pair else {
19458 return false;
19459 };
19460 pair.newline_only
19461 && buffer
19462 .chars_for_range(pair.open_range.end..range.start)
19463 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19464 .all(|c| c.is_whitespace() && c != '\n')
19465}
19466
19467fn update_uncommitted_diff_for_buffer(
19468 editor: Entity<Editor>,
19469 project: &Entity<Project>,
19470 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19471 buffer: Entity<MultiBuffer>,
19472 cx: &mut App,
19473) -> Task<()> {
19474 let mut tasks = Vec::new();
19475 project.update(cx, |project, cx| {
19476 for buffer in buffers {
19477 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19478 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19479 }
19480 }
19481 });
19482 cx.spawn(async move |cx| {
19483 let diffs = future::join_all(tasks).await;
19484 if editor
19485 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19486 .unwrap_or(false)
19487 {
19488 return;
19489 }
19490
19491 buffer
19492 .update(cx, |buffer, cx| {
19493 for diff in diffs.into_iter().flatten() {
19494 buffer.add_diff(diff, cx);
19495 }
19496 })
19497 .ok();
19498 })
19499}
19500
19501pub trait CollaborationHub {
19502 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19503 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19504 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19505}
19506
19507impl CollaborationHub for Entity<Project> {
19508 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19509 self.read(cx).collaborators()
19510 }
19511
19512 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19513 self.read(cx).user_store().read(cx).participant_indices()
19514 }
19515
19516 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19517 let this = self.read(cx);
19518 let user_ids = this.collaborators().values().map(|c| c.user_id);
19519 this.user_store().read_with(cx, |user_store, cx| {
19520 user_store.participant_names(user_ids, cx)
19521 })
19522 }
19523}
19524
19525pub trait SemanticsProvider {
19526 fn hover(
19527 &self,
19528 buffer: &Entity<Buffer>,
19529 position: text::Anchor,
19530 cx: &mut App,
19531 ) -> Option<Task<Vec<project::Hover>>>;
19532
19533 fn inline_values(
19534 &self,
19535 buffer_handle: Entity<Buffer>,
19536 range: Range<text::Anchor>,
19537 cx: &mut App,
19538 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19539
19540 fn inlay_hints(
19541 &self,
19542 buffer_handle: Entity<Buffer>,
19543 range: Range<text::Anchor>,
19544 cx: &mut App,
19545 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19546
19547 fn resolve_inlay_hint(
19548 &self,
19549 hint: InlayHint,
19550 buffer_handle: Entity<Buffer>,
19551 server_id: LanguageServerId,
19552 cx: &mut App,
19553 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19554
19555 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19556
19557 fn document_highlights(
19558 &self,
19559 buffer: &Entity<Buffer>,
19560 position: text::Anchor,
19561 cx: &mut App,
19562 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19563
19564 fn definitions(
19565 &self,
19566 buffer: &Entity<Buffer>,
19567 position: text::Anchor,
19568 kind: GotoDefinitionKind,
19569 cx: &mut App,
19570 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19571
19572 fn range_for_rename(
19573 &self,
19574 buffer: &Entity<Buffer>,
19575 position: text::Anchor,
19576 cx: &mut App,
19577 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19578
19579 fn perform_rename(
19580 &self,
19581 buffer: &Entity<Buffer>,
19582 position: text::Anchor,
19583 new_name: String,
19584 cx: &mut App,
19585 ) -> Option<Task<Result<ProjectTransaction>>>;
19586}
19587
19588pub trait CompletionProvider {
19589 fn completions(
19590 &self,
19591 excerpt_id: ExcerptId,
19592 buffer: &Entity<Buffer>,
19593 buffer_position: text::Anchor,
19594 trigger: CompletionContext,
19595 window: &mut Window,
19596 cx: &mut Context<Editor>,
19597 ) -> Task<Result<Option<Vec<Completion>>>>;
19598
19599 fn resolve_completions(
19600 &self,
19601 buffer: Entity<Buffer>,
19602 completion_indices: Vec<usize>,
19603 completions: Rc<RefCell<Box<[Completion]>>>,
19604 cx: &mut Context<Editor>,
19605 ) -> Task<Result<bool>>;
19606
19607 fn apply_additional_edits_for_completion(
19608 &self,
19609 _buffer: Entity<Buffer>,
19610 _completions: Rc<RefCell<Box<[Completion]>>>,
19611 _completion_index: usize,
19612 _push_to_history: bool,
19613 _cx: &mut Context<Editor>,
19614 ) -> Task<Result<Option<language::Transaction>>> {
19615 Task::ready(Ok(None))
19616 }
19617
19618 fn is_completion_trigger(
19619 &self,
19620 buffer: &Entity<Buffer>,
19621 position: language::Anchor,
19622 text: &str,
19623 trigger_in_words: bool,
19624 cx: &mut Context<Editor>,
19625 ) -> bool;
19626
19627 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
19628
19629 fn sort_completions(&self) -> bool {
19630 true
19631 }
19632
19633 fn filter_completions(&self) -> bool {
19634 true
19635 }
19636}
19637
19638pub trait CodeActionProvider {
19639 fn id(&self) -> Arc<str>;
19640
19641 fn code_actions(
19642 &self,
19643 buffer: &Entity<Buffer>,
19644 range: Range<text::Anchor>,
19645 window: &mut Window,
19646 cx: &mut App,
19647 ) -> Task<Result<Vec<CodeAction>>>;
19648
19649 fn apply_code_action(
19650 &self,
19651 buffer_handle: Entity<Buffer>,
19652 action: CodeAction,
19653 excerpt_id: ExcerptId,
19654 push_to_history: bool,
19655 window: &mut Window,
19656 cx: &mut App,
19657 ) -> Task<Result<ProjectTransaction>>;
19658}
19659
19660impl CodeActionProvider for Entity<Project> {
19661 fn id(&self) -> Arc<str> {
19662 "project".into()
19663 }
19664
19665 fn code_actions(
19666 &self,
19667 buffer: &Entity<Buffer>,
19668 range: Range<text::Anchor>,
19669 _window: &mut Window,
19670 cx: &mut App,
19671 ) -> Task<Result<Vec<CodeAction>>> {
19672 self.update(cx, |project, cx| {
19673 let code_lens = project.code_lens(buffer, range.clone(), cx);
19674 let code_actions = project.code_actions(buffer, range, None, cx);
19675 cx.background_spawn(async move {
19676 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19677 Ok(code_lens
19678 .context("code lens fetch")?
19679 .into_iter()
19680 .chain(code_actions.context("code action fetch")?)
19681 .collect())
19682 })
19683 })
19684 }
19685
19686 fn apply_code_action(
19687 &self,
19688 buffer_handle: Entity<Buffer>,
19689 action: CodeAction,
19690 _excerpt_id: ExcerptId,
19691 push_to_history: bool,
19692 _window: &mut Window,
19693 cx: &mut App,
19694 ) -> Task<Result<ProjectTransaction>> {
19695 self.update(cx, |project, cx| {
19696 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19697 })
19698 }
19699}
19700
19701fn snippet_completions(
19702 project: &Project,
19703 buffer: &Entity<Buffer>,
19704 buffer_position: text::Anchor,
19705 cx: &mut App,
19706) -> Task<Result<Vec<Completion>>> {
19707 let languages = buffer.read(cx).languages_at(buffer_position);
19708 let snippet_store = project.snippets().read(cx);
19709
19710 let scopes: Vec<_> = languages
19711 .iter()
19712 .filter_map(|language| {
19713 let language_name = language.lsp_id();
19714 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19715
19716 if snippets.is_empty() {
19717 None
19718 } else {
19719 Some((language.default_scope(), snippets))
19720 }
19721 })
19722 .collect();
19723
19724 if scopes.is_empty() {
19725 return Task::ready(Ok(vec![]));
19726 }
19727
19728 let snapshot = buffer.read(cx).text_snapshot();
19729 let chars: String = snapshot
19730 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19731 .collect();
19732 let executor = cx.background_executor().clone();
19733
19734 cx.background_spawn(async move {
19735 let mut all_results: Vec<Completion> = Vec::new();
19736 for (scope, snippets) in scopes.into_iter() {
19737 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19738 let mut last_word = chars
19739 .chars()
19740 .take_while(|c| classifier.is_word(*c))
19741 .collect::<String>();
19742 last_word = last_word.chars().rev().collect();
19743
19744 if last_word.is_empty() {
19745 return Ok(vec![]);
19746 }
19747
19748 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19749 let to_lsp = |point: &text::Anchor| {
19750 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19751 point_to_lsp(end)
19752 };
19753 let lsp_end = to_lsp(&buffer_position);
19754
19755 let candidates = snippets
19756 .iter()
19757 .enumerate()
19758 .flat_map(|(ix, snippet)| {
19759 snippet
19760 .prefix
19761 .iter()
19762 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19763 })
19764 .collect::<Vec<StringMatchCandidate>>();
19765
19766 let mut matches = fuzzy::match_strings(
19767 &candidates,
19768 &last_word,
19769 last_word.chars().any(|c| c.is_uppercase()),
19770 100,
19771 &Default::default(),
19772 executor.clone(),
19773 )
19774 .await;
19775
19776 // Remove all candidates where the query's start does not match the start of any word in the candidate
19777 if let Some(query_start) = last_word.chars().next() {
19778 matches.retain(|string_match| {
19779 split_words(&string_match.string).any(|word| {
19780 // Check that the first codepoint of the word as lowercase matches the first
19781 // codepoint of the query as lowercase
19782 word.chars()
19783 .flat_map(|codepoint| codepoint.to_lowercase())
19784 .zip(query_start.to_lowercase())
19785 .all(|(word_cp, query_cp)| word_cp == query_cp)
19786 })
19787 });
19788 }
19789
19790 let matched_strings = matches
19791 .into_iter()
19792 .map(|m| m.string)
19793 .collect::<HashSet<_>>();
19794
19795 let mut result: Vec<Completion> = snippets
19796 .iter()
19797 .filter_map(|snippet| {
19798 let matching_prefix = snippet
19799 .prefix
19800 .iter()
19801 .find(|prefix| matched_strings.contains(*prefix))?;
19802 let start = as_offset - last_word.len();
19803 let start = snapshot.anchor_before(start);
19804 let range = start..buffer_position;
19805 let lsp_start = to_lsp(&start);
19806 let lsp_range = lsp::Range {
19807 start: lsp_start,
19808 end: lsp_end,
19809 };
19810 Some(Completion {
19811 replace_range: range,
19812 new_text: snippet.body.clone(),
19813 source: CompletionSource::Lsp {
19814 insert_range: None,
19815 server_id: LanguageServerId(usize::MAX),
19816 resolved: true,
19817 lsp_completion: Box::new(lsp::CompletionItem {
19818 label: snippet.prefix.first().unwrap().clone(),
19819 kind: Some(CompletionItemKind::SNIPPET),
19820 label_details: snippet.description.as_ref().map(|description| {
19821 lsp::CompletionItemLabelDetails {
19822 detail: Some(description.clone()),
19823 description: None,
19824 }
19825 }),
19826 insert_text_format: Some(InsertTextFormat::SNIPPET),
19827 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19828 lsp::InsertReplaceEdit {
19829 new_text: snippet.body.clone(),
19830 insert: lsp_range,
19831 replace: lsp_range,
19832 },
19833 )),
19834 filter_text: Some(snippet.body.clone()),
19835 sort_text: Some(char::MAX.to_string()),
19836 ..lsp::CompletionItem::default()
19837 }),
19838 lsp_defaults: None,
19839 },
19840 label: CodeLabel {
19841 text: matching_prefix.clone(),
19842 runs: Vec::new(),
19843 filter_range: 0..matching_prefix.len(),
19844 },
19845 icon_path: None,
19846 documentation: Some(
19847 CompletionDocumentation::SingleLineAndMultiLinePlainText {
19848 single_line: snippet.name.clone().into(),
19849 plain_text: snippet
19850 .description
19851 .clone()
19852 .map(|description| description.into()),
19853 },
19854 ),
19855 insert_text_mode: None,
19856 confirm: None,
19857 })
19858 })
19859 .collect();
19860
19861 all_results.append(&mut result);
19862 }
19863
19864 Ok(all_results)
19865 })
19866}
19867
19868impl CompletionProvider for Entity<Project> {
19869 fn completions(
19870 &self,
19871 _excerpt_id: ExcerptId,
19872 buffer: &Entity<Buffer>,
19873 buffer_position: text::Anchor,
19874 options: CompletionContext,
19875 _window: &mut Window,
19876 cx: &mut Context<Editor>,
19877 ) -> Task<Result<Option<Vec<Completion>>>> {
19878 self.update(cx, |project, cx| {
19879 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19880 let project_completions = project.completions(buffer, buffer_position, options, cx);
19881 cx.background_spawn(async move {
19882 let snippets_completions = snippets.await?;
19883 match project_completions.await? {
19884 Some(mut completions) => {
19885 completions.extend(snippets_completions);
19886 Ok(Some(completions))
19887 }
19888 None => {
19889 if snippets_completions.is_empty() {
19890 Ok(None)
19891 } else {
19892 Ok(Some(snippets_completions))
19893 }
19894 }
19895 }
19896 })
19897 })
19898 }
19899
19900 fn resolve_completions(
19901 &self,
19902 buffer: Entity<Buffer>,
19903 completion_indices: Vec<usize>,
19904 completions: Rc<RefCell<Box<[Completion]>>>,
19905 cx: &mut Context<Editor>,
19906 ) -> Task<Result<bool>> {
19907 self.update(cx, |project, cx| {
19908 project.lsp_store().update(cx, |lsp_store, cx| {
19909 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19910 })
19911 })
19912 }
19913
19914 fn apply_additional_edits_for_completion(
19915 &self,
19916 buffer: Entity<Buffer>,
19917 completions: Rc<RefCell<Box<[Completion]>>>,
19918 completion_index: usize,
19919 push_to_history: bool,
19920 cx: &mut Context<Editor>,
19921 ) -> Task<Result<Option<language::Transaction>>> {
19922 self.update(cx, |project, cx| {
19923 project.lsp_store().update(cx, |lsp_store, cx| {
19924 lsp_store.apply_additional_edits_for_completion(
19925 buffer,
19926 completions,
19927 completion_index,
19928 push_to_history,
19929 cx,
19930 )
19931 })
19932 })
19933 }
19934
19935 fn is_completion_trigger(
19936 &self,
19937 buffer: &Entity<Buffer>,
19938 position: language::Anchor,
19939 text: &str,
19940 trigger_in_words: bool,
19941 cx: &mut Context<Editor>,
19942 ) -> bool {
19943 let mut chars = text.chars();
19944 let char = if let Some(char) = chars.next() {
19945 char
19946 } else {
19947 return false;
19948 };
19949 if chars.next().is_some() {
19950 return false;
19951 }
19952
19953 let buffer = buffer.read(cx);
19954 let snapshot = buffer.snapshot();
19955 if !snapshot.settings_at(position, cx).show_completions_on_input {
19956 return false;
19957 }
19958 let classifier = snapshot.char_classifier_at(position).for_completion(true);
19959 if trigger_in_words && classifier.is_word(char) {
19960 return true;
19961 }
19962
19963 buffer.completion_triggers().contains(text)
19964 }
19965}
19966
19967impl SemanticsProvider for Entity<Project> {
19968 fn hover(
19969 &self,
19970 buffer: &Entity<Buffer>,
19971 position: text::Anchor,
19972 cx: &mut App,
19973 ) -> Option<Task<Vec<project::Hover>>> {
19974 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
19975 }
19976
19977 fn document_highlights(
19978 &self,
19979 buffer: &Entity<Buffer>,
19980 position: text::Anchor,
19981 cx: &mut App,
19982 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
19983 Some(self.update(cx, |project, cx| {
19984 project.document_highlights(buffer, position, cx)
19985 }))
19986 }
19987
19988 fn definitions(
19989 &self,
19990 buffer: &Entity<Buffer>,
19991 position: text::Anchor,
19992 kind: GotoDefinitionKind,
19993 cx: &mut App,
19994 ) -> Option<Task<Result<Vec<LocationLink>>>> {
19995 Some(self.update(cx, |project, cx| match kind {
19996 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
19997 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
19998 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
19999 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20000 }))
20001 }
20002
20003 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20004 // TODO: make this work for remote projects
20005 self.update(cx, |project, cx| {
20006 if project
20007 .active_debug_session(cx)
20008 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20009 {
20010 return true;
20011 }
20012
20013 buffer.update(cx, |buffer, cx| {
20014 project.any_language_server_supports_inlay_hints(buffer, cx)
20015 })
20016 })
20017 }
20018
20019 fn inline_values(
20020 &self,
20021 buffer_handle: Entity<Buffer>,
20022
20023 range: Range<text::Anchor>,
20024 cx: &mut App,
20025 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20026 self.update(cx, |project, cx| {
20027 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20028
20029 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20030 })
20031 }
20032
20033 fn inlay_hints(
20034 &self,
20035 buffer_handle: Entity<Buffer>,
20036 range: Range<text::Anchor>,
20037 cx: &mut App,
20038 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20039 Some(self.update(cx, |project, cx| {
20040 project.inlay_hints(buffer_handle, range, cx)
20041 }))
20042 }
20043
20044 fn resolve_inlay_hint(
20045 &self,
20046 hint: InlayHint,
20047 buffer_handle: Entity<Buffer>,
20048 server_id: LanguageServerId,
20049 cx: &mut App,
20050 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20051 Some(self.update(cx, |project, cx| {
20052 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20053 }))
20054 }
20055
20056 fn range_for_rename(
20057 &self,
20058 buffer: &Entity<Buffer>,
20059 position: text::Anchor,
20060 cx: &mut App,
20061 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20062 Some(self.update(cx, |project, cx| {
20063 let buffer = buffer.clone();
20064 let task = project.prepare_rename(buffer.clone(), position, cx);
20065 cx.spawn(async move |_, cx| {
20066 Ok(match task.await? {
20067 PrepareRenameResponse::Success(range) => Some(range),
20068 PrepareRenameResponse::InvalidPosition => None,
20069 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20070 // Fallback on using TreeSitter info to determine identifier range
20071 buffer.update(cx, |buffer, _| {
20072 let snapshot = buffer.snapshot();
20073 let (range, kind) = snapshot.surrounding_word(position);
20074 if kind != Some(CharKind::Word) {
20075 return None;
20076 }
20077 Some(
20078 snapshot.anchor_before(range.start)
20079 ..snapshot.anchor_after(range.end),
20080 )
20081 })?
20082 }
20083 })
20084 })
20085 }))
20086 }
20087
20088 fn perform_rename(
20089 &self,
20090 buffer: &Entity<Buffer>,
20091 position: text::Anchor,
20092 new_name: String,
20093 cx: &mut App,
20094 ) -> Option<Task<Result<ProjectTransaction>>> {
20095 Some(self.update(cx, |project, cx| {
20096 project.perform_rename(buffer.clone(), position, new_name, cx)
20097 }))
20098 }
20099}
20100
20101fn inlay_hint_settings(
20102 location: Anchor,
20103 snapshot: &MultiBufferSnapshot,
20104 cx: &mut Context<Editor>,
20105) -> InlayHintSettings {
20106 let file = snapshot.file_at(location);
20107 let language = snapshot.language_at(location).map(|l| l.name());
20108 language_settings(language, file, cx).inlay_hints
20109}
20110
20111fn consume_contiguous_rows(
20112 contiguous_row_selections: &mut Vec<Selection<Point>>,
20113 selection: &Selection<Point>,
20114 display_map: &DisplaySnapshot,
20115 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20116) -> (MultiBufferRow, MultiBufferRow) {
20117 contiguous_row_selections.push(selection.clone());
20118 let start_row = MultiBufferRow(selection.start.row);
20119 let mut end_row = ending_row(selection, display_map);
20120
20121 while let Some(next_selection) = selections.peek() {
20122 if next_selection.start.row <= end_row.0 {
20123 end_row = ending_row(next_selection, display_map);
20124 contiguous_row_selections.push(selections.next().unwrap().clone());
20125 } else {
20126 break;
20127 }
20128 }
20129 (start_row, end_row)
20130}
20131
20132fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20133 if next_selection.end.column > 0 || next_selection.is_empty() {
20134 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20135 } else {
20136 MultiBufferRow(next_selection.end.row)
20137 }
20138}
20139
20140impl EditorSnapshot {
20141 pub fn remote_selections_in_range<'a>(
20142 &'a self,
20143 range: &'a Range<Anchor>,
20144 collaboration_hub: &dyn CollaborationHub,
20145 cx: &'a App,
20146 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20147 let participant_names = collaboration_hub.user_names(cx);
20148 let participant_indices = collaboration_hub.user_participant_indices(cx);
20149 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20150 let collaborators_by_replica_id = collaborators_by_peer_id
20151 .values()
20152 .map(|collaborator| (collaborator.replica_id, collaborator))
20153 .collect::<HashMap<_, _>>();
20154 self.buffer_snapshot
20155 .selections_in_range(range, false)
20156 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20157 if replica_id == AGENT_REPLICA_ID {
20158 Some(RemoteSelection {
20159 replica_id,
20160 selection,
20161 cursor_shape,
20162 line_mode,
20163 collaborator_id: CollaboratorId::Agent,
20164 user_name: Some("Agent".into()),
20165 color: cx.theme().players().agent(),
20166 })
20167 } else {
20168 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20169 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20170 let user_name = participant_names.get(&collaborator.user_id).cloned();
20171 Some(RemoteSelection {
20172 replica_id,
20173 selection,
20174 cursor_shape,
20175 line_mode,
20176 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20177 user_name,
20178 color: if let Some(index) = participant_index {
20179 cx.theme().players().color_for_participant(index.0)
20180 } else {
20181 cx.theme().players().absent()
20182 },
20183 })
20184 }
20185 })
20186 }
20187
20188 pub fn hunks_for_ranges(
20189 &self,
20190 ranges: impl IntoIterator<Item = Range<Point>>,
20191 ) -> Vec<MultiBufferDiffHunk> {
20192 let mut hunks = Vec::new();
20193 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20194 HashMap::default();
20195 for query_range in ranges {
20196 let query_rows =
20197 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20198 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20199 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20200 ) {
20201 // Include deleted hunks that are adjacent to the query range, because
20202 // otherwise they would be missed.
20203 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20204 if hunk.status().is_deleted() {
20205 intersects_range |= hunk.row_range.start == query_rows.end;
20206 intersects_range |= hunk.row_range.end == query_rows.start;
20207 }
20208 if intersects_range {
20209 if !processed_buffer_rows
20210 .entry(hunk.buffer_id)
20211 .or_default()
20212 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20213 {
20214 continue;
20215 }
20216 hunks.push(hunk);
20217 }
20218 }
20219 }
20220
20221 hunks
20222 }
20223
20224 fn display_diff_hunks_for_rows<'a>(
20225 &'a self,
20226 display_rows: Range<DisplayRow>,
20227 folded_buffers: &'a HashSet<BufferId>,
20228 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20229 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20230 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20231
20232 self.buffer_snapshot
20233 .diff_hunks_in_range(buffer_start..buffer_end)
20234 .filter_map(|hunk| {
20235 if folded_buffers.contains(&hunk.buffer_id) {
20236 return None;
20237 }
20238
20239 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20240 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20241
20242 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20243 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20244
20245 let display_hunk = if hunk_display_start.column() != 0 {
20246 DisplayDiffHunk::Folded {
20247 display_row: hunk_display_start.row(),
20248 }
20249 } else {
20250 let mut end_row = hunk_display_end.row();
20251 if hunk_display_end.column() > 0 {
20252 end_row.0 += 1;
20253 }
20254 let is_created_file = hunk.is_created_file();
20255 DisplayDiffHunk::Unfolded {
20256 status: hunk.status(),
20257 diff_base_byte_range: hunk.diff_base_byte_range,
20258 display_row_range: hunk_display_start.row()..end_row,
20259 multi_buffer_range: Anchor::range_in_buffer(
20260 hunk.excerpt_id,
20261 hunk.buffer_id,
20262 hunk.buffer_range,
20263 ),
20264 is_created_file,
20265 }
20266 };
20267
20268 Some(display_hunk)
20269 })
20270 }
20271
20272 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20273 self.display_snapshot.buffer_snapshot.language_at(position)
20274 }
20275
20276 pub fn is_focused(&self) -> bool {
20277 self.is_focused
20278 }
20279
20280 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20281 self.placeholder_text.as_ref()
20282 }
20283
20284 pub fn scroll_position(&self) -> gpui::Point<f32> {
20285 self.scroll_anchor.scroll_position(&self.display_snapshot)
20286 }
20287
20288 fn gutter_dimensions(
20289 &self,
20290 font_id: FontId,
20291 font_size: Pixels,
20292 max_line_number_width: Pixels,
20293 cx: &App,
20294 ) -> Option<GutterDimensions> {
20295 if !self.show_gutter {
20296 return None;
20297 }
20298
20299 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20300 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20301
20302 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20303 matches!(
20304 ProjectSettings::get_global(cx).git.git_gutter,
20305 Some(GitGutterSetting::TrackedFiles)
20306 )
20307 });
20308 let gutter_settings = EditorSettings::get_global(cx).gutter;
20309 let show_line_numbers = self
20310 .show_line_numbers
20311 .unwrap_or(gutter_settings.line_numbers);
20312 let line_gutter_width = if show_line_numbers {
20313 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20314 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20315 max_line_number_width.max(min_width_for_number_on_gutter)
20316 } else {
20317 0.0.into()
20318 };
20319
20320 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20321 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20322
20323 let git_blame_entries_width =
20324 self.git_blame_gutter_max_author_length
20325 .map(|max_author_length| {
20326 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20327 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20328
20329 /// The number of characters to dedicate to gaps and margins.
20330 const SPACING_WIDTH: usize = 4;
20331
20332 let max_char_count = max_author_length.min(renderer.max_author_length())
20333 + ::git::SHORT_SHA_LENGTH
20334 + MAX_RELATIVE_TIMESTAMP.len()
20335 + SPACING_WIDTH;
20336
20337 em_advance * max_char_count
20338 });
20339
20340 let is_singleton = self.buffer_snapshot.is_singleton();
20341
20342 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20343 left_padding += if !is_singleton {
20344 em_width * 4.0
20345 } else if show_runnables || show_breakpoints {
20346 em_width * 3.0
20347 } else if show_git_gutter && show_line_numbers {
20348 em_width * 2.0
20349 } else if show_git_gutter || show_line_numbers {
20350 em_width
20351 } else {
20352 px(0.)
20353 };
20354
20355 let shows_folds = is_singleton && gutter_settings.folds;
20356
20357 let right_padding = if shows_folds && show_line_numbers {
20358 em_width * 4.0
20359 } else if shows_folds || (!is_singleton && show_line_numbers) {
20360 em_width * 3.0
20361 } else if show_line_numbers {
20362 em_width
20363 } else {
20364 px(0.)
20365 };
20366
20367 Some(GutterDimensions {
20368 left_padding,
20369 right_padding,
20370 width: line_gutter_width + left_padding + right_padding,
20371 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20372 git_blame_entries_width,
20373 })
20374 }
20375
20376 pub fn render_crease_toggle(
20377 &self,
20378 buffer_row: MultiBufferRow,
20379 row_contains_cursor: bool,
20380 editor: Entity<Editor>,
20381 window: &mut Window,
20382 cx: &mut App,
20383 ) -> Option<AnyElement> {
20384 let folded = self.is_line_folded(buffer_row);
20385 let mut is_foldable = false;
20386
20387 if let Some(crease) = self
20388 .crease_snapshot
20389 .query_row(buffer_row, &self.buffer_snapshot)
20390 {
20391 is_foldable = true;
20392 match crease {
20393 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20394 if let Some(render_toggle) = render_toggle {
20395 let toggle_callback =
20396 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20397 if folded {
20398 editor.update(cx, |editor, cx| {
20399 editor.fold_at(buffer_row, window, cx)
20400 });
20401 } else {
20402 editor.update(cx, |editor, cx| {
20403 editor.unfold_at(buffer_row, window, cx)
20404 });
20405 }
20406 });
20407 return Some((render_toggle)(
20408 buffer_row,
20409 folded,
20410 toggle_callback,
20411 window,
20412 cx,
20413 ));
20414 }
20415 }
20416 }
20417 }
20418
20419 is_foldable |= self.starts_indent(buffer_row);
20420
20421 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20422 Some(
20423 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20424 .toggle_state(folded)
20425 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20426 if folded {
20427 this.unfold_at(buffer_row, window, cx);
20428 } else {
20429 this.fold_at(buffer_row, window, cx);
20430 }
20431 }))
20432 .into_any_element(),
20433 )
20434 } else {
20435 None
20436 }
20437 }
20438
20439 pub fn render_crease_trailer(
20440 &self,
20441 buffer_row: MultiBufferRow,
20442 window: &mut Window,
20443 cx: &mut App,
20444 ) -> Option<AnyElement> {
20445 let folded = self.is_line_folded(buffer_row);
20446 if let Crease::Inline { render_trailer, .. } = self
20447 .crease_snapshot
20448 .query_row(buffer_row, &self.buffer_snapshot)?
20449 {
20450 let render_trailer = render_trailer.as_ref()?;
20451 Some(render_trailer(buffer_row, folded, window, cx))
20452 } else {
20453 None
20454 }
20455 }
20456}
20457
20458impl Deref for EditorSnapshot {
20459 type Target = DisplaySnapshot;
20460
20461 fn deref(&self) -> &Self::Target {
20462 &self.display_snapshot
20463 }
20464}
20465
20466#[derive(Clone, Debug, PartialEq, Eq)]
20467pub enum EditorEvent {
20468 InputIgnored {
20469 text: Arc<str>,
20470 },
20471 InputHandled {
20472 utf16_range_to_replace: Option<Range<isize>>,
20473 text: Arc<str>,
20474 },
20475 ExcerptsAdded {
20476 buffer: Entity<Buffer>,
20477 predecessor: ExcerptId,
20478 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20479 },
20480 ExcerptsRemoved {
20481 ids: Vec<ExcerptId>,
20482 removed_buffer_ids: Vec<BufferId>,
20483 },
20484 BufferFoldToggled {
20485 ids: Vec<ExcerptId>,
20486 folded: bool,
20487 },
20488 ExcerptsEdited {
20489 ids: Vec<ExcerptId>,
20490 },
20491 ExcerptsExpanded {
20492 ids: Vec<ExcerptId>,
20493 },
20494 BufferEdited,
20495 Edited {
20496 transaction_id: clock::Lamport,
20497 },
20498 Reparsed(BufferId),
20499 Focused,
20500 FocusedIn,
20501 Blurred,
20502 DirtyChanged,
20503 Saved,
20504 TitleChanged,
20505 DiffBaseChanged,
20506 SelectionsChanged {
20507 local: bool,
20508 },
20509 ScrollPositionChanged {
20510 local: bool,
20511 autoscroll: bool,
20512 },
20513 Closed,
20514 TransactionUndone {
20515 transaction_id: clock::Lamport,
20516 },
20517 TransactionBegun {
20518 transaction_id: clock::Lamport,
20519 },
20520 Reloaded,
20521 CursorShapeChanged,
20522 PushedToNavHistory {
20523 anchor: Anchor,
20524 is_deactivate: bool,
20525 },
20526}
20527
20528impl EventEmitter<EditorEvent> for Editor {}
20529
20530impl Focusable for Editor {
20531 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20532 self.focus_handle.clone()
20533 }
20534}
20535
20536impl Render for Editor {
20537 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20538 let settings = ThemeSettings::get_global(cx);
20539
20540 let mut text_style = match self.mode {
20541 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20542 color: cx.theme().colors().editor_foreground,
20543 font_family: settings.ui_font.family.clone(),
20544 font_features: settings.ui_font.features.clone(),
20545 font_fallbacks: settings.ui_font.fallbacks.clone(),
20546 font_size: rems(0.875).into(),
20547 font_weight: settings.ui_font.weight,
20548 line_height: relative(settings.buffer_line_height.value()),
20549 ..Default::default()
20550 },
20551 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20552 color: cx.theme().colors().editor_foreground,
20553 font_family: settings.buffer_font.family.clone(),
20554 font_features: settings.buffer_font.features.clone(),
20555 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20556 font_size: settings.buffer_font_size(cx).into(),
20557 font_weight: settings.buffer_font.weight,
20558 line_height: relative(settings.buffer_line_height.value()),
20559 ..Default::default()
20560 },
20561 };
20562 if let Some(text_style_refinement) = &self.text_style_refinement {
20563 text_style.refine(text_style_refinement)
20564 }
20565
20566 let background = match self.mode {
20567 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20568 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20569 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20570 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20571 };
20572
20573 EditorElement::new(
20574 &cx.entity(),
20575 EditorStyle {
20576 background,
20577 local_player: cx.theme().players().local(),
20578 text: text_style,
20579 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20580 syntax: cx.theme().syntax().clone(),
20581 status: cx.theme().status().clone(),
20582 inlay_hints_style: make_inlay_hints_style(cx),
20583 inline_completion_styles: make_suggestion_styles(cx),
20584 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20585 show_underlines: !self.mode.is_minimap(),
20586 },
20587 )
20588 }
20589}
20590
20591impl EntityInputHandler for Editor {
20592 fn text_for_range(
20593 &mut self,
20594 range_utf16: Range<usize>,
20595 adjusted_range: &mut Option<Range<usize>>,
20596 _: &mut Window,
20597 cx: &mut Context<Self>,
20598 ) -> Option<String> {
20599 let snapshot = self.buffer.read(cx).read(cx);
20600 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20601 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20602 if (start.0..end.0) != range_utf16 {
20603 adjusted_range.replace(start.0..end.0);
20604 }
20605 Some(snapshot.text_for_range(start..end).collect())
20606 }
20607
20608 fn selected_text_range(
20609 &mut self,
20610 ignore_disabled_input: bool,
20611 _: &mut Window,
20612 cx: &mut Context<Self>,
20613 ) -> Option<UTF16Selection> {
20614 // Prevent the IME menu from appearing when holding down an alphabetic key
20615 // while input is disabled.
20616 if !ignore_disabled_input && !self.input_enabled {
20617 return None;
20618 }
20619
20620 let selection = self.selections.newest::<OffsetUtf16>(cx);
20621 let range = selection.range();
20622
20623 Some(UTF16Selection {
20624 range: range.start.0..range.end.0,
20625 reversed: selection.reversed,
20626 })
20627 }
20628
20629 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20630 let snapshot = self.buffer.read(cx).read(cx);
20631 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20632 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20633 }
20634
20635 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20636 self.clear_highlights::<InputComposition>(cx);
20637 self.ime_transaction.take();
20638 }
20639
20640 fn replace_text_in_range(
20641 &mut self,
20642 range_utf16: Option<Range<usize>>,
20643 text: &str,
20644 window: &mut Window,
20645 cx: &mut Context<Self>,
20646 ) {
20647 if !self.input_enabled {
20648 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20649 return;
20650 }
20651
20652 self.transact(window, cx, |this, window, cx| {
20653 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20654 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20655 Some(this.selection_replacement_ranges(range_utf16, cx))
20656 } else {
20657 this.marked_text_ranges(cx)
20658 };
20659
20660 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20661 let newest_selection_id = this.selections.newest_anchor().id;
20662 this.selections
20663 .all::<OffsetUtf16>(cx)
20664 .iter()
20665 .zip(ranges_to_replace.iter())
20666 .find_map(|(selection, range)| {
20667 if selection.id == newest_selection_id {
20668 Some(
20669 (range.start.0 as isize - selection.head().0 as isize)
20670 ..(range.end.0 as isize - selection.head().0 as isize),
20671 )
20672 } else {
20673 None
20674 }
20675 })
20676 });
20677
20678 cx.emit(EditorEvent::InputHandled {
20679 utf16_range_to_replace: range_to_replace,
20680 text: text.into(),
20681 });
20682
20683 if let Some(new_selected_ranges) = new_selected_ranges {
20684 this.change_selections(None, window, cx, |selections| {
20685 selections.select_ranges(new_selected_ranges)
20686 });
20687 this.backspace(&Default::default(), window, cx);
20688 }
20689
20690 this.handle_input(text, window, cx);
20691 });
20692
20693 if let Some(transaction) = self.ime_transaction {
20694 self.buffer.update(cx, |buffer, cx| {
20695 buffer.group_until_transaction(transaction, cx);
20696 });
20697 }
20698
20699 self.unmark_text(window, cx);
20700 }
20701
20702 fn replace_and_mark_text_in_range(
20703 &mut self,
20704 range_utf16: Option<Range<usize>>,
20705 text: &str,
20706 new_selected_range_utf16: Option<Range<usize>>,
20707 window: &mut Window,
20708 cx: &mut Context<Self>,
20709 ) {
20710 if !self.input_enabled {
20711 return;
20712 }
20713
20714 let transaction = self.transact(window, cx, |this, window, cx| {
20715 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20716 let snapshot = this.buffer.read(cx).read(cx);
20717 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20718 for marked_range in &mut marked_ranges {
20719 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20720 marked_range.start.0 += relative_range_utf16.start;
20721 marked_range.start =
20722 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20723 marked_range.end =
20724 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20725 }
20726 }
20727 Some(marked_ranges)
20728 } else if let Some(range_utf16) = range_utf16 {
20729 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20730 Some(this.selection_replacement_ranges(range_utf16, cx))
20731 } else {
20732 None
20733 };
20734
20735 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20736 let newest_selection_id = this.selections.newest_anchor().id;
20737 this.selections
20738 .all::<OffsetUtf16>(cx)
20739 .iter()
20740 .zip(ranges_to_replace.iter())
20741 .find_map(|(selection, range)| {
20742 if selection.id == newest_selection_id {
20743 Some(
20744 (range.start.0 as isize - selection.head().0 as isize)
20745 ..(range.end.0 as isize - selection.head().0 as isize),
20746 )
20747 } else {
20748 None
20749 }
20750 })
20751 });
20752
20753 cx.emit(EditorEvent::InputHandled {
20754 utf16_range_to_replace: range_to_replace,
20755 text: text.into(),
20756 });
20757
20758 if let Some(ranges) = ranges_to_replace {
20759 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20760 }
20761
20762 let marked_ranges = {
20763 let snapshot = this.buffer.read(cx).read(cx);
20764 this.selections
20765 .disjoint_anchors()
20766 .iter()
20767 .map(|selection| {
20768 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20769 })
20770 .collect::<Vec<_>>()
20771 };
20772
20773 if text.is_empty() {
20774 this.unmark_text(window, cx);
20775 } else {
20776 this.highlight_text::<InputComposition>(
20777 marked_ranges.clone(),
20778 HighlightStyle {
20779 underline: Some(UnderlineStyle {
20780 thickness: px(1.),
20781 color: None,
20782 wavy: false,
20783 }),
20784 ..Default::default()
20785 },
20786 cx,
20787 );
20788 }
20789
20790 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20791 let use_autoclose = this.use_autoclose;
20792 let use_auto_surround = this.use_auto_surround;
20793 this.set_use_autoclose(false);
20794 this.set_use_auto_surround(false);
20795 this.handle_input(text, window, cx);
20796 this.set_use_autoclose(use_autoclose);
20797 this.set_use_auto_surround(use_auto_surround);
20798
20799 if let Some(new_selected_range) = new_selected_range_utf16 {
20800 let snapshot = this.buffer.read(cx).read(cx);
20801 let new_selected_ranges = marked_ranges
20802 .into_iter()
20803 .map(|marked_range| {
20804 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20805 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20806 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20807 snapshot.clip_offset_utf16(new_start, Bias::Left)
20808 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20809 })
20810 .collect::<Vec<_>>();
20811
20812 drop(snapshot);
20813 this.change_selections(None, window, cx, |selections| {
20814 selections.select_ranges(new_selected_ranges)
20815 });
20816 }
20817 });
20818
20819 self.ime_transaction = self.ime_transaction.or(transaction);
20820 if let Some(transaction) = self.ime_transaction {
20821 self.buffer.update(cx, |buffer, cx| {
20822 buffer.group_until_transaction(transaction, cx);
20823 });
20824 }
20825
20826 if self.text_highlights::<InputComposition>(cx).is_none() {
20827 self.ime_transaction.take();
20828 }
20829 }
20830
20831 fn bounds_for_range(
20832 &mut self,
20833 range_utf16: Range<usize>,
20834 element_bounds: gpui::Bounds<Pixels>,
20835 window: &mut Window,
20836 cx: &mut Context<Self>,
20837 ) -> Option<gpui::Bounds<Pixels>> {
20838 let text_layout_details = self.text_layout_details(window);
20839 let gpui::Size {
20840 width: em_width,
20841 height: line_height,
20842 } = self.character_size(window);
20843
20844 let snapshot = self.snapshot(window, cx);
20845 let scroll_position = snapshot.scroll_position();
20846 let scroll_left = scroll_position.x * em_width;
20847
20848 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20849 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20850 + self.gutter_dimensions.width
20851 + self.gutter_dimensions.margin;
20852 let y = line_height * (start.row().as_f32() - scroll_position.y);
20853
20854 Some(Bounds {
20855 origin: element_bounds.origin + point(x, y),
20856 size: size(em_width, line_height),
20857 })
20858 }
20859
20860 fn character_index_for_point(
20861 &mut self,
20862 point: gpui::Point<Pixels>,
20863 _window: &mut Window,
20864 _cx: &mut Context<Self>,
20865 ) -> Option<usize> {
20866 let position_map = self.last_position_map.as_ref()?;
20867 if !position_map.text_hitbox.contains(&point) {
20868 return None;
20869 }
20870 let display_point = position_map.point_for_position(point).previous_valid;
20871 let anchor = position_map
20872 .snapshot
20873 .display_point_to_anchor(display_point, Bias::Left);
20874 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20875 Some(utf16_offset.0)
20876 }
20877}
20878
20879trait SelectionExt {
20880 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20881 fn spanned_rows(
20882 &self,
20883 include_end_if_at_line_start: bool,
20884 map: &DisplaySnapshot,
20885 ) -> Range<MultiBufferRow>;
20886}
20887
20888impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20889 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20890 let start = self
20891 .start
20892 .to_point(&map.buffer_snapshot)
20893 .to_display_point(map);
20894 let end = self
20895 .end
20896 .to_point(&map.buffer_snapshot)
20897 .to_display_point(map);
20898 if self.reversed {
20899 end..start
20900 } else {
20901 start..end
20902 }
20903 }
20904
20905 fn spanned_rows(
20906 &self,
20907 include_end_if_at_line_start: bool,
20908 map: &DisplaySnapshot,
20909 ) -> Range<MultiBufferRow> {
20910 let start = self.start.to_point(&map.buffer_snapshot);
20911 let mut end = self.end.to_point(&map.buffer_snapshot);
20912 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20913 end.row -= 1;
20914 }
20915
20916 let buffer_start = map.prev_line_boundary(start).0;
20917 let buffer_end = map.next_line_boundary(end).0;
20918 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20919 }
20920}
20921
20922impl<T: InvalidationRegion> InvalidationStack<T> {
20923 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20924 where
20925 S: Clone + ToOffset,
20926 {
20927 while let Some(region) = self.last() {
20928 let all_selections_inside_invalidation_ranges =
20929 if selections.len() == region.ranges().len() {
20930 selections
20931 .iter()
20932 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20933 .all(|(selection, invalidation_range)| {
20934 let head = selection.head().to_offset(buffer);
20935 invalidation_range.start <= head && invalidation_range.end >= head
20936 })
20937 } else {
20938 false
20939 };
20940
20941 if all_selections_inside_invalidation_ranges {
20942 break;
20943 } else {
20944 self.pop();
20945 }
20946 }
20947 }
20948}
20949
20950impl<T> Default for InvalidationStack<T> {
20951 fn default() -> Self {
20952 Self(Default::default())
20953 }
20954}
20955
20956impl<T> Deref for InvalidationStack<T> {
20957 type Target = Vec<T>;
20958
20959 fn deref(&self) -> &Self::Target {
20960 &self.0
20961 }
20962}
20963
20964impl<T> DerefMut for InvalidationStack<T> {
20965 fn deref_mut(&mut self) -> &mut Self::Target {
20966 &mut self.0
20967 }
20968}
20969
20970impl InvalidationRegion for SnippetState {
20971 fn ranges(&self) -> &[Range<Anchor>] {
20972 &self.ranges[self.active_index]
20973 }
20974}
20975
20976fn inline_completion_edit_text(
20977 current_snapshot: &BufferSnapshot,
20978 edits: &[(Range<Anchor>, String)],
20979 edit_preview: &EditPreview,
20980 include_deletions: bool,
20981 cx: &App,
20982) -> HighlightedText {
20983 let edits = edits
20984 .iter()
20985 .map(|(anchor, text)| {
20986 (
20987 anchor.start.text_anchor..anchor.end.text_anchor,
20988 text.clone(),
20989 )
20990 })
20991 .collect::<Vec<_>>();
20992
20993 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
20994}
20995
20996pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
20997 match severity {
20998 lsp::DiagnosticSeverity::ERROR => colors.error,
20999 lsp::DiagnosticSeverity::WARNING => colors.warning,
21000 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21001 lsp::DiagnosticSeverity::HINT => colors.info,
21002 _ => colors.ignored,
21003 }
21004}
21005
21006pub fn styled_runs_for_code_label<'a>(
21007 label: &'a CodeLabel,
21008 syntax_theme: &'a theme::SyntaxTheme,
21009) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21010 let fade_out = HighlightStyle {
21011 fade_out: Some(0.35),
21012 ..Default::default()
21013 };
21014
21015 let mut prev_end = label.filter_range.end;
21016 label
21017 .runs
21018 .iter()
21019 .enumerate()
21020 .flat_map(move |(ix, (range, highlight_id))| {
21021 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21022 style
21023 } else {
21024 return Default::default();
21025 };
21026 let mut muted_style = style;
21027 muted_style.highlight(fade_out);
21028
21029 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21030 if range.start >= label.filter_range.end {
21031 if range.start > prev_end {
21032 runs.push((prev_end..range.start, fade_out));
21033 }
21034 runs.push((range.clone(), muted_style));
21035 } else if range.end <= label.filter_range.end {
21036 runs.push((range.clone(), style));
21037 } else {
21038 runs.push((range.start..label.filter_range.end, style));
21039 runs.push((label.filter_range.end..range.end, muted_style));
21040 }
21041 prev_end = cmp::max(prev_end, range.end);
21042
21043 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21044 runs.push((prev_end..label.text.len(), fade_out));
21045 }
21046
21047 runs
21048 })
21049}
21050
21051pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21052 let mut prev_index = 0;
21053 let mut prev_codepoint: Option<char> = None;
21054 text.char_indices()
21055 .chain([(text.len(), '\0')])
21056 .filter_map(move |(index, codepoint)| {
21057 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21058 let is_boundary = index == text.len()
21059 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21060 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21061 if is_boundary {
21062 let chunk = &text[prev_index..index];
21063 prev_index = index;
21064 Some(chunk)
21065 } else {
21066 None
21067 }
21068 })
21069}
21070
21071pub trait RangeToAnchorExt: Sized {
21072 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21073
21074 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21075 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21076 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21077 }
21078}
21079
21080impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21081 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21082 let start_offset = self.start.to_offset(snapshot);
21083 let end_offset = self.end.to_offset(snapshot);
21084 if start_offset == end_offset {
21085 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21086 } else {
21087 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21088 }
21089 }
21090}
21091
21092pub trait RowExt {
21093 fn as_f32(&self) -> f32;
21094
21095 fn next_row(&self) -> Self;
21096
21097 fn previous_row(&self) -> Self;
21098
21099 fn minus(&self, other: Self) -> u32;
21100}
21101
21102impl RowExt for DisplayRow {
21103 fn as_f32(&self) -> f32 {
21104 self.0 as f32
21105 }
21106
21107 fn next_row(&self) -> Self {
21108 Self(self.0 + 1)
21109 }
21110
21111 fn previous_row(&self) -> Self {
21112 Self(self.0.saturating_sub(1))
21113 }
21114
21115 fn minus(&self, other: Self) -> u32 {
21116 self.0 - other.0
21117 }
21118}
21119
21120impl RowExt for MultiBufferRow {
21121 fn as_f32(&self) -> f32 {
21122 self.0 as f32
21123 }
21124
21125 fn next_row(&self) -> Self {
21126 Self(self.0 + 1)
21127 }
21128
21129 fn previous_row(&self) -> Self {
21130 Self(self.0.saturating_sub(1))
21131 }
21132
21133 fn minus(&self, other: Self) -> u32 {
21134 self.0 - other.0
21135 }
21136}
21137
21138trait RowRangeExt {
21139 type Row;
21140
21141 fn len(&self) -> usize;
21142
21143 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21144}
21145
21146impl RowRangeExt for Range<MultiBufferRow> {
21147 type Row = MultiBufferRow;
21148
21149 fn len(&self) -> usize {
21150 (self.end.0 - self.start.0) as usize
21151 }
21152
21153 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21154 (self.start.0..self.end.0).map(MultiBufferRow)
21155 }
21156}
21157
21158impl RowRangeExt for Range<DisplayRow> {
21159 type Row = DisplayRow;
21160
21161 fn len(&self) -> usize {
21162 (self.end.0 - self.start.0) as usize
21163 }
21164
21165 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21166 (self.start.0..self.end.0).map(DisplayRow)
21167 }
21168}
21169
21170/// If select range has more than one line, we
21171/// just point the cursor to range.start.
21172fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21173 if range.start.row == range.end.row {
21174 range
21175 } else {
21176 range.start..range.start
21177 }
21178}
21179pub struct KillRing(ClipboardItem);
21180impl Global for KillRing {}
21181
21182const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21183
21184enum BreakpointPromptEditAction {
21185 Log,
21186 Condition,
21187 HitCondition,
21188}
21189
21190struct BreakpointPromptEditor {
21191 pub(crate) prompt: Entity<Editor>,
21192 editor: WeakEntity<Editor>,
21193 breakpoint_anchor: Anchor,
21194 breakpoint: Breakpoint,
21195 edit_action: BreakpointPromptEditAction,
21196 block_ids: HashSet<CustomBlockId>,
21197 editor_margins: Arc<Mutex<EditorMargins>>,
21198 _subscriptions: Vec<Subscription>,
21199}
21200
21201impl BreakpointPromptEditor {
21202 const MAX_LINES: u8 = 4;
21203
21204 fn new(
21205 editor: WeakEntity<Editor>,
21206 breakpoint_anchor: Anchor,
21207 breakpoint: Breakpoint,
21208 edit_action: BreakpointPromptEditAction,
21209 window: &mut Window,
21210 cx: &mut Context<Self>,
21211 ) -> Self {
21212 let base_text = match edit_action {
21213 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21214 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21215 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21216 }
21217 .map(|msg| msg.to_string())
21218 .unwrap_or_default();
21219
21220 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21221 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21222
21223 let prompt = cx.new(|cx| {
21224 let mut prompt = Editor::new(
21225 EditorMode::AutoHeight {
21226 max_lines: Self::MAX_LINES as usize,
21227 },
21228 buffer,
21229 None,
21230 window,
21231 cx,
21232 );
21233 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21234 prompt.set_show_cursor_when_unfocused(false, cx);
21235 prompt.set_placeholder_text(
21236 match edit_action {
21237 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21238 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21239 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21240 },
21241 cx,
21242 );
21243
21244 prompt
21245 });
21246
21247 Self {
21248 prompt,
21249 editor,
21250 breakpoint_anchor,
21251 breakpoint,
21252 edit_action,
21253 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21254 block_ids: Default::default(),
21255 _subscriptions: vec![],
21256 }
21257 }
21258
21259 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21260 self.block_ids.extend(block_ids)
21261 }
21262
21263 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21264 if let Some(editor) = self.editor.upgrade() {
21265 let message = self
21266 .prompt
21267 .read(cx)
21268 .buffer
21269 .read(cx)
21270 .as_singleton()
21271 .expect("A multi buffer in breakpoint prompt isn't possible")
21272 .read(cx)
21273 .as_rope()
21274 .to_string();
21275
21276 editor.update(cx, |editor, cx| {
21277 editor.edit_breakpoint_at_anchor(
21278 self.breakpoint_anchor,
21279 self.breakpoint.clone(),
21280 match self.edit_action {
21281 BreakpointPromptEditAction::Log => {
21282 BreakpointEditAction::EditLogMessage(message.into())
21283 }
21284 BreakpointPromptEditAction::Condition => {
21285 BreakpointEditAction::EditCondition(message.into())
21286 }
21287 BreakpointPromptEditAction::HitCondition => {
21288 BreakpointEditAction::EditHitCondition(message.into())
21289 }
21290 },
21291 cx,
21292 );
21293
21294 editor.remove_blocks(self.block_ids.clone(), None, cx);
21295 cx.focus_self(window);
21296 });
21297 }
21298 }
21299
21300 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21301 self.editor
21302 .update(cx, |editor, cx| {
21303 editor.remove_blocks(self.block_ids.clone(), None, cx);
21304 window.focus(&editor.focus_handle);
21305 })
21306 .log_err();
21307 }
21308
21309 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21310 let settings = ThemeSettings::get_global(cx);
21311 let text_style = TextStyle {
21312 color: if self.prompt.read(cx).read_only(cx) {
21313 cx.theme().colors().text_disabled
21314 } else {
21315 cx.theme().colors().text
21316 },
21317 font_family: settings.buffer_font.family.clone(),
21318 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21319 font_size: settings.buffer_font_size(cx).into(),
21320 font_weight: settings.buffer_font.weight,
21321 line_height: relative(settings.buffer_line_height.value()),
21322 ..Default::default()
21323 };
21324 EditorElement::new(
21325 &self.prompt,
21326 EditorStyle {
21327 background: cx.theme().colors().editor_background,
21328 local_player: cx.theme().players().local(),
21329 text: text_style,
21330 ..Default::default()
21331 },
21332 )
21333 }
21334}
21335
21336impl Render for BreakpointPromptEditor {
21337 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21338 let editor_margins = *self.editor_margins.lock();
21339 let gutter_dimensions = editor_margins.gutter;
21340 h_flex()
21341 .key_context("Editor")
21342 .bg(cx.theme().colors().editor_background)
21343 .border_y_1()
21344 .border_color(cx.theme().status().info_border)
21345 .size_full()
21346 .py(window.line_height() / 2.5)
21347 .on_action(cx.listener(Self::confirm))
21348 .on_action(cx.listener(Self::cancel))
21349 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21350 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21351 }
21352}
21353
21354impl Focusable for BreakpointPromptEditor {
21355 fn focus_handle(&self, cx: &App) -> FocusHandle {
21356 self.prompt.focus_handle(cx)
21357 }
21358}
21359
21360fn all_edits_insertions_or_deletions(
21361 edits: &Vec<(Range<Anchor>, String)>,
21362 snapshot: &MultiBufferSnapshot,
21363) -> bool {
21364 let mut all_insertions = true;
21365 let mut all_deletions = true;
21366
21367 for (range, new_text) in edits.iter() {
21368 let range_is_empty = range.to_offset(&snapshot).is_empty();
21369 let text_is_empty = new_text.is_empty();
21370
21371 if range_is_empty != text_is_empty {
21372 if range_is_empty {
21373 all_deletions = false;
21374 } else {
21375 all_insertions = false;
21376 }
21377 } else {
21378 return false;
21379 }
21380
21381 if !all_insertions && !all_deletions {
21382 return false;
21383 }
21384 }
21385 all_insertions || all_deletions
21386}
21387
21388struct MissingEditPredictionKeybindingTooltip;
21389
21390impl Render for MissingEditPredictionKeybindingTooltip {
21391 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21392 ui::tooltip_container(window, cx, |container, _, cx| {
21393 container
21394 .flex_shrink_0()
21395 .max_w_80()
21396 .min_h(rems_from_px(124.))
21397 .justify_between()
21398 .child(
21399 v_flex()
21400 .flex_1()
21401 .text_ui_sm(cx)
21402 .child(Label::new("Conflict with Accept Keybinding"))
21403 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21404 )
21405 .child(
21406 h_flex()
21407 .pb_1()
21408 .gap_1()
21409 .items_end()
21410 .w_full()
21411 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21412 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21413 }))
21414 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21415 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21416 })),
21417 )
21418 })
21419 }
21420}
21421
21422#[derive(Debug, Clone, Copy, PartialEq)]
21423pub struct LineHighlight {
21424 pub background: Background,
21425 pub border: Option<gpui::Hsla>,
21426 pub include_gutter: bool,
21427 pub type_id: Option<TypeId>,
21428}
21429
21430fn render_diff_hunk_controls(
21431 row: u32,
21432 status: &DiffHunkStatus,
21433 hunk_range: Range<Anchor>,
21434 is_created_file: bool,
21435 line_height: Pixels,
21436 editor: &Entity<Editor>,
21437 _window: &mut Window,
21438 cx: &mut App,
21439) -> AnyElement {
21440 h_flex()
21441 .h(line_height)
21442 .mr_1()
21443 .gap_1()
21444 .px_0p5()
21445 .pb_1()
21446 .border_x_1()
21447 .border_b_1()
21448 .border_color(cx.theme().colors().border_variant)
21449 .rounded_b_lg()
21450 .bg(cx.theme().colors().editor_background)
21451 .gap_1()
21452 .occlude()
21453 .shadow_md()
21454 .child(if status.has_secondary_hunk() {
21455 Button::new(("stage", row as u64), "Stage")
21456 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21457 .tooltip({
21458 let focus_handle = editor.focus_handle(cx);
21459 move |window, cx| {
21460 Tooltip::for_action_in(
21461 "Stage Hunk",
21462 &::git::ToggleStaged,
21463 &focus_handle,
21464 window,
21465 cx,
21466 )
21467 }
21468 })
21469 .on_click({
21470 let editor = editor.clone();
21471 move |_event, _window, cx| {
21472 editor.update(cx, |editor, cx| {
21473 editor.stage_or_unstage_diff_hunks(
21474 true,
21475 vec![hunk_range.start..hunk_range.start],
21476 cx,
21477 );
21478 });
21479 }
21480 })
21481 } else {
21482 Button::new(("unstage", row as u64), "Unstage")
21483 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21484 .tooltip({
21485 let focus_handle = editor.focus_handle(cx);
21486 move |window, cx| {
21487 Tooltip::for_action_in(
21488 "Unstage Hunk",
21489 &::git::ToggleStaged,
21490 &focus_handle,
21491 window,
21492 cx,
21493 )
21494 }
21495 })
21496 .on_click({
21497 let editor = editor.clone();
21498 move |_event, _window, cx| {
21499 editor.update(cx, |editor, cx| {
21500 editor.stage_or_unstage_diff_hunks(
21501 false,
21502 vec![hunk_range.start..hunk_range.start],
21503 cx,
21504 );
21505 });
21506 }
21507 })
21508 })
21509 .child(
21510 Button::new(("restore", row as u64), "Restore")
21511 .tooltip({
21512 let focus_handle = editor.focus_handle(cx);
21513 move |window, cx| {
21514 Tooltip::for_action_in(
21515 "Restore Hunk",
21516 &::git::Restore,
21517 &focus_handle,
21518 window,
21519 cx,
21520 )
21521 }
21522 })
21523 .on_click({
21524 let editor = editor.clone();
21525 move |_event, window, cx| {
21526 editor.update(cx, |editor, cx| {
21527 let snapshot = editor.snapshot(window, cx);
21528 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21529 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21530 });
21531 }
21532 })
21533 .disabled(is_created_file),
21534 )
21535 .when(
21536 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21537 |el| {
21538 el.child(
21539 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21540 .shape(IconButtonShape::Square)
21541 .icon_size(IconSize::Small)
21542 // .disabled(!has_multiple_hunks)
21543 .tooltip({
21544 let focus_handle = editor.focus_handle(cx);
21545 move |window, cx| {
21546 Tooltip::for_action_in(
21547 "Next Hunk",
21548 &GoToHunk,
21549 &focus_handle,
21550 window,
21551 cx,
21552 )
21553 }
21554 })
21555 .on_click({
21556 let editor = editor.clone();
21557 move |_event, window, cx| {
21558 editor.update(cx, |editor, cx| {
21559 let snapshot = editor.snapshot(window, cx);
21560 let position =
21561 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21562 editor.go_to_hunk_before_or_after_position(
21563 &snapshot,
21564 position,
21565 Direction::Next,
21566 window,
21567 cx,
21568 );
21569 editor.expand_selected_diff_hunks(cx);
21570 });
21571 }
21572 }),
21573 )
21574 .child(
21575 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21576 .shape(IconButtonShape::Square)
21577 .icon_size(IconSize::Small)
21578 // .disabled(!has_multiple_hunks)
21579 .tooltip({
21580 let focus_handle = editor.focus_handle(cx);
21581 move |window, cx| {
21582 Tooltip::for_action_in(
21583 "Previous Hunk",
21584 &GoToPreviousHunk,
21585 &focus_handle,
21586 window,
21587 cx,
21588 )
21589 }
21590 })
21591 .on_click({
21592 let editor = editor.clone();
21593 move |_event, window, cx| {
21594 editor.update(cx, |editor, cx| {
21595 let snapshot = editor.snapshot(window, cx);
21596 let point =
21597 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21598 editor.go_to_hunk_before_or_after_position(
21599 &snapshot,
21600 point,
21601 Direction::Prev,
21602 window,
21603 cx,
21604 );
21605 editor.expand_selected_diff_hunks(cx);
21606 });
21607 }
21608 }),
21609 )
21610 },
21611 )
21612 .into_any_element()
21613}