1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82
83use ::git::blame::BlameEntry;
84use ::git::{Restore, blame::ParsedCommitMessage};
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use git::blame::{GitBlame, GlobalBlameRenderer};
90use gpui::{
91 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
92 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
93 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
94 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
95 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
96 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
97 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
98 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
99};
100use highlight_matching_bracket::refresh_matching_bracket_highlights;
101use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
102pub use hover_popover::hover_markdown_style;
103use hover_popover::{HoverState, hide_hover};
104use indent_guides::ActiveIndentGuidesState;
105use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
106pub use inline_completion::Direction;
107use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
108pub use items::MAX_TAB_TITLE_LEN;
109use itertools::Itertools;
110use language::{
111 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
112 CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
113 EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
114 OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
115 WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 project_settings::DiagnosticSeverity,
137};
138
139pub use git::blame::BlameRenderer;
140pub use proposed_changes_editor::{
141 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
142};
143use std::{cell::OnceCell, iter::Peekable, ops::Not};
144use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
145
146pub use lsp::CompletionContext;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId, LanguageServerName,
150};
151
152use language::BufferSnapshot;
153pub use lsp_ext::lsp_tasks;
154use movement::TextLayoutDetails;
155pub use multi_buffer::{
156 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
157 RowInfo, ToOffset, ToPoint,
158};
159use multi_buffer::{
160 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
161 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
162};
163use parking_lot::Mutex;
164use project::{
165 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
166 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
167 TaskSourceKind,
168 debugger::breakpoint_store::Breakpoint,
169 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
170 project_settings::{GitGutterSetting, ProjectSettings},
171};
172use rand::prelude::*;
173use rpc::{ErrorExt, proto::*};
174use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
175use selections_collection::{
176 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
177};
178use serde::{Deserialize, Serialize};
179use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
180use smallvec::{SmallVec, smallvec};
181use snippet::Snippet;
182use std::sync::Arc;
183use std::{
184 any::TypeId,
185 borrow::Cow,
186 cell::RefCell,
187 cmp::{self, Ordering, Reverse},
188 mem,
189 num::NonZeroU32,
190 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
191 path::{Path, PathBuf},
192 rc::Rc,
193 time::{Duration, Instant},
194};
195pub use sum_tree::Bias;
196use sum_tree::TreeMap;
197use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
198use theme::{
199 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
200 observe_buffer_font_size_adjustment,
201};
202use ui::{
203 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
204 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
205};
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
209 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
210 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::{
217 code_context_menus::CompletionsMenuSource,
218 hover_links::{find_url, find_url_from_range},
219};
220use crate::{
221 editor_settings::MultiCursorModifier,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293enum DocumentHighlightRead {}
294enum DocumentHighlightWrite {}
295enum InputComposition {}
296pub enum PendingInput {}
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 mode: ColumnarMode,
451 goal_column: u32,
452 },
453 Extend {
454 position: DisplayPoint,
455 click_count: usize,
456 },
457 Update {
458 position: DisplayPoint,
459 goal_column: u32,
460 scroll_delta: gpui::Point<f32>,
461 },
462 End,
463}
464
465#[derive(Clone, Debug, PartialEq)]
466pub enum ColumnarMode {
467 FromMouse,
468 FromSelection,
469}
470
471#[derive(Clone, Debug)]
472pub enum SelectMode {
473 Character,
474 Word(Range<Anchor>),
475 Line(Range<Anchor>),
476 All,
477}
478
479#[derive(Clone, PartialEq, Eq, Debug)]
480pub enum EditorMode {
481 SingleLine {
482 auto_width: bool,
483 },
484 AutoHeight {
485 min_lines: usize,
486 max_lines: usize,
487 },
488 Full {
489 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
490 scale_ui_elements_with_buffer_font_size: bool,
491 /// When set to `true`, the editor will render a background for the active line.
492 show_active_line_background: bool,
493 /// When set to `true`, the editor's height will be determined by its content.
494 sized_by_content: bool,
495 },
496 Minimap {
497 parent: WeakEntity<Editor>,
498 },
499}
500
501impl EditorMode {
502 pub fn full() -> Self {
503 Self::Full {
504 scale_ui_elements_with_buffer_font_size: true,
505 show_active_line_background: true,
506 sized_by_content: false,
507 }
508 }
509
510 pub fn is_full(&self) -> bool {
511 matches!(self, Self::Full { .. })
512 }
513
514 fn is_minimap(&self) -> bool {
515 matches!(self, Self::Minimap { .. })
516 }
517}
518
519#[derive(Copy, Clone, Debug)]
520pub enum SoftWrap {
521 /// Prefer not to wrap at all.
522 ///
523 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
524 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
525 GitDiff,
526 /// Prefer a single line generally, unless an overly long line is encountered.
527 None,
528 /// Soft wrap lines that exceed the editor width.
529 EditorWidth,
530 /// Soft wrap lines at the preferred line length.
531 Column(u32),
532 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
533 Bounded(u32),
534}
535
536#[derive(Clone)]
537pub struct EditorStyle {
538 pub background: Hsla,
539 pub local_player: PlayerColor,
540 pub text: TextStyle,
541 pub scrollbar_width: Pixels,
542 pub syntax: Arc<SyntaxTheme>,
543 pub status: StatusColors,
544 pub inlay_hints_style: HighlightStyle,
545 pub inline_completion_styles: InlineCompletionStyles,
546 pub unnecessary_code_fade: f32,
547 pub show_underlines: bool,
548}
549
550impl Default for EditorStyle {
551 fn default() -> Self {
552 Self {
553 background: Hsla::default(),
554 local_player: PlayerColor::default(),
555 text: TextStyle::default(),
556 scrollbar_width: Pixels::default(),
557 syntax: Default::default(),
558 // HACK: Status colors don't have a real default.
559 // We should look into removing the status colors from the editor
560 // style and retrieve them directly from the theme.
561 status: StatusColors::dark(),
562 inlay_hints_style: HighlightStyle::default(),
563 inline_completion_styles: InlineCompletionStyles {
564 insertion: HighlightStyle::default(),
565 whitespace: HighlightStyle::default(),
566 },
567 unnecessary_code_fade: Default::default(),
568 show_underlines: true,
569 }
570 }
571}
572
573pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
574 let show_background = language_settings::language_settings(None, None, cx)
575 .inlay_hints
576 .show_background;
577
578 HighlightStyle {
579 color: Some(cx.theme().status().hint),
580 background_color: show_background.then(|| cx.theme().status().hint_background),
581 ..HighlightStyle::default()
582 }
583}
584
585pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
586 InlineCompletionStyles {
587 insertion: HighlightStyle {
588 color: Some(cx.theme().status().predictive),
589 ..HighlightStyle::default()
590 },
591 whitespace: HighlightStyle {
592 background_color: Some(cx.theme().status().created_background),
593 ..HighlightStyle::default()
594 },
595 }
596}
597
598type CompletionId = usize;
599
600pub(crate) enum EditDisplayMode {
601 TabAccept,
602 DiffPopover,
603 Inline,
604}
605
606enum InlineCompletion {
607 Edit {
608 edits: Vec<(Range<Anchor>, String)>,
609 edit_preview: Option<EditPreview>,
610 display_mode: EditDisplayMode,
611 snapshot: BufferSnapshot,
612 },
613 Move {
614 target: Anchor,
615 snapshot: BufferSnapshot,
616 },
617}
618
619struct InlineCompletionState {
620 inlay_ids: Vec<InlayId>,
621 completion: InlineCompletion,
622 completion_id: Option<SharedString>,
623 invalidation_range: Range<Anchor>,
624}
625
626enum EditPredictionSettings {
627 Disabled,
628 Enabled {
629 show_in_menu: bool,
630 preview_requires_modifier: bool,
631 },
632}
633
634enum InlineCompletionHighlight {}
635
636#[derive(Debug, Clone)]
637struct InlineDiagnostic {
638 message: SharedString,
639 group_id: usize,
640 is_primary: bool,
641 start: Point,
642 severity: lsp::DiagnosticSeverity,
643}
644
645pub enum MenuInlineCompletionsPolicy {
646 Never,
647 ByProvider,
648}
649
650pub enum EditPredictionPreview {
651 /// Modifier is not pressed
652 Inactive { released_too_fast: bool },
653 /// Modifier pressed
654 Active {
655 since: Instant,
656 previous_scroll_position: Option<ScrollAnchor>,
657 },
658}
659
660impl EditPredictionPreview {
661 pub fn released_too_fast(&self) -> bool {
662 match self {
663 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
664 EditPredictionPreview::Active { .. } => false,
665 }
666 }
667
668 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
669 if let EditPredictionPreview::Active {
670 previous_scroll_position,
671 ..
672 } = self
673 {
674 *previous_scroll_position = scroll_position;
675 }
676 }
677}
678
679pub struct ContextMenuOptions {
680 pub min_entries_visible: usize,
681 pub max_entries_visible: usize,
682 pub placement: Option<ContextMenuPlacement>,
683}
684
685#[derive(Debug, Clone, PartialEq, Eq)]
686pub enum ContextMenuPlacement {
687 Above,
688 Below,
689}
690
691#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
692struct EditorActionId(usize);
693
694impl EditorActionId {
695 pub fn post_inc(&mut self) -> Self {
696 let answer = self.0;
697
698 *self = Self(answer + 1);
699
700 Self(answer)
701 }
702}
703
704// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
705// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
706
707#[derive(Clone)]
708pub struct BackgroundHighlight {
709 pub range: Range<Anchor>,
710 pub color_fetcher: fn(&Theme) -> Hsla,
711}
712
713type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
714
715#[derive(Default)]
716struct ScrollbarMarkerState {
717 scrollbar_size: Size<Pixels>,
718 dirty: bool,
719 markers: Arc<[PaintQuad]>,
720 pending_refresh: Option<Task<Result<()>>>,
721}
722
723impl ScrollbarMarkerState {
724 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
725 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
726 }
727}
728
729#[derive(Clone, Copy, PartialEq, Eq)]
730pub enum MinimapVisibility {
731 Disabled,
732 Enabled {
733 /// The configuration currently present in the users settings.
734 setting_configuration: bool,
735 /// Whether to override the currently set visibility from the users setting.
736 toggle_override: bool,
737 },
738}
739
740impl MinimapVisibility {
741 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
742 if mode.is_full() {
743 Self::Enabled {
744 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
745 toggle_override: false,
746 }
747 } else {
748 Self::Disabled
749 }
750 }
751
752 fn hidden(&self) -> Self {
753 match *self {
754 Self::Enabled {
755 setting_configuration,
756 ..
757 } => Self::Enabled {
758 setting_configuration,
759 toggle_override: setting_configuration,
760 },
761 Self::Disabled => Self::Disabled,
762 }
763 }
764
765 fn disabled(&self) -> bool {
766 match *self {
767 Self::Disabled => true,
768 _ => false,
769 }
770 }
771
772 fn settings_visibility(&self) -> bool {
773 match *self {
774 Self::Enabled {
775 setting_configuration,
776 ..
777 } => setting_configuration,
778 _ => false,
779 }
780 }
781
782 fn visible(&self) -> bool {
783 match *self {
784 Self::Enabled {
785 setting_configuration,
786 toggle_override,
787 } => setting_configuration ^ toggle_override,
788 _ => false,
789 }
790 }
791
792 fn toggle_visibility(&self) -> Self {
793 match *self {
794 Self::Enabled {
795 toggle_override,
796 setting_configuration,
797 } => Self::Enabled {
798 setting_configuration,
799 toggle_override: !toggle_override,
800 },
801 Self::Disabled => Self::Disabled,
802 }
803 }
804}
805
806#[derive(Clone, Debug)]
807struct RunnableTasks {
808 templates: Vec<(TaskSourceKind, TaskTemplate)>,
809 offset: multi_buffer::Anchor,
810 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
811 column: u32,
812 // Values of all named captures, including those starting with '_'
813 extra_variables: HashMap<String, String>,
814 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
815 context_range: Range<BufferOffset>,
816}
817
818impl RunnableTasks {
819 fn resolve<'a>(
820 &'a self,
821 cx: &'a task::TaskContext,
822 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
823 self.templates.iter().filter_map(|(kind, template)| {
824 template
825 .resolve_task(&kind.to_id_base(), cx)
826 .map(|task| (kind.clone(), task))
827 })
828 }
829}
830
831#[derive(Clone)]
832pub struct ResolvedTasks {
833 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
834 position: Anchor,
835}
836
837#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
838struct BufferOffset(usize);
839
840// Addons allow storing per-editor state in other crates (e.g. Vim)
841pub trait Addon: 'static {
842 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
843
844 fn render_buffer_header_controls(
845 &self,
846 _: &ExcerptInfo,
847 _: &Window,
848 _: &App,
849 ) -> Option<AnyElement> {
850 None
851 }
852
853 fn to_any(&self) -> &dyn std::any::Any;
854
855 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
856 None
857 }
858}
859
860/// A set of caret positions, registered when the editor was edited.
861pub struct ChangeList {
862 changes: Vec<Vec<Anchor>>,
863 /// Currently "selected" change.
864 position: Option<usize>,
865}
866
867impl ChangeList {
868 pub fn new() -> Self {
869 Self {
870 changes: Vec::new(),
871 position: None,
872 }
873 }
874
875 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
876 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
877 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
878 if self.changes.is_empty() {
879 return None;
880 }
881
882 let prev = self.position.unwrap_or(self.changes.len());
883 let next = if direction == Direction::Prev {
884 prev.saturating_sub(count)
885 } else {
886 (prev + count).min(self.changes.len() - 1)
887 };
888 self.position = Some(next);
889 self.changes.get(next).map(|anchors| anchors.as_slice())
890 }
891
892 /// Adds a new change to the list, resetting the change list position.
893 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
894 self.position.take();
895 if pop_state {
896 self.changes.pop();
897 }
898 self.changes.push(new_positions.clone());
899 }
900
901 pub fn last(&self) -> Option<&[Anchor]> {
902 self.changes.last().map(|anchors| anchors.as_slice())
903 }
904}
905
906#[derive(Clone)]
907struct InlineBlamePopoverState {
908 scroll_handle: ScrollHandle,
909 commit_message: Option<ParsedCommitMessage>,
910 markdown: Entity<Markdown>,
911}
912
913struct InlineBlamePopover {
914 position: gpui::Point<Pixels>,
915 hide_task: Option<Task<()>>,
916 popover_bounds: Option<Bounds<Pixels>>,
917 popover_state: InlineBlamePopoverState,
918}
919
920enum SelectionDragState {
921 /// State when no drag related activity is detected.
922 None,
923 /// State when the mouse is down on a selection that is about to be dragged.
924 ReadyToDrag {
925 selection: Selection<Anchor>,
926 click_position: gpui::Point<Pixels>,
927 mouse_down_time: Instant,
928 },
929 /// State when the mouse is dragging the selection in the editor.
930 Dragging {
931 selection: Selection<Anchor>,
932 drop_cursor: Selection<Anchor>,
933 hide_drop_cursor: bool,
934 },
935}
936
937enum ColumnarSelectionState {
938 FromMouse {
939 selection_tail: Anchor,
940 display_point: Option<DisplayPoint>,
941 },
942 FromSelection {
943 selection_tail: Anchor,
944 },
945}
946
947/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
948/// a breakpoint on them.
949#[derive(Clone, Copy, Debug, PartialEq, Eq)]
950struct PhantomBreakpointIndicator {
951 display_row: DisplayRow,
952 /// There's a small debounce between hovering over the line and showing the indicator.
953 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
954 is_active: bool,
955 collides_with_existing_breakpoint: bool,
956}
957
958/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
959///
960/// See the [module level documentation](self) for more information.
961pub struct Editor {
962 focus_handle: FocusHandle,
963 last_focused_descendant: Option<WeakFocusHandle>,
964 /// The text buffer being edited
965 buffer: Entity<MultiBuffer>,
966 /// Map of how text in the buffer should be displayed.
967 /// Handles soft wraps, folds, fake inlay text insertions, etc.
968 pub display_map: Entity<DisplayMap>,
969 pub selections: SelectionsCollection,
970 pub scroll_manager: ScrollManager,
971 /// When inline assist editors are linked, they all render cursors because
972 /// typing enters text into each of them, even the ones that aren't focused.
973 pub(crate) show_cursor_when_unfocused: bool,
974 columnar_selection_state: Option<ColumnarSelectionState>,
975 add_selections_state: Option<AddSelectionsState>,
976 select_next_state: Option<SelectNextState>,
977 select_prev_state: Option<SelectNextState>,
978 selection_history: SelectionHistory,
979 defer_selection_effects: bool,
980 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
981 autoclose_regions: Vec<AutocloseRegion>,
982 snippet_stack: InvalidationStack<SnippetState>,
983 select_syntax_node_history: SelectSyntaxNodeHistory,
984 ime_transaction: Option<TransactionId>,
985 pub diagnostics_max_severity: DiagnosticSeverity,
986 active_diagnostics: ActiveDiagnostic,
987 show_inline_diagnostics: bool,
988 inline_diagnostics_update: Task<()>,
989 inline_diagnostics_enabled: bool,
990 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
991 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
992 hard_wrap: Option<usize>,
993
994 // TODO: make this a access method
995 pub project: Option<Entity<Project>>,
996 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
997 completion_provider: Option<Rc<dyn CompletionProvider>>,
998 collaboration_hub: Option<Box<dyn CollaborationHub>>,
999 blink_manager: Entity<BlinkManager>,
1000 show_cursor_names: bool,
1001 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1002 pub show_local_selections: bool,
1003 mode: EditorMode,
1004 show_breadcrumbs: bool,
1005 show_gutter: bool,
1006 show_scrollbars: ScrollbarAxes,
1007 minimap_visibility: MinimapVisibility,
1008 offset_content: bool,
1009 disable_expand_excerpt_buttons: bool,
1010 show_line_numbers: Option<bool>,
1011 use_relative_line_numbers: Option<bool>,
1012 show_git_diff_gutter: Option<bool>,
1013 show_code_actions: Option<bool>,
1014 show_runnables: Option<bool>,
1015 show_breakpoints: Option<bool>,
1016 show_wrap_guides: Option<bool>,
1017 show_indent_guides: Option<bool>,
1018 placeholder_text: Option<Arc<str>>,
1019 highlight_order: usize,
1020 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1021 background_highlights: TreeMap<TypeId, Vec<BackgroundHighlight>>,
1022 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1023 scrollbar_marker_state: ScrollbarMarkerState,
1024 active_indent_guides_state: ActiveIndentGuidesState,
1025 nav_history: Option<ItemNavHistory>,
1026 context_menu: RefCell<Option<CodeContextMenu>>,
1027 context_menu_options: Option<ContextMenuOptions>,
1028 mouse_context_menu: Option<MouseContextMenu>,
1029 completion_tasks: Vec<(CompletionId, Task<()>)>,
1030 inline_blame_popover: Option<InlineBlamePopover>,
1031 inline_blame_popover_show_task: Option<Task<()>>,
1032 signature_help_state: SignatureHelpState,
1033 auto_signature_help: Option<bool>,
1034 find_all_references_task_sources: Vec<Anchor>,
1035 next_completion_id: CompletionId,
1036 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1037 code_actions_task: Option<Task<Result<()>>>,
1038 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1039 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1040 document_highlights_task: Option<Task<()>>,
1041 linked_editing_range_task: Option<Task<Option<()>>>,
1042 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1043 pending_rename: Option<RenameState>,
1044 searchable: bool,
1045 cursor_shape: CursorShape,
1046 current_line_highlight: Option<CurrentLineHighlight>,
1047 collapse_matches: bool,
1048 autoindent_mode: Option<AutoindentMode>,
1049 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1050 input_enabled: bool,
1051 use_modal_editing: bool,
1052 read_only: bool,
1053 leader_id: Option<CollaboratorId>,
1054 remote_id: Option<ViewId>,
1055 pub hover_state: HoverState,
1056 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1057 gutter_hovered: bool,
1058 hovered_link_state: Option<HoveredLinkState>,
1059 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1060 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1061 active_inline_completion: Option<InlineCompletionState>,
1062 /// Used to prevent flickering as the user types while the menu is open
1063 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1064 edit_prediction_settings: EditPredictionSettings,
1065 inline_completions_hidden_for_vim_mode: bool,
1066 show_inline_completions_override: Option<bool>,
1067 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1068 edit_prediction_preview: EditPredictionPreview,
1069 edit_prediction_indent_conflict: bool,
1070 edit_prediction_requires_modifier_in_indent_conflict: bool,
1071 inlay_hint_cache: InlayHintCache,
1072 next_inlay_id: usize,
1073 _subscriptions: Vec<Subscription>,
1074 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1075 gutter_dimensions: GutterDimensions,
1076 style: Option<EditorStyle>,
1077 text_style_refinement: Option<TextStyleRefinement>,
1078 next_editor_action_id: EditorActionId,
1079 editor_actions: Rc<
1080 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1081 >,
1082 use_autoclose: bool,
1083 use_auto_surround: bool,
1084 auto_replace_emoji_shortcode: bool,
1085 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1086 show_git_blame_gutter: bool,
1087 show_git_blame_inline: bool,
1088 show_git_blame_inline_delay_task: Option<Task<()>>,
1089 git_blame_inline_enabled: bool,
1090 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1091 serialize_dirty_buffers: bool,
1092 show_selection_menu: Option<bool>,
1093 blame: Option<Entity<GitBlame>>,
1094 blame_subscription: Option<Subscription>,
1095 custom_context_menu: Option<
1096 Box<
1097 dyn 'static
1098 + Fn(
1099 &mut Self,
1100 DisplayPoint,
1101 &mut Window,
1102 &mut Context<Self>,
1103 ) -> Option<Entity<ui::ContextMenu>>,
1104 >,
1105 >,
1106 last_bounds: Option<Bounds<Pixels>>,
1107 last_position_map: Option<Rc<PositionMap>>,
1108 expect_bounds_change: Option<Bounds<Pixels>>,
1109 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1110 tasks_update_task: Option<Task<()>>,
1111 breakpoint_store: Option<Entity<BreakpointStore>>,
1112 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1113 hovered_diff_hunk_row: Option<DisplayRow>,
1114 pull_diagnostics_task: Task<()>,
1115 in_project_search: bool,
1116 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1117 breadcrumb_header: Option<String>,
1118 focused_block: Option<FocusedBlock>,
1119 next_scroll_position: NextScrollCursorCenterTopBottom,
1120 addons: HashMap<TypeId, Box<dyn Addon>>,
1121 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1122 load_diff_task: Option<Shared<Task<()>>>,
1123 /// Whether we are temporarily displaying a diff other than git's
1124 temporary_diff_override: bool,
1125 selection_mark_mode: bool,
1126 toggle_fold_multiple_buffers: Task<()>,
1127 _scroll_cursor_center_top_bottom_task: Task<()>,
1128 serialize_selections: Task<()>,
1129 serialize_folds: Task<()>,
1130 mouse_cursor_hidden: bool,
1131 minimap: Option<Entity<Self>>,
1132 hide_mouse_mode: HideMouseMode,
1133 pub change_list: ChangeList,
1134 inline_value_cache: InlineValueCache,
1135 selection_drag_state: SelectionDragState,
1136 drag_and_drop_selection_enabled: bool,
1137}
1138
1139#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1140enum NextScrollCursorCenterTopBottom {
1141 #[default]
1142 Center,
1143 Top,
1144 Bottom,
1145}
1146
1147impl NextScrollCursorCenterTopBottom {
1148 fn next(&self) -> Self {
1149 match self {
1150 Self::Center => Self::Top,
1151 Self::Top => Self::Bottom,
1152 Self::Bottom => Self::Center,
1153 }
1154 }
1155}
1156
1157#[derive(Clone)]
1158pub struct EditorSnapshot {
1159 pub mode: EditorMode,
1160 show_gutter: bool,
1161 show_line_numbers: Option<bool>,
1162 show_git_diff_gutter: Option<bool>,
1163 show_code_actions: Option<bool>,
1164 show_runnables: Option<bool>,
1165 show_breakpoints: Option<bool>,
1166 git_blame_gutter_max_author_length: Option<usize>,
1167 pub display_snapshot: DisplaySnapshot,
1168 pub placeholder_text: Option<Arc<str>>,
1169 is_focused: bool,
1170 scroll_anchor: ScrollAnchor,
1171 ongoing_scroll: OngoingScroll,
1172 current_line_highlight: CurrentLineHighlight,
1173 gutter_hovered: bool,
1174}
1175
1176#[derive(Default, Debug, Clone, Copy)]
1177pub struct GutterDimensions {
1178 pub left_padding: Pixels,
1179 pub right_padding: Pixels,
1180 pub width: Pixels,
1181 pub margin: Pixels,
1182 pub git_blame_entries_width: Option<Pixels>,
1183}
1184
1185impl GutterDimensions {
1186 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1187 Self {
1188 margin: Self::default_gutter_margin(font_id, font_size, cx),
1189 ..Default::default()
1190 }
1191 }
1192
1193 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1194 -cx.text_system().descent(font_id, font_size)
1195 }
1196 /// The full width of the space taken up by the gutter.
1197 pub fn full_width(&self) -> Pixels {
1198 self.margin + self.width
1199 }
1200
1201 /// The width of the space reserved for the fold indicators,
1202 /// use alongside 'justify_end' and `gutter_width` to
1203 /// right align content with the line numbers
1204 pub fn fold_area_width(&self) -> Pixels {
1205 self.margin + self.right_padding
1206 }
1207}
1208
1209#[derive(Debug)]
1210pub struct RemoteSelection {
1211 pub replica_id: ReplicaId,
1212 pub selection: Selection<Anchor>,
1213 pub cursor_shape: CursorShape,
1214 pub collaborator_id: CollaboratorId,
1215 pub line_mode: bool,
1216 pub user_name: Option<SharedString>,
1217 pub color: PlayerColor,
1218}
1219
1220#[derive(Clone, Debug)]
1221struct SelectionHistoryEntry {
1222 selections: Arc<[Selection<Anchor>]>,
1223 select_next_state: Option<SelectNextState>,
1224 select_prev_state: Option<SelectNextState>,
1225 add_selections_state: Option<AddSelectionsState>,
1226}
1227
1228#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1229enum SelectionHistoryMode {
1230 Normal,
1231 Undoing,
1232 Redoing,
1233 Skipping,
1234}
1235
1236#[derive(Clone, PartialEq, Eq, Hash)]
1237struct HoveredCursor {
1238 replica_id: u16,
1239 selection_id: usize,
1240}
1241
1242impl Default for SelectionHistoryMode {
1243 fn default() -> Self {
1244 Self::Normal
1245 }
1246}
1247
1248#[derive(Debug)]
1249pub struct SelectionEffects {
1250 nav_history: bool,
1251 completions: bool,
1252 scroll: Option<Autoscroll>,
1253}
1254
1255impl Default for SelectionEffects {
1256 fn default() -> Self {
1257 Self {
1258 nav_history: true,
1259 completions: true,
1260 scroll: Some(Autoscroll::fit()),
1261 }
1262 }
1263}
1264impl SelectionEffects {
1265 pub fn scroll(scroll: Autoscroll) -> Self {
1266 Self {
1267 scroll: Some(scroll),
1268 ..Default::default()
1269 }
1270 }
1271
1272 pub fn no_scroll() -> Self {
1273 Self {
1274 scroll: None,
1275 ..Default::default()
1276 }
1277 }
1278
1279 pub fn completions(self, completions: bool) -> Self {
1280 Self {
1281 completions,
1282 ..self
1283 }
1284 }
1285
1286 pub fn nav_history(self, nav_history: bool) -> Self {
1287 Self {
1288 nav_history,
1289 ..self
1290 }
1291 }
1292}
1293
1294struct DeferredSelectionEffectsState {
1295 changed: bool,
1296 effects: SelectionEffects,
1297 old_cursor_position: Anchor,
1298 history_entry: SelectionHistoryEntry,
1299}
1300
1301#[derive(Default)]
1302struct SelectionHistory {
1303 #[allow(clippy::type_complexity)]
1304 selections_by_transaction:
1305 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1306 mode: SelectionHistoryMode,
1307 undo_stack: VecDeque<SelectionHistoryEntry>,
1308 redo_stack: VecDeque<SelectionHistoryEntry>,
1309}
1310
1311impl SelectionHistory {
1312 #[track_caller]
1313 fn insert_transaction(
1314 &mut self,
1315 transaction_id: TransactionId,
1316 selections: Arc<[Selection<Anchor>]>,
1317 ) {
1318 if selections.is_empty() {
1319 log::error!(
1320 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1321 std::panic::Location::caller()
1322 );
1323 return;
1324 }
1325 self.selections_by_transaction
1326 .insert(transaction_id, (selections, None));
1327 }
1328
1329 #[allow(clippy::type_complexity)]
1330 fn transaction(
1331 &self,
1332 transaction_id: TransactionId,
1333 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1334 self.selections_by_transaction.get(&transaction_id)
1335 }
1336
1337 #[allow(clippy::type_complexity)]
1338 fn transaction_mut(
1339 &mut self,
1340 transaction_id: TransactionId,
1341 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1342 self.selections_by_transaction.get_mut(&transaction_id)
1343 }
1344
1345 fn push(&mut self, entry: SelectionHistoryEntry) {
1346 if !entry.selections.is_empty() {
1347 match self.mode {
1348 SelectionHistoryMode::Normal => {
1349 self.push_undo(entry);
1350 self.redo_stack.clear();
1351 }
1352 SelectionHistoryMode::Undoing => self.push_redo(entry),
1353 SelectionHistoryMode::Redoing => self.push_undo(entry),
1354 SelectionHistoryMode::Skipping => {}
1355 }
1356 }
1357 }
1358
1359 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1360 if self
1361 .undo_stack
1362 .back()
1363 .map_or(true, |e| e.selections != entry.selections)
1364 {
1365 self.undo_stack.push_back(entry);
1366 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1367 self.undo_stack.pop_front();
1368 }
1369 }
1370 }
1371
1372 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1373 if self
1374 .redo_stack
1375 .back()
1376 .map_or(true, |e| e.selections != entry.selections)
1377 {
1378 self.redo_stack.push_back(entry);
1379 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1380 self.redo_stack.pop_front();
1381 }
1382 }
1383 }
1384}
1385
1386#[derive(Clone, Copy)]
1387pub struct RowHighlightOptions {
1388 pub autoscroll: bool,
1389 pub include_gutter: bool,
1390}
1391
1392impl Default for RowHighlightOptions {
1393 fn default() -> Self {
1394 Self {
1395 autoscroll: Default::default(),
1396 include_gutter: true,
1397 }
1398 }
1399}
1400
1401struct RowHighlight {
1402 index: usize,
1403 range: Range<Anchor>,
1404 color: Hsla,
1405 options: RowHighlightOptions,
1406 type_id: TypeId,
1407}
1408
1409#[derive(Clone, Debug)]
1410struct AddSelectionsState {
1411 groups: Vec<AddSelectionsGroup>,
1412}
1413
1414#[derive(Clone, Debug)]
1415struct AddSelectionsGroup {
1416 above: bool,
1417 stack: Vec<usize>,
1418}
1419
1420#[derive(Clone)]
1421struct SelectNextState {
1422 query: AhoCorasick,
1423 wordwise: bool,
1424 done: bool,
1425}
1426
1427impl std::fmt::Debug for SelectNextState {
1428 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1429 f.debug_struct(std::any::type_name::<Self>())
1430 .field("wordwise", &self.wordwise)
1431 .field("done", &self.done)
1432 .finish()
1433 }
1434}
1435
1436#[derive(Debug)]
1437struct AutocloseRegion {
1438 selection_id: usize,
1439 range: Range<Anchor>,
1440 pair: BracketPair,
1441}
1442
1443#[derive(Debug)]
1444struct SnippetState {
1445 ranges: Vec<Vec<Range<Anchor>>>,
1446 active_index: usize,
1447 choices: Vec<Option<Vec<String>>>,
1448}
1449
1450#[doc(hidden)]
1451pub struct RenameState {
1452 pub range: Range<Anchor>,
1453 pub old_name: Arc<str>,
1454 pub editor: Entity<Editor>,
1455 block_id: CustomBlockId,
1456}
1457
1458struct InvalidationStack<T>(Vec<T>);
1459
1460struct RegisteredInlineCompletionProvider {
1461 provider: Arc<dyn InlineCompletionProviderHandle>,
1462 _subscription: Subscription,
1463}
1464
1465#[derive(Debug, PartialEq, Eq)]
1466pub struct ActiveDiagnosticGroup {
1467 pub active_range: Range<Anchor>,
1468 pub active_message: String,
1469 pub group_id: usize,
1470 pub blocks: HashSet<CustomBlockId>,
1471}
1472
1473#[derive(Debug, PartialEq, Eq)]
1474
1475pub(crate) enum ActiveDiagnostic {
1476 None,
1477 All,
1478 Group(ActiveDiagnosticGroup),
1479}
1480
1481#[derive(Serialize, Deserialize, Clone, Debug)]
1482pub struct ClipboardSelection {
1483 /// The number of bytes in this selection.
1484 pub len: usize,
1485 /// Whether this was a full-line selection.
1486 pub is_entire_line: bool,
1487 /// The indentation of the first line when this content was originally copied.
1488 pub first_line_indent: u32,
1489}
1490
1491// selections, scroll behavior, was newest selection reversed
1492type SelectSyntaxNodeHistoryState = (
1493 Box<[Selection<usize>]>,
1494 SelectSyntaxNodeScrollBehavior,
1495 bool,
1496);
1497
1498#[derive(Default)]
1499struct SelectSyntaxNodeHistory {
1500 stack: Vec<SelectSyntaxNodeHistoryState>,
1501 // disable temporarily to allow changing selections without losing the stack
1502 pub disable_clearing: bool,
1503}
1504
1505impl SelectSyntaxNodeHistory {
1506 pub fn try_clear(&mut self) {
1507 if !self.disable_clearing {
1508 self.stack.clear();
1509 }
1510 }
1511
1512 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1513 self.stack.push(selection);
1514 }
1515
1516 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1517 self.stack.pop()
1518 }
1519}
1520
1521enum SelectSyntaxNodeScrollBehavior {
1522 CursorTop,
1523 FitSelection,
1524 CursorBottom,
1525}
1526
1527#[derive(Debug)]
1528pub(crate) struct NavigationData {
1529 cursor_anchor: Anchor,
1530 cursor_position: Point,
1531 scroll_anchor: ScrollAnchor,
1532 scroll_top_row: u32,
1533}
1534
1535#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1536pub enum GotoDefinitionKind {
1537 Symbol,
1538 Declaration,
1539 Type,
1540 Implementation,
1541}
1542
1543#[derive(Debug, Clone)]
1544enum InlayHintRefreshReason {
1545 ModifiersChanged(bool),
1546 Toggle(bool),
1547 SettingsChange(InlayHintSettings),
1548 NewLinesShown,
1549 BufferEdited(HashSet<Arc<Language>>),
1550 RefreshRequested,
1551 ExcerptsRemoved(Vec<ExcerptId>),
1552}
1553
1554impl InlayHintRefreshReason {
1555 fn description(&self) -> &'static str {
1556 match self {
1557 Self::ModifiersChanged(_) => "modifiers changed",
1558 Self::Toggle(_) => "toggle",
1559 Self::SettingsChange(_) => "settings change",
1560 Self::NewLinesShown => "new lines shown",
1561 Self::BufferEdited(_) => "buffer edited",
1562 Self::RefreshRequested => "refresh requested",
1563 Self::ExcerptsRemoved(_) => "excerpts removed",
1564 }
1565 }
1566}
1567
1568pub enum FormatTarget {
1569 Buffers(HashSet<Entity<Buffer>>),
1570 Ranges(Vec<Range<MultiBufferPoint>>),
1571}
1572
1573pub(crate) struct FocusedBlock {
1574 id: BlockId,
1575 focus_handle: WeakFocusHandle,
1576}
1577
1578#[derive(Clone)]
1579enum JumpData {
1580 MultiBufferRow {
1581 row: MultiBufferRow,
1582 line_offset_from_top: u32,
1583 },
1584 MultiBufferPoint {
1585 excerpt_id: ExcerptId,
1586 position: Point,
1587 anchor: text::Anchor,
1588 line_offset_from_top: u32,
1589 },
1590}
1591
1592pub enum MultibufferSelectionMode {
1593 First,
1594 All,
1595}
1596
1597#[derive(Clone, Copy, Debug, Default)]
1598pub struct RewrapOptions {
1599 pub override_language_settings: bool,
1600 pub preserve_existing_whitespace: bool,
1601}
1602
1603impl Editor {
1604 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1605 let buffer = cx.new(|cx| Buffer::local("", cx));
1606 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1607 Self::new(
1608 EditorMode::SingleLine { auto_width: false },
1609 buffer,
1610 None,
1611 window,
1612 cx,
1613 )
1614 }
1615
1616 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1617 let buffer = cx.new(|cx| Buffer::local("", cx));
1618 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1619 Self::new(EditorMode::full(), buffer, None, window, cx)
1620 }
1621
1622 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1623 let buffer = cx.new(|cx| Buffer::local("", cx));
1624 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1625 Self::new(
1626 EditorMode::SingleLine { auto_width: true },
1627 buffer,
1628 None,
1629 window,
1630 cx,
1631 )
1632 }
1633
1634 pub fn auto_height(
1635 min_lines: usize,
1636 max_lines: usize,
1637 window: &mut Window,
1638 cx: &mut Context<Self>,
1639 ) -> Self {
1640 let buffer = cx.new(|cx| Buffer::local("", cx));
1641 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1642 Self::new(
1643 EditorMode::AutoHeight {
1644 min_lines,
1645 max_lines,
1646 },
1647 buffer,
1648 None,
1649 window,
1650 cx,
1651 )
1652 }
1653
1654 pub fn for_buffer(
1655 buffer: Entity<Buffer>,
1656 project: Option<Entity<Project>>,
1657 window: &mut Window,
1658 cx: &mut Context<Self>,
1659 ) -> Self {
1660 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1661 Self::new(EditorMode::full(), buffer, project, window, cx)
1662 }
1663
1664 pub fn for_multibuffer(
1665 buffer: Entity<MultiBuffer>,
1666 project: Option<Entity<Project>>,
1667 window: &mut Window,
1668 cx: &mut Context<Self>,
1669 ) -> Self {
1670 Self::new(EditorMode::full(), buffer, project, window, cx)
1671 }
1672
1673 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let mut clone = Self::new(
1675 self.mode.clone(),
1676 self.buffer.clone(),
1677 self.project.clone(),
1678 window,
1679 cx,
1680 );
1681 self.display_map.update(cx, |display_map, cx| {
1682 let snapshot = display_map.snapshot(cx);
1683 clone.display_map.update(cx, |display_map, cx| {
1684 display_map.set_state(&snapshot, cx);
1685 });
1686 });
1687 clone.folds_did_change(cx);
1688 clone.selections.clone_state(&self.selections);
1689 clone.scroll_manager.clone_state(&self.scroll_manager);
1690 clone.searchable = self.searchable;
1691 clone.read_only = self.read_only;
1692 clone
1693 }
1694
1695 pub fn new(
1696 mode: EditorMode,
1697 buffer: Entity<MultiBuffer>,
1698 project: Option<Entity<Project>>,
1699 window: &mut Window,
1700 cx: &mut Context<Self>,
1701 ) -> Self {
1702 Editor::new_internal(mode, buffer, project, None, window, cx)
1703 }
1704
1705 fn new_internal(
1706 mode: EditorMode,
1707 buffer: Entity<MultiBuffer>,
1708 project: Option<Entity<Project>>,
1709 display_map: Option<Entity<DisplayMap>>,
1710 window: &mut Window,
1711 cx: &mut Context<Self>,
1712 ) -> Self {
1713 debug_assert!(
1714 display_map.is_none() || mode.is_minimap(),
1715 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1716 );
1717
1718 let full_mode = mode.is_full();
1719 let diagnostics_max_severity = if full_mode {
1720 EditorSettings::get_global(cx)
1721 .diagnostics_max_severity
1722 .unwrap_or(DiagnosticSeverity::Hint)
1723 } else {
1724 DiagnosticSeverity::Off
1725 };
1726 let style = window.text_style();
1727 let font_size = style.font_size.to_pixels(window.rem_size());
1728 let editor = cx.entity().downgrade();
1729 let fold_placeholder = FoldPlaceholder {
1730 constrain_width: true,
1731 render: Arc::new(move |fold_id, fold_range, cx| {
1732 let editor = editor.clone();
1733 div()
1734 .id(fold_id)
1735 .bg(cx.theme().colors().ghost_element_background)
1736 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1737 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1738 .rounded_xs()
1739 .size_full()
1740 .cursor_pointer()
1741 .child("⋯")
1742 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1743 .on_click(move |_, _window, cx| {
1744 editor
1745 .update(cx, |editor, cx| {
1746 editor.unfold_ranges(
1747 &[fold_range.start..fold_range.end],
1748 true,
1749 false,
1750 cx,
1751 );
1752 cx.stop_propagation();
1753 })
1754 .ok();
1755 })
1756 .into_any()
1757 }),
1758 merge_adjacent: true,
1759 ..FoldPlaceholder::default()
1760 };
1761 let display_map = display_map.unwrap_or_else(|| {
1762 cx.new(|cx| {
1763 DisplayMap::new(
1764 buffer.clone(),
1765 style.font(),
1766 font_size,
1767 None,
1768 FILE_HEADER_HEIGHT,
1769 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1770 fold_placeholder,
1771 diagnostics_max_severity,
1772 cx,
1773 )
1774 })
1775 });
1776
1777 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1778
1779 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1780
1781 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1782 .then(|| language_settings::SoftWrap::None);
1783
1784 let mut project_subscriptions = Vec::new();
1785 if mode.is_full() {
1786 if let Some(project) = project.as_ref() {
1787 project_subscriptions.push(cx.subscribe_in(
1788 project,
1789 window,
1790 |editor, _, event, window, cx| match event {
1791 project::Event::RefreshCodeLens => {
1792 // we always query lens with actions, without storing them, always refreshing them
1793 }
1794 project::Event::RefreshInlayHints => {
1795 editor
1796 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1797 }
1798 project::Event::LanguageServerAdded(..)
1799 | project::Event::LanguageServerRemoved(..) => {
1800 if editor.tasks_update_task.is_none() {
1801 editor.tasks_update_task =
1802 Some(editor.refresh_runnables(window, cx));
1803 }
1804 editor.pull_diagnostics(None, window, cx);
1805 }
1806 project::Event::SnippetEdit(id, snippet_edits) => {
1807 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1808 let focus_handle = editor.focus_handle(cx);
1809 if focus_handle.is_focused(window) {
1810 let snapshot = buffer.read(cx).snapshot();
1811 for (range, snippet) in snippet_edits {
1812 let editor_range =
1813 language::range_from_lsp(*range).to_offset(&snapshot);
1814 editor
1815 .insert_snippet(
1816 &[editor_range],
1817 snippet.clone(),
1818 window,
1819 cx,
1820 )
1821 .ok();
1822 }
1823 }
1824 }
1825 }
1826 _ => {}
1827 },
1828 ));
1829 if let Some(task_inventory) = project
1830 .read(cx)
1831 .task_store()
1832 .read(cx)
1833 .task_inventory()
1834 .cloned()
1835 {
1836 project_subscriptions.push(cx.observe_in(
1837 &task_inventory,
1838 window,
1839 |editor, _, window, cx| {
1840 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1841 },
1842 ));
1843 };
1844
1845 project_subscriptions.push(cx.subscribe_in(
1846 &project.read(cx).breakpoint_store(),
1847 window,
1848 |editor, _, event, window, cx| match event {
1849 BreakpointStoreEvent::ClearDebugLines => {
1850 editor.clear_row_highlights::<ActiveDebugLine>();
1851 editor.refresh_inline_values(cx);
1852 }
1853 BreakpointStoreEvent::SetDebugLine => {
1854 if editor.go_to_active_debug_line(window, cx) {
1855 cx.stop_propagation();
1856 }
1857
1858 editor.refresh_inline_values(cx);
1859 }
1860 _ => {}
1861 },
1862 ));
1863 }
1864 }
1865
1866 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1867
1868 let inlay_hint_settings =
1869 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1870 let focus_handle = cx.focus_handle();
1871 cx.on_focus(&focus_handle, window, Self::handle_focus)
1872 .detach();
1873 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1874 .detach();
1875 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1876 .detach();
1877 cx.on_blur(&focus_handle, window, Self::handle_blur)
1878 .detach();
1879 cx.observe_pending_input(window, Self::observe_pending_input)
1880 .detach();
1881
1882 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1883 Some(false)
1884 } else {
1885 None
1886 };
1887
1888 let breakpoint_store = match (&mode, project.as_ref()) {
1889 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1890 _ => None,
1891 };
1892
1893 let mut code_action_providers = Vec::new();
1894 let mut load_uncommitted_diff = None;
1895 if let Some(project) = project.clone() {
1896 load_uncommitted_diff = Some(
1897 update_uncommitted_diff_for_buffer(
1898 cx.entity(),
1899 &project,
1900 buffer.read(cx).all_buffers(),
1901 buffer.clone(),
1902 cx,
1903 )
1904 .shared(),
1905 );
1906 code_action_providers.push(Rc::new(project) as Rc<_>);
1907 }
1908
1909 let mut editor = Self {
1910 focus_handle,
1911 show_cursor_when_unfocused: false,
1912 last_focused_descendant: None,
1913 buffer: buffer.clone(),
1914 display_map: display_map.clone(),
1915 selections,
1916 scroll_manager: ScrollManager::new(cx),
1917 columnar_selection_state: None,
1918 add_selections_state: None,
1919 select_next_state: None,
1920 select_prev_state: None,
1921 selection_history: SelectionHistory::default(),
1922 defer_selection_effects: false,
1923 deferred_selection_effects_state: None,
1924 autoclose_regions: Vec::new(),
1925 snippet_stack: InvalidationStack::default(),
1926 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1927 ime_transaction: None,
1928 active_diagnostics: ActiveDiagnostic::None,
1929 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1930 inline_diagnostics_update: Task::ready(()),
1931 inline_diagnostics: Vec::new(),
1932 soft_wrap_mode_override,
1933 diagnostics_max_severity,
1934 hard_wrap: None,
1935 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1936 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1937 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1938 project,
1939 blink_manager: blink_manager.clone(),
1940 show_local_selections: true,
1941 show_scrollbars: ScrollbarAxes {
1942 horizontal: full_mode,
1943 vertical: full_mode,
1944 },
1945 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1946 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1947 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1948 show_gutter: mode.is_full(),
1949 show_line_numbers: None,
1950 use_relative_line_numbers: None,
1951 disable_expand_excerpt_buttons: false,
1952 show_git_diff_gutter: None,
1953 show_code_actions: None,
1954 show_runnables: None,
1955 show_breakpoints: None,
1956 show_wrap_guides: None,
1957 show_indent_guides,
1958 placeholder_text: None,
1959 highlight_order: 0,
1960 highlighted_rows: HashMap::default(),
1961 background_highlights: TreeMap::default(),
1962 gutter_highlights: TreeMap::default(),
1963 scrollbar_marker_state: ScrollbarMarkerState::default(),
1964 active_indent_guides_state: ActiveIndentGuidesState::default(),
1965 nav_history: None,
1966 context_menu: RefCell::new(None),
1967 context_menu_options: None,
1968 mouse_context_menu: None,
1969 completion_tasks: Vec::new(),
1970 inline_blame_popover: None,
1971 inline_blame_popover_show_task: None,
1972 signature_help_state: SignatureHelpState::default(),
1973 auto_signature_help: None,
1974 find_all_references_task_sources: Vec::new(),
1975 next_completion_id: 0,
1976 next_inlay_id: 0,
1977 code_action_providers,
1978 available_code_actions: None,
1979 code_actions_task: None,
1980 quick_selection_highlight_task: None,
1981 debounced_selection_highlight_task: None,
1982 document_highlights_task: None,
1983 linked_editing_range_task: None,
1984 pending_rename: None,
1985 searchable: true,
1986 cursor_shape: EditorSettings::get_global(cx)
1987 .cursor_shape
1988 .unwrap_or_default(),
1989 current_line_highlight: None,
1990 autoindent_mode: Some(AutoindentMode::EachLine),
1991 collapse_matches: false,
1992 workspace: None,
1993 input_enabled: true,
1994 use_modal_editing: mode.is_full(),
1995 read_only: mode.is_minimap(),
1996 use_autoclose: true,
1997 use_auto_surround: true,
1998 auto_replace_emoji_shortcode: false,
1999 jsx_tag_auto_close_enabled_in_any_buffer: false,
2000 leader_id: None,
2001 remote_id: None,
2002 hover_state: HoverState::default(),
2003 pending_mouse_down: None,
2004 hovered_link_state: None,
2005 edit_prediction_provider: None,
2006 active_inline_completion: None,
2007 stale_inline_completion_in_menu: None,
2008 edit_prediction_preview: EditPredictionPreview::Inactive {
2009 released_too_fast: false,
2010 },
2011 inline_diagnostics_enabled: mode.is_full(),
2012 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2013 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2014
2015 gutter_hovered: false,
2016 pixel_position_of_newest_cursor: None,
2017 last_bounds: None,
2018 last_position_map: None,
2019 expect_bounds_change: None,
2020 gutter_dimensions: GutterDimensions::default(),
2021 style: None,
2022 show_cursor_names: false,
2023 hovered_cursors: HashMap::default(),
2024 next_editor_action_id: EditorActionId::default(),
2025 editor_actions: Rc::default(),
2026 inline_completions_hidden_for_vim_mode: false,
2027 show_inline_completions_override: None,
2028 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2029 edit_prediction_settings: EditPredictionSettings::Disabled,
2030 edit_prediction_indent_conflict: false,
2031 edit_prediction_requires_modifier_in_indent_conflict: true,
2032 custom_context_menu: None,
2033 show_git_blame_gutter: false,
2034 show_git_blame_inline: false,
2035 show_selection_menu: None,
2036 show_git_blame_inline_delay_task: None,
2037 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2038 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2039 serialize_dirty_buffers: !mode.is_minimap()
2040 && ProjectSettings::get_global(cx)
2041 .session
2042 .restore_unsaved_buffers,
2043 blame: None,
2044 blame_subscription: None,
2045 tasks: BTreeMap::default(),
2046
2047 breakpoint_store,
2048 gutter_breakpoint_indicator: (None, None),
2049 hovered_diff_hunk_row: None,
2050 _subscriptions: vec![
2051 cx.observe(&buffer, Self::on_buffer_changed),
2052 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2053 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2054 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2055 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2056 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2057 cx.observe_window_activation(window, |editor, window, cx| {
2058 let active = window.is_window_active();
2059 editor.blink_manager.update(cx, |blink_manager, cx| {
2060 if active {
2061 blink_manager.enable(cx);
2062 } else {
2063 blink_manager.disable(cx);
2064 }
2065 });
2066 if active {
2067 editor.show_mouse_cursor(cx);
2068 }
2069 }),
2070 ],
2071 tasks_update_task: None,
2072 pull_diagnostics_task: Task::ready(()),
2073 linked_edit_ranges: Default::default(),
2074 in_project_search: false,
2075 previous_search_ranges: None,
2076 breadcrumb_header: None,
2077 focused_block: None,
2078 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2079 addons: HashMap::default(),
2080 registered_buffers: HashMap::default(),
2081 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2082 selection_mark_mode: false,
2083 toggle_fold_multiple_buffers: Task::ready(()),
2084 serialize_selections: Task::ready(()),
2085 serialize_folds: Task::ready(()),
2086 text_style_refinement: None,
2087 load_diff_task: load_uncommitted_diff,
2088 temporary_diff_override: false,
2089 mouse_cursor_hidden: false,
2090 minimap: None,
2091 hide_mouse_mode: EditorSettings::get_global(cx)
2092 .hide_mouse
2093 .unwrap_or_default(),
2094 change_list: ChangeList::new(),
2095 mode,
2096 selection_drag_state: SelectionDragState::None,
2097 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2098 };
2099 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2100 editor
2101 ._subscriptions
2102 .push(cx.observe(breakpoints, |_, _, cx| {
2103 cx.notify();
2104 }));
2105 }
2106 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2107 editor._subscriptions.extend(project_subscriptions);
2108
2109 editor._subscriptions.push(cx.subscribe_in(
2110 &cx.entity(),
2111 window,
2112 |editor, _, e: &EditorEvent, window, cx| match e {
2113 EditorEvent::ScrollPositionChanged { local, .. } => {
2114 if *local {
2115 let new_anchor = editor.scroll_manager.anchor();
2116 let snapshot = editor.snapshot(window, cx);
2117 editor.update_restoration_data(cx, move |data| {
2118 data.scroll_position = (
2119 new_anchor.top_row(&snapshot.buffer_snapshot),
2120 new_anchor.offset,
2121 );
2122 });
2123 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2124 editor.inline_blame_popover.take();
2125 }
2126 }
2127 EditorEvent::Edited { .. } => {
2128 if !vim_enabled(cx) {
2129 let (map, selections) = editor.selections.all_adjusted_display(cx);
2130 let pop_state = editor
2131 .change_list
2132 .last()
2133 .map(|previous| {
2134 previous.len() == selections.len()
2135 && previous.iter().enumerate().all(|(ix, p)| {
2136 p.to_display_point(&map).row()
2137 == selections[ix].head().row()
2138 })
2139 })
2140 .unwrap_or(false);
2141 let new_positions = selections
2142 .into_iter()
2143 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2144 .collect();
2145 editor
2146 .change_list
2147 .push_to_change_list(pop_state, new_positions);
2148 }
2149 }
2150 _ => (),
2151 },
2152 ));
2153
2154 if let Some(dap_store) = editor
2155 .project
2156 .as_ref()
2157 .map(|project| project.read(cx).dap_store())
2158 {
2159 let weak_editor = cx.weak_entity();
2160
2161 editor
2162 ._subscriptions
2163 .push(
2164 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2165 let session_entity = cx.entity();
2166 weak_editor
2167 .update(cx, |editor, cx| {
2168 editor._subscriptions.push(
2169 cx.subscribe(&session_entity, Self::on_debug_session_event),
2170 );
2171 })
2172 .ok();
2173 }),
2174 );
2175
2176 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2177 editor
2178 ._subscriptions
2179 .push(cx.subscribe(&session, Self::on_debug_session_event));
2180 }
2181 }
2182
2183 // skip adding the initial selection to selection history
2184 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2185 editor.end_selection(window, cx);
2186 editor.selection_history.mode = SelectionHistoryMode::Normal;
2187
2188 editor.scroll_manager.show_scrollbars(window, cx);
2189 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2190
2191 if full_mode {
2192 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2193 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2194
2195 if editor.git_blame_inline_enabled {
2196 editor.start_git_blame_inline(false, window, cx);
2197 }
2198
2199 editor.go_to_active_debug_line(window, cx);
2200
2201 if let Some(buffer) = buffer.read(cx).as_singleton() {
2202 if let Some(project) = editor.project.as_ref() {
2203 let handle = project.update(cx, |project, cx| {
2204 project.register_buffer_with_language_servers(&buffer, cx)
2205 });
2206 editor
2207 .registered_buffers
2208 .insert(buffer.read(cx).remote_id(), handle);
2209 }
2210 }
2211
2212 editor.minimap =
2213 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2214 editor.pull_diagnostics(None, window, cx);
2215 }
2216
2217 editor.report_editor_event("Editor Opened", None, cx);
2218 editor
2219 }
2220
2221 pub fn deploy_mouse_context_menu(
2222 &mut self,
2223 position: gpui::Point<Pixels>,
2224 context_menu: Entity<ContextMenu>,
2225 window: &mut Window,
2226 cx: &mut Context<Self>,
2227 ) {
2228 self.mouse_context_menu = Some(MouseContextMenu::new(
2229 self,
2230 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2231 context_menu,
2232 window,
2233 cx,
2234 ));
2235 }
2236
2237 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2238 self.mouse_context_menu
2239 .as_ref()
2240 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2241 }
2242
2243 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2244 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2245 }
2246
2247 fn key_context_internal(
2248 &self,
2249 has_active_edit_prediction: bool,
2250 window: &Window,
2251 cx: &App,
2252 ) -> KeyContext {
2253 let mut key_context = KeyContext::new_with_defaults();
2254 key_context.add("Editor");
2255 let mode = match self.mode {
2256 EditorMode::SingleLine { .. } => "single_line",
2257 EditorMode::AutoHeight { .. } => "auto_height",
2258 EditorMode::Minimap { .. } => "minimap",
2259 EditorMode::Full { .. } => "full",
2260 };
2261
2262 if EditorSettings::jupyter_enabled(cx) {
2263 key_context.add("jupyter");
2264 }
2265
2266 key_context.set("mode", mode);
2267 if self.pending_rename.is_some() {
2268 key_context.add("renaming");
2269 }
2270
2271 match self.context_menu.borrow().as_ref() {
2272 Some(CodeContextMenu::Completions(_)) => {
2273 key_context.add("menu");
2274 key_context.add("showing_completions");
2275 }
2276 Some(CodeContextMenu::CodeActions(_)) => {
2277 key_context.add("menu");
2278 key_context.add("showing_code_actions")
2279 }
2280 None => {}
2281 }
2282
2283 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2284 if !self.focus_handle(cx).contains_focused(window, cx)
2285 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2286 {
2287 for addon in self.addons.values() {
2288 addon.extend_key_context(&mut key_context, cx)
2289 }
2290 }
2291
2292 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2293 if let Some(extension) = singleton_buffer
2294 .read(cx)
2295 .file()
2296 .and_then(|file| file.path().extension()?.to_str())
2297 {
2298 key_context.set("extension", extension.to_string());
2299 }
2300 } else {
2301 key_context.add("multibuffer");
2302 }
2303
2304 if has_active_edit_prediction {
2305 if self.edit_prediction_in_conflict() {
2306 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2307 } else {
2308 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2309 key_context.add("copilot_suggestion");
2310 }
2311 }
2312
2313 if self.selection_mark_mode {
2314 key_context.add("selection_mode");
2315 }
2316
2317 key_context
2318 }
2319
2320 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2321 if self.mouse_cursor_hidden {
2322 self.mouse_cursor_hidden = false;
2323 cx.notify();
2324 }
2325 }
2326
2327 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2328 let hide_mouse_cursor = match origin {
2329 HideMouseCursorOrigin::TypingAction => {
2330 matches!(
2331 self.hide_mouse_mode,
2332 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2333 )
2334 }
2335 HideMouseCursorOrigin::MovementAction => {
2336 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2337 }
2338 };
2339 if self.mouse_cursor_hidden != hide_mouse_cursor {
2340 self.mouse_cursor_hidden = hide_mouse_cursor;
2341 cx.notify();
2342 }
2343 }
2344
2345 pub fn edit_prediction_in_conflict(&self) -> bool {
2346 if !self.show_edit_predictions_in_menu() {
2347 return false;
2348 }
2349
2350 let showing_completions = self
2351 .context_menu
2352 .borrow()
2353 .as_ref()
2354 .map_or(false, |context| {
2355 matches!(context, CodeContextMenu::Completions(_))
2356 });
2357
2358 showing_completions
2359 || self.edit_prediction_requires_modifier()
2360 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2361 // bindings to insert tab characters.
2362 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2363 }
2364
2365 pub fn accept_edit_prediction_keybind(
2366 &self,
2367 accept_partial: bool,
2368 window: &Window,
2369 cx: &App,
2370 ) -> AcceptEditPredictionBinding {
2371 let key_context = self.key_context_internal(true, window, cx);
2372 let in_conflict = self.edit_prediction_in_conflict();
2373
2374 let bindings = if accept_partial {
2375 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2376 } else {
2377 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2378 };
2379
2380 // TODO: if the binding contains multiple keystrokes, display all of them, not
2381 // just the first one.
2382 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2383 !in_conflict
2384 || binding
2385 .keystrokes()
2386 .first()
2387 .map_or(false, |keystroke| keystroke.modifiers.modified())
2388 }))
2389 }
2390
2391 pub fn new_file(
2392 workspace: &mut Workspace,
2393 _: &workspace::NewFile,
2394 window: &mut Window,
2395 cx: &mut Context<Workspace>,
2396 ) {
2397 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2398 "Failed to create buffer",
2399 window,
2400 cx,
2401 |e, _, _| match e.error_code() {
2402 ErrorCode::RemoteUpgradeRequired => Some(format!(
2403 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2404 e.error_tag("required").unwrap_or("the latest version")
2405 )),
2406 _ => None,
2407 },
2408 );
2409 }
2410
2411 pub fn new_in_workspace(
2412 workspace: &mut Workspace,
2413 window: &mut Window,
2414 cx: &mut Context<Workspace>,
2415 ) -> Task<Result<Entity<Editor>>> {
2416 let project = workspace.project().clone();
2417 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2418
2419 cx.spawn_in(window, async move |workspace, cx| {
2420 let buffer = create.await?;
2421 workspace.update_in(cx, |workspace, window, cx| {
2422 let editor =
2423 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2424 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2425 editor
2426 })
2427 })
2428 }
2429
2430 fn new_file_vertical(
2431 workspace: &mut Workspace,
2432 _: &workspace::NewFileSplitVertical,
2433 window: &mut Window,
2434 cx: &mut Context<Workspace>,
2435 ) {
2436 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2437 }
2438
2439 fn new_file_horizontal(
2440 workspace: &mut Workspace,
2441 _: &workspace::NewFileSplitHorizontal,
2442 window: &mut Window,
2443 cx: &mut Context<Workspace>,
2444 ) {
2445 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2446 }
2447
2448 fn new_file_in_direction(
2449 workspace: &mut Workspace,
2450 direction: SplitDirection,
2451 window: &mut Window,
2452 cx: &mut Context<Workspace>,
2453 ) {
2454 let project = workspace.project().clone();
2455 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2456
2457 cx.spawn_in(window, async move |workspace, cx| {
2458 let buffer = create.await?;
2459 workspace.update_in(cx, move |workspace, window, cx| {
2460 workspace.split_item(
2461 direction,
2462 Box::new(
2463 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2464 ),
2465 window,
2466 cx,
2467 )
2468 })?;
2469 anyhow::Ok(())
2470 })
2471 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2472 match e.error_code() {
2473 ErrorCode::RemoteUpgradeRequired => Some(format!(
2474 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2475 e.error_tag("required").unwrap_or("the latest version")
2476 )),
2477 _ => None,
2478 }
2479 });
2480 }
2481
2482 pub fn leader_id(&self) -> Option<CollaboratorId> {
2483 self.leader_id
2484 }
2485
2486 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2487 &self.buffer
2488 }
2489
2490 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2491 self.workspace.as_ref()?.0.upgrade()
2492 }
2493
2494 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2495 self.buffer().read(cx).title(cx)
2496 }
2497
2498 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2499 let git_blame_gutter_max_author_length = self
2500 .render_git_blame_gutter(cx)
2501 .then(|| {
2502 if let Some(blame) = self.blame.as_ref() {
2503 let max_author_length =
2504 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2505 Some(max_author_length)
2506 } else {
2507 None
2508 }
2509 })
2510 .flatten();
2511
2512 EditorSnapshot {
2513 mode: self.mode.clone(),
2514 show_gutter: self.show_gutter,
2515 show_line_numbers: self.show_line_numbers,
2516 show_git_diff_gutter: self.show_git_diff_gutter,
2517 show_code_actions: self.show_code_actions,
2518 show_runnables: self.show_runnables,
2519 show_breakpoints: self.show_breakpoints,
2520 git_blame_gutter_max_author_length,
2521 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2522 scroll_anchor: self.scroll_manager.anchor(),
2523 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2524 placeholder_text: self.placeholder_text.clone(),
2525 is_focused: self.focus_handle.is_focused(window),
2526 current_line_highlight: self
2527 .current_line_highlight
2528 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2529 gutter_hovered: self.gutter_hovered,
2530 }
2531 }
2532
2533 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2534 self.buffer.read(cx).language_at(point, cx)
2535 }
2536
2537 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2538 self.buffer.read(cx).read(cx).file_at(point).cloned()
2539 }
2540
2541 pub fn active_excerpt(
2542 &self,
2543 cx: &App,
2544 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2545 self.buffer
2546 .read(cx)
2547 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2548 }
2549
2550 pub fn mode(&self) -> &EditorMode {
2551 &self.mode
2552 }
2553
2554 pub fn set_mode(&mut self, mode: EditorMode) {
2555 self.mode = mode;
2556 }
2557
2558 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2559 self.collaboration_hub.as_deref()
2560 }
2561
2562 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2563 self.collaboration_hub = Some(hub);
2564 }
2565
2566 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2567 self.in_project_search = in_project_search;
2568 }
2569
2570 pub fn set_custom_context_menu(
2571 &mut self,
2572 f: impl 'static
2573 + Fn(
2574 &mut Self,
2575 DisplayPoint,
2576 &mut Window,
2577 &mut Context<Self>,
2578 ) -> Option<Entity<ui::ContextMenu>>,
2579 ) {
2580 self.custom_context_menu = Some(Box::new(f))
2581 }
2582
2583 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2584 self.completion_provider = provider;
2585 }
2586
2587 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2588 self.semantics_provider.clone()
2589 }
2590
2591 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2592 self.semantics_provider = provider;
2593 }
2594
2595 pub fn set_edit_prediction_provider<T>(
2596 &mut self,
2597 provider: Option<Entity<T>>,
2598 window: &mut Window,
2599 cx: &mut Context<Self>,
2600 ) where
2601 T: EditPredictionProvider,
2602 {
2603 self.edit_prediction_provider =
2604 provider.map(|provider| RegisteredInlineCompletionProvider {
2605 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2606 if this.focus_handle.is_focused(window) {
2607 this.update_visible_inline_completion(window, cx);
2608 }
2609 }),
2610 provider: Arc::new(provider),
2611 });
2612 self.update_edit_prediction_settings(cx);
2613 self.refresh_inline_completion(false, false, window, cx);
2614 }
2615
2616 pub fn placeholder_text(&self) -> Option<&str> {
2617 self.placeholder_text.as_deref()
2618 }
2619
2620 pub fn set_placeholder_text(
2621 &mut self,
2622 placeholder_text: impl Into<Arc<str>>,
2623 cx: &mut Context<Self>,
2624 ) {
2625 let placeholder_text = Some(placeholder_text.into());
2626 if self.placeholder_text != placeholder_text {
2627 self.placeholder_text = placeholder_text;
2628 cx.notify();
2629 }
2630 }
2631
2632 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2633 self.cursor_shape = cursor_shape;
2634
2635 // Disrupt blink for immediate user feedback that the cursor shape has changed
2636 self.blink_manager.update(cx, BlinkManager::show_cursor);
2637
2638 cx.notify();
2639 }
2640
2641 pub fn set_current_line_highlight(
2642 &mut self,
2643 current_line_highlight: Option<CurrentLineHighlight>,
2644 ) {
2645 self.current_line_highlight = current_line_highlight;
2646 }
2647
2648 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2649 self.collapse_matches = collapse_matches;
2650 }
2651
2652 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2653 let buffers = self.buffer.read(cx).all_buffers();
2654 let Some(project) = self.project.as_ref() else {
2655 return;
2656 };
2657 project.update(cx, |project, cx| {
2658 for buffer in buffers {
2659 self.registered_buffers
2660 .entry(buffer.read(cx).remote_id())
2661 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2662 }
2663 })
2664 }
2665
2666 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2667 if self.collapse_matches {
2668 return range.start..range.start;
2669 }
2670 range.clone()
2671 }
2672
2673 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2674 if self.display_map.read(cx).clip_at_line_ends != clip {
2675 self.display_map
2676 .update(cx, |map, _| map.clip_at_line_ends = clip);
2677 }
2678 }
2679
2680 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2681 self.input_enabled = input_enabled;
2682 }
2683
2684 pub fn set_inline_completions_hidden_for_vim_mode(
2685 &mut self,
2686 hidden: bool,
2687 window: &mut Window,
2688 cx: &mut Context<Self>,
2689 ) {
2690 if hidden != self.inline_completions_hidden_for_vim_mode {
2691 self.inline_completions_hidden_for_vim_mode = hidden;
2692 if hidden {
2693 self.update_visible_inline_completion(window, cx);
2694 } else {
2695 self.refresh_inline_completion(true, false, window, cx);
2696 }
2697 }
2698 }
2699
2700 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2701 self.menu_inline_completions_policy = value;
2702 }
2703
2704 pub fn set_autoindent(&mut self, autoindent: bool) {
2705 if autoindent {
2706 self.autoindent_mode = Some(AutoindentMode::EachLine);
2707 } else {
2708 self.autoindent_mode = None;
2709 }
2710 }
2711
2712 pub fn read_only(&self, cx: &App) -> bool {
2713 self.read_only || self.buffer.read(cx).read_only()
2714 }
2715
2716 pub fn set_read_only(&mut self, read_only: bool) {
2717 self.read_only = read_only;
2718 }
2719
2720 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2721 self.use_autoclose = autoclose;
2722 }
2723
2724 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2725 self.use_auto_surround = auto_surround;
2726 }
2727
2728 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2729 self.auto_replace_emoji_shortcode = auto_replace;
2730 }
2731
2732 pub fn toggle_edit_predictions(
2733 &mut self,
2734 _: &ToggleEditPrediction,
2735 window: &mut Window,
2736 cx: &mut Context<Self>,
2737 ) {
2738 if self.show_inline_completions_override.is_some() {
2739 self.set_show_edit_predictions(None, window, cx);
2740 } else {
2741 let show_edit_predictions = !self.edit_predictions_enabled();
2742 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2743 }
2744 }
2745
2746 pub fn set_show_edit_predictions(
2747 &mut self,
2748 show_edit_predictions: Option<bool>,
2749 window: &mut Window,
2750 cx: &mut Context<Self>,
2751 ) {
2752 self.show_inline_completions_override = show_edit_predictions;
2753 self.update_edit_prediction_settings(cx);
2754
2755 if let Some(false) = show_edit_predictions {
2756 self.discard_inline_completion(false, cx);
2757 } else {
2758 self.refresh_inline_completion(false, true, window, cx);
2759 }
2760 }
2761
2762 fn inline_completions_disabled_in_scope(
2763 &self,
2764 buffer: &Entity<Buffer>,
2765 buffer_position: language::Anchor,
2766 cx: &App,
2767 ) -> bool {
2768 let snapshot = buffer.read(cx).snapshot();
2769 let settings = snapshot.settings_at(buffer_position, cx);
2770
2771 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2772 return false;
2773 };
2774
2775 scope.override_name().map_or(false, |scope_name| {
2776 settings
2777 .edit_predictions_disabled_in
2778 .iter()
2779 .any(|s| s == scope_name)
2780 })
2781 }
2782
2783 pub fn set_use_modal_editing(&mut self, to: bool) {
2784 self.use_modal_editing = to;
2785 }
2786
2787 pub fn use_modal_editing(&self) -> bool {
2788 self.use_modal_editing
2789 }
2790
2791 fn selections_did_change(
2792 &mut self,
2793 local: bool,
2794 old_cursor_position: &Anchor,
2795 effects: SelectionEffects,
2796 window: &mut Window,
2797 cx: &mut Context<Self>,
2798 ) {
2799 window.invalidate_character_coordinates();
2800
2801 // Copy selections to primary selection buffer
2802 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2803 if local {
2804 let selections = self.selections.all::<usize>(cx);
2805 let buffer_handle = self.buffer.read(cx).read(cx);
2806
2807 let mut text = String::new();
2808 for (index, selection) in selections.iter().enumerate() {
2809 let text_for_selection = buffer_handle
2810 .text_for_range(selection.start..selection.end)
2811 .collect::<String>();
2812
2813 text.push_str(&text_for_selection);
2814 if index != selections.len() - 1 {
2815 text.push('\n');
2816 }
2817 }
2818
2819 if !text.is_empty() {
2820 cx.write_to_primary(ClipboardItem::new_string(text));
2821 }
2822 }
2823
2824 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2825 self.buffer.update(cx, |buffer, cx| {
2826 buffer.set_active_selections(
2827 &self.selections.disjoint_anchors(),
2828 self.selections.line_mode,
2829 self.cursor_shape,
2830 cx,
2831 )
2832 });
2833 }
2834 let display_map = self
2835 .display_map
2836 .update(cx, |display_map, cx| display_map.snapshot(cx));
2837 let buffer = &display_map.buffer_snapshot;
2838 if self.selections.count() == 1 {
2839 self.add_selections_state = None;
2840 }
2841 self.select_next_state = None;
2842 self.select_prev_state = None;
2843 self.select_syntax_node_history.try_clear();
2844 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2845 self.snippet_stack
2846 .invalidate(&self.selections.disjoint_anchors(), buffer);
2847 self.take_rename(false, window, cx);
2848
2849 let newest_selection = self.selections.newest_anchor();
2850 let new_cursor_position = newest_selection.head();
2851 let selection_start = newest_selection.start;
2852
2853 if effects.nav_history {
2854 self.push_to_nav_history(
2855 *old_cursor_position,
2856 Some(new_cursor_position.to_point(buffer)),
2857 false,
2858 cx,
2859 );
2860 }
2861
2862 if local {
2863 if let Some(buffer_id) = new_cursor_position.buffer_id {
2864 if !self.registered_buffers.contains_key(&buffer_id) {
2865 if let Some(project) = self.project.as_ref() {
2866 project.update(cx, |project, cx| {
2867 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2868 return;
2869 };
2870 self.registered_buffers.insert(
2871 buffer_id,
2872 project.register_buffer_with_language_servers(&buffer, cx),
2873 );
2874 })
2875 }
2876 }
2877 }
2878
2879 let mut context_menu = self.context_menu.borrow_mut();
2880 let completion_menu = match context_menu.as_ref() {
2881 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2882 Some(CodeContextMenu::CodeActions(_)) => {
2883 *context_menu = None;
2884 None
2885 }
2886 None => None,
2887 };
2888 let completion_position = completion_menu.map(|menu| menu.initial_position);
2889 drop(context_menu);
2890
2891 if effects.completions {
2892 if let Some(completion_position) = completion_position {
2893 let start_offset = selection_start.to_offset(buffer);
2894 let position_matches = start_offset == completion_position.to_offset(buffer);
2895 let continue_showing = if position_matches {
2896 if self.snippet_stack.is_empty() {
2897 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2898 } else {
2899 // Snippet choices can be shown even when the cursor is in whitespace.
2900 // Dismissing the menu with actions like backspace is handled by
2901 // invalidation regions.
2902 true
2903 }
2904 } else {
2905 false
2906 };
2907
2908 if continue_showing {
2909 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2910 } else {
2911 self.hide_context_menu(window, cx);
2912 }
2913 }
2914 }
2915
2916 hide_hover(self, cx);
2917
2918 if old_cursor_position.to_display_point(&display_map).row()
2919 != new_cursor_position.to_display_point(&display_map).row()
2920 {
2921 self.available_code_actions.take();
2922 }
2923 self.refresh_code_actions(window, cx);
2924 self.refresh_document_highlights(cx);
2925 self.refresh_selected_text_highlights(false, window, cx);
2926 refresh_matching_bracket_highlights(self, window, cx);
2927 self.update_visible_inline_completion(window, cx);
2928 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2929 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2930 self.inline_blame_popover.take();
2931 if self.git_blame_inline_enabled {
2932 self.start_inline_blame_timer(window, cx);
2933 }
2934 }
2935
2936 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2937 cx.emit(EditorEvent::SelectionsChanged { local });
2938
2939 let selections = &self.selections.disjoint;
2940 if selections.len() == 1 {
2941 cx.emit(SearchEvent::ActiveMatchChanged)
2942 }
2943 if local {
2944 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2945 let inmemory_selections = selections
2946 .iter()
2947 .map(|s| {
2948 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2949 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2950 })
2951 .collect();
2952 self.update_restoration_data(cx, |data| {
2953 data.selections = inmemory_selections;
2954 });
2955
2956 if WorkspaceSettings::get(None, cx).restore_on_startup
2957 != RestoreOnStartupBehavior::None
2958 {
2959 if let Some(workspace_id) =
2960 self.workspace.as_ref().and_then(|workspace| workspace.1)
2961 {
2962 let snapshot = self.buffer().read(cx).snapshot(cx);
2963 let selections = selections.clone();
2964 let background_executor = cx.background_executor().clone();
2965 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2966 self.serialize_selections = cx.background_spawn(async move {
2967 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2968 let db_selections = selections
2969 .iter()
2970 .map(|selection| {
2971 (
2972 selection.start.to_offset(&snapshot),
2973 selection.end.to_offset(&snapshot),
2974 )
2975 })
2976 .collect();
2977
2978 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2979 .await
2980 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2981 .log_err();
2982 });
2983 }
2984 }
2985 }
2986 }
2987
2988 cx.notify();
2989 }
2990
2991 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2992 use text::ToOffset as _;
2993 use text::ToPoint as _;
2994
2995 if self.mode.is_minimap()
2996 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2997 {
2998 return;
2999 }
3000
3001 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3002 return;
3003 };
3004
3005 let snapshot = singleton.read(cx).snapshot();
3006 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3007 let display_snapshot = display_map.snapshot(cx);
3008
3009 display_snapshot
3010 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3011 .map(|fold| {
3012 fold.range.start.text_anchor.to_point(&snapshot)
3013 ..fold.range.end.text_anchor.to_point(&snapshot)
3014 })
3015 .collect()
3016 });
3017 self.update_restoration_data(cx, |data| {
3018 data.folds = inmemory_folds;
3019 });
3020
3021 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3022 return;
3023 };
3024 let background_executor = cx.background_executor().clone();
3025 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3026 let db_folds = self.display_map.update(cx, |display_map, cx| {
3027 display_map
3028 .snapshot(cx)
3029 .folds_in_range(0..snapshot.len())
3030 .map(|fold| {
3031 (
3032 fold.range.start.text_anchor.to_offset(&snapshot),
3033 fold.range.end.text_anchor.to_offset(&snapshot),
3034 )
3035 })
3036 .collect()
3037 });
3038 self.serialize_folds = cx.background_spawn(async move {
3039 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3040 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3041 .await
3042 .with_context(|| {
3043 format!(
3044 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3045 )
3046 })
3047 .log_err();
3048 });
3049 }
3050
3051 pub fn sync_selections(
3052 &mut self,
3053 other: Entity<Editor>,
3054 cx: &mut Context<Self>,
3055 ) -> gpui::Subscription {
3056 let other_selections = other.read(cx).selections.disjoint.to_vec();
3057 self.selections.change_with(cx, |selections| {
3058 selections.select_anchors(other_selections);
3059 });
3060
3061 let other_subscription =
3062 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3063 EditorEvent::SelectionsChanged { local: true } => {
3064 let other_selections = other.read(cx).selections.disjoint.to_vec();
3065 if other_selections.is_empty() {
3066 return;
3067 }
3068 this.selections.change_with(cx, |selections| {
3069 selections.select_anchors(other_selections);
3070 });
3071 }
3072 _ => {}
3073 });
3074
3075 let this_subscription =
3076 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3077 EditorEvent::SelectionsChanged { local: true } => {
3078 let these_selections = this.selections.disjoint.to_vec();
3079 if these_selections.is_empty() {
3080 return;
3081 }
3082 other.update(cx, |other_editor, cx| {
3083 other_editor.selections.change_with(cx, |selections| {
3084 selections.select_anchors(these_selections);
3085 })
3086 });
3087 }
3088 _ => {}
3089 });
3090
3091 Subscription::join(other_subscription, this_subscription)
3092 }
3093
3094 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3095 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3096 /// effects of selection change occur at the end of the transaction.
3097 pub fn change_selections<R>(
3098 &mut self,
3099 effects: impl Into<SelectionEffects>,
3100 window: &mut Window,
3101 cx: &mut Context<Self>,
3102 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3103 ) -> R {
3104 let effects = effects.into();
3105 if let Some(state) = &mut self.deferred_selection_effects_state {
3106 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3107 state.effects.completions = effects.completions;
3108 state.effects.nav_history |= effects.nav_history;
3109 let (changed, result) = self.selections.change_with(cx, change);
3110 state.changed |= changed;
3111 return result;
3112 }
3113 let mut state = DeferredSelectionEffectsState {
3114 changed: false,
3115 effects,
3116 old_cursor_position: self.selections.newest_anchor().head(),
3117 history_entry: SelectionHistoryEntry {
3118 selections: self.selections.disjoint_anchors(),
3119 select_next_state: self.select_next_state.clone(),
3120 select_prev_state: self.select_prev_state.clone(),
3121 add_selections_state: self.add_selections_state.clone(),
3122 },
3123 };
3124 let (changed, result) = self.selections.change_with(cx, change);
3125 state.changed = state.changed || changed;
3126 if self.defer_selection_effects {
3127 self.deferred_selection_effects_state = Some(state);
3128 } else {
3129 self.apply_selection_effects(state, window, cx);
3130 }
3131 result
3132 }
3133
3134 /// Defers the effects of selection change, so that the effects of multiple calls to
3135 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3136 /// to selection history and the state of popovers based on selection position aren't
3137 /// erroneously updated.
3138 pub fn with_selection_effects_deferred<R>(
3139 &mut self,
3140 window: &mut Window,
3141 cx: &mut Context<Self>,
3142 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3143 ) -> R {
3144 let already_deferred = self.defer_selection_effects;
3145 self.defer_selection_effects = true;
3146 let result = update(self, window, cx);
3147 if !already_deferred {
3148 self.defer_selection_effects = false;
3149 if let Some(state) = self.deferred_selection_effects_state.take() {
3150 self.apply_selection_effects(state, window, cx);
3151 }
3152 }
3153 result
3154 }
3155
3156 fn apply_selection_effects(
3157 &mut self,
3158 state: DeferredSelectionEffectsState,
3159 window: &mut Window,
3160 cx: &mut Context<Self>,
3161 ) {
3162 if state.changed {
3163 self.selection_history.push(state.history_entry);
3164
3165 if let Some(autoscroll) = state.effects.scroll {
3166 self.request_autoscroll(autoscroll, cx);
3167 }
3168
3169 let old_cursor_position = &state.old_cursor_position;
3170
3171 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3172
3173 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3174 self.show_signature_help(&ShowSignatureHelp, window, cx);
3175 }
3176 }
3177 }
3178
3179 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3180 where
3181 I: IntoIterator<Item = (Range<S>, T)>,
3182 S: ToOffset,
3183 T: Into<Arc<str>>,
3184 {
3185 if self.read_only(cx) {
3186 return;
3187 }
3188
3189 self.buffer
3190 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3191 }
3192
3193 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3194 where
3195 I: IntoIterator<Item = (Range<S>, T)>,
3196 S: ToOffset,
3197 T: Into<Arc<str>>,
3198 {
3199 if self.read_only(cx) {
3200 return;
3201 }
3202
3203 self.buffer.update(cx, |buffer, cx| {
3204 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3205 });
3206 }
3207
3208 pub fn edit_with_block_indent<I, S, T>(
3209 &mut self,
3210 edits: I,
3211 original_indent_columns: Vec<Option<u32>>,
3212 cx: &mut Context<Self>,
3213 ) where
3214 I: IntoIterator<Item = (Range<S>, T)>,
3215 S: ToOffset,
3216 T: Into<Arc<str>>,
3217 {
3218 if self.read_only(cx) {
3219 return;
3220 }
3221
3222 self.buffer.update(cx, |buffer, cx| {
3223 buffer.edit(
3224 edits,
3225 Some(AutoindentMode::Block {
3226 original_indent_columns,
3227 }),
3228 cx,
3229 )
3230 });
3231 }
3232
3233 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3234 self.hide_context_menu(window, cx);
3235
3236 match phase {
3237 SelectPhase::Begin {
3238 position,
3239 add,
3240 click_count,
3241 } => self.begin_selection(position, add, click_count, window, cx),
3242 SelectPhase::BeginColumnar {
3243 position,
3244 goal_column,
3245 reset,
3246 mode,
3247 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3248 SelectPhase::Extend {
3249 position,
3250 click_count,
3251 } => self.extend_selection(position, click_count, window, cx),
3252 SelectPhase::Update {
3253 position,
3254 goal_column,
3255 scroll_delta,
3256 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3257 SelectPhase::End => self.end_selection(window, cx),
3258 }
3259 }
3260
3261 fn extend_selection(
3262 &mut self,
3263 position: DisplayPoint,
3264 click_count: usize,
3265 window: &mut Window,
3266 cx: &mut Context<Self>,
3267 ) {
3268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3269 let tail = self.selections.newest::<usize>(cx).tail();
3270 self.begin_selection(position, false, click_count, window, cx);
3271
3272 let position = position.to_offset(&display_map, Bias::Left);
3273 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3274
3275 let mut pending_selection = self
3276 .selections
3277 .pending_anchor()
3278 .expect("extend_selection not called with pending selection");
3279 if position >= tail {
3280 pending_selection.start = tail_anchor;
3281 } else {
3282 pending_selection.end = tail_anchor;
3283 pending_selection.reversed = true;
3284 }
3285
3286 let mut pending_mode = self.selections.pending_mode().unwrap();
3287 match &mut pending_mode {
3288 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3289 _ => {}
3290 }
3291
3292 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3293 SelectionEffects::scroll(Autoscroll::fit())
3294 } else {
3295 SelectionEffects::no_scroll()
3296 };
3297
3298 self.change_selections(effects, window, cx, |s| {
3299 s.set_pending(pending_selection, pending_mode)
3300 });
3301 }
3302
3303 fn begin_selection(
3304 &mut self,
3305 position: DisplayPoint,
3306 add: bool,
3307 click_count: usize,
3308 window: &mut Window,
3309 cx: &mut Context<Self>,
3310 ) {
3311 if !self.focus_handle.is_focused(window) {
3312 self.last_focused_descendant = None;
3313 window.focus(&self.focus_handle);
3314 }
3315
3316 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3317 let buffer = &display_map.buffer_snapshot;
3318 let position = display_map.clip_point(position, Bias::Left);
3319
3320 let start;
3321 let end;
3322 let mode;
3323 let mut auto_scroll;
3324 match click_count {
3325 1 => {
3326 start = buffer.anchor_before(position.to_point(&display_map));
3327 end = start;
3328 mode = SelectMode::Character;
3329 auto_scroll = true;
3330 }
3331 2 => {
3332 let range = movement::surrounding_word(&display_map, position);
3333 start = buffer.anchor_before(range.start.to_point(&display_map));
3334 end = buffer.anchor_before(range.end.to_point(&display_map));
3335 mode = SelectMode::Word(start..end);
3336 auto_scroll = true;
3337 }
3338 3 => {
3339 let position = display_map
3340 .clip_point(position, Bias::Left)
3341 .to_point(&display_map);
3342 let line_start = display_map.prev_line_boundary(position).0;
3343 let next_line_start = buffer.clip_point(
3344 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3345 Bias::Left,
3346 );
3347 start = buffer.anchor_before(line_start);
3348 end = buffer.anchor_before(next_line_start);
3349 mode = SelectMode::Line(start..end);
3350 auto_scroll = true;
3351 }
3352 _ => {
3353 start = buffer.anchor_before(0);
3354 end = buffer.anchor_before(buffer.len());
3355 mode = SelectMode::All;
3356 auto_scroll = false;
3357 }
3358 }
3359 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3360
3361 let point_to_delete: Option<usize> = {
3362 let selected_points: Vec<Selection<Point>> =
3363 self.selections.disjoint_in_range(start..end, cx);
3364
3365 if !add || click_count > 1 {
3366 None
3367 } else if !selected_points.is_empty() {
3368 Some(selected_points[0].id)
3369 } else {
3370 let clicked_point_already_selected =
3371 self.selections.disjoint.iter().find(|selection| {
3372 selection.start.to_point(buffer) == start.to_point(buffer)
3373 || selection.end.to_point(buffer) == end.to_point(buffer)
3374 });
3375
3376 clicked_point_already_selected.map(|selection| selection.id)
3377 }
3378 };
3379
3380 let selections_count = self.selections.count();
3381
3382 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3383 if let Some(point_to_delete) = point_to_delete {
3384 s.delete(point_to_delete);
3385
3386 if selections_count == 1 {
3387 s.set_pending_anchor_range(start..end, mode);
3388 }
3389 } else {
3390 if !add {
3391 s.clear_disjoint();
3392 }
3393
3394 s.set_pending_anchor_range(start..end, mode);
3395 }
3396 });
3397 }
3398
3399 fn begin_columnar_selection(
3400 &mut self,
3401 position: DisplayPoint,
3402 goal_column: u32,
3403 reset: bool,
3404 mode: ColumnarMode,
3405 window: &mut Window,
3406 cx: &mut Context<Self>,
3407 ) {
3408 if !self.focus_handle.is_focused(window) {
3409 self.last_focused_descendant = None;
3410 window.focus(&self.focus_handle);
3411 }
3412
3413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3414
3415 if reset {
3416 let pointer_position = display_map
3417 .buffer_snapshot
3418 .anchor_before(position.to_point(&display_map));
3419
3420 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3421 s.clear_disjoint();
3422 s.set_pending_anchor_range(
3423 pointer_position..pointer_position,
3424 SelectMode::Character,
3425 );
3426 });
3427 };
3428
3429 let tail = self.selections.newest::<Point>(cx).tail();
3430 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3431 self.columnar_selection_state = match mode {
3432 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3433 selection_tail: selection_anchor,
3434 display_point: if reset {
3435 if position.column() != goal_column {
3436 Some(DisplayPoint::new(position.row(), goal_column))
3437 } else {
3438 None
3439 }
3440 } else {
3441 None
3442 },
3443 }),
3444 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3445 selection_tail: selection_anchor,
3446 }),
3447 };
3448
3449 if !reset {
3450 self.select_columns(position, goal_column, &display_map, window, cx);
3451 }
3452 }
3453
3454 fn update_selection(
3455 &mut self,
3456 position: DisplayPoint,
3457 goal_column: u32,
3458 scroll_delta: gpui::Point<f32>,
3459 window: &mut Window,
3460 cx: &mut Context<Self>,
3461 ) {
3462 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3463
3464 if self.columnar_selection_state.is_some() {
3465 self.select_columns(position, goal_column, &display_map, window, cx);
3466 } else if let Some(mut pending) = self.selections.pending_anchor() {
3467 let buffer = self.buffer.read(cx).snapshot(cx);
3468 let head;
3469 let tail;
3470 let mode = self.selections.pending_mode().unwrap();
3471 match &mode {
3472 SelectMode::Character => {
3473 head = position.to_point(&display_map);
3474 tail = pending.tail().to_point(&buffer);
3475 }
3476 SelectMode::Word(original_range) => {
3477 let original_display_range = original_range.start.to_display_point(&display_map)
3478 ..original_range.end.to_display_point(&display_map);
3479 let original_buffer_range = original_display_range.start.to_point(&display_map)
3480 ..original_display_range.end.to_point(&display_map);
3481 if movement::is_inside_word(&display_map, position)
3482 || original_display_range.contains(&position)
3483 {
3484 let word_range = movement::surrounding_word(&display_map, position);
3485 if word_range.start < original_display_range.start {
3486 head = word_range.start.to_point(&display_map);
3487 } else {
3488 head = word_range.end.to_point(&display_map);
3489 }
3490 } else {
3491 head = position.to_point(&display_map);
3492 }
3493
3494 if head <= original_buffer_range.start {
3495 tail = original_buffer_range.end;
3496 } else {
3497 tail = original_buffer_range.start;
3498 }
3499 }
3500 SelectMode::Line(original_range) => {
3501 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3502
3503 let position = display_map
3504 .clip_point(position, Bias::Left)
3505 .to_point(&display_map);
3506 let line_start = display_map.prev_line_boundary(position).0;
3507 let next_line_start = buffer.clip_point(
3508 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3509 Bias::Left,
3510 );
3511
3512 if line_start < original_range.start {
3513 head = line_start
3514 } else {
3515 head = next_line_start
3516 }
3517
3518 if head <= original_range.start {
3519 tail = original_range.end;
3520 } else {
3521 tail = original_range.start;
3522 }
3523 }
3524 SelectMode::All => {
3525 return;
3526 }
3527 };
3528
3529 if head < tail {
3530 pending.start = buffer.anchor_before(head);
3531 pending.end = buffer.anchor_before(tail);
3532 pending.reversed = true;
3533 } else {
3534 pending.start = buffer.anchor_before(tail);
3535 pending.end = buffer.anchor_before(head);
3536 pending.reversed = false;
3537 }
3538
3539 self.change_selections(None, window, cx, |s| {
3540 s.set_pending(pending, mode);
3541 });
3542 } else {
3543 log::error!("update_selection dispatched with no pending selection");
3544 return;
3545 }
3546
3547 self.apply_scroll_delta(scroll_delta, window, cx);
3548 cx.notify();
3549 }
3550
3551 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3552 self.columnar_selection_state.take();
3553 if self.selections.pending_anchor().is_some() {
3554 let selections = self.selections.all::<usize>(cx);
3555 self.change_selections(None, window, cx, |s| {
3556 s.select(selections);
3557 s.clear_pending();
3558 });
3559 }
3560 }
3561
3562 fn select_columns(
3563 &mut self,
3564 head: DisplayPoint,
3565 goal_column: u32,
3566 display_map: &DisplaySnapshot,
3567 window: &mut Window,
3568 cx: &mut Context<Self>,
3569 ) {
3570 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3571 return;
3572 };
3573
3574 let tail = match columnar_state {
3575 ColumnarSelectionState::FromMouse {
3576 selection_tail,
3577 display_point,
3578 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3579 ColumnarSelectionState::FromSelection { selection_tail } => {
3580 selection_tail.to_display_point(&display_map)
3581 }
3582 };
3583
3584 let start_row = cmp::min(tail.row(), head.row());
3585 let end_row = cmp::max(tail.row(), head.row());
3586 let start_column = cmp::min(tail.column(), goal_column);
3587 let end_column = cmp::max(tail.column(), goal_column);
3588 let reversed = start_column < tail.column();
3589
3590 let selection_ranges = (start_row.0..=end_row.0)
3591 .map(DisplayRow)
3592 .filter_map(|row| {
3593 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3594 || start_column <= display_map.line_len(row))
3595 && !display_map.is_block_line(row)
3596 {
3597 let start = display_map
3598 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3599 .to_point(display_map);
3600 let end = display_map
3601 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3602 .to_point(display_map);
3603 if reversed {
3604 Some(end..start)
3605 } else {
3606 Some(start..end)
3607 }
3608 } else {
3609 None
3610 }
3611 })
3612 .collect::<Vec<_>>();
3613
3614 let ranges = match columnar_state {
3615 ColumnarSelectionState::FromMouse { .. } => {
3616 let mut non_empty_ranges = selection_ranges
3617 .iter()
3618 .filter(|selection_range| selection_range.start != selection_range.end)
3619 .peekable();
3620 if non_empty_ranges.peek().is_some() {
3621 non_empty_ranges.cloned().collect()
3622 } else {
3623 selection_ranges
3624 }
3625 }
3626 _ => selection_ranges,
3627 };
3628
3629 self.change_selections(None, window, cx, |s| {
3630 s.select_ranges(ranges);
3631 });
3632 cx.notify();
3633 }
3634
3635 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3636 self.selections
3637 .all_adjusted(cx)
3638 .iter()
3639 .any(|selection| !selection.is_empty())
3640 }
3641
3642 pub fn has_pending_nonempty_selection(&self) -> bool {
3643 let pending_nonempty_selection = match self.selections.pending_anchor() {
3644 Some(Selection { start, end, .. }) => start != end,
3645 None => false,
3646 };
3647
3648 pending_nonempty_selection
3649 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3650 }
3651
3652 pub fn has_pending_selection(&self) -> bool {
3653 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3654 }
3655
3656 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3657 self.selection_mark_mode = false;
3658 self.selection_drag_state = SelectionDragState::None;
3659
3660 if self.clear_expanded_diff_hunks(cx) {
3661 cx.notify();
3662 return;
3663 }
3664 if self.dismiss_menus_and_popups(true, window, cx) {
3665 return;
3666 }
3667
3668 if self.mode.is_full()
3669 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3670 {
3671 return;
3672 }
3673
3674 cx.propagate();
3675 }
3676
3677 pub fn dismiss_menus_and_popups(
3678 &mut self,
3679 is_user_requested: bool,
3680 window: &mut Window,
3681 cx: &mut Context<Self>,
3682 ) -> bool {
3683 if self.take_rename(false, window, cx).is_some() {
3684 return true;
3685 }
3686
3687 if hide_hover(self, cx) {
3688 return true;
3689 }
3690
3691 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3692 return true;
3693 }
3694
3695 if self.hide_context_menu(window, cx).is_some() {
3696 return true;
3697 }
3698
3699 if self.mouse_context_menu.take().is_some() {
3700 return true;
3701 }
3702
3703 if is_user_requested && self.discard_inline_completion(true, cx) {
3704 return true;
3705 }
3706
3707 if self.snippet_stack.pop().is_some() {
3708 return true;
3709 }
3710
3711 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3712 self.dismiss_diagnostics(cx);
3713 return true;
3714 }
3715
3716 false
3717 }
3718
3719 fn linked_editing_ranges_for(
3720 &self,
3721 selection: Range<text::Anchor>,
3722 cx: &App,
3723 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3724 if self.linked_edit_ranges.is_empty() {
3725 return None;
3726 }
3727 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3728 selection.end.buffer_id.and_then(|end_buffer_id| {
3729 if selection.start.buffer_id != Some(end_buffer_id) {
3730 return None;
3731 }
3732 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3733 let snapshot = buffer.read(cx).snapshot();
3734 self.linked_edit_ranges
3735 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3736 .map(|ranges| (ranges, snapshot, buffer))
3737 })?;
3738 use text::ToOffset as TO;
3739 // find offset from the start of current range to current cursor position
3740 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3741
3742 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3743 let start_difference = start_offset - start_byte_offset;
3744 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3745 let end_difference = end_offset - start_byte_offset;
3746 // Current range has associated linked ranges.
3747 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3748 for range in linked_ranges.iter() {
3749 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3750 let end_offset = start_offset + end_difference;
3751 let start_offset = start_offset + start_difference;
3752 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3753 continue;
3754 }
3755 if self.selections.disjoint_anchor_ranges().any(|s| {
3756 if s.start.buffer_id != selection.start.buffer_id
3757 || s.end.buffer_id != selection.end.buffer_id
3758 {
3759 return false;
3760 }
3761 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3762 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3763 }) {
3764 continue;
3765 }
3766 let start = buffer_snapshot.anchor_after(start_offset);
3767 let end = buffer_snapshot.anchor_after(end_offset);
3768 linked_edits
3769 .entry(buffer.clone())
3770 .or_default()
3771 .push(start..end);
3772 }
3773 Some(linked_edits)
3774 }
3775
3776 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3777 let text: Arc<str> = text.into();
3778
3779 if self.read_only(cx) {
3780 return;
3781 }
3782
3783 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3784
3785 let selections = self.selections.all_adjusted(cx);
3786 let mut bracket_inserted = false;
3787 let mut edits = Vec::new();
3788 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3789 let mut new_selections = Vec::with_capacity(selections.len());
3790 let mut new_autoclose_regions = Vec::new();
3791 let snapshot = self.buffer.read(cx).read(cx);
3792 let mut clear_linked_edit_ranges = false;
3793
3794 for (selection, autoclose_region) in
3795 self.selections_with_autoclose_regions(selections, &snapshot)
3796 {
3797 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3798 // Determine if the inserted text matches the opening or closing
3799 // bracket of any of this language's bracket pairs.
3800 let mut bracket_pair = None;
3801 let mut is_bracket_pair_start = false;
3802 let mut is_bracket_pair_end = false;
3803 if !text.is_empty() {
3804 let mut bracket_pair_matching_end = None;
3805 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3806 // and they are removing the character that triggered IME popup.
3807 for (pair, enabled) in scope.brackets() {
3808 if !pair.close && !pair.surround {
3809 continue;
3810 }
3811
3812 if enabled && pair.start.ends_with(text.as_ref()) {
3813 let prefix_len = pair.start.len() - text.len();
3814 let preceding_text_matches_prefix = prefix_len == 0
3815 || (selection.start.column >= (prefix_len as u32)
3816 && snapshot.contains_str_at(
3817 Point::new(
3818 selection.start.row,
3819 selection.start.column - (prefix_len as u32),
3820 ),
3821 &pair.start[..prefix_len],
3822 ));
3823 if preceding_text_matches_prefix {
3824 bracket_pair = Some(pair.clone());
3825 is_bracket_pair_start = true;
3826 break;
3827 }
3828 }
3829 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3830 {
3831 // take first bracket pair matching end, but don't break in case a later bracket
3832 // pair matches start
3833 bracket_pair_matching_end = Some(pair.clone());
3834 }
3835 }
3836 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3837 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3838 is_bracket_pair_end = true;
3839 }
3840 }
3841
3842 if let Some(bracket_pair) = bracket_pair {
3843 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3844 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3845 let auto_surround =
3846 self.use_auto_surround && snapshot_settings.use_auto_surround;
3847 if selection.is_empty() {
3848 if is_bracket_pair_start {
3849 // If the inserted text is a suffix of an opening bracket and the
3850 // selection is preceded by the rest of the opening bracket, then
3851 // insert the closing bracket.
3852 let following_text_allows_autoclose = snapshot
3853 .chars_at(selection.start)
3854 .next()
3855 .map_or(true, |c| scope.should_autoclose_before(c));
3856
3857 let preceding_text_allows_autoclose = selection.start.column == 0
3858 || snapshot.reversed_chars_at(selection.start).next().map_or(
3859 true,
3860 |c| {
3861 bracket_pair.start != bracket_pair.end
3862 || !snapshot
3863 .char_classifier_at(selection.start)
3864 .is_word(c)
3865 },
3866 );
3867
3868 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3869 && bracket_pair.start.len() == 1
3870 {
3871 let target = bracket_pair.start.chars().next().unwrap();
3872 let current_line_count = snapshot
3873 .reversed_chars_at(selection.start)
3874 .take_while(|&c| c != '\n')
3875 .filter(|&c| c == target)
3876 .count();
3877 current_line_count % 2 == 1
3878 } else {
3879 false
3880 };
3881
3882 if autoclose
3883 && bracket_pair.close
3884 && following_text_allows_autoclose
3885 && preceding_text_allows_autoclose
3886 && !is_closing_quote
3887 {
3888 let anchor = snapshot.anchor_before(selection.end);
3889 new_selections.push((selection.map(|_| anchor), text.len()));
3890 new_autoclose_regions.push((
3891 anchor,
3892 text.len(),
3893 selection.id,
3894 bracket_pair.clone(),
3895 ));
3896 edits.push((
3897 selection.range(),
3898 format!("{}{}", text, bracket_pair.end).into(),
3899 ));
3900 bracket_inserted = true;
3901 continue;
3902 }
3903 }
3904
3905 if let Some(region) = autoclose_region {
3906 // If the selection is followed by an auto-inserted closing bracket,
3907 // then don't insert that closing bracket again; just move the selection
3908 // past the closing bracket.
3909 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3910 && text.as_ref() == region.pair.end.as_str();
3911 if should_skip {
3912 let anchor = snapshot.anchor_after(selection.end);
3913 new_selections
3914 .push((selection.map(|_| anchor), region.pair.end.len()));
3915 continue;
3916 }
3917 }
3918
3919 let always_treat_brackets_as_autoclosed = snapshot
3920 .language_settings_at(selection.start, cx)
3921 .always_treat_brackets_as_autoclosed;
3922 if always_treat_brackets_as_autoclosed
3923 && is_bracket_pair_end
3924 && snapshot.contains_str_at(selection.end, text.as_ref())
3925 {
3926 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3927 // and the inserted text is a closing bracket and the selection is followed
3928 // by the closing bracket then move the selection past the closing bracket.
3929 let anchor = snapshot.anchor_after(selection.end);
3930 new_selections.push((selection.map(|_| anchor), text.len()));
3931 continue;
3932 }
3933 }
3934 // If an opening bracket is 1 character long and is typed while
3935 // text is selected, then surround that text with the bracket pair.
3936 else if auto_surround
3937 && bracket_pair.surround
3938 && is_bracket_pair_start
3939 && bracket_pair.start.chars().count() == 1
3940 {
3941 edits.push((selection.start..selection.start, text.clone()));
3942 edits.push((
3943 selection.end..selection.end,
3944 bracket_pair.end.as_str().into(),
3945 ));
3946 bracket_inserted = true;
3947 new_selections.push((
3948 Selection {
3949 id: selection.id,
3950 start: snapshot.anchor_after(selection.start),
3951 end: snapshot.anchor_before(selection.end),
3952 reversed: selection.reversed,
3953 goal: selection.goal,
3954 },
3955 0,
3956 ));
3957 continue;
3958 }
3959 }
3960 }
3961
3962 if self.auto_replace_emoji_shortcode
3963 && selection.is_empty()
3964 && text.as_ref().ends_with(':')
3965 {
3966 if let Some(possible_emoji_short_code) =
3967 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3968 {
3969 if !possible_emoji_short_code.is_empty() {
3970 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3971 let emoji_shortcode_start = Point::new(
3972 selection.start.row,
3973 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3974 );
3975
3976 // Remove shortcode from buffer
3977 edits.push((
3978 emoji_shortcode_start..selection.start,
3979 "".to_string().into(),
3980 ));
3981 new_selections.push((
3982 Selection {
3983 id: selection.id,
3984 start: snapshot.anchor_after(emoji_shortcode_start),
3985 end: snapshot.anchor_before(selection.start),
3986 reversed: selection.reversed,
3987 goal: selection.goal,
3988 },
3989 0,
3990 ));
3991
3992 // Insert emoji
3993 let selection_start_anchor = snapshot.anchor_after(selection.start);
3994 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3995 edits.push((selection.start..selection.end, emoji.to_string().into()));
3996
3997 continue;
3998 }
3999 }
4000 }
4001 }
4002
4003 // If not handling any auto-close operation, then just replace the selected
4004 // text with the given input and move the selection to the end of the
4005 // newly inserted text.
4006 let anchor = snapshot.anchor_after(selection.end);
4007 if !self.linked_edit_ranges.is_empty() {
4008 let start_anchor = snapshot.anchor_before(selection.start);
4009
4010 let is_word_char = text.chars().next().map_or(true, |char| {
4011 let classifier = snapshot
4012 .char_classifier_at(start_anchor.to_offset(&snapshot))
4013 .ignore_punctuation(true);
4014 classifier.is_word(char)
4015 });
4016
4017 if is_word_char {
4018 if let Some(ranges) = self
4019 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4020 {
4021 for (buffer, edits) in ranges {
4022 linked_edits
4023 .entry(buffer.clone())
4024 .or_default()
4025 .extend(edits.into_iter().map(|range| (range, text.clone())));
4026 }
4027 }
4028 } else {
4029 clear_linked_edit_ranges = true;
4030 }
4031 }
4032
4033 new_selections.push((selection.map(|_| anchor), 0));
4034 edits.push((selection.start..selection.end, text.clone()));
4035 }
4036
4037 drop(snapshot);
4038
4039 self.transact(window, cx, |this, window, cx| {
4040 if clear_linked_edit_ranges {
4041 this.linked_edit_ranges.clear();
4042 }
4043 let initial_buffer_versions =
4044 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4045
4046 this.buffer.update(cx, |buffer, cx| {
4047 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4048 });
4049 for (buffer, edits) in linked_edits {
4050 buffer.update(cx, |buffer, cx| {
4051 let snapshot = buffer.snapshot();
4052 let edits = edits
4053 .into_iter()
4054 .map(|(range, text)| {
4055 use text::ToPoint as TP;
4056 let end_point = TP::to_point(&range.end, &snapshot);
4057 let start_point = TP::to_point(&range.start, &snapshot);
4058 (start_point..end_point, text)
4059 })
4060 .sorted_by_key(|(range, _)| range.start);
4061 buffer.edit(edits, None, cx);
4062 })
4063 }
4064 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4065 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4066 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4067 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4068 .zip(new_selection_deltas)
4069 .map(|(selection, delta)| Selection {
4070 id: selection.id,
4071 start: selection.start + delta,
4072 end: selection.end + delta,
4073 reversed: selection.reversed,
4074 goal: SelectionGoal::None,
4075 })
4076 .collect::<Vec<_>>();
4077
4078 let mut i = 0;
4079 for (position, delta, selection_id, pair) in new_autoclose_regions {
4080 let position = position.to_offset(&map.buffer_snapshot) + delta;
4081 let start = map.buffer_snapshot.anchor_before(position);
4082 let end = map.buffer_snapshot.anchor_after(position);
4083 while let Some(existing_state) = this.autoclose_regions.get(i) {
4084 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4085 Ordering::Less => i += 1,
4086 Ordering::Greater => break,
4087 Ordering::Equal => {
4088 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4089 Ordering::Less => i += 1,
4090 Ordering::Equal => break,
4091 Ordering::Greater => break,
4092 }
4093 }
4094 }
4095 }
4096 this.autoclose_regions.insert(
4097 i,
4098 AutocloseRegion {
4099 selection_id,
4100 range: start..end,
4101 pair,
4102 },
4103 );
4104 }
4105
4106 let had_active_inline_completion = this.has_active_inline_completion();
4107 this.change_selections(
4108 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4109 window,
4110 cx,
4111 |s| s.select(new_selections),
4112 );
4113
4114 if !bracket_inserted {
4115 if let Some(on_type_format_task) =
4116 this.trigger_on_type_formatting(text.to_string(), window, cx)
4117 {
4118 on_type_format_task.detach_and_log_err(cx);
4119 }
4120 }
4121
4122 let editor_settings = EditorSettings::get_global(cx);
4123 if bracket_inserted
4124 && (editor_settings.auto_signature_help
4125 || editor_settings.show_signature_help_after_edits)
4126 {
4127 this.show_signature_help(&ShowSignatureHelp, window, cx);
4128 }
4129
4130 let trigger_in_words =
4131 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4132 if this.hard_wrap.is_some() {
4133 let latest: Range<Point> = this.selections.newest(cx).range();
4134 if latest.is_empty()
4135 && this
4136 .buffer()
4137 .read(cx)
4138 .snapshot(cx)
4139 .line_len(MultiBufferRow(latest.start.row))
4140 == latest.start.column
4141 {
4142 this.rewrap_impl(
4143 RewrapOptions {
4144 override_language_settings: true,
4145 preserve_existing_whitespace: true,
4146 },
4147 cx,
4148 )
4149 }
4150 }
4151 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4152 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4153 this.refresh_inline_completion(true, false, window, cx);
4154 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4155 });
4156 }
4157
4158 fn find_possible_emoji_shortcode_at_position(
4159 snapshot: &MultiBufferSnapshot,
4160 position: Point,
4161 ) -> Option<String> {
4162 let mut chars = Vec::new();
4163 let mut found_colon = false;
4164 for char in snapshot.reversed_chars_at(position).take(100) {
4165 // Found a possible emoji shortcode in the middle of the buffer
4166 if found_colon {
4167 if char.is_whitespace() {
4168 chars.reverse();
4169 return Some(chars.iter().collect());
4170 }
4171 // If the previous character is not a whitespace, we are in the middle of a word
4172 // and we only want to complete the shortcode if the word is made up of other emojis
4173 let mut containing_word = String::new();
4174 for ch in snapshot
4175 .reversed_chars_at(position)
4176 .skip(chars.len() + 1)
4177 .take(100)
4178 {
4179 if ch.is_whitespace() {
4180 break;
4181 }
4182 containing_word.push(ch);
4183 }
4184 let containing_word = containing_word.chars().rev().collect::<String>();
4185 if util::word_consists_of_emojis(containing_word.as_str()) {
4186 chars.reverse();
4187 return Some(chars.iter().collect());
4188 }
4189 }
4190
4191 if char.is_whitespace() || !char.is_ascii() {
4192 return None;
4193 }
4194 if char == ':' {
4195 found_colon = true;
4196 } else {
4197 chars.push(char);
4198 }
4199 }
4200 // Found a possible emoji shortcode at the beginning of the buffer
4201 chars.reverse();
4202 Some(chars.iter().collect())
4203 }
4204
4205 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4207 self.transact(window, cx, |this, window, cx| {
4208 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4209 let selections = this.selections.all::<usize>(cx);
4210 let multi_buffer = this.buffer.read(cx);
4211 let buffer = multi_buffer.snapshot(cx);
4212 selections
4213 .iter()
4214 .map(|selection| {
4215 let start_point = selection.start.to_point(&buffer);
4216 let mut existing_indent =
4217 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4218 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4219 let start = selection.start;
4220 let end = selection.end;
4221 let selection_is_empty = start == end;
4222 let language_scope = buffer.language_scope_at(start);
4223 let (
4224 comment_delimiter,
4225 doc_delimiter,
4226 insert_extra_newline,
4227 indent_on_newline,
4228 indent_on_extra_newline,
4229 ) = if let Some(language) = &language_scope {
4230 let mut insert_extra_newline =
4231 insert_extra_newline_brackets(&buffer, start..end, language)
4232 || insert_extra_newline_tree_sitter(&buffer, start..end);
4233
4234 // Comment extension on newline is allowed only for cursor selections
4235 let comment_delimiter = maybe!({
4236 if !selection_is_empty {
4237 return None;
4238 }
4239
4240 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4241 return None;
4242 }
4243
4244 let delimiters = language.line_comment_prefixes();
4245 let max_len_of_delimiter =
4246 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4247 let (snapshot, range) =
4248 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4249
4250 let num_of_whitespaces = snapshot
4251 .chars_for_range(range.clone())
4252 .take_while(|c| c.is_whitespace())
4253 .count();
4254 let comment_candidate = snapshot
4255 .chars_for_range(range)
4256 .skip(num_of_whitespaces)
4257 .take(max_len_of_delimiter)
4258 .collect::<String>();
4259 let (delimiter, trimmed_len) = delimiters
4260 .iter()
4261 .filter_map(|delimiter| {
4262 let prefix = delimiter.trim_end();
4263 if comment_candidate.starts_with(prefix) {
4264 Some((delimiter, prefix.len()))
4265 } else {
4266 None
4267 }
4268 })
4269 .max_by_key(|(_, len)| *len)?;
4270
4271 let cursor_is_placed_after_comment_marker =
4272 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4273 if cursor_is_placed_after_comment_marker {
4274 Some(delimiter.clone())
4275 } else {
4276 None
4277 }
4278 });
4279
4280 let mut indent_on_newline = IndentSize::spaces(0);
4281 let mut indent_on_extra_newline = IndentSize::spaces(0);
4282
4283 let doc_delimiter = maybe!({
4284 if !selection_is_empty {
4285 return None;
4286 }
4287
4288 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4289 return None;
4290 }
4291
4292 let DocumentationConfig {
4293 start: start_tag,
4294 end: end_tag,
4295 prefix: delimiter,
4296 tab_size: len,
4297 } = language.documentation()?;
4298
4299 let is_within_block_comment = buffer
4300 .language_scope_at(start_point)
4301 .is_some_and(|scope| scope.override_name() == Some("comment"));
4302 if !is_within_block_comment {
4303 return None;
4304 }
4305
4306 let (snapshot, range) =
4307 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4308
4309 let num_of_whitespaces = snapshot
4310 .chars_for_range(range.clone())
4311 .take_while(|c| c.is_whitespace())
4312 .count();
4313
4314 // 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.
4315 let column = start_point.column;
4316 let cursor_is_after_start_tag = {
4317 let start_tag_len = start_tag.len();
4318 let start_tag_line = snapshot
4319 .chars_for_range(range.clone())
4320 .skip(num_of_whitespaces)
4321 .take(start_tag_len)
4322 .collect::<String>();
4323 if start_tag_line.starts_with(start_tag.as_ref()) {
4324 num_of_whitespaces + start_tag_len <= column as usize
4325 } else {
4326 false
4327 }
4328 };
4329
4330 let cursor_is_after_delimiter = {
4331 let delimiter_trim = delimiter.trim_end();
4332 let delimiter_line = snapshot
4333 .chars_for_range(range.clone())
4334 .skip(num_of_whitespaces)
4335 .take(delimiter_trim.len())
4336 .collect::<String>();
4337 if delimiter_line.starts_with(delimiter_trim) {
4338 num_of_whitespaces + delimiter_trim.len() <= column as usize
4339 } else {
4340 false
4341 }
4342 };
4343
4344 let cursor_is_before_end_tag_if_exists = {
4345 let mut char_position = 0u32;
4346 let mut end_tag_offset = None;
4347
4348 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4349 if let Some(byte_pos) = chunk.find(&**end_tag) {
4350 let chars_before_match =
4351 chunk[..byte_pos].chars().count() as u32;
4352 end_tag_offset =
4353 Some(char_position + chars_before_match);
4354 break 'outer;
4355 }
4356 char_position += chunk.chars().count() as u32;
4357 }
4358
4359 if let Some(end_tag_offset) = end_tag_offset {
4360 let cursor_is_before_end_tag = column <= end_tag_offset;
4361 if cursor_is_after_start_tag {
4362 if cursor_is_before_end_tag {
4363 insert_extra_newline = true;
4364 }
4365 let cursor_is_at_start_of_end_tag =
4366 column == end_tag_offset;
4367 if cursor_is_at_start_of_end_tag {
4368 indent_on_extra_newline.len = (*len).into();
4369 }
4370 }
4371 cursor_is_before_end_tag
4372 } else {
4373 true
4374 }
4375 };
4376
4377 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4378 && cursor_is_before_end_tag_if_exists
4379 {
4380 if cursor_is_after_start_tag {
4381 indent_on_newline.len = (*len).into();
4382 }
4383 Some(delimiter.clone())
4384 } else {
4385 None
4386 }
4387 });
4388
4389 (
4390 comment_delimiter,
4391 doc_delimiter,
4392 insert_extra_newline,
4393 indent_on_newline,
4394 indent_on_extra_newline,
4395 )
4396 } else {
4397 (
4398 None,
4399 None,
4400 false,
4401 IndentSize::default(),
4402 IndentSize::default(),
4403 )
4404 };
4405
4406 let prevent_auto_indent = doc_delimiter.is_some();
4407 let delimiter = comment_delimiter.or(doc_delimiter);
4408
4409 let capacity_for_delimiter =
4410 delimiter.as_deref().map(str::len).unwrap_or_default();
4411 let mut new_text = String::with_capacity(
4412 1 + capacity_for_delimiter
4413 + existing_indent.len as usize
4414 + indent_on_newline.len as usize
4415 + indent_on_extra_newline.len as usize,
4416 );
4417 new_text.push('\n');
4418 new_text.extend(existing_indent.chars());
4419 new_text.extend(indent_on_newline.chars());
4420
4421 if let Some(delimiter) = &delimiter {
4422 new_text.push_str(delimiter);
4423 }
4424
4425 if insert_extra_newline {
4426 new_text.push('\n');
4427 new_text.extend(existing_indent.chars());
4428 new_text.extend(indent_on_extra_newline.chars());
4429 }
4430
4431 let anchor = buffer.anchor_after(end);
4432 let new_selection = selection.map(|_| anchor);
4433 (
4434 ((start..end, new_text), prevent_auto_indent),
4435 (insert_extra_newline, new_selection),
4436 )
4437 })
4438 .unzip()
4439 };
4440
4441 let mut auto_indent_edits = Vec::new();
4442 let mut edits = Vec::new();
4443 for (edit, prevent_auto_indent) in edits_with_flags {
4444 if prevent_auto_indent {
4445 edits.push(edit);
4446 } else {
4447 auto_indent_edits.push(edit);
4448 }
4449 }
4450 if !edits.is_empty() {
4451 this.edit(edits, cx);
4452 }
4453 if !auto_indent_edits.is_empty() {
4454 this.edit_with_autoindent(auto_indent_edits, cx);
4455 }
4456
4457 let buffer = this.buffer.read(cx).snapshot(cx);
4458 let new_selections = selection_info
4459 .into_iter()
4460 .map(|(extra_newline_inserted, new_selection)| {
4461 let mut cursor = new_selection.end.to_point(&buffer);
4462 if extra_newline_inserted {
4463 cursor.row -= 1;
4464 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4465 }
4466 new_selection.map(|_| cursor)
4467 })
4468 .collect();
4469
4470 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4471 s.select(new_selections)
4472 });
4473 this.refresh_inline_completion(true, false, window, cx);
4474 });
4475 }
4476
4477 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4478 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4479
4480 let buffer = self.buffer.read(cx);
4481 let snapshot = buffer.snapshot(cx);
4482
4483 let mut edits = Vec::new();
4484 let mut rows = Vec::new();
4485
4486 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4487 let cursor = selection.head();
4488 let row = cursor.row;
4489
4490 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4491
4492 let newline = "\n".to_string();
4493 edits.push((start_of_line..start_of_line, newline));
4494
4495 rows.push(row + rows_inserted as u32);
4496 }
4497
4498 self.transact(window, cx, |editor, window, cx| {
4499 editor.edit(edits, cx);
4500
4501 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4502 let mut index = 0;
4503 s.move_cursors_with(|map, _, _| {
4504 let row = rows[index];
4505 index += 1;
4506
4507 let point = Point::new(row, 0);
4508 let boundary = map.next_line_boundary(point).1;
4509 let clipped = map.clip_point(boundary, Bias::Left);
4510
4511 (clipped, SelectionGoal::None)
4512 });
4513 });
4514
4515 let mut indent_edits = Vec::new();
4516 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4517 for row in rows {
4518 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4519 for (row, indent) in indents {
4520 if indent.len == 0 {
4521 continue;
4522 }
4523
4524 let text = match indent.kind {
4525 IndentKind::Space => " ".repeat(indent.len as usize),
4526 IndentKind::Tab => "\t".repeat(indent.len as usize),
4527 };
4528 let point = Point::new(row.0, 0);
4529 indent_edits.push((point..point, text));
4530 }
4531 }
4532 editor.edit(indent_edits, cx);
4533 });
4534 }
4535
4536 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4538
4539 let buffer = self.buffer.read(cx);
4540 let snapshot = buffer.snapshot(cx);
4541
4542 let mut edits = Vec::new();
4543 let mut rows = Vec::new();
4544 let mut rows_inserted = 0;
4545
4546 for selection in self.selections.all_adjusted(cx) {
4547 let cursor = selection.head();
4548 let row = cursor.row;
4549
4550 let point = Point::new(row + 1, 0);
4551 let start_of_line = snapshot.clip_point(point, Bias::Left);
4552
4553 let newline = "\n".to_string();
4554 edits.push((start_of_line..start_of_line, newline));
4555
4556 rows_inserted += 1;
4557 rows.push(row + rows_inserted);
4558 }
4559
4560 self.transact(window, cx, |editor, window, cx| {
4561 editor.edit(edits, cx);
4562
4563 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4564 let mut index = 0;
4565 s.move_cursors_with(|map, _, _| {
4566 let row = rows[index];
4567 index += 1;
4568
4569 let point = Point::new(row, 0);
4570 let boundary = map.next_line_boundary(point).1;
4571 let clipped = map.clip_point(boundary, Bias::Left);
4572
4573 (clipped, SelectionGoal::None)
4574 });
4575 });
4576
4577 let mut indent_edits = Vec::new();
4578 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4579 for row in rows {
4580 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4581 for (row, indent) in indents {
4582 if indent.len == 0 {
4583 continue;
4584 }
4585
4586 let text = match indent.kind {
4587 IndentKind::Space => " ".repeat(indent.len as usize),
4588 IndentKind::Tab => "\t".repeat(indent.len as usize),
4589 };
4590 let point = Point::new(row.0, 0);
4591 indent_edits.push((point..point, text));
4592 }
4593 }
4594 editor.edit(indent_edits, cx);
4595 });
4596 }
4597
4598 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4599 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4600 original_indent_columns: Vec::new(),
4601 });
4602 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4603 }
4604
4605 fn insert_with_autoindent_mode(
4606 &mut self,
4607 text: &str,
4608 autoindent_mode: Option<AutoindentMode>,
4609 window: &mut Window,
4610 cx: &mut Context<Self>,
4611 ) {
4612 if self.read_only(cx) {
4613 return;
4614 }
4615
4616 let text: Arc<str> = text.into();
4617 self.transact(window, cx, |this, window, cx| {
4618 let old_selections = this.selections.all_adjusted(cx);
4619 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4620 let anchors = {
4621 let snapshot = buffer.read(cx);
4622 old_selections
4623 .iter()
4624 .map(|s| {
4625 let anchor = snapshot.anchor_after(s.head());
4626 s.map(|_| anchor)
4627 })
4628 .collect::<Vec<_>>()
4629 };
4630 buffer.edit(
4631 old_selections
4632 .iter()
4633 .map(|s| (s.start..s.end, text.clone())),
4634 autoindent_mode,
4635 cx,
4636 );
4637 anchors
4638 });
4639
4640 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4641 s.select_anchors(selection_anchors);
4642 });
4643
4644 cx.notify();
4645 });
4646 }
4647
4648 fn trigger_completion_on_input(
4649 &mut self,
4650 text: &str,
4651 trigger_in_words: bool,
4652 window: &mut Window,
4653 cx: &mut Context<Self>,
4654 ) {
4655 let completions_source = self
4656 .context_menu
4657 .borrow()
4658 .as_ref()
4659 .and_then(|menu| match menu {
4660 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4661 CodeContextMenu::CodeActions(_) => None,
4662 });
4663
4664 match completions_source {
4665 Some(CompletionsMenuSource::Words) => {
4666 self.show_word_completions(&ShowWordCompletions, window, cx)
4667 }
4668 Some(CompletionsMenuSource::Normal)
4669 | Some(CompletionsMenuSource::SnippetChoices)
4670 | None
4671 if self.is_completion_trigger(
4672 text,
4673 trigger_in_words,
4674 completions_source.is_some(),
4675 cx,
4676 ) =>
4677 {
4678 self.show_completions(
4679 &ShowCompletions {
4680 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4681 },
4682 window,
4683 cx,
4684 )
4685 }
4686 _ => {
4687 self.hide_context_menu(window, cx);
4688 }
4689 }
4690 }
4691
4692 fn is_completion_trigger(
4693 &self,
4694 text: &str,
4695 trigger_in_words: bool,
4696 menu_is_open: bool,
4697 cx: &mut Context<Self>,
4698 ) -> bool {
4699 let position = self.selections.newest_anchor().head();
4700 let multibuffer = self.buffer.read(cx);
4701 let Some(buffer) = position
4702 .buffer_id
4703 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4704 else {
4705 return false;
4706 };
4707
4708 if let Some(completion_provider) = &self.completion_provider {
4709 completion_provider.is_completion_trigger(
4710 &buffer,
4711 position.text_anchor,
4712 text,
4713 trigger_in_words,
4714 menu_is_open,
4715 cx,
4716 )
4717 } else {
4718 false
4719 }
4720 }
4721
4722 /// If any empty selections is touching the start of its innermost containing autoclose
4723 /// region, expand it to select the brackets.
4724 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4725 let selections = self.selections.all::<usize>(cx);
4726 let buffer = self.buffer.read(cx).read(cx);
4727 let new_selections = self
4728 .selections_with_autoclose_regions(selections, &buffer)
4729 .map(|(mut selection, region)| {
4730 if !selection.is_empty() {
4731 return selection;
4732 }
4733
4734 if let Some(region) = region {
4735 let mut range = region.range.to_offset(&buffer);
4736 if selection.start == range.start && range.start >= region.pair.start.len() {
4737 range.start -= region.pair.start.len();
4738 if buffer.contains_str_at(range.start, ®ion.pair.start)
4739 && buffer.contains_str_at(range.end, ®ion.pair.end)
4740 {
4741 range.end += region.pair.end.len();
4742 selection.start = range.start;
4743 selection.end = range.end;
4744
4745 return selection;
4746 }
4747 }
4748 }
4749
4750 let always_treat_brackets_as_autoclosed = buffer
4751 .language_settings_at(selection.start, cx)
4752 .always_treat_brackets_as_autoclosed;
4753
4754 if !always_treat_brackets_as_autoclosed {
4755 return selection;
4756 }
4757
4758 if let Some(scope) = buffer.language_scope_at(selection.start) {
4759 for (pair, enabled) in scope.brackets() {
4760 if !enabled || !pair.close {
4761 continue;
4762 }
4763
4764 if buffer.contains_str_at(selection.start, &pair.end) {
4765 let pair_start_len = pair.start.len();
4766 if buffer.contains_str_at(
4767 selection.start.saturating_sub(pair_start_len),
4768 &pair.start,
4769 ) {
4770 selection.start -= pair_start_len;
4771 selection.end += pair.end.len();
4772
4773 return selection;
4774 }
4775 }
4776 }
4777 }
4778
4779 selection
4780 })
4781 .collect();
4782
4783 drop(buffer);
4784 self.change_selections(None, window, cx, |selections| {
4785 selections.select(new_selections)
4786 });
4787 }
4788
4789 /// Iterate the given selections, and for each one, find the smallest surrounding
4790 /// autoclose region. This uses the ordering of the selections and the autoclose
4791 /// regions to avoid repeated comparisons.
4792 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4793 &'a self,
4794 selections: impl IntoIterator<Item = Selection<D>>,
4795 buffer: &'a MultiBufferSnapshot,
4796 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4797 let mut i = 0;
4798 let mut regions = self.autoclose_regions.as_slice();
4799 selections.into_iter().map(move |selection| {
4800 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4801
4802 let mut enclosing = None;
4803 while let Some(pair_state) = regions.get(i) {
4804 if pair_state.range.end.to_offset(buffer) < range.start {
4805 regions = ®ions[i + 1..];
4806 i = 0;
4807 } else if pair_state.range.start.to_offset(buffer) > range.end {
4808 break;
4809 } else {
4810 if pair_state.selection_id == selection.id {
4811 enclosing = Some(pair_state);
4812 }
4813 i += 1;
4814 }
4815 }
4816
4817 (selection, enclosing)
4818 })
4819 }
4820
4821 /// Remove any autoclose regions that no longer contain their selection.
4822 fn invalidate_autoclose_regions(
4823 &mut self,
4824 mut selections: &[Selection<Anchor>],
4825 buffer: &MultiBufferSnapshot,
4826 ) {
4827 self.autoclose_regions.retain(|state| {
4828 let mut i = 0;
4829 while let Some(selection) = selections.get(i) {
4830 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4831 selections = &selections[1..];
4832 continue;
4833 }
4834 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4835 break;
4836 }
4837 if selection.id == state.selection_id {
4838 return true;
4839 } else {
4840 i += 1;
4841 }
4842 }
4843 false
4844 });
4845 }
4846
4847 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4848 let offset = position.to_offset(buffer);
4849 let (word_range, kind) = buffer.surrounding_word(offset, true);
4850 if offset > word_range.start && kind == Some(CharKind::Word) {
4851 Some(
4852 buffer
4853 .text_for_range(word_range.start..offset)
4854 .collect::<String>(),
4855 )
4856 } else {
4857 None
4858 }
4859 }
4860
4861 pub fn toggle_inline_values(
4862 &mut self,
4863 _: &ToggleInlineValues,
4864 _: &mut Window,
4865 cx: &mut Context<Self>,
4866 ) {
4867 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4868
4869 self.refresh_inline_values(cx);
4870 }
4871
4872 pub fn toggle_inlay_hints(
4873 &mut self,
4874 _: &ToggleInlayHints,
4875 _: &mut Window,
4876 cx: &mut Context<Self>,
4877 ) {
4878 self.refresh_inlay_hints(
4879 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4880 cx,
4881 );
4882 }
4883
4884 pub fn inlay_hints_enabled(&self) -> bool {
4885 self.inlay_hint_cache.enabled
4886 }
4887
4888 pub fn inline_values_enabled(&self) -> bool {
4889 self.inline_value_cache.enabled
4890 }
4891
4892 #[cfg(any(test, feature = "test-support"))]
4893 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4894 self.display_map
4895 .read(cx)
4896 .current_inlays()
4897 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4898 .cloned()
4899 .collect()
4900 }
4901
4902 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4903 if self.semantics_provider.is_none() || !self.mode.is_full() {
4904 return;
4905 }
4906
4907 let reason_description = reason.description();
4908 let ignore_debounce = matches!(
4909 reason,
4910 InlayHintRefreshReason::SettingsChange(_)
4911 | InlayHintRefreshReason::Toggle(_)
4912 | InlayHintRefreshReason::ExcerptsRemoved(_)
4913 | InlayHintRefreshReason::ModifiersChanged(_)
4914 );
4915 let (invalidate_cache, required_languages) = match reason {
4916 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4917 match self.inlay_hint_cache.modifiers_override(enabled) {
4918 Some(enabled) => {
4919 if enabled {
4920 (InvalidationStrategy::RefreshRequested, None)
4921 } else {
4922 self.splice_inlays(
4923 &self
4924 .visible_inlay_hints(cx)
4925 .iter()
4926 .map(|inlay| inlay.id)
4927 .collect::<Vec<InlayId>>(),
4928 Vec::new(),
4929 cx,
4930 );
4931 return;
4932 }
4933 }
4934 None => return,
4935 }
4936 }
4937 InlayHintRefreshReason::Toggle(enabled) => {
4938 if self.inlay_hint_cache.toggle(enabled) {
4939 if enabled {
4940 (InvalidationStrategy::RefreshRequested, None)
4941 } else {
4942 self.splice_inlays(
4943 &self
4944 .visible_inlay_hints(cx)
4945 .iter()
4946 .map(|inlay| inlay.id)
4947 .collect::<Vec<InlayId>>(),
4948 Vec::new(),
4949 cx,
4950 );
4951 return;
4952 }
4953 } else {
4954 return;
4955 }
4956 }
4957 InlayHintRefreshReason::SettingsChange(new_settings) => {
4958 match self.inlay_hint_cache.update_settings(
4959 &self.buffer,
4960 new_settings,
4961 self.visible_inlay_hints(cx),
4962 cx,
4963 ) {
4964 ControlFlow::Break(Some(InlaySplice {
4965 to_remove,
4966 to_insert,
4967 })) => {
4968 self.splice_inlays(&to_remove, to_insert, cx);
4969 return;
4970 }
4971 ControlFlow::Break(None) => return,
4972 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4973 }
4974 }
4975 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4976 if let Some(InlaySplice {
4977 to_remove,
4978 to_insert,
4979 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4980 {
4981 self.splice_inlays(&to_remove, to_insert, cx);
4982 }
4983 self.display_map.update(cx, |display_map, _| {
4984 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4985 });
4986 return;
4987 }
4988 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4989 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4990 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4991 }
4992 InlayHintRefreshReason::RefreshRequested => {
4993 (InvalidationStrategy::RefreshRequested, None)
4994 }
4995 };
4996
4997 if let Some(InlaySplice {
4998 to_remove,
4999 to_insert,
5000 }) = self.inlay_hint_cache.spawn_hint_refresh(
5001 reason_description,
5002 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
5003 invalidate_cache,
5004 ignore_debounce,
5005 cx,
5006 ) {
5007 self.splice_inlays(&to_remove, to_insert, cx);
5008 }
5009 }
5010
5011 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5012 self.display_map
5013 .read(cx)
5014 .current_inlays()
5015 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5016 .cloned()
5017 .collect()
5018 }
5019
5020 pub fn excerpts_for_inlay_hints_query(
5021 &self,
5022 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5023 cx: &mut Context<Editor>,
5024 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5025 let Some(project) = self.project.as_ref() else {
5026 return HashMap::default();
5027 };
5028 let project = project.read(cx);
5029 let multi_buffer = self.buffer().read(cx);
5030 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5031 let multi_buffer_visible_start = self
5032 .scroll_manager
5033 .anchor()
5034 .anchor
5035 .to_point(&multi_buffer_snapshot);
5036 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5037 multi_buffer_visible_start
5038 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5039 Bias::Left,
5040 );
5041 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5042 multi_buffer_snapshot
5043 .range_to_buffer_ranges(multi_buffer_visible_range)
5044 .into_iter()
5045 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5046 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5047 let buffer_file = project::File::from_dyn(buffer.file())?;
5048 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5049 let worktree_entry = buffer_worktree
5050 .read(cx)
5051 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5052 if worktree_entry.is_ignored {
5053 return None;
5054 }
5055
5056 let language = buffer.language()?;
5057 if let Some(restrict_to_languages) = restrict_to_languages {
5058 if !restrict_to_languages.contains(language) {
5059 return None;
5060 }
5061 }
5062 Some((
5063 excerpt_id,
5064 (
5065 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5066 buffer.version().clone(),
5067 excerpt_visible_range,
5068 ),
5069 ))
5070 })
5071 .collect()
5072 }
5073
5074 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5075 TextLayoutDetails {
5076 text_system: window.text_system().clone(),
5077 editor_style: self.style.clone().unwrap(),
5078 rem_size: window.rem_size(),
5079 scroll_anchor: self.scroll_manager.anchor(),
5080 visible_rows: self.visible_line_count(),
5081 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5082 }
5083 }
5084
5085 pub fn splice_inlays(
5086 &self,
5087 to_remove: &[InlayId],
5088 to_insert: Vec<Inlay>,
5089 cx: &mut Context<Self>,
5090 ) {
5091 self.display_map.update(cx, |display_map, cx| {
5092 display_map.splice_inlays(to_remove, to_insert, cx)
5093 });
5094 cx.notify();
5095 }
5096
5097 fn trigger_on_type_formatting(
5098 &self,
5099 input: String,
5100 window: &mut Window,
5101 cx: &mut Context<Self>,
5102 ) -> Option<Task<Result<()>>> {
5103 if input.len() != 1 {
5104 return None;
5105 }
5106
5107 let project = self.project.as_ref()?;
5108 let position = self.selections.newest_anchor().head();
5109 let (buffer, buffer_position) = self
5110 .buffer
5111 .read(cx)
5112 .text_anchor_for_position(position, cx)?;
5113
5114 let settings = language_settings::language_settings(
5115 buffer
5116 .read(cx)
5117 .language_at(buffer_position)
5118 .map(|l| l.name()),
5119 buffer.read(cx).file(),
5120 cx,
5121 );
5122 if !settings.use_on_type_format {
5123 return None;
5124 }
5125
5126 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5127 // hence we do LSP request & edit on host side only — add formats to host's history.
5128 let push_to_lsp_host_history = true;
5129 // If this is not the host, append its history with new edits.
5130 let push_to_client_history = project.read(cx).is_via_collab();
5131
5132 let on_type_formatting = project.update(cx, |project, cx| {
5133 project.on_type_format(
5134 buffer.clone(),
5135 buffer_position,
5136 input,
5137 push_to_lsp_host_history,
5138 cx,
5139 )
5140 });
5141 Some(cx.spawn_in(window, async move |editor, cx| {
5142 if let Some(transaction) = on_type_formatting.await? {
5143 if push_to_client_history {
5144 buffer
5145 .update(cx, |buffer, _| {
5146 buffer.push_transaction(transaction, Instant::now());
5147 buffer.finalize_last_transaction();
5148 })
5149 .ok();
5150 }
5151 editor.update(cx, |editor, cx| {
5152 editor.refresh_document_highlights(cx);
5153 })?;
5154 }
5155 Ok(())
5156 }))
5157 }
5158
5159 pub fn show_word_completions(
5160 &mut self,
5161 _: &ShowWordCompletions,
5162 window: &mut Window,
5163 cx: &mut Context<Self>,
5164 ) {
5165 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5166 }
5167
5168 pub fn show_completions(
5169 &mut self,
5170 options: &ShowCompletions,
5171 window: &mut Window,
5172 cx: &mut Context<Self>,
5173 ) {
5174 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5175 }
5176
5177 fn open_or_update_completions_menu(
5178 &mut self,
5179 requested_source: Option<CompletionsMenuSource>,
5180 trigger: Option<&str>,
5181 window: &mut Window,
5182 cx: &mut Context<Self>,
5183 ) {
5184 if self.pending_rename.is_some() {
5185 return;
5186 }
5187
5188 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5189
5190 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5191 // inserted and selected. To handle that case, the start of the selection is used so that
5192 // the menu starts with all choices.
5193 let position = self
5194 .selections
5195 .newest_anchor()
5196 .start
5197 .bias_right(&multibuffer_snapshot);
5198 if position.diff_base_anchor.is_some() {
5199 return;
5200 }
5201 let (buffer, buffer_position) =
5202 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5203 output
5204 } else {
5205 return;
5206 };
5207 let buffer_snapshot = buffer.read(cx).snapshot();
5208
5209 let query: Option<Arc<String>> =
5210 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5211
5212 drop(multibuffer_snapshot);
5213
5214 let provider = match requested_source {
5215 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5216 Some(CompletionsMenuSource::Words) => None,
5217 Some(CompletionsMenuSource::SnippetChoices) => {
5218 log::error!("bug: SnippetChoices requested_source is not handled");
5219 None
5220 }
5221 };
5222
5223 let sort_completions = provider
5224 .as_ref()
5225 .map_or(false, |provider| provider.sort_completions());
5226
5227 let filter_completions = provider
5228 .as_ref()
5229 .map_or(true, |provider| provider.filter_completions());
5230
5231 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5232 if filter_completions {
5233 menu.filter(query.clone(), provider.clone(), window, cx);
5234 }
5235 // When `is_incomplete` is false, no need to re-query completions when the current query
5236 // is a suffix of the initial query.
5237 if !menu.is_incomplete {
5238 // If the new query is a suffix of the old query (typing more characters) and
5239 // the previous result was complete, the existing completions can be filtered.
5240 //
5241 // Note that this is always true for snippet completions.
5242 let query_matches = match (&menu.initial_query, &query) {
5243 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5244 (None, _) => true,
5245 _ => false,
5246 };
5247 if query_matches {
5248 let position_matches = if menu.initial_position == position {
5249 true
5250 } else {
5251 let snapshot = self.buffer.read(cx).read(cx);
5252 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5253 };
5254 if position_matches {
5255 return;
5256 }
5257 }
5258 }
5259 };
5260
5261 let trigger_kind = match trigger {
5262 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5263 CompletionTriggerKind::TRIGGER_CHARACTER
5264 }
5265 _ => CompletionTriggerKind::INVOKED,
5266 };
5267 let completion_context = CompletionContext {
5268 trigger_character: trigger.and_then(|trigger| {
5269 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5270 Some(String::from(trigger))
5271 } else {
5272 None
5273 }
5274 }),
5275 trigger_kind,
5276 };
5277
5278 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5279 buffer_snapshot.surrounding_word(buffer_position)
5280 {
5281 let word_to_exclude = buffer_snapshot
5282 .text_for_range(word_range.clone())
5283 .collect::<String>();
5284 (
5285 buffer_snapshot.anchor_before(word_range.start)
5286 ..buffer_snapshot.anchor_after(buffer_position),
5287 Some(word_to_exclude),
5288 )
5289 } else {
5290 (buffer_position..buffer_position, None)
5291 };
5292
5293 let language = buffer_snapshot
5294 .language_at(buffer_position)
5295 .map(|language| language.name());
5296
5297 let completion_settings =
5298 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5299
5300 let show_completion_documentation = buffer_snapshot
5301 .settings_at(buffer_position, cx)
5302 .show_completion_documentation;
5303
5304 // The document can be large, so stay in reasonable bounds when searching for words,
5305 // otherwise completion pop-up might be slow to appear.
5306 const WORD_LOOKUP_ROWS: u32 = 5_000;
5307 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5308 let min_word_search = buffer_snapshot.clip_point(
5309 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5310 Bias::Left,
5311 );
5312 let max_word_search = buffer_snapshot.clip_point(
5313 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5314 Bias::Right,
5315 );
5316 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5317 ..buffer_snapshot.point_to_offset(max_word_search);
5318
5319 let skip_digits = query
5320 .as_ref()
5321 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5322
5323 let (mut words, provider_responses) = match &provider {
5324 Some(provider) => {
5325 let provider_responses = provider.completions(
5326 position.excerpt_id,
5327 &buffer,
5328 buffer_position,
5329 completion_context,
5330 window,
5331 cx,
5332 );
5333
5334 let words = match completion_settings.words {
5335 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5336 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5337 .background_spawn(async move {
5338 buffer_snapshot.words_in_range(WordsQuery {
5339 fuzzy_contents: None,
5340 range: word_search_range,
5341 skip_digits,
5342 })
5343 }),
5344 };
5345
5346 (words, provider_responses)
5347 }
5348 None => (
5349 cx.background_spawn(async move {
5350 buffer_snapshot.words_in_range(WordsQuery {
5351 fuzzy_contents: None,
5352 range: word_search_range,
5353 skip_digits,
5354 })
5355 }),
5356 Task::ready(Ok(Vec::new())),
5357 ),
5358 };
5359
5360 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5361
5362 let id = post_inc(&mut self.next_completion_id);
5363 let task = cx.spawn_in(window, async move |editor, cx| {
5364 let Ok(()) = editor.update(cx, |this, _| {
5365 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5366 }) else {
5367 return;
5368 };
5369
5370 // TODO: Ideally completions from different sources would be selectively re-queried, so
5371 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5372 let mut completions = Vec::new();
5373 let mut is_incomplete = false;
5374 if let Some(provider_responses) = provider_responses.await.log_err() {
5375 if !provider_responses.is_empty() {
5376 for response in provider_responses {
5377 completions.extend(response.completions);
5378 is_incomplete = is_incomplete || response.is_incomplete;
5379 }
5380 if completion_settings.words == WordsCompletionMode::Fallback {
5381 words = Task::ready(BTreeMap::default());
5382 }
5383 }
5384 }
5385
5386 let mut words = words.await;
5387 if let Some(word_to_exclude) = &word_to_exclude {
5388 words.remove(word_to_exclude);
5389 }
5390 for lsp_completion in &completions {
5391 words.remove(&lsp_completion.new_text);
5392 }
5393 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5394 replace_range: word_replace_range.clone(),
5395 new_text: word.clone(),
5396 label: CodeLabel::plain(word, None),
5397 icon_path: None,
5398 documentation: None,
5399 source: CompletionSource::BufferWord {
5400 word_range,
5401 resolved: false,
5402 },
5403 insert_text_mode: Some(InsertTextMode::AS_IS),
5404 confirm: None,
5405 }));
5406
5407 let menu = if completions.is_empty() {
5408 None
5409 } else {
5410 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5411 let languages = editor
5412 .workspace
5413 .as_ref()
5414 .and_then(|(workspace, _)| workspace.upgrade())
5415 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5416 let menu = CompletionsMenu::new(
5417 id,
5418 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5419 sort_completions,
5420 show_completion_documentation,
5421 position,
5422 query.clone(),
5423 is_incomplete,
5424 buffer.clone(),
5425 completions.into(),
5426 snippet_sort_order,
5427 languages,
5428 language,
5429 cx,
5430 );
5431
5432 let query = if filter_completions { query } else { None };
5433 let matches_task = if let Some(query) = query {
5434 menu.do_async_filtering(query, cx)
5435 } else {
5436 Task::ready(menu.unfiltered_matches())
5437 };
5438 (menu, matches_task)
5439 }) else {
5440 return;
5441 };
5442
5443 let matches = matches_task.await;
5444
5445 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5446 // Newer menu already set, so exit.
5447 match editor.context_menu.borrow().as_ref() {
5448 Some(CodeContextMenu::Completions(prev_menu)) => {
5449 if prev_menu.id > id {
5450 return;
5451 }
5452 }
5453 _ => {}
5454 };
5455
5456 // Only valid to take prev_menu because it the new menu is immediately set
5457 // below, or the menu is hidden.
5458 match editor.context_menu.borrow_mut().take() {
5459 Some(CodeContextMenu::Completions(prev_menu)) => {
5460 let position_matches =
5461 if prev_menu.initial_position == menu.initial_position {
5462 true
5463 } else {
5464 let snapshot = editor.buffer.read(cx).read(cx);
5465 prev_menu.initial_position.to_offset(&snapshot)
5466 == menu.initial_position.to_offset(&snapshot)
5467 };
5468 if position_matches {
5469 // Preserve markdown cache before `set_filter_results` because it will
5470 // try to populate the documentation cache.
5471 menu.preserve_markdown_cache(prev_menu);
5472 }
5473 }
5474 _ => {}
5475 };
5476
5477 menu.set_filter_results(matches, provider, window, cx);
5478 }) else {
5479 return;
5480 };
5481
5482 menu.visible().then_some(menu)
5483 };
5484
5485 editor
5486 .update_in(cx, |editor, window, cx| {
5487 if editor.focus_handle.is_focused(window) {
5488 if let Some(menu) = menu {
5489 *editor.context_menu.borrow_mut() =
5490 Some(CodeContextMenu::Completions(menu));
5491
5492 crate::hover_popover::hide_hover(editor, cx);
5493 if editor.show_edit_predictions_in_menu() {
5494 editor.update_visible_inline_completion(window, cx);
5495 } else {
5496 editor.discard_inline_completion(false, cx);
5497 }
5498
5499 cx.notify();
5500 return;
5501 }
5502 }
5503
5504 if editor.completion_tasks.len() <= 1 {
5505 // If there are no more completion tasks and the last menu was empty, we should hide it.
5506 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5507 // If it was already hidden and we don't show inline completions in the menu, we should
5508 // also show the inline-completion when available.
5509 if was_hidden && editor.show_edit_predictions_in_menu() {
5510 editor.update_visible_inline_completion(window, cx);
5511 }
5512 }
5513 })
5514 .ok();
5515 });
5516
5517 self.completion_tasks.push((id, task));
5518 }
5519
5520 #[cfg(feature = "test-support")]
5521 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5522 let menu = self.context_menu.borrow();
5523 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5524 let completions = menu.completions.borrow();
5525 Some(completions.to_vec())
5526 } else {
5527 None
5528 }
5529 }
5530
5531 pub fn with_completions_menu_matching_id<R>(
5532 &self,
5533 id: CompletionId,
5534 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5535 ) -> R {
5536 let mut context_menu = self.context_menu.borrow_mut();
5537 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5538 return f(None);
5539 };
5540 if completions_menu.id != id {
5541 return f(None);
5542 }
5543 f(Some(completions_menu))
5544 }
5545
5546 pub fn confirm_completion(
5547 &mut self,
5548 action: &ConfirmCompletion,
5549 window: &mut Window,
5550 cx: &mut Context<Self>,
5551 ) -> Option<Task<Result<()>>> {
5552 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5553 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5554 }
5555
5556 pub fn confirm_completion_insert(
5557 &mut self,
5558 _: &ConfirmCompletionInsert,
5559 window: &mut Window,
5560 cx: &mut Context<Self>,
5561 ) -> Option<Task<Result<()>>> {
5562 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5563 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5564 }
5565
5566 pub fn confirm_completion_replace(
5567 &mut self,
5568 _: &ConfirmCompletionReplace,
5569 window: &mut Window,
5570 cx: &mut Context<Self>,
5571 ) -> Option<Task<Result<()>>> {
5572 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5573 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5574 }
5575
5576 pub fn compose_completion(
5577 &mut self,
5578 action: &ComposeCompletion,
5579 window: &mut Window,
5580 cx: &mut Context<Self>,
5581 ) -> Option<Task<Result<()>>> {
5582 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5583 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5584 }
5585
5586 fn do_completion(
5587 &mut self,
5588 item_ix: Option<usize>,
5589 intent: CompletionIntent,
5590 window: &mut Window,
5591 cx: &mut Context<Editor>,
5592 ) -> Option<Task<Result<()>>> {
5593 use language::ToOffset as _;
5594
5595 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5596 else {
5597 return None;
5598 };
5599
5600 let candidate_id = {
5601 let entries = completions_menu.entries.borrow();
5602 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5603 if self.show_edit_predictions_in_menu() {
5604 self.discard_inline_completion(true, cx);
5605 }
5606 mat.candidate_id
5607 };
5608
5609 let completion = completions_menu
5610 .completions
5611 .borrow()
5612 .get(candidate_id)?
5613 .clone();
5614 cx.stop_propagation();
5615
5616 let buffer_handle = completions_menu.buffer.clone();
5617
5618 let CompletionEdit {
5619 new_text,
5620 snippet,
5621 replace_range,
5622 } = process_completion_for_edit(
5623 &completion,
5624 intent,
5625 &buffer_handle,
5626 &completions_menu.initial_position.text_anchor,
5627 cx,
5628 );
5629
5630 let buffer = buffer_handle.read(cx);
5631 let snapshot = self.buffer.read(cx).snapshot(cx);
5632 let newest_anchor = self.selections.newest_anchor();
5633 let replace_range_multibuffer = {
5634 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5635 let multibuffer_anchor = snapshot
5636 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5637 .unwrap()
5638 ..snapshot
5639 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5640 .unwrap();
5641 multibuffer_anchor.start.to_offset(&snapshot)
5642 ..multibuffer_anchor.end.to_offset(&snapshot)
5643 };
5644 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5645 return None;
5646 }
5647
5648 let old_text = buffer
5649 .text_for_range(replace_range.clone())
5650 .collect::<String>();
5651 let lookbehind = newest_anchor
5652 .start
5653 .text_anchor
5654 .to_offset(buffer)
5655 .saturating_sub(replace_range.start);
5656 let lookahead = replace_range
5657 .end
5658 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5659 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5660 let suffix = &old_text[lookbehind.min(old_text.len())..];
5661
5662 let selections = self.selections.all::<usize>(cx);
5663 let mut ranges = Vec::new();
5664 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5665
5666 for selection in &selections {
5667 let range = if selection.id == newest_anchor.id {
5668 replace_range_multibuffer.clone()
5669 } else {
5670 let mut range = selection.range();
5671
5672 // if prefix is present, don't duplicate it
5673 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5674 range.start = range.start.saturating_sub(lookbehind);
5675
5676 // if suffix is also present, mimic the newest cursor and replace it
5677 if selection.id != newest_anchor.id
5678 && snapshot.contains_str_at(range.end, suffix)
5679 {
5680 range.end += lookahead;
5681 }
5682 }
5683 range
5684 };
5685
5686 ranges.push(range.clone());
5687
5688 if !self.linked_edit_ranges.is_empty() {
5689 let start_anchor = snapshot.anchor_before(range.start);
5690 let end_anchor = snapshot.anchor_after(range.end);
5691 if let Some(ranges) = self
5692 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5693 {
5694 for (buffer, edits) in ranges {
5695 linked_edits
5696 .entry(buffer.clone())
5697 .or_default()
5698 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5699 }
5700 }
5701 }
5702 }
5703
5704 let common_prefix_len = old_text
5705 .chars()
5706 .zip(new_text.chars())
5707 .take_while(|(a, b)| a == b)
5708 .map(|(a, _)| a.len_utf8())
5709 .sum::<usize>();
5710
5711 cx.emit(EditorEvent::InputHandled {
5712 utf16_range_to_replace: None,
5713 text: new_text[common_prefix_len..].into(),
5714 });
5715
5716 self.transact(window, cx, |this, window, cx| {
5717 if let Some(mut snippet) = snippet {
5718 snippet.text = new_text.to_string();
5719 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5720 } else {
5721 this.buffer.update(cx, |buffer, cx| {
5722 let auto_indent = match completion.insert_text_mode {
5723 Some(InsertTextMode::AS_IS) => None,
5724 _ => this.autoindent_mode.clone(),
5725 };
5726 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5727 buffer.edit(edits, auto_indent, cx);
5728 });
5729 }
5730 for (buffer, edits) in linked_edits {
5731 buffer.update(cx, |buffer, cx| {
5732 let snapshot = buffer.snapshot();
5733 let edits = edits
5734 .into_iter()
5735 .map(|(range, text)| {
5736 use text::ToPoint as TP;
5737 let end_point = TP::to_point(&range.end, &snapshot);
5738 let start_point = TP::to_point(&range.start, &snapshot);
5739 (start_point..end_point, text)
5740 })
5741 .sorted_by_key(|(range, _)| range.start);
5742 buffer.edit(edits, None, cx);
5743 })
5744 }
5745
5746 this.refresh_inline_completion(true, false, window, cx);
5747 });
5748
5749 let show_new_completions_on_confirm = completion
5750 .confirm
5751 .as_ref()
5752 .map_or(false, |confirm| confirm(intent, window, cx));
5753 if show_new_completions_on_confirm {
5754 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5755 }
5756
5757 let provider = self.completion_provider.as_ref()?;
5758 drop(completion);
5759 let apply_edits = provider.apply_additional_edits_for_completion(
5760 buffer_handle,
5761 completions_menu.completions.clone(),
5762 candidate_id,
5763 true,
5764 cx,
5765 );
5766
5767 let editor_settings = EditorSettings::get_global(cx);
5768 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5769 // After the code completion is finished, users often want to know what signatures are needed.
5770 // so we should automatically call signature_help
5771 self.show_signature_help(&ShowSignatureHelp, window, cx);
5772 }
5773
5774 Some(cx.foreground_executor().spawn(async move {
5775 apply_edits.await?;
5776 Ok(())
5777 }))
5778 }
5779
5780 pub fn toggle_code_actions(
5781 &mut self,
5782 action: &ToggleCodeActions,
5783 window: &mut Window,
5784 cx: &mut Context<Self>,
5785 ) {
5786 let quick_launch = action.quick_launch;
5787 let mut context_menu = self.context_menu.borrow_mut();
5788 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5789 if code_actions.deployed_from == action.deployed_from {
5790 // Toggle if we're selecting the same one
5791 *context_menu = None;
5792 cx.notify();
5793 return;
5794 } else {
5795 // Otherwise, clear it and start a new one
5796 *context_menu = None;
5797 cx.notify();
5798 }
5799 }
5800 drop(context_menu);
5801 let snapshot = self.snapshot(window, cx);
5802 let deployed_from = action.deployed_from.clone();
5803 let action = action.clone();
5804 self.completion_tasks.clear();
5805 self.discard_inline_completion(false, cx);
5806
5807 let multibuffer_point = match &action.deployed_from {
5808 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5809 DisplayPoint::new(*row, 0).to_point(&snapshot)
5810 }
5811 _ => self.selections.newest::<Point>(cx).head(),
5812 };
5813 let Some((buffer, buffer_row)) = snapshot
5814 .buffer_snapshot
5815 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5816 .and_then(|(buffer_snapshot, range)| {
5817 self.buffer()
5818 .read(cx)
5819 .buffer(buffer_snapshot.remote_id())
5820 .map(|buffer| (buffer, range.start.row))
5821 })
5822 else {
5823 return;
5824 };
5825 let buffer_id = buffer.read(cx).remote_id();
5826 let tasks = self
5827 .tasks
5828 .get(&(buffer_id, buffer_row))
5829 .map(|t| Arc::new(t.to_owned()));
5830
5831 if !self.focus_handle.is_focused(window) {
5832 return;
5833 }
5834 let project = self.project.clone();
5835
5836 let code_actions_task = match deployed_from {
5837 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5838 _ => self.code_actions(buffer_row, window, cx),
5839 };
5840
5841 let runnable_task = match deployed_from {
5842 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5843 _ => {
5844 let mut task_context_task = Task::ready(None);
5845 if let Some(tasks) = &tasks {
5846 if let Some(project) = project {
5847 task_context_task =
5848 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5849 }
5850 }
5851
5852 cx.spawn_in(window, {
5853 let buffer = buffer.clone();
5854 async move |editor, cx| {
5855 let task_context = task_context_task.await;
5856
5857 let resolved_tasks =
5858 tasks
5859 .zip(task_context.clone())
5860 .map(|(tasks, task_context)| ResolvedTasks {
5861 templates: tasks.resolve(&task_context).collect(),
5862 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5863 multibuffer_point.row,
5864 tasks.column,
5865 )),
5866 });
5867 let debug_scenarios = editor
5868 .update(cx, |editor, cx| {
5869 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5870 })?
5871 .await;
5872 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5873 }
5874 })
5875 }
5876 };
5877
5878 cx.spawn_in(window, async move |editor, cx| {
5879 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5880 let code_actions = code_actions_task.await;
5881 let spawn_straight_away = quick_launch
5882 && resolved_tasks
5883 .as_ref()
5884 .map_or(false, |tasks| tasks.templates.len() == 1)
5885 && code_actions
5886 .as_ref()
5887 .map_or(true, |actions| actions.is_empty())
5888 && debug_scenarios.is_empty();
5889
5890 editor.update_in(cx, |editor, window, cx| {
5891 crate::hover_popover::hide_hover(editor, cx);
5892 *editor.context_menu.borrow_mut() =
5893 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5894 buffer,
5895 actions: CodeActionContents::new(
5896 resolved_tasks,
5897 code_actions,
5898 debug_scenarios,
5899 task_context.unwrap_or_default(),
5900 ),
5901 selected_item: Default::default(),
5902 scroll_handle: UniformListScrollHandle::default(),
5903 deployed_from,
5904 }));
5905 cx.notify();
5906 if spawn_straight_away {
5907 if let Some(task) = editor.confirm_code_action(
5908 &ConfirmCodeAction { item_ix: Some(0) },
5909 window,
5910 cx,
5911 ) {
5912 return task;
5913 }
5914 }
5915
5916 Task::ready(Ok(()))
5917 })
5918 })
5919 .detach_and_log_err(cx);
5920 }
5921
5922 fn debug_scenarios(
5923 &mut self,
5924 resolved_tasks: &Option<ResolvedTasks>,
5925 buffer: &Entity<Buffer>,
5926 cx: &mut App,
5927 ) -> Task<Vec<task::DebugScenario>> {
5928 if cx.has_flag::<DebuggerFeatureFlag>() {
5929 maybe!({
5930 let project = self.project.as_ref()?;
5931 let dap_store = project.read(cx).dap_store();
5932 let mut scenarios = vec![];
5933 let resolved_tasks = resolved_tasks.as_ref()?;
5934 let buffer = buffer.read(cx);
5935 let language = buffer.language()?;
5936 let file = buffer.file();
5937 let debug_adapter = language_settings(language.name().into(), file, cx)
5938 .debuggers
5939 .first()
5940 .map(SharedString::from)
5941 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5942
5943 dap_store.update(cx, |dap_store, cx| {
5944 for (_, task) in &resolved_tasks.templates {
5945 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5946 task.original_task().clone(),
5947 debug_adapter.clone().into(),
5948 task.display_label().to_owned().into(),
5949 cx,
5950 );
5951 scenarios.push(maybe_scenario);
5952 }
5953 });
5954 Some(cx.background_spawn(async move {
5955 let scenarios = futures::future::join_all(scenarios)
5956 .await
5957 .into_iter()
5958 .flatten()
5959 .collect::<Vec<_>>();
5960 scenarios
5961 }))
5962 })
5963 .unwrap_or_else(|| Task::ready(vec![]))
5964 } else {
5965 Task::ready(vec![])
5966 }
5967 }
5968
5969 fn code_actions(
5970 &mut self,
5971 buffer_row: u32,
5972 window: &mut Window,
5973 cx: &mut Context<Self>,
5974 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5975 let mut task = self.code_actions_task.take();
5976 cx.spawn_in(window, async move |editor, cx| {
5977 while let Some(prev_task) = task {
5978 prev_task.await.log_err();
5979 task = editor
5980 .update(cx, |this, _| this.code_actions_task.take())
5981 .ok()?;
5982 }
5983
5984 editor
5985 .update(cx, |editor, cx| {
5986 editor
5987 .available_code_actions
5988 .clone()
5989 .and_then(|(location, code_actions)| {
5990 let snapshot = location.buffer.read(cx).snapshot();
5991 let point_range = location.range.to_point(&snapshot);
5992 let point_range = point_range.start.row..=point_range.end.row;
5993 if point_range.contains(&buffer_row) {
5994 Some(code_actions)
5995 } else {
5996 None
5997 }
5998 })
5999 })
6000 .ok()
6001 .flatten()
6002 })
6003 }
6004
6005 pub fn confirm_code_action(
6006 &mut self,
6007 action: &ConfirmCodeAction,
6008 window: &mut Window,
6009 cx: &mut Context<Self>,
6010 ) -> Option<Task<Result<()>>> {
6011 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6012
6013 let actions_menu =
6014 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6015 menu
6016 } else {
6017 return None;
6018 };
6019
6020 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6021 let action = actions_menu.actions.get(action_ix)?;
6022 let title = action.label();
6023 let buffer = actions_menu.buffer;
6024 let workspace = self.workspace()?;
6025
6026 match action {
6027 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6028 workspace.update(cx, |workspace, cx| {
6029 workspace.schedule_resolved_task(
6030 task_source_kind,
6031 resolved_task,
6032 false,
6033 window,
6034 cx,
6035 );
6036
6037 Some(Task::ready(Ok(())))
6038 })
6039 }
6040 CodeActionsItem::CodeAction {
6041 excerpt_id,
6042 action,
6043 provider,
6044 } => {
6045 let apply_code_action =
6046 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6047 let workspace = workspace.downgrade();
6048 Some(cx.spawn_in(window, async move |editor, cx| {
6049 let project_transaction = apply_code_action.await?;
6050 Self::open_project_transaction(
6051 &editor,
6052 workspace,
6053 project_transaction,
6054 title,
6055 cx,
6056 )
6057 .await
6058 }))
6059 }
6060 CodeActionsItem::DebugScenario(scenario) => {
6061 let context = actions_menu.actions.context.clone();
6062
6063 workspace.update(cx, |workspace, cx| {
6064 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6065 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6066 });
6067 Some(Task::ready(Ok(())))
6068 }
6069 }
6070 }
6071
6072 pub async fn open_project_transaction(
6073 this: &WeakEntity<Editor>,
6074 workspace: WeakEntity<Workspace>,
6075 transaction: ProjectTransaction,
6076 title: String,
6077 cx: &mut AsyncWindowContext,
6078 ) -> Result<()> {
6079 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6080 cx.update(|_, cx| {
6081 entries.sort_unstable_by_key(|(buffer, _)| {
6082 buffer.read(cx).file().map(|f| f.path().clone())
6083 });
6084 })?;
6085
6086 // If the project transaction's edits are all contained within this editor, then
6087 // avoid opening a new editor to display them.
6088
6089 if let Some((buffer, transaction)) = entries.first() {
6090 if entries.len() == 1 {
6091 let excerpt = this.update(cx, |editor, cx| {
6092 editor
6093 .buffer()
6094 .read(cx)
6095 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6096 })?;
6097 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6098 if excerpted_buffer == *buffer {
6099 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6100 let excerpt_range = excerpt_range.to_offset(buffer);
6101 buffer
6102 .edited_ranges_for_transaction::<usize>(transaction)
6103 .all(|range| {
6104 excerpt_range.start <= range.start
6105 && excerpt_range.end >= range.end
6106 })
6107 })?;
6108
6109 if all_edits_within_excerpt {
6110 return Ok(());
6111 }
6112 }
6113 }
6114 }
6115 } else {
6116 return Ok(());
6117 }
6118
6119 let mut ranges_to_highlight = Vec::new();
6120 let excerpt_buffer = cx.new(|cx| {
6121 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6122 for (buffer_handle, transaction) in &entries {
6123 let edited_ranges = buffer_handle
6124 .read(cx)
6125 .edited_ranges_for_transaction::<Point>(transaction)
6126 .collect::<Vec<_>>();
6127 let (ranges, _) = multibuffer.set_excerpts_for_path(
6128 PathKey::for_buffer(buffer_handle, cx),
6129 buffer_handle.clone(),
6130 edited_ranges,
6131 DEFAULT_MULTIBUFFER_CONTEXT,
6132 cx,
6133 );
6134
6135 ranges_to_highlight.extend(ranges);
6136 }
6137 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6138 multibuffer
6139 })?;
6140
6141 workspace.update_in(cx, |workspace, window, cx| {
6142 let project = workspace.project().clone();
6143 let editor =
6144 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6145 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6146 editor.update(cx, |editor, cx| {
6147 editor.highlight_background::<Self>(
6148 &ranges_to_highlight,
6149 |theme| theme.colors().editor_highlighted_line_background,
6150 cx,
6151 );
6152 });
6153 })?;
6154
6155 Ok(())
6156 }
6157
6158 pub fn clear_code_action_providers(&mut self) {
6159 self.code_action_providers.clear();
6160 self.available_code_actions.take();
6161 }
6162
6163 pub fn add_code_action_provider(
6164 &mut self,
6165 provider: Rc<dyn CodeActionProvider>,
6166 window: &mut Window,
6167 cx: &mut Context<Self>,
6168 ) {
6169 if self
6170 .code_action_providers
6171 .iter()
6172 .any(|existing_provider| existing_provider.id() == provider.id())
6173 {
6174 return;
6175 }
6176
6177 self.code_action_providers.push(provider);
6178 self.refresh_code_actions(window, cx);
6179 }
6180
6181 pub fn remove_code_action_provider(
6182 &mut self,
6183 id: Arc<str>,
6184 window: &mut Window,
6185 cx: &mut Context<Self>,
6186 ) {
6187 self.code_action_providers
6188 .retain(|provider| provider.id() != id);
6189 self.refresh_code_actions(window, cx);
6190 }
6191
6192 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6193 !self.code_action_providers.is_empty()
6194 && EditorSettings::get_global(cx).toolbar.code_actions
6195 }
6196
6197 pub fn has_available_code_actions(&self) -> bool {
6198 self.available_code_actions
6199 .as_ref()
6200 .is_some_and(|(_, actions)| !actions.is_empty())
6201 }
6202
6203 fn render_inline_code_actions(
6204 &self,
6205 icon_size: ui::IconSize,
6206 display_row: DisplayRow,
6207 is_active: bool,
6208 cx: &mut Context<Self>,
6209 ) -> AnyElement {
6210 let show_tooltip = !self.context_menu_visible();
6211 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6212 .icon_size(icon_size)
6213 .shape(ui::IconButtonShape::Square)
6214 .style(ButtonStyle::Transparent)
6215 .icon_color(ui::Color::Hidden)
6216 .toggle_state(is_active)
6217 .when(show_tooltip, |this| {
6218 this.tooltip({
6219 let focus_handle = self.focus_handle.clone();
6220 move |window, cx| {
6221 Tooltip::for_action_in(
6222 "Toggle Code Actions",
6223 &ToggleCodeActions {
6224 deployed_from: None,
6225 quick_launch: false,
6226 },
6227 &focus_handle,
6228 window,
6229 cx,
6230 )
6231 }
6232 })
6233 })
6234 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6235 window.focus(&editor.focus_handle(cx));
6236 editor.toggle_code_actions(
6237 &crate::actions::ToggleCodeActions {
6238 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6239 display_row,
6240 )),
6241 quick_launch: false,
6242 },
6243 window,
6244 cx,
6245 );
6246 }))
6247 .into_any_element()
6248 }
6249
6250 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6251 &self.context_menu
6252 }
6253
6254 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6255 let newest_selection = self.selections.newest_anchor().clone();
6256 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6257 let buffer = self.buffer.read(cx);
6258 if newest_selection.head().diff_base_anchor.is_some() {
6259 return None;
6260 }
6261 let (start_buffer, start) =
6262 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6263 let (end_buffer, end) =
6264 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6265 if start_buffer != end_buffer {
6266 return None;
6267 }
6268
6269 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6270 cx.background_executor()
6271 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6272 .await;
6273
6274 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6275 let providers = this.code_action_providers.clone();
6276 let tasks = this
6277 .code_action_providers
6278 .iter()
6279 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6280 .collect::<Vec<_>>();
6281 (providers, tasks)
6282 })?;
6283
6284 let mut actions = Vec::new();
6285 for (provider, provider_actions) in
6286 providers.into_iter().zip(future::join_all(tasks).await)
6287 {
6288 if let Some(provider_actions) = provider_actions.log_err() {
6289 actions.extend(provider_actions.into_iter().map(|action| {
6290 AvailableCodeAction {
6291 excerpt_id: newest_selection.start.excerpt_id,
6292 action,
6293 provider: provider.clone(),
6294 }
6295 }));
6296 }
6297 }
6298
6299 this.update(cx, |this, cx| {
6300 this.available_code_actions = if actions.is_empty() {
6301 None
6302 } else {
6303 Some((
6304 Location {
6305 buffer: start_buffer,
6306 range: start..end,
6307 },
6308 actions.into(),
6309 ))
6310 };
6311 cx.notify();
6312 })
6313 }));
6314 None
6315 }
6316
6317 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6318 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6319 self.show_git_blame_inline = false;
6320
6321 self.show_git_blame_inline_delay_task =
6322 Some(cx.spawn_in(window, async move |this, cx| {
6323 cx.background_executor().timer(delay).await;
6324
6325 this.update(cx, |this, cx| {
6326 this.show_git_blame_inline = true;
6327 cx.notify();
6328 })
6329 .log_err();
6330 }));
6331 }
6332 }
6333
6334 fn show_blame_popover(
6335 &mut self,
6336 blame_entry: &BlameEntry,
6337 position: gpui::Point<Pixels>,
6338 cx: &mut Context<Self>,
6339 ) {
6340 if let Some(state) = &mut self.inline_blame_popover {
6341 state.hide_task.take();
6342 } else {
6343 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6344 let blame_entry = blame_entry.clone();
6345 let show_task = cx.spawn(async move |editor, cx| {
6346 cx.background_executor()
6347 .timer(std::time::Duration::from_millis(delay))
6348 .await;
6349 editor
6350 .update(cx, |editor, cx| {
6351 editor.inline_blame_popover_show_task.take();
6352 let Some(blame) = editor.blame.as_ref() else {
6353 return;
6354 };
6355 let blame = blame.read(cx);
6356 let details = blame.details_for_entry(&blame_entry);
6357 let markdown = cx.new(|cx| {
6358 Markdown::new(
6359 details
6360 .as_ref()
6361 .map(|message| message.message.clone())
6362 .unwrap_or_default(),
6363 None,
6364 None,
6365 cx,
6366 )
6367 });
6368 editor.inline_blame_popover = Some(InlineBlamePopover {
6369 position,
6370 hide_task: None,
6371 popover_bounds: None,
6372 popover_state: InlineBlamePopoverState {
6373 scroll_handle: ScrollHandle::new(),
6374 commit_message: details,
6375 markdown,
6376 },
6377 });
6378 cx.notify();
6379 })
6380 .ok();
6381 });
6382 self.inline_blame_popover_show_task = Some(show_task);
6383 }
6384 }
6385
6386 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6387 self.inline_blame_popover_show_task.take();
6388 if let Some(state) = &mut self.inline_blame_popover {
6389 let hide_task = cx.spawn(async move |editor, cx| {
6390 cx.background_executor()
6391 .timer(std::time::Duration::from_millis(100))
6392 .await;
6393 editor
6394 .update(cx, |editor, cx| {
6395 editor.inline_blame_popover.take();
6396 cx.notify();
6397 })
6398 .ok();
6399 });
6400 state.hide_task = Some(hide_task);
6401 }
6402 }
6403
6404 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6405 if self.pending_rename.is_some() {
6406 return None;
6407 }
6408
6409 let provider = self.semantics_provider.clone()?;
6410 let buffer = self.buffer.read(cx);
6411 let newest_selection = self.selections.newest_anchor().clone();
6412 let cursor_position = newest_selection.head();
6413 let (cursor_buffer, cursor_buffer_position) =
6414 buffer.text_anchor_for_position(cursor_position, cx)?;
6415 let (tail_buffer, tail_buffer_position) =
6416 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6417 if cursor_buffer != tail_buffer {
6418 return None;
6419 }
6420
6421 let snapshot = cursor_buffer.read(cx).snapshot();
6422 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6423 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6424 if start_word_range != end_word_range {
6425 self.document_highlights_task.take();
6426 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6427 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6428 return None;
6429 }
6430
6431 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6432 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6433 cx.background_executor()
6434 .timer(Duration::from_millis(debounce))
6435 .await;
6436
6437 let highlights = if let Some(highlights) = cx
6438 .update(|cx| {
6439 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6440 })
6441 .ok()
6442 .flatten()
6443 {
6444 highlights.await.log_err()
6445 } else {
6446 None
6447 };
6448
6449 if let Some(highlights) = highlights {
6450 this.update(cx, |this, cx| {
6451 if this.pending_rename.is_some() {
6452 return;
6453 }
6454
6455 let buffer_id = cursor_position.buffer_id;
6456 let buffer = this.buffer.read(cx);
6457 if !buffer
6458 .text_anchor_for_position(cursor_position, cx)
6459 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6460 {
6461 return;
6462 }
6463
6464 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6465 let mut write_ranges = Vec::new();
6466 let mut read_ranges = Vec::new();
6467 for highlight in highlights {
6468 for (excerpt_id, excerpt_range) in
6469 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6470 {
6471 let start = highlight
6472 .range
6473 .start
6474 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6475 let end = highlight
6476 .range
6477 .end
6478 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6479 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6480 continue;
6481 }
6482
6483 let range = Anchor {
6484 buffer_id,
6485 excerpt_id,
6486 text_anchor: start,
6487 diff_base_anchor: None,
6488 }..Anchor {
6489 buffer_id,
6490 excerpt_id,
6491 text_anchor: end,
6492 diff_base_anchor: None,
6493 };
6494 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6495 write_ranges.push(range);
6496 } else {
6497 read_ranges.push(range);
6498 }
6499 }
6500 }
6501
6502 this.highlight_background::<DocumentHighlightRead>(
6503 &read_ranges,
6504 |theme| theme.colors().editor_document_highlight_read_background,
6505 cx,
6506 );
6507 this.highlight_background::<DocumentHighlightWrite>(
6508 &write_ranges,
6509 |theme| theme.colors().editor_document_highlight_write_background,
6510 cx,
6511 );
6512 cx.notify();
6513 })
6514 .log_err();
6515 }
6516 }));
6517 None
6518 }
6519
6520 fn prepare_highlight_query_from_selection(
6521 &mut self,
6522 cx: &mut Context<Editor>,
6523 ) -> Option<(String, Range<Anchor>)> {
6524 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6525 return None;
6526 }
6527 if !EditorSettings::get_global(cx).selection_highlight {
6528 return None;
6529 }
6530 if self.selections.count() != 1 || self.selections.line_mode {
6531 return None;
6532 }
6533 let selection = self.selections.newest::<Point>(cx);
6534 if selection.is_empty() || selection.start.row != selection.end.row {
6535 return None;
6536 }
6537 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6538 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6539 let query = multi_buffer_snapshot
6540 .text_for_range(selection_anchor_range.clone())
6541 .collect::<String>();
6542 if query.trim().is_empty() {
6543 return None;
6544 }
6545 Some((query, selection_anchor_range))
6546 }
6547
6548 fn update_selection_occurrence_highlights(
6549 &mut self,
6550 query_text: String,
6551 query_range: Range<Anchor>,
6552 multi_buffer_range_to_query: Range<Point>,
6553 use_debounce: bool,
6554 window: &mut Window,
6555 cx: &mut Context<Editor>,
6556 ) -> Task<()> {
6557 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6558 cx.spawn_in(window, async move |editor, cx| {
6559 if use_debounce {
6560 cx.background_executor()
6561 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6562 .await;
6563 }
6564 let match_task = cx.background_spawn(async move {
6565 let buffer_ranges = multi_buffer_snapshot
6566 .range_to_buffer_ranges(multi_buffer_range_to_query)
6567 .into_iter()
6568 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6569 let mut match_ranges = Vec::new();
6570 let Ok(regex) = project::search::SearchQuery::text(
6571 query_text.clone(),
6572 false,
6573 false,
6574 false,
6575 Default::default(),
6576 Default::default(),
6577 false,
6578 None,
6579 ) else {
6580 return Vec::default();
6581 };
6582 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6583 match_ranges.extend(
6584 regex
6585 .search(&buffer_snapshot, Some(search_range.clone()))
6586 .await
6587 .into_iter()
6588 .filter_map(|match_range| {
6589 let match_start = buffer_snapshot
6590 .anchor_after(search_range.start + match_range.start);
6591 let match_end = buffer_snapshot
6592 .anchor_before(search_range.start + match_range.end);
6593 let match_anchor_range = Anchor::range_in_buffer(
6594 excerpt_id,
6595 buffer_snapshot.remote_id(),
6596 match_start..match_end,
6597 );
6598 (match_anchor_range != query_range).then_some(match_anchor_range)
6599 }),
6600 );
6601 }
6602 match_ranges
6603 });
6604 let match_ranges = match_task.await;
6605 editor
6606 .update_in(cx, |editor, _, cx| {
6607 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6608 if !match_ranges.is_empty() {
6609 editor.highlight_background::<SelectedTextHighlight>(
6610 &match_ranges,
6611 |theme| theme.colors().editor_document_highlight_bracket_background,
6612 cx,
6613 )
6614 }
6615 })
6616 .log_err();
6617 })
6618 }
6619
6620 fn refresh_selected_text_highlights(
6621 &mut self,
6622 on_buffer_edit: bool,
6623 window: &mut Window,
6624 cx: &mut Context<Editor>,
6625 ) {
6626 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6627 else {
6628 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6629 self.quick_selection_highlight_task.take();
6630 self.debounced_selection_highlight_task.take();
6631 return;
6632 };
6633 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6634 if on_buffer_edit
6635 || self
6636 .quick_selection_highlight_task
6637 .as_ref()
6638 .map_or(true, |(prev_anchor_range, _)| {
6639 prev_anchor_range != &query_range
6640 })
6641 {
6642 let multi_buffer_visible_start = self
6643 .scroll_manager
6644 .anchor()
6645 .anchor
6646 .to_point(&multi_buffer_snapshot);
6647 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6648 multi_buffer_visible_start
6649 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6650 Bias::Left,
6651 );
6652 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6653 self.quick_selection_highlight_task = Some((
6654 query_range.clone(),
6655 self.update_selection_occurrence_highlights(
6656 query_text.clone(),
6657 query_range.clone(),
6658 multi_buffer_visible_range,
6659 false,
6660 window,
6661 cx,
6662 ),
6663 ));
6664 }
6665 if on_buffer_edit
6666 || self
6667 .debounced_selection_highlight_task
6668 .as_ref()
6669 .map_or(true, |(prev_anchor_range, _)| {
6670 prev_anchor_range != &query_range
6671 })
6672 {
6673 let multi_buffer_start = multi_buffer_snapshot
6674 .anchor_before(0)
6675 .to_point(&multi_buffer_snapshot);
6676 let multi_buffer_end = multi_buffer_snapshot
6677 .anchor_after(multi_buffer_snapshot.len())
6678 .to_point(&multi_buffer_snapshot);
6679 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6680 self.debounced_selection_highlight_task = Some((
6681 query_range.clone(),
6682 self.update_selection_occurrence_highlights(
6683 query_text,
6684 query_range,
6685 multi_buffer_full_range,
6686 true,
6687 window,
6688 cx,
6689 ),
6690 ));
6691 }
6692 }
6693
6694 pub fn refresh_inline_completion(
6695 &mut self,
6696 debounce: bool,
6697 user_requested: bool,
6698 window: &mut Window,
6699 cx: &mut Context<Self>,
6700 ) -> Option<()> {
6701 let provider = self.edit_prediction_provider()?;
6702 let cursor = self.selections.newest_anchor().head();
6703 let (buffer, cursor_buffer_position) =
6704 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6705
6706 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6707 self.discard_inline_completion(false, cx);
6708 return None;
6709 }
6710
6711 if !user_requested
6712 && (!self.should_show_edit_predictions()
6713 || !self.is_focused(window)
6714 || buffer.read(cx).is_empty())
6715 {
6716 self.discard_inline_completion(false, cx);
6717 return None;
6718 }
6719
6720 self.update_visible_inline_completion(window, cx);
6721 provider.refresh(
6722 self.project.clone(),
6723 buffer,
6724 cursor_buffer_position,
6725 debounce,
6726 cx,
6727 );
6728 Some(())
6729 }
6730
6731 fn show_edit_predictions_in_menu(&self) -> bool {
6732 match self.edit_prediction_settings {
6733 EditPredictionSettings::Disabled => false,
6734 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6735 }
6736 }
6737
6738 pub fn edit_predictions_enabled(&self) -> bool {
6739 match self.edit_prediction_settings {
6740 EditPredictionSettings::Disabled => false,
6741 EditPredictionSettings::Enabled { .. } => true,
6742 }
6743 }
6744
6745 fn edit_prediction_requires_modifier(&self) -> bool {
6746 match self.edit_prediction_settings {
6747 EditPredictionSettings::Disabled => false,
6748 EditPredictionSettings::Enabled {
6749 preview_requires_modifier,
6750 ..
6751 } => preview_requires_modifier,
6752 }
6753 }
6754
6755 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6756 if self.edit_prediction_provider.is_none() {
6757 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6758 } else {
6759 let selection = self.selections.newest_anchor();
6760 let cursor = selection.head();
6761
6762 if let Some((buffer, cursor_buffer_position)) =
6763 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6764 {
6765 self.edit_prediction_settings =
6766 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6767 }
6768 }
6769 }
6770
6771 fn edit_prediction_settings_at_position(
6772 &self,
6773 buffer: &Entity<Buffer>,
6774 buffer_position: language::Anchor,
6775 cx: &App,
6776 ) -> EditPredictionSettings {
6777 if !self.mode.is_full()
6778 || !self.show_inline_completions_override.unwrap_or(true)
6779 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6780 {
6781 return EditPredictionSettings::Disabled;
6782 }
6783
6784 let buffer = buffer.read(cx);
6785
6786 let file = buffer.file();
6787
6788 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6789 return EditPredictionSettings::Disabled;
6790 };
6791
6792 let by_provider = matches!(
6793 self.menu_inline_completions_policy,
6794 MenuInlineCompletionsPolicy::ByProvider
6795 );
6796
6797 let show_in_menu = by_provider
6798 && self
6799 .edit_prediction_provider
6800 .as_ref()
6801 .map_or(false, |provider| {
6802 provider.provider.show_completions_in_menu()
6803 });
6804
6805 let preview_requires_modifier =
6806 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6807
6808 EditPredictionSettings::Enabled {
6809 show_in_menu,
6810 preview_requires_modifier,
6811 }
6812 }
6813
6814 fn should_show_edit_predictions(&self) -> bool {
6815 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6816 }
6817
6818 pub fn edit_prediction_preview_is_active(&self) -> bool {
6819 matches!(
6820 self.edit_prediction_preview,
6821 EditPredictionPreview::Active { .. }
6822 )
6823 }
6824
6825 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6826 let cursor = self.selections.newest_anchor().head();
6827 if let Some((buffer, cursor_position)) =
6828 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6829 {
6830 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6831 } else {
6832 false
6833 }
6834 }
6835
6836 pub fn supports_minimap(&self, cx: &App) -> bool {
6837 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6838 }
6839
6840 fn edit_predictions_enabled_in_buffer(
6841 &self,
6842 buffer: &Entity<Buffer>,
6843 buffer_position: language::Anchor,
6844 cx: &App,
6845 ) -> bool {
6846 maybe!({
6847 if self.read_only(cx) {
6848 return Some(false);
6849 }
6850 let provider = self.edit_prediction_provider()?;
6851 if !provider.is_enabled(&buffer, buffer_position, cx) {
6852 return Some(false);
6853 }
6854 let buffer = buffer.read(cx);
6855 let Some(file) = buffer.file() else {
6856 return Some(true);
6857 };
6858 let settings = all_language_settings(Some(file), cx);
6859 Some(settings.edit_predictions_enabled_for_file(file, cx))
6860 })
6861 .unwrap_or(false)
6862 }
6863
6864 fn cycle_inline_completion(
6865 &mut self,
6866 direction: Direction,
6867 window: &mut Window,
6868 cx: &mut Context<Self>,
6869 ) -> Option<()> {
6870 let provider = self.edit_prediction_provider()?;
6871 let cursor = self.selections.newest_anchor().head();
6872 let (buffer, cursor_buffer_position) =
6873 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6874 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6875 return None;
6876 }
6877
6878 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6879 self.update_visible_inline_completion(window, cx);
6880
6881 Some(())
6882 }
6883
6884 pub fn show_inline_completion(
6885 &mut self,
6886 _: &ShowEditPrediction,
6887 window: &mut Window,
6888 cx: &mut Context<Self>,
6889 ) {
6890 if !self.has_active_inline_completion() {
6891 self.refresh_inline_completion(false, true, window, cx);
6892 return;
6893 }
6894
6895 self.update_visible_inline_completion(window, cx);
6896 }
6897
6898 pub fn display_cursor_names(
6899 &mut self,
6900 _: &DisplayCursorNames,
6901 window: &mut Window,
6902 cx: &mut Context<Self>,
6903 ) {
6904 self.show_cursor_names(window, cx);
6905 }
6906
6907 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6908 self.show_cursor_names = true;
6909 cx.notify();
6910 cx.spawn_in(window, async move |this, cx| {
6911 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6912 this.update(cx, |this, cx| {
6913 this.show_cursor_names = false;
6914 cx.notify()
6915 })
6916 .ok()
6917 })
6918 .detach();
6919 }
6920
6921 pub fn next_edit_prediction(
6922 &mut self,
6923 _: &NextEditPrediction,
6924 window: &mut Window,
6925 cx: &mut Context<Self>,
6926 ) {
6927 if self.has_active_inline_completion() {
6928 self.cycle_inline_completion(Direction::Next, window, cx);
6929 } else {
6930 let is_copilot_disabled = self
6931 .refresh_inline_completion(false, true, window, cx)
6932 .is_none();
6933 if is_copilot_disabled {
6934 cx.propagate();
6935 }
6936 }
6937 }
6938
6939 pub fn previous_edit_prediction(
6940 &mut self,
6941 _: &PreviousEditPrediction,
6942 window: &mut Window,
6943 cx: &mut Context<Self>,
6944 ) {
6945 if self.has_active_inline_completion() {
6946 self.cycle_inline_completion(Direction::Prev, window, cx);
6947 } else {
6948 let is_copilot_disabled = self
6949 .refresh_inline_completion(false, true, window, cx)
6950 .is_none();
6951 if is_copilot_disabled {
6952 cx.propagate();
6953 }
6954 }
6955 }
6956
6957 pub fn accept_edit_prediction(
6958 &mut self,
6959 _: &AcceptEditPrediction,
6960 window: &mut Window,
6961 cx: &mut Context<Self>,
6962 ) {
6963 if self.show_edit_predictions_in_menu() {
6964 self.hide_context_menu(window, cx);
6965 }
6966
6967 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6968 return;
6969 };
6970
6971 self.report_inline_completion_event(
6972 active_inline_completion.completion_id.clone(),
6973 true,
6974 cx,
6975 );
6976
6977 match &active_inline_completion.completion {
6978 InlineCompletion::Move { target, .. } => {
6979 let target = *target;
6980
6981 if let Some(position_map) = &self.last_position_map {
6982 if position_map
6983 .visible_row_range
6984 .contains(&target.to_display_point(&position_map.snapshot).row())
6985 || !self.edit_prediction_requires_modifier()
6986 {
6987 self.unfold_ranges(&[target..target], true, false, cx);
6988 // Note that this is also done in vim's handler of the Tab action.
6989 self.change_selections(
6990 Some(Autoscroll::newest()),
6991 window,
6992 cx,
6993 |selections| {
6994 selections.select_anchor_ranges([target..target]);
6995 },
6996 );
6997 self.clear_row_highlights::<EditPredictionPreview>();
6998
6999 self.edit_prediction_preview
7000 .set_previous_scroll_position(None);
7001 } else {
7002 self.edit_prediction_preview
7003 .set_previous_scroll_position(Some(
7004 position_map.snapshot.scroll_anchor,
7005 ));
7006
7007 self.highlight_rows::<EditPredictionPreview>(
7008 target..target,
7009 cx.theme().colors().editor_highlighted_line_background,
7010 RowHighlightOptions {
7011 autoscroll: true,
7012 ..Default::default()
7013 },
7014 cx,
7015 );
7016 self.request_autoscroll(Autoscroll::fit(), cx);
7017 }
7018 }
7019 }
7020 InlineCompletion::Edit { edits, .. } => {
7021 if let Some(provider) = self.edit_prediction_provider() {
7022 provider.accept(cx);
7023 }
7024
7025 // Store the transaction ID and selections before applying the edit
7026 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7027
7028 let snapshot = self.buffer.read(cx).snapshot(cx);
7029 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7030
7031 self.buffer.update(cx, |buffer, cx| {
7032 buffer.edit(edits.iter().cloned(), None, cx)
7033 });
7034
7035 self.change_selections(None, window, cx, |s| {
7036 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7037 });
7038
7039 let selections = self.selections.disjoint_anchors();
7040 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7041 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7042 if has_new_transaction {
7043 self.selection_history
7044 .insert_transaction(transaction_id_now, selections);
7045 }
7046 }
7047
7048 self.update_visible_inline_completion(window, cx);
7049 if self.active_inline_completion.is_none() {
7050 self.refresh_inline_completion(true, true, window, cx);
7051 }
7052
7053 cx.notify();
7054 }
7055 }
7056
7057 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7058 }
7059
7060 pub fn accept_partial_inline_completion(
7061 &mut self,
7062 _: &AcceptPartialEditPrediction,
7063 window: &mut Window,
7064 cx: &mut Context<Self>,
7065 ) {
7066 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7067 return;
7068 };
7069 if self.selections.count() != 1 {
7070 return;
7071 }
7072
7073 self.report_inline_completion_event(
7074 active_inline_completion.completion_id.clone(),
7075 true,
7076 cx,
7077 );
7078
7079 match &active_inline_completion.completion {
7080 InlineCompletion::Move { target, .. } => {
7081 let target = *target;
7082 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7083 selections.select_anchor_ranges([target..target]);
7084 });
7085 }
7086 InlineCompletion::Edit { edits, .. } => {
7087 // Find an insertion that starts at the cursor position.
7088 let snapshot = self.buffer.read(cx).snapshot(cx);
7089 let cursor_offset = self.selections.newest::<usize>(cx).head();
7090 let insertion = edits.iter().find_map(|(range, text)| {
7091 let range = range.to_offset(&snapshot);
7092 if range.is_empty() && range.start == cursor_offset {
7093 Some(text)
7094 } else {
7095 None
7096 }
7097 });
7098
7099 if let Some(text) = insertion {
7100 let mut partial_completion = text
7101 .chars()
7102 .by_ref()
7103 .take_while(|c| c.is_alphabetic())
7104 .collect::<String>();
7105 if partial_completion.is_empty() {
7106 partial_completion = text
7107 .chars()
7108 .by_ref()
7109 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7110 .collect::<String>();
7111 }
7112
7113 cx.emit(EditorEvent::InputHandled {
7114 utf16_range_to_replace: None,
7115 text: partial_completion.clone().into(),
7116 });
7117
7118 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7119
7120 self.refresh_inline_completion(true, true, window, cx);
7121 cx.notify();
7122 } else {
7123 self.accept_edit_prediction(&Default::default(), window, cx);
7124 }
7125 }
7126 }
7127 }
7128
7129 fn discard_inline_completion(
7130 &mut self,
7131 should_report_inline_completion_event: bool,
7132 cx: &mut Context<Self>,
7133 ) -> bool {
7134 if should_report_inline_completion_event {
7135 let completion_id = self
7136 .active_inline_completion
7137 .as_ref()
7138 .and_then(|active_completion| active_completion.completion_id.clone());
7139
7140 self.report_inline_completion_event(completion_id, false, cx);
7141 }
7142
7143 if let Some(provider) = self.edit_prediction_provider() {
7144 provider.discard(cx);
7145 }
7146
7147 self.take_active_inline_completion(cx)
7148 }
7149
7150 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7151 let Some(provider) = self.edit_prediction_provider() else {
7152 return;
7153 };
7154
7155 let Some((_, buffer, _)) = self
7156 .buffer
7157 .read(cx)
7158 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7159 else {
7160 return;
7161 };
7162
7163 let extension = buffer
7164 .read(cx)
7165 .file()
7166 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7167
7168 let event_type = match accepted {
7169 true => "Edit Prediction Accepted",
7170 false => "Edit Prediction Discarded",
7171 };
7172 telemetry::event!(
7173 event_type,
7174 provider = provider.name(),
7175 prediction_id = id,
7176 suggestion_accepted = accepted,
7177 file_extension = extension,
7178 );
7179 }
7180
7181 pub fn has_active_inline_completion(&self) -> bool {
7182 self.active_inline_completion.is_some()
7183 }
7184
7185 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7186 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7187 return false;
7188 };
7189
7190 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7191 self.clear_highlights::<InlineCompletionHighlight>(cx);
7192 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7193 true
7194 }
7195
7196 /// Returns true when we're displaying the edit prediction popover below the cursor
7197 /// like we are not previewing and the LSP autocomplete menu is visible
7198 /// or we are in `when_holding_modifier` mode.
7199 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7200 if self.edit_prediction_preview_is_active()
7201 || !self.show_edit_predictions_in_menu()
7202 || !self.edit_predictions_enabled()
7203 {
7204 return false;
7205 }
7206
7207 if self.has_visible_completions_menu() {
7208 return true;
7209 }
7210
7211 has_completion && self.edit_prediction_requires_modifier()
7212 }
7213
7214 fn handle_modifiers_changed(
7215 &mut self,
7216 modifiers: Modifiers,
7217 position_map: &PositionMap,
7218 window: &mut Window,
7219 cx: &mut Context<Self>,
7220 ) {
7221 if self.show_edit_predictions_in_menu() {
7222 self.update_edit_prediction_preview(&modifiers, window, cx);
7223 }
7224
7225 self.update_selection_mode(&modifiers, position_map, window, cx);
7226
7227 let mouse_position = window.mouse_position();
7228 if !position_map.text_hitbox.is_hovered(window) {
7229 return;
7230 }
7231
7232 self.update_hovered_link(
7233 position_map.point_for_position(mouse_position),
7234 &position_map.snapshot,
7235 modifiers,
7236 window,
7237 cx,
7238 )
7239 }
7240
7241 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7242 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7243 if invert {
7244 match multi_cursor_setting {
7245 MultiCursorModifier::Alt => modifiers.alt,
7246 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7247 }
7248 } else {
7249 match multi_cursor_setting {
7250 MultiCursorModifier::Alt => modifiers.secondary(),
7251 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7252 }
7253 }
7254 }
7255
7256 fn columnar_selection_mode(
7257 modifiers: &Modifiers,
7258 cx: &mut Context<Self>,
7259 ) -> Option<ColumnarMode> {
7260 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7261 if Self::multi_cursor_modifier(false, modifiers, cx) {
7262 Some(ColumnarMode::FromMouse)
7263 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7264 Some(ColumnarMode::FromSelection)
7265 } else {
7266 None
7267 }
7268 } else {
7269 None
7270 }
7271 }
7272
7273 fn update_selection_mode(
7274 &mut self,
7275 modifiers: &Modifiers,
7276 position_map: &PositionMap,
7277 window: &mut Window,
7278 cx: &mut Context<Self>,
7279 ) {
7280 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7281 return;
7282 };
7283 if self.selections.pending.is_none() {
7284 return;
7285 }
7286
7287 let mouse_position = window.mouse_position();
7288 let point_for_position = position_map.point_for_position(mouse_position);
7289 let position = point_for_position.previous_valid;
7290
7291 self.select(
7292 SelectPhase::BeginColumnar {
7293 position,
7294 reset: false,
7295 mode,
7296 goal_column: point_for_position.exact_unclipped.column(),
7297 },
7298 window,
7299 cx,
7300 );
7301 }
7302
7303 fn update_edit_prediction_preview(
7304 &mut self,
7305 modifiers: &Modifiers,
7306 window: &mut Window,
7307 cx: &mut Context<Self>,
7308 ) {
7309 let mut modifiers_held = false;
7310 if let Some(accept_keystroke) = self
7311 .accept_edit_prediction_keybind(false, window, cx)
7312 .keystroke()
7313 {
7314 modifiers_held = modifiers_held
7315 || (&accept_keystroke.modifiers == modifiers
7316 && accept_keystroke.modifiers.modified());
7317 };
7318 if let Some(accept_partial_keystroke) = self
7319 .accept_edit_prediction_keybind(true, window, cx)
7320 .keystroke()
7321 {
7322 modifiers_held = modifiers_held
7323 || (&accept_partial_keystroke.modifiers == modifiers
7324 && accept_partial_keystroke.modifiers.modified());
7325 }
7326
7327 if modifiers_held {
7328 if matches!(
7329 self.edit_prediction_preview,
7330 EditPredictionPreview::Inactive { .. }
7331 ) {
7332 self.edit_prediction_preview = EditPredictionPreview::Active {
7333 previous_scroll_position: None,
7334 since: Instant::now(),
7335 };
7336
7337 self.update_visible_inline_completion(window, cx);
7338 cx.notify();
7339 }
7340 } else if let EditPredictionPreview::Active {
7341 previous_scroll_position,
7342 since,
7343 } = self.edit_prediction_preview
7344 {
7345 if let (Some(previous_scroll_position), Some(position_map)) =
7346 (previous_scroll_position, self.last_position_map.as_ref())
7347 {
7348 self.set_scroll_position(
7349 previous_scroll_position
7350 .scroll_position(&position_map.snapshot.display_snapshot),
7351 window,
7352 cx,
7353 );
7354 }
7355
7356 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7357 released_too_fast: since.elapsed() < Duration::from_millis(200),
7358 };
7359 self.clear_row_highlights::<EditPredictionPreview>();
7360 self.update_visible_inline_completion(window, cx);
7361 cx.notify();
7362 }
7363 }
7364
7365 fn update_visible_inline_completion(
7366 &mut self,
7367 _window: &mut Window,
7368 cx: &mut Context<Self>,
7369 ) -> Option<()> {
7370 let selection = self.selections.newest_anchor();
7371 let cursor = selection.head();
7372 let multibuffer = self.buffer.read(cx).snapshot(cx);
7373 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7374 let excerpt_id = cursor.excerpt_id;
7375
7376 let show_in_menu = self.show_edit_predictions_in_menu();
7377 let completions_menu_has_precedence = !show_in_menu
7378 && (self.context_menu.borrow().is_some()
7379 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7380
7381 if completions_menu_has_precedence
7382 || !offset_selection.is_empty()
7383 || self
7384 .active_inline_completion
7385 .as_ref()
7386 .map_or(false, |completion| {
7387 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7388 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7389 !invalidation_range.contains(&offset_selection.head())
7390 })
7391 {
7392 self.discard_inline_completion(false, cx);
7393 return None;
7394 }
7395
7396 self.take_active_inline_completion(cx);
7397 let Some(provider) = self.edit_prediction_provider() else {
7398 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7399 return None;
7400 };
7401
7402 let (buffer, cursor_buffer_position) =
7403 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7404
7405 self.edit_prediction_settings =
7406 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7407
7408 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7409
7410 if self.edit_prediction_indent_conflict {
7411 let cursor_point = cursor.to_point(&multibuffer);
7412
7413 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7414
7415 if let Some((_, indent)) = indents.iter().next() {
7416 if indent.len == cursor_point.column {
7417 self.edit_prediction_indent_conflict = false;
7418 }
7419 }
7420 }
7421
7422 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7423 let edits = inline_completion
7424 .edits
7425 .into_iter()
7426 .flat_map(|(range, new_text)| {
7427 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7428 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7429 Some((start..end, new_text))
7430 })
7431 .collect::<Vec<_>>();
7432 if edits.is_empty() {
7433 return None;
7434 }
7435
7436 let first_edit_start = edits.first().unwrap().0.start;
7437 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7438 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7439
7440 let last_edit_end = edits.last().unwrap().0.end;
7441 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7442 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7443
7444 let cursor_row = cursor.to_point(&multibuffer).row;
7445
7446 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7447
7448 let mut inlay_ids = Vec::new();
7449 let invalidation_row_range;
7450 let move_invalidation_row_range = if cursor_row < edit_start_row {
7451 Some(cursor_row..edit_end_row)
7452 } else if cursor_row > edit_end_row {
7453 Some(edit_start_row..cursor_row)
7454 } else {
7455 None
7456 };
7457 let is_move =
7458 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7459 let completion = if is_move {
7460 invalidation_row_range =
7461 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7462 let target = first_edit_start;
7463 InlineCompletion::Move { target, snapshot }
7464 } else {
7465 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7466 && !self.inline_completions_hidden_for_vim_mode;
7467
7468 if show_completions_in_buffer {
7469 if edits
7470 .iter()
7471 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7472 {
7473 let mut inlays = Vec::new();
7474 for (range, new_text) in &edits {
7475 let inlay = Inlay::inline_completion(
7476 post_inc(&mut self.next_inlay_id),
7477 range.start,
7478 new_text.as_str(),
7479 );
7480 inlay_ids.push(inlay.id);
7481 inlays.push(inlay);
7482 }
7483
7484 self.splice_inlays(&[], inlays, cx);
7485 } else {
7486 let background_color = cx.theme().status().deleted_background;
7487 let style = HighlightStyle {
7488 background_color: Some(background_color),
7489 ..Default::default()
7490 };
7491 self.highlight_text::<InlineCompletionHighlight>(
7492 edits
7493 .iter()
7494 .map(|(range, _)| (range.clone(), style))
7495 .collect(),
7496 cx,
7497 );
7498 }
7499 }
7500
7501 invalidation_row_range = edit_start_row..edit_end_row;
7502
7503 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7504 if provider.show_tab_accept_marker() {
7505 EditDisplayMode::TabAccept
7506 } else {
7507 EditDisplayMode::Inline
7508 }
7509 } else {
7510 EditDisplayMode::DiffPopover
7511 };
7512
7513 InlineCompletion::Edit {
7514 edits,
7515 edit_preview: inline_completion.edit_preview,
7516 display_mode,
7517 snapshot,
7518 }
7519 };
7520
7521 let invalidation_range = multibuffer
7522 .anchor_before(Point::new(invalidation_row_range.start, 0))
7523 ..multibuffer.anchor_after(Point::new(
7524 invalidation_row_range.end,
7525 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7526 ));
7527
7528 self.stale_inline_completion_in_menu = None;
7529 self.active_inline_completion = Some(InlineCompletionState {
7530 inlay_ids,
7531 completion,
7532 completion_id: inline_completion.id,
7533 invalidation_range,
7534 });
7535
7536 cx.notify();
7537
7538 Some(())
7539 }
7540
7541 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7542 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7543 }
7544
7545 fn clear_tasks(&mut self) {
7546 self.tasks.clear()
7547 }
7548
7549 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7550 if self.tasks.insert(key, value).is_some() {
7551 // This case should hopefully be rare, but just in case...
7552 log::error!(
7553 "multiple different run targets found on a single line, only the last target will be rendered"
7554 )
7555 }
7556 }
7557
7558 /// Get all display points of breakpoints that will be rendered within editor
7559 ///
7560 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7561 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7562 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7563 fn active_breakpoints(
7564 &self,
7565 range: Range<DisplayRow>,
7566 window: &mut Window,
7567 cx: &mut Context<Self>,
7568 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7569 let mut breakpoint_display_points = HashMap::default();
7570
7571 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7572 return breakpoint_display_points;
7573 };
7574
7575 let snapshot = self.snapshot(window, cx);
7576
7577 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7578 let Some(project) = self.project.as_ref() else {
7579 return breakpoint_display_points;
7580 };
7581
7582 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7583 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7584
7585 for (buffer_snapshot, range, excerpt_id) in
7586 multi_buffer_snapshot.range_to_buffer_ranges(range)
7587 {
7588 let Some(buffer) = project
7589 .read(cx)
7590 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7591 else {
7592 continue;
7593 };
7594 let breakpoints = breakpoint_store.read(cx).breakpoints(
7595 &buffer,
7596 Some(
7597 buffer_snapshot.anchor_before(range.start)
7598 ..buffer_snapshot.anchor_after(range.end),
7599 ),
7600 buffer_snapshot,
7601 cx,
7602 );
7603 for (breakpoint, state) in breakpoints {
7604 let multi_buffer_anchor =
7605 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7606 let position = multi_buffer_anchor
7607 .to_point(&multi_buffer_snapshot)
7608 .to_display_point(&snapshot);
7609
7610 breakpoint_display_points.insert(
7611 position.row(),
7612 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7613 );
7614 }
7615 }
7616
7617 breakpoint_display_points
7618 }
7619
7620 fn breakpoint_context_menu(
7621 &self,
7622 anchor: Anchor,
7623 window: &mut Window,
7624 cx: &mut Context<Self>,
7625 ) -> Entity<ui::ContextMenu> {
7626 let weak_editor = cx.weak_entity();
7627 let focus_handle = self.focus_handle(cx);
7628
7629 let row = self
7630 .buffer
7631 .read(cx)
7632 .snapshot(cx)
7633 .summary_for_anchor::<Point>(&anchor)
7634 .row;
7635
7636 let breakpoint = self
7637 .breakpoint_at_row(row, window, cx)
7638 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7639
7640 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7641 "Edit Log Breakpoint"
7642 } else {
7643 "Set Log Breakpoint"
7644 };
7645
7646 let condition_breakpoint_msg = if breakpoint
7647 .as_ref()
7648 .is_some_and(|bp| bp.1.condition.is_some())
7649 {
7650 "Edit Condition Breakpoint"
7651 } else {
7652 "Set Condition Breakpoint"
7653 };
7654
7655 let hit_condition_breakpoint_msg = if breakpoint
7656 .as_ref()
7657 .is_some_and(|bp| bp.1.hit_condition.is_some())
7658 {
7659 "Edit Hit Condition Breakpoint"
7660 } else {
7661 "Set Hit Condition Breakpoint"
7662 };
7663
7664 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7665 "Unset Breakpoint"
7666 } else {
7667 "Set Breakpoint"
7668 };
7669
7670 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7671
7672 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7673 BreakpointState::Enabled => Some("Disable"),
7674 BreakpointState::Disabled => Some("Enable"),
7675 });
7676
7677 let (anchor, breakpoint) =
7678 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7679
7680 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7681 menu.on_blur_subscription(Subscription::new(|| {}))
7682 .context(focus_handle)
7683 .when(run_to_cursor, |this| {
7684 let weak_editor = weak_editor.clone();
7685 this.entry("Run to cursor", None, move |window, cx| {
7686 weak_editor
7687 .update(cx, |editor, cx| {
7688 editor.change_selections(None, window, cx, |s| {
7689 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7690 });
7691 })
7692 .ok();
7693
7694 window.dispatch_action(Box::new(RunToCursor), cx);
7695 })
7696 .separator()
7697 })
7698 .when_some(toggle_state_msg, |this, msg| {
7699 this.entry(msg, None, {
7700 let weak_editor = weak_editor.clone();
7701 let breakpoint = breakpoint.clone();
7702 move |_window, cx| {
7703 weak_editor
7704 .update(cx, |this, cx| {
7705 this.edit_breakpoint_at_anchor(
7706 anchor,
7707 breakpoint.as_ref().clone(),
7708 BreakpointEditAction::InvertState,
7709 cx,
7710 );
7711 })
7712 .log_err();
7713 }
7714 })
7715 })
7716 .entry(set_breakpoint_msg, None, {
7717 let weak_editor = weak_editor.clone();
7718 let breakpoint = breakpoint.clone();
7719 move |_window, cx| {
7720 weak_editor
7721 .update(cx, |this, cx| {
7722 this.edit_breakpoint_at_anchor(
7723 anchor,
7724 breakpoint.as_ref().clone(),
7725 BreakpointEditAction::Toggle,
7726 cx,
7727 );
7728 })
7729 .log_err();
7730 }
7731 })
7732 .entry(log_breakpoint_msg, None, {
7733 let breakpoint = breakpoint.clone();
7734 let weak_editor = weak_editor.clone();
7735 move |window, cx| {
7736 weak_editor
7737 .update(cx, |this, cx| {
7738 this.add_edit_breakpoint_block(
7739 anchor,
7740 breakpoint.as_ref(),
7741 BreakpointPromptEditAction::Log,
7742 window,
7743 cx,
7744 );
7745 })
7746 .log_err();
7747 }
7748 })
7749 .entry(condition_breakpoint_msg, None, {
7750 let breakpoint = breakpoint.clone();
7751 let weak_editor = weak_editor.clone();
7752 move |window, cx| {
7753 weak_editor
7754 .update(cx, |this, cx| {
7755 this.add_edit_breakpoint_block(
7756 anchor,
7757 breakpoint.as_ref(),
7758 BreakpointPromptEditAction::Condition,
7759 window,
7760 cx,
7761 );
7762 })
7763 .log_err();
7764 }
7765 })
7766 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7767 weak_editor
7768 .update(cx, |this, cx| {
7769 this.add_edit_breakpoint_block(
7770 anchor,
7771 breakpoint.as_ref(),
7772 BreakpointPromptEditAction::HitCondition,
7773 window,
7774 cx,
7775 );
7776 })
7777 .log_err();
7778 })
7779 })
7780 }
7781
7782 fn render_breakpoint(
7783 &self,
7784 position: Anchor,
7785 row: DisplayRow,
7786 breakpoint: &Breakpoint,
7787 state: Option<BreakpointSessionState>,
7788 cx: &mut Context<Self>,
7789 ) -> IconButton {
7790 let is_rejected = state.is_some_and(|s| !s.verified);
7791 // Is it a breakpoint that shows up when hovering over gutter?
7792 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7793 (false, false),
7794 |PhantomBreakpointIndicator {
7795 is_active,
7796 display_row,
7797 collides_with_existing_breakpoint,
7798 }| {
7799 (
7800 is_active && display_row == row,
7801 collides_with_existing_breakpoint,
7802 )
7803 },
7804 );
7805
7806 let (color, icon) = {
7807 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7808 (false, false) => ui::IconName::DebugBreakpoint,
7809 (true, false) => ui::IconName::DebugLogBreakpoint,
7810 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7811 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7812 };
7813
7814 let color = if is_phantom {
7815 Color::Hint
7816 } else if is_rejected {
7817 Color::Disabled
7818 } else {
7819 Color::Debugger
7820 };
7821
7822 (color, icon)
7823 };
7824
7825 let breakpoint = Arc::from(breakpoint.clone());
7826
7827 let alt_as_text = gpui::Keystroke {
7828 modifiers: Modifiers::secondary_key(),
7829 ..Default::default()
7830 };
7831 let primary_action_text = if breakpoint.is_disabled() {
7832 "Enable breakpoint"
7833 } else if is_phantom && !collides_with_existing {
7834 "Set breakpoint"
7835 } else {
7836 "Unset breakpoint"
7837 };
7838 let focus_handle = self.focus_handle.clone();
7839
7840 let meta = if is_rejected {
7841 SharedString::from("No executable code is associated with this line.")
7842 } else if collides_with_existing && !breakpoint.is_disabled() {
7843 SharedString::from(format!(
7844 "{alt_as_text}-click to disable,\nright-click for more options."
7845 ))
7846 } else {
7847 SharedString::from("Right-click for more options.")
7848 };
7849 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7850 .icon_size(IconSize::XSmall)
7851 .size(ui::ButtonSize::None)
7852 .when(is_rejected, |this| {
7853 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7854 })
7855 .icon_color(color)
7856 .style(ButtonStyle::Transparent)
7857 .on_click(cx.listener({
7858 let breakpoint = breakpoint.clone();
7859
7860 move |editor, event: &ClickEvent, window, cx| {
7861 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7862 BreakpointEditAction::InvertState
7863 } else {
7864 BreakpointEditAction::Toggle
7865 };
7866
7867 window.focus(&editor.focus_handle(cx));
7868 editor.edit_breakpoint_at_anchor(
7869 position,
7870 breakpoint.as_ref().clone(),
7871 edit_action,
7872 cx,
7873 );
7874 }
7875 }))
7876 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7877 editor.set_breakpoint_context_menu(
7878 row,
7879 Some(position),
7880 event.down.position,
7881 window,
7882 cx,
7883 );
7884 }))
7885 .tooltip(move |window, cx| {
7886 Tooltip::with_meta_in(
7887 primary_action_text,
7888 Some(&ToggleBreakpoint),
7889 meta.clone(),
7890 &focus_handle,
7891 window,
7892 cx,
7893 )
7894 })
7895 }
7896
7897 fn build_tasks_context(
7898 project: &Entity<Project>,
7899 buffer: &Entity<Buffer>,
7900 buffer_row: u32,
7901 tasks: &Arc<RunnableTasks>,
7902 cx: &mut Context<Self>,
7903 ) -> Task<Option<task::TaskContext>> {
7904 let position = Point::new(buffer_row, tasks.column);
7905 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7906 let location = Location {
7907 buffer: buffer.clone(),
7908 range: range_start..range_start,
7909 };
7910 // Fill in the environmental variables from the tree-sitter captures
7911 let mut captured_task_variables = TaskVariables::default();
7912 for (capture_name, value) in tasks.extra_variables.clone() {
7913 captured_task_variables.insert(
7914 task::VariableName::Custom(capture_name.into()),
7915 value.clone(),
7916 );
7917 }
7918 project.update(cx, |project, cx| {
7919 project.task_store().update(cx, |task_store, cx| {
7920 task_store.task_context_for_location(captured_task_variables, location, cx)
7921 })
7922 })
7923 }
7924
7925 pub fn spawn_nearest_task(
7926 &mut self,
7927 action: &SpawnNearestTask,
7928 window: &mut Window,
7929 cx: &mut Context<Self>,
7930 ) {
7931 let Some((workspace, _)) = self.workspace.clone() else {
7932 return;
7933 };
7934 let Some(project) = self.project.clone() else {
7935 return;
7936 };
7937
7938 // Try to find a closest, enclosing node using tree-sitter that has a
7939 // task
7940 let Some((buffer, buffer_row, tasks)) = self
7941 .find_enclosing_node_task(cx)
7942 // Or find the task that's closest in row-distance.
7943 .or_else(|| self.find_closest_task(cx))
7944 else {
7945 return;
7946 };
7947
7948 let reveal_strategy = action.reveal;
7949 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7950 cx.spawn_in(window, async move |_, cx| {
7951 let context = task_context.await?;
7952 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7953
7954 let resolved = &mut resolved_task.resolved;
7955 resolved.reveal = reveal_strategy;
7956
7957 workspace
7958 .update_in(cx, |workspace, window, cx| {
7959 workspace.schedule_resolved_task(
7960 task_source_kind,
7961 resolved_task,
7962 false,
7963 window,
7964 cx,
7965 );
7966 })
7967 .ok()
7968 })
7969 .detach();
7970 }
7971
7972 fn find_closest_task(
7973 &mut self,
7974 cx: &mut Context<Self>,
7975 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7976 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7977
7978 let ((buffer_id, row), tasks) = self
7979 .tasks
7980 .iter()
7981 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7982
7983 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7984 let tasks = Arc::new(tasks.to_owned());
7985 Some((buffer, *row, tasks))
7986 }
7987
7988 fn find_enclosing_node_task(
7989 &mut self,
7990 cx: &mut Context<Self>,
7991 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7992 let snapshot = self.buffer.read(cx).snapshot(cx);
7993 let offset = self.selections.newest::<usize>(cx).head();
7994 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7995 let buffer_id = excerpt.buffer().remote_id();
7996
7997 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7998 let mut cursor = layer.node().walk();
7999
8000 while cursor.goto_first_child_for_byte(offset).is_some() {
8001 if cursor.node().end_byte() == offset {
8002 cursor.goto_next_sibling();
8003 }
8004 }
8005
8006 // Ascend to the smallest ancestor that contains the range and has a task.
8007 loop {
8008 let node = cursor.node();
8009 let node_range = node.byte_range();
8010 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8011
8012 // Check if this node contains our offset
8013 if node_range.start <= offset && node_range.end >= offset {
8014 // If it contains offset, check for task
8015 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8016 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8017 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8018 }
8019 }
8020
8021 if !cursor.goto_parent() {
8022 break;
8023 }
8024 }
8025 None
8026 }
8027
8028 fn render_run_indicator(
8029 &self,
8030 _style: &EditorStyle,
8031 is_active: bool,
8032 row: DisplayRow,
8033 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8034 cx: &mut Context<Self>,
8035 ) -> IconButton {
8036 let color = Color::Muted;
8037 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8038
8039 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8040 .shape(ui::IconButtonShape::Square)
8041 .icon_size(IconSize::XSmall)
8042 .icon_color(color)
8043 .toggle_state(is_active)
8044 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8045 let quick_launch = e.down.button == MouseButton::Left;
8046 window.focus(&editor.focus_handle(cx));
8047 editor.toggle_code_actions(
8048 &ToggleCodeActions {
8049 deployed_from: Some(CodeActionSource::RunMenu(row)),
8050 quick_launch,
8051 },
8052 window,
8053 cx,
8054 );
8055 }))
8056 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8057 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8058 }))
8059 }
8060
8061 pub fn context_menu_visible(&self) -> bool {
8062 !self.edit_prediction_preview_is_active()
8063 && self
8064 .context_menu
8065 .borrow()
8066 .as_ref()
8067 .map_or(false, |menu| menu.visible())
8068 }
8069
8070 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8071 self.context_menu
8072 .borrow()
8073 .as_ref()
8074 .map(|menu| menu.origin())
8075 }
8076
8077 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8078 self.context_menu_options = Some(options);
8079 }
8080
8081 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8082 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8083
8084 fn render_edit_prediction_popover(
8085 &mut self,
8086 text_bounds: &Bounds<Pixels>,
8087 content_origin: gpui::Point<Pixels>,
8088 right_margin: Pixels,
8089 editor_snapshot: &EditorSnapshot,
8090 visible_row_range: Range<DisplayRow>,
8091 scroll_top: f32,
8092 scroll_bottom: f32,
8093 line_layouts: &[LineWithInvisibles],
8094 line_height: Pixels,
8095 scroll_pixel_position: gpui::Point<Pixels>,
8096 newest_selection_head: Option<DisplayPoint>,
8097 editor_width: Pixels,
8098 style: &EditorStyle,
8099 window: &mut Window,
8100 cx: &mut App,
8101 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8102 if self.mode().is_minimap() {
8103 return None;
8104 }
8105 let active_inline_completion = self.active_inline_completion.as_ref()?;
8106
8107 if self.edit_prediction_visible_in_cursor_popover(true) {
8108 return None;
8109 }
8110
8111 match &active_inline_completion.completion {
8112 InlineCompletion::Move { target, .. } => {
8113 let target_display_point = target.to_display_point(editor_snapshot);
8114
8115 if self.edit_prediction_requires_modifier() {
8116 if !self.edit_prediction_preview_is_active() {
8117 return None;
8118 }
8119
8120 self.render_edit_prediction_modifier_jump_popover(
8121 text_bounds,
8122 content_origin,
8123 visible_row_range,
8124 line_layouts,
8125 line_height,
8126 scroll_pixel_position,
8127 newest_selection_head,
8128 target_display_point,
8129 window,
8130 cx,
8131 )
8132 } else {
8133 self.render_edit_prediction_eager_jump_popover(
8134 text_bounds,
8135 content_origin,
8136 editor_snapshot,
8137 visible_row_range,
8138 scroll_top,
8139 scroll_bottom,
8140 line_height,
8141 scroll_pixel_position,
8142 target_display_point,
8143 editor_width,
8144 window,
8145 cx,
8146 )
8147 }
8148 }
8149 InlineCompletion::Edit {
8150 display_mode: EditDisplayMode::Inline,
8151 ..
8152 } => None,
8153 InlineCompletion::Edit {
8154 display_mode: EditDisplayMode::TabAccept,
8155 edits,
8156 ..
8157 } => {
8158 let range = &edits.first()?.0;
8159 let target_display_point = range.end.to_display_point(editor_snapshot);
8160
8161 self.render_edit_prediction_end_of_line_popover(
8162 "Accept",
8163 editor_snapshot,
8164 visible_row_range,
8165 target_display_point,
8166 line_height,
8167 scroll_pixel_position,
8168 content_origin,
8169 editor_width,
8170 window,
8171 cx,
8172 )
8173 }
8174 InlineCompletion::Edit {
8175 edits,
8176 edit_preview,
8177 display_mode: EditDisplayMode::DiffPopover,
8178 snapshot,
8179 } => self.render_edit_prediction_diff_popover(
8180 text_bounds,
8181 content_origin,
8182 right_margin,
8183 editor_snapshot,
8184 visible_row_range,
8185 line_layouts,
8186 line_height,
8187 scroll_pixel_position,
8188 newest_selection_head,
8189 editor_width,
8190 style,
8191 edits,
8192 edit_preview,
8193 snapshot,
8194 window,
8195 cx,
8196 ),
8197 }
8198 }
8199
8200 fn render_edit_prediction_modifier_jump_popover(
8201 &mut self,
8202 text_bounds: &Bounds<Pixels>,
8203 content_origin: gpui::Point<Pixels>,
8204 visible_row_range: Range<DisplayRow>,
8205 line_layouts: &[LineWithInvisibles],
8206 line_height: Pixels,
8207 scroll_pixel_position: gpui::Point<Pixels>,
8208 newest_selection_head: Option<DisplayPoint>,
8209 target_display_point: DisplayPoint,
8210 window: &mut Window,
8211 cx: &mut App,
8212 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8213 let scrolled_content_origin =
8214 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8215
8216 const SCROLL_PADDING_Y: Pixels = px(12.);
8217
8218 if target_display_point.row() < visible_row_range.start {
8219 return self.render_edit_prediction_scroll_popover(
8220 |_| SCROLL_PADDING_Y,
8221 IconName::ArrowUp,
8222 visible_row_range,
8223 line_layouts,
8224 newest_selection_head,
8225 scrolled_content_origin,
8226 window,
8227 cx,
8228 );
8229 } else if target_display_point.row() >= visible_row_range.end {
8230 return self.render_edit_prediction_scroll_popover(
8231 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8232 IconName::ArrowDown,
8233 visible_row_range,
8234 line_layouts,
8235 newest_selection_head,
8236 scrolled_content_origin,
8237 window,
8238 cx,
8239 );
8240 }
8241
8242 const POLE_WIDTH: Pixels = px(2.);
8243
8244 let line_layout =
8245 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8246 let target_column = target_display_point.column() as usize;
8247
8248 let target_x = line_layout.x_for_index(target_column);
8249 let target_y =
8250 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8251
8252 let flag_on_right = target_x < text_bounds.size.width / 2.;
8253
8254 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8255 border_color.l += 0.001;
8256
8257 let mut element = v_flex()
8258 .items_end()
8259 .when(flag_on_right, |el| el.items_start())
8260 .child(if flag_on_right {
8261 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8262 .rounded_bl(px(0.))
8263 .rounded_tl(px(0.))
8264 .border_l_2()
8265 .border_color(border_color)
8266 } else {
8267 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8268 .rounded_br(px(0.))
8269 .rounded_tr(px(0.))
8270 .border_r_2()
8271 .border_color(border_color)
8272 })
8273 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8274 .into_any();
8275
8276 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8277
8278 let mut origin = scrolled_content_origin + point(target_x, target_y)
8279 - point(
8280 if flag_on_right {
8281 POLE_WIDTH
8282 } else {
8283 size.width - POLE_WIDTH
8284 },
8285 size.height - line_height,
8286 );
8287
8288 origin.x = origin.x.max(content_origin.x);
8289
8290 element.prepaint_at(origin, window, cx);
8291
8292 Some((element, origin))
8293 }
8294
8295 fn render_edit_prediction_scroll_popover(
8296 &mut self,
8297 to_y: impl Fn(Size<Pixels>) -> Pixels,
8298 scroll_icon: IconName,
8299 visible_row_range: Range<DisplayRow>,
8300 line_layouts: &[LineWithInvisibles],
8301 newest_selection_head: Option<DisplayPoint>,
8302 scrolled_content_origin: gpui::Point<Pixels>,
8303 window: &mut Window,
8304 cx: &mut App,
8305 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8306 let mut element = self
8307 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8308 .into_any();
8309
8310 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8311
8312 let cursor = newest_selection_head?;
8313 let cursor_row_layout =
8314 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8315 let cursor_column = cursor.column() as usize;
8316
8317 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8318
8319 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8320
8321 element.prepaint_at(origin, window, cx);
8322 Some((element, origin))
8323 }
8324
8325 fn render_edit_prediction_eager_jump_popover(
8326 &mut self,
8327 text_bounds: &Bounds<Pixels>,
8328 content_origin: gpui::Point<Pixels>,
8329 editor_snapshot: &EditorSnapshot,
8330 visible_row_range: Range<DisplayRow>,
8331 scroll_top: f32,
8332 scroll_bottom: f32,
8333 line_height: Pixels,
8334 scroll_pixel_position: gpui::Point<Pixels>,
8335 target_display_point: DisplayPoint,
8336 editor_width: Pixels,
8337 window: &mut Window,
8338 cx: &mut App,
8339 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8340 if target_display_point.row().as_f32() < scroll_top {
8341 let mut element = self
8342 .render_edit_prediction_line_popover(
8343 "Jump to Edit",
8344 Some(IconName::ArrowUp),
8345 window,
8346 cx,
8347 )?
8348 .into_any();
8349
8350 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8351 let offset = point(
8352 (text_bounds.size.width - size.width) / 2.,
8353 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8354 );
8355
8356 let origin = text_bounds.origin + offset;
8357 element.prepaint_at(origin, window, cx);
8358 Some((element, origin))
8359 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8360 let mut element = self
8361 .render_edit_prediction_line_popover(
8362 "Jump to Edit",
8363 Some(IconName::ArrowDown),
8364 window,
8365 cx,
8366 )?
8367 .into_any();
8368
8369 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8370 let offset = point(
8371 (text_bounds.size.width - size.width) / 2.,
8372 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8373 );
8374
8375 let origin = text_bounds.origin + offset;
8376 element.prepaint_at(origin, window, cx);
8377 Some((element, origin))
8378 } else {
8379 self.render_edit_prediction_end_of_line_popover(
8380 "Jump to Edit",
8381 editor_snapshot,
8382 visible_row_range,
8383 target_display_point,
8384 line_height,
8385 scroll_pixel_position,
8386 content_origin,
8387 editor_width,
8388 window,
8389 cx,
8390 )
8391 }
8392 }
8393
8394 fn render_edit_prediction_end_of_line_popover(
8395 self: &mut Editor,
8396 label: &'static str,
8397 editor_snapshot: &EditorSnapshot,
8398 visible_row_range: Range<DisplayRow>,
8399 target_display_point: DisplayPoint,
8400 line_height: Pixels,
8401 scroll_pixel_position: gpui::Point<Pixels>,
8402 content_origin: gpui::Point<Pixels>,
8403 editor_width: Pixels,
8404 window: &mut Window,
8405 cx: &mut App,
8406 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8407 let target_line_end = DisplayPoint::new(
8408 target_display_point.row(),
8409 editor_snapshot.line_len(target_display_point.row()),
8410 );
8411
8412 let mut element = self
8413 .render_edit_prediction_line_popover(label, None, window, cx)?
8414 .into_any();
8415
8416 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8417
8418 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8419
8420 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8421 let mut origin = start_point
8422 + line_origin
8423 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8424 origin.x = origin.x.max(content_origin.x);
8425
8426 let max_x = content_origin.x + editor_width - size.width;
8427
8428 if origin.x > max_x {
8429 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8430
8431 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8432 origin.y += offset;
8433 IconName::ArrowUp
8434 } else {
8435 origin.y -= offset;
8436 IconName::ArrowDown
8437 };
8438
8439 element = self
8440 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8441 .into_any();
8442
8443 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8444
8445 origin.x = content_origin.x + editor_width - size.width - px(2.);
8446 }
8447
8448 element.prepaint_at(origin, window, cx);
8449 Some((element, origin))
8450 }
8451
8452 fn render_edit_prediction_diff_popover(
8453 self: &Editor,
8454 text_bounds: &Bounds<Pixels>,
8455 content_origin: gpui::Point<Pixels>,
8456 right_margin: Pixels,
8457 editor_snapshot: &EditorSnapshot,
8458 visible_row_range: Range<DisplayRow>,
8459 line_layouts: &[LineWithInvisibles],
8460 line_height: Pixels,
8461 scroll_pixel_position: gpui::Point<Pixels>,
8462 newest_selection_head: Option<DisplayPoint>,
8463 editor_width: Pixels,
8464 style: &EditorStyle,
8465 edits: &Vec<(Range<Anchor>, String)>,
8466 edit_preview: &Option<language::EditPreview>,
8467 snapshot: &language::BufferSnapshot,
8468 window: &mut Window,
8469 cx: &mut App,
8470 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8471 let edit_start = edits
8472 .first()
8473 .unwrap()
8474 .0
8475 .start
8476 .to_display_point(editor_snapshot);
8477 let edit_end = edits
8478 .last()
8479 .unwrap()
8480 .0
8481 .end
8482 .to_display_point(editor_snapshot);
8483
8484 let is_visible = visible_row_range.contains(&edit_start.row())
8485 || visible_row_range.contains(&edit_end.row());
8486 if !is_visible {
8487 return None;
8488 }
8489
8490 let highlighted_edits =
8491 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8492
8493 let styled_text = highlighted_edits.to_styled_text(&style.text);
8494 let line_count = highlighted_edits.text.lines().count();
8495
8496 const BORDER_WIDTH: Pixels = px(1.);
8497
8498 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8499 let has_keybind = keybind.is_some();
8500
8501 let mut element = h_flex()
8502 .items_start()
8503 .child(
8504 h_flex()
8505 .bg(cx.theme().colors().editor_background)
8506 .border(BORDER_WIDTH)
8507 .shadow_sm()
8508 .border_color(cx.theme().colors().border)
8509 .rounded_l_lg()
8510 .when(line_count > 1, |el| el.rounded_br_lg())
8511 .pr_1()
8512 .child(styled_text),
8513 )
8514 .child(
8515 h_flex()
8516 .h(line_height + BORDER_WIDTH * 2.)
8517 .px_1p5()
8518 .gap_1()
8519 // Workaround: For some reason, there's a gap if we don't do this
8520 .ml(-BORDER_WIDTH)
8521 .shadow(vec![gpui::BoxShadow {
8522 color: gpui::black().opacity(0.05),
8523 offset: point(px(1.), px(1.)),
8524 blur_radius: px(2.),
8525 spread_radius: px(0.),
8526 }])
8527 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8528 .border(BORDER_WIDTH)
8529 .border_color(cx.theme().colors().border)
8530 .rounded_r_lg()
8531 .id("edit_prediction_diff_popover_keybind")
8532 .when(!has_keybind, |el| {
8533 let status_colors = cx.theme().status();
8534
8535 el.bg(status_colors.error_background)
8536 .border_color(status_colors.error.opacity(0.6))
8537 .child(Icon::new(IconName::Info).color(Color::Error))
8538 .cursor_default()
8539 .hoverable_tooltip(move |_window, cx| {
8540 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8541 })
8542 })
8543 .children(keybind),
8544 )
8545 .into_any();
8546
8547 let longest_row =
8548 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8549 let longest_line_width = if visible_row_range.contains(&longest_row) {
8550 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8551 } else {
8552 layout_line(
8553 longest_row,
8554 editor_snapshot,
8555 style,
8556 editor_width,
8557 |_| false,
8558 window,
8559 cx,
8560 )
8561 .width
8562 };
8563
8564 let viewport_bounds =
8565 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8566 right: -right_margin,
8567 ..Default::default()
8568 });
8569
8570 let x_after_longest =
8571 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8572 - scroll_pixel_position.x;
8573
8574 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8575
8576 // Fully visible if it can be displayed within the window (allow overlapping other
8577 // panes). However, this is only allowed if the popover starts within text_bounds.
8578 let can_position_to_the_right = x_after_longest < text_bounds.right()
8579 && x_after_longest + element_bounds.width < viewport_bounds.right();
8580
8581 let mut origin = if can_position_to_the_right {
8582 point(
8583 x_after_longest,
8584 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8585 - scroll_pixel_position.y,
8586 )
8587 } else {
8588 let cursor_row = newest_selection_head.map(|head| head.row());
8589 let above_edit = edit_start
8590 .row()
8591 .0
8592 .checked_sub(line_count as u32)
8593 .map(DisplayRow);
8594 let below_edit = Some(edit_end.row() + 1);
8595 let above_cursor =
8596 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8597 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8598
8599 // Place the edit popover adjacent to the edit if there is a location
8600 // available that is onscreen and does not obscure the cursor. Otherwise,
8601 // place it adjacent to the cursor.
8602 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8603 .into_iter()
8604 .flatten()
8605 .find(|&start_row| {
8606 let end_row = start_row + line_count as u32;
8607 visible_row_range.contains(&start_row)
8608 && visible_row_range.contains(&end_row)
8609 && cursor_row.map_or(true, |cursor_row| {
8610 !((start_row..end_row).contains(&cursor_row))
8611 })
8612 })?;
8613
8614 content_origin
8615 + point(
8616 -scroll_pixel_position.x,
8617 row_target.as_f32() * line_height - scroll_pixel_position.y,
8618 )
8619 };
8620
8621 origin.x -= BORDER_WIDTH;
8622
8623 window.defer_draw(element, origin, 1);
8624
8625 // Do not return an element, since it will already be drawn due to defer_draw.
8626 None
8627 }
8628
8629 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8630 px(30.)
8631 }
8632
8633 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8634 if self.read_only(cx) {
8635 cx.theme().players().read_only()
8636 } else {
8637 self.style.as_ref().unwrap().local_player
8638 }
8639 }
8640
8641 fn render_edit_prediction_accept_keybind(
8642 &self,
8643 window: &mut Window,
8644 cx: &App,
8645 ) -> Option<AnyElement> {
8646 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8647 let accept_keystroke = accept_binding.keystroke()?;
8648
8649 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8650
8651 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8652 Color::Accent
8653 } else {
8654 Color::Muted
8655 };
8656
8657 h_flex()
8658 .px_0p5()
8659 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8660 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8661 .text_size(TextSize::XSmall.rems(cx))
8662 .child(h_flex().children(ui::render_modifiers(
8663 &accept_keystroke.modifiers,
8664 PlatformStyle::platform(),
8665 Some(modifiers_color),
8666 Some(IconSize::XSmall.rems().into()),
8667 true,
8668 )))
8669 .when(is_platform_style_mac, |parent| {
8670 parent.child(accept_keystroke.key.clone())
8671 })
8672 .when(!is_platform_style_mac, |parent| {
8673 parent.child(
8674 Key::new(
8675 util::capitalize(&accept_keystroke.key),
8676 Some(Color::Default),
8677 )
8678 .size(Some(IconSize::XSmall.rems().into())),
8679 )
8680 })
8681 .into_any()
8682 .into()
8683 }
8684
8685 fn render_edit_prediction_line_popover(
8686 &self,
8687 label: impl Into<SharedString>,
8688 icon: Option<IconName>,
8689 window: &mut Window,
8690 cx: &App,
8691 ) -> Option<Stateful<Div>> {
8692 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8693
8694 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8695 let has_keybind = keybind.is_some();
8696
8697 let result = h_flex()
8698 .id("ep-line-popover")
8699 .py_0p5()
8700 .pl_1()
8701 .pr(padding_right)
8702 .gap_1()
8703 .rounded_md()
8704 .border_1()
8705 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8706 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8707 .shadow_sm()
8708 .when(!has_keybind, |el| {
8709 let status_colors = cx.theme().status();
8710
8711 el.bg(status_colors.error_background)
8712 .border_color(status_colors.error.opacity(0.6))
8713 .pl_2()
8714 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8715 .cursor_default()
8716 .hoverable_tooltip(move |_window, cx| {
8717 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8718 })
8719 })
8720 .children(keybind)
8721 .child(
8722 Label::new(label)
8723 .size(LabelSize::Small)
8724 .when(!has_keybind, |el| {
8725 el.color(cx.theme().status().error.into()).strikethrough()
8726 }),
8727 )
8728 .when(!has_keybind, |el| {
8729 el.child(
8730 h_flex().ml_1().child(
8731 Icon::new(IconName::Info)
8732 .size(IconSize::Small)
8733 .color(cx.theme().status().error.into()),
8734 ),
8735 )
8736 })
8737 .when_some(icon, |element, icon| {
8738 element.child(
8739 div()
8740 .mt(px(1.5))
8741 .child(Icon::new(icon).size(IconSize::Small)),
8742 )
8743 });
8744
8745 Some(result)
8746 }
8747
8748 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8749 let accent_color = cx.theme().colors().text_accent;
8750 let editor_bg_color = cx.theme().colors().editor_background;
8751 editor_bg_color.blend(accent_color.opacity(0.1))
8752 }
8753
8754 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8755 let accent_color = cx.theme().colors().text_accent;
8756 let editor_bg_color = cx.theme().colors().editor_background;
8757 editor_bg_color.blend(accent_color.opacity(0.6))
8758 }
8759
8760 fn render_edit_prediction_cursor_popover(
8761 &self,
8762 min_width: Pixels,
8763 max_width: Pixels,
8764 cursor_point: Point,
8765 style: &EditorStyle,
8766 accept_keystroke: Option<&gpui::Keystroke>,
8767 _window: &Window,
8768 cx: &mut Context<Editor>,
8769 ) -> Option<AnyElement> {
8770 let provider = self.edit_prediction_provider.as_ref()?;
8771
8772 if provider.provider.needs_terms_acceptance(cx) {
8773 return Some(
8774 h_flex()
8775 .min_w(min_width)
8776 .flex_1()
8777 .px_2()
8778 .py_1()
8779 .gap_3()
8780 .elevation_2(cx)
8781 .hover(|style| style.bg(cx.theme().colors().element_hover))
8782 .id("accept-terms")
8783 .cursor_pointer()
8784 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8785 .on_click(cx.listener(|this, _event, window, cx| {
8786 cx.stop_propagation();
8787 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8788 window.dispatch_action(
8789 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8790 cx,
8791 );
8792 }))
8793 .child(
8794 h_flex()
8795 .flex_1()
8796 .gap_2()
8797 .child(Icon::new(IconName::ZedPredict))
8798 .child(Label::new("Accept Terms of Service"))
8799 .child(div().w_full())
8800 .child(
8801 Icon::new(IconName::ArrowUpRight)
8802 .color(Color::Muted)
8803 .size(IconSize::Small),
8804 )
8805 .into_any_element(),
8806 )
8807 .into_any(),
8808 );
8809 }
8810
8811 let is_refreshing = provider.provider.is_refreshing(cx);
8812
8813 fn pending_completion_container() -> Div {
8814 h_flex()
8815 .h_full()
8816 .flex_1()
8817 .gap_2()
8818 .child(Icon::new(IconName::ZedPredict))
8819 }
8820
8821 let completion = match &self.active_inline_completion {
8822 Some(prediction) => {
8823 if !self.has_visible_completions_menu() {
8824 const RADIUS: Pixels = px(6.);
8825 const BORDER_WIDTH: Pixels = px(1.);
8826
8827 return Some(
8828 h_flex()
8829 .elevation_2(cx)
8830 .border(BORDER_WIDTH)
8831 .border_color(cx.theme().colors().border)
8832 .when(accept_keystroke.is_none(), |el| {
8833 el.border_color(cx.theme().status().error)
8834 })
8835 .rounded(RADIUS)
8836 .rounded_tl(px(0.))
8837 .overflow_hidden()
8838 .child(div().px_1p5().child(match &prediction.completion {
8839 InlineCompletion::Move { target, snapshot } => {
8840 use text::ToPoint as _;
8841 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8842 {
8843 Icon::new(IconName::ZedPredictDown)
8844 } else {
8845 Icon::new(IconName::ZedPredictUp)
8846 }
8847 }
8848 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8849 }))
8850 .child(
8851 h_flex()
8852 .gap_1()
8853 .py_1()
8854 .px_2()
8855 .rounded_r(RADIUS - BORDER_WIDTH)
8856 .border_l_1()
8857 .border_color(cx.theme().colors().border)
8858 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8859 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8860 el.child(
8861 Label::new("Hold")
8862 .size(LabelSize::Small)
8863 .when(accept_keystroke.is_none(), |el| {
8864 el.strikethrough()
8865 })
8866 .line_height_style(LineHeightStyle::UiLabel),
8867 )
8868 })
8869 .id("edit_prediction_cursor_popover_keybind")
8870 .when(accept_keystroke.is_none(), |el| {
8871 let status_colors = cx.theme().status();
8872
8873 el.bg(status_colors.error_background)
8874 .border_color(status_colors.error.opacity(0.6))
8875 .child(Icon::new(IconName::Info).color(Color::Error))
8876 .cursor_default()
8877 .hoverable_tooltip(move |_window, cx| {
8878 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8879 .into()
8880 })
8881 })
8882 .when_some(
8883 accept_keystroke.as_ref(),
8884 |el, accept_keystroke| {
8885 el.child(h_flex().children(ui::render_modifiers(
8886 &accept_keystroke.modifiers,
8887 PlatformStyle::platform(),
8888 Some(Color::Default),
8889 Some(IconSize::XSmall.rems().into()),
8890 false,
8891 )))
8892 },
8893 ),
8894 )
8895 .into_any(),
8896 );
8897 }
8898
8899 self.render_edit_prediction_cursor_popover_preview(
8900 prediction,
8901 cursor_point,
8902 style,
8903 cx,
8904 )?
8905 }
8906
8907 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8908 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8909 stale_completion,
8910 cursor_point,
8911 style,
8912 cx,
8913 )?,
8914
8915 None => {
8916 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8917 }
8918 },
8919
8920 None => pending_completion_container().child(Label::new("No Prediction")),
8921 };
8922
8923 let completion = if is_refreshing {
8924 completion
8925 .with_animation(
8926 "loading-completion",
8927 Animation::new(Duration::from_secs(2))
8928 .repeat()
8929 .with_easing(pulsating_between(0.4, 0.8)),
8930 |label, delta| label.opacity(delta),
8931 )
8932 .into_any_element()
8933 } else {
8934 completion.into_any_element()
8935 };
8936
8937 let has_completion = self.active_inline_completion.is_some();
8938
8939 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8940 Some(
8941 h_flex()
8942 .min_w(min_width)
8943 .max_w(max_width)
8944 .flex_1()
8945 .elevation_2(cx)
8946 .border_color(cx.theme().colors().border)
8947 .child(
8948 div()
8949 .flex_1()
8950 .py_1()
8951 .px_2()
8952 .overflow_hidden()
8953 .child(completion),
8954 )
8955 .when_some(accept_keystroke, |el, accept_keystroke| {
8956 if !accept_keystroke.modifiers.modified() {
8957 return el;
8958 }
8959
8960 el.child(
8961 h_flex()
8962 .h_full()
8963 .border_l_1()
8964 .rounded_r_lg()
8965 .border_color(cx.theme().colors().border)
8966 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8967 .gap_1()
8968 .py_1()
8969 .px_2()
8970 .child(
8971 h_flex()
8972 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8973 .when(is_platform_style_mac, |parent| parent.gap_1())
8974 .child(h_flex().children(ui::render_modifiers(
8975 &accept_keystroke.modifiers,
8976 PlatformStyle::platform(),
8977 Some(if !has_completion {
8978 Color::Muted
8979 } else {
8980 Color::Default
8981 }),
8982 None,
8983 false,
8984 ))),
8985 )
8986 .child(Label::new("Preview").into_any_element())
8987 .opacity(if has_completion { 1.0 } else { 0.4 }),
8988 )
8989 })
8990 .into_any(),
8991 )
8992 }
8993
8994 fn render_edit_prediction_cursor_popover_preview(
8995 &self,
8996 completion: &InlineCompletionState,
8997 cursor_point: Point,
8998 style: &EditorStyle,
8999 cx: &mut Context<Editor>,
9000 ) -> Option<Div> {
9001 use text::ToPoint as _;
9002
9003 fn render_relative_row_jump(
9004 prefix: impl Into<String>,
9005 current_row: u32,
9006 target_row: u32,
9007 ) -> Div {
9008 let (row_diff, arrow) = if target_row < current_row {
9009 (current_row - target_row, IconName::ArrowUp)
9010 } else {
9011 (target_row - current_row, IconName::ArrowDown)
9012 };
9013
9014 h_flex()
9015 .child(
9016 Label::new(format!("{}{}", prefix.into(), row_diff))
9017 .color(Color::Muted)
9018 .size(LabelSize::Small),
9019 )
9020 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9021 }
9022
9023 match &completion.completion {
9024 InlineCompletion::Move {
9025 target, snapshot, ..
9026 } => Some(
9027 h_flex()
9028 .px_2()
9029 .gap_2()
9030 .flex_1()
9031 .child(
9032 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9033 Icon::new(IconName::ZedPredictDown)
9034 } else {
9035 Icon::new(IconName::ZedPredictUp)
9036 },
9037 )
9038 .child(Label::new("Jump to Edit")),
9039 ),
9040
9041 InlineCompletion::Edit {
9042 edits,
9043 edit_preview,
9044 snapshot,
9045 display_mode: _,
9046 } => {
9047 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9048
9049 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9050 &snapshot,
9051 &edits,
9052 edit_preview.as_ref()?,
9053 true,
9054 cx,
9055 )
9056 .first_line_preview();
9057
9058 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9059 .with_default_highlights(&style.text, highlighted_edits.highlights);
9060
9061 let preview = h_flex()
9062 .gap_1()
9063 .min_w_16()
9064 .child(styled_text)
9065 .when(has_more_lines, |parent| parent.child("…"));
9066
9067 let left = if first_edit_row != cursor_point.row {
9068 render_relative_row_jump("", cursor_point.row, first_edit_row)
9069 .into_any_element()
9070 } else {
9071 Icon::new(IconName::ZedPredict).into_any_element()
9072 };
9073
9074 Some(
9075 h_flex()
9076 .h_full()
9077 .flex_1()
9078 .gap_2()
9079 .pr_1()
9080 .overflow_x_hidden()
9081 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9082 .child(left)
9083 .child(preview),
9084 )
9085 }
9086 }
9087 }
9088
9089 pub fn render_context_menu(
9090 &self,
9091 style: &EditorStyle,
9092 max_height_in_lines: u32,
9093 window: &mut Window,
9094 cx: &mut Context<Editor>,
9095 ) -> Option<AnyElement> {
9096 let menu = self.context_menu.borrow();
9097 let menu = menu.as_ref()?;
9098 if !menu.visible() {
9099 return None;
9100 };
9101 Some(menu.render(style, max_height_in_lines, window, cx))
9102 }
9103
9104 fn render_context_menu_aside(
9105 &mut self,
9106 max_size: Size<Pixels>,
9107 window: &mut Window,
9108 cx: &mut Context<Editor>,
9109 ) -> Option<AnyElement> {
9110 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9111 if menu.visible() {
9112 menu.render_aside(max_size, window, cx)
9113 } else {
9114 None
9115 }
9116 })
9117 }
9118
9119 fn hide_context_menu(
9120 &mut self,
9121 window: &mut Window,
9122 cx: &mut Context<Self>,
9123 ) -> Option<CodeContextMenu> {
9124 cx.notify();
9125 self.completion_tasks.clear();
9126 let context_menu = self.context_menu.borrow_mut().take();
9127 self.stale_inline_completion_in_menu.take();
9128 self.update_visible_inline_completion(window, cx);
9129 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9130 if let Some(completion_provider) = &self.completion_provider {
9131 completion_provider.selection_changed(None, window, cx);
9132 }
9133 }
9134 context_menu
9135 }
9136
9137 fn show_snippet_choices(
9138 &mut self,
9139 choices: &Vec<String>,
9140 selection: Range<Anchor>,
9141 cx: &mut Context<Self>,
9142 ) {
9143 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9144 (Some(a), Some(b)) if a == b => a,
9145 _ => {
9146 log::error!("expected anchor range to have matching buffer IDs");
9147 return;
9148 }
9149 };
9150 let multi_buffer = self.buffer().read(cx);
9151 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9152 return;
9153 };
9154
9155 let id = post_inc(&mut self.next_completion_id);
9156 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9157 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9158 CompletionsMenu::new_snippet_choices(
9159 id,
9160 true,
9161 choices,
9162 selection,
9163 buffer,
9164 snippet_sort_order,
9165 ),
9166 ));
9167 }
9168
9169 pub fn insert_snippet(
9170 &mut self,
9171 insertion_ranges: &[Range<usize>],
9172 snippet: Snippet,
9173 window: &mut Window,
9174 cx: &mut Context<Self>,
9175 ) -> Result<()> {
9176 struct Tabstop<T> {
9177 is_end_tabstop: bool,
9178 ranges: Vec<Range<T>>,
9179 choices: Option<Vec<String>>,
9180 }
9181
9182 let tabstops = self.buffer.update(cx, |buffer, cx| {
9183 let snippet_text: Arc<str> = snippet.text.clone().into();
9184 let edits = insertion_ranges
9185 .iter()
9186 .cloned()
9187 .map(|range| (range, snippet_text.clone()));
9188 let autoindent_mode = AutoindentMode::Block {
9189 original_indent_columns: Vec::new(),
9190 };
9191 buffer.edit(edits, Some(autoindent_mode), cx);
9192
9193 let snapshot = &*buffer.read(cx);
9194 let snippet = &snippet;
9195 snippet
9196 .tabstops
9197 .iter()
9198 .map(|tabstop| {
9199 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9200 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9201 });
9202 let mut tabstop_ranges = tabstop
9203 .ranges
9204 .iter()
9205 .flat_map(|tabstop_range| {
9206 let mut delta = 0_isize;
9207 insertion_ranges.iter().map(move |insertion_range| {
9208 let insertion_start = insertion_range.start as isize + delta;
9209 delta +=
9210 snippet.text.len() as isize - insertion_range.len() as isize;
9211
9212 let start = ((insertion_start + tabstop_range.start) as usize)
9213 .min(snapshot.len());
9214 let end = ((insertion_start + tabstop_range.end) as usize)
9215 .min(snapshot.len());
9216 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9217 })
9218 })
9219 .collect::<Vec<_>>();
9220 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9221
9222 Tabstop {
9223 is_end_tabstop,
9224 ranges: tabstop_ranges,
9225 choices: tabstop.choices.clone(),
9226 }
9227 })
9228 .collect::<Vec<_>>()
9229 });
9230 if let Some(tabstop) = tabstops.first() {
9231 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9232 // Reverse order so that the first range is the newest created selection.
9233 // Completions will use it and autoscroll will prioritize it.
9234 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9235 });
9236
9237 if let Some(choices) = &tabstop.choices {
9238 if let Some(selection) = tabstop.ranges.first() {
9239 self.show_snippet_choices(choices, selection.clone(), cx)
9240 }
9241 }
9242
9243 // If we're already at the last tabstop and it's at the end of the snippet,
9244 // we're done, we don't need to keep the state around.
9245 if !tabstop.is_end_tabstop {
9246 let choices = tabstops
9247 .iter()
9248 .map(|tabstop| tabstop.choices.clone())
9249 .collect();
9250
9251 let ranges = tabstops
9252 .into_iter()
9253 .map(|tabstop| tabstop.ranges)
9254 .collect::<Vec<_>>();
9255
9256 self.snippet_stack.push(SnippetState {
9257 active_index: 0,
9258 ranges,
9259 choices,
9260 });
9261 }
9262
9263 // Check whether the just-entered snippet ends with an auto-closable bracket.
9264 if self.autoclose_regions.is_empty() {
9265 let snapshot = self.buffer.read(cx).snapshot(cx);
9266 for selection in &mut self.selections.all::<Point>(cx) {
9267 let selection_head = selection.head();
9268 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9269 continue;
9270 };
9271
9272 let mut bracket_pair = None;
9273 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9274 let prev_chars = snapshot
9275 .reversed_chars_at(selection_head)
9276 .collect::<String>();
9277 for (pair, enabled) in scope.brackets() {
9278 if enabled
9279 && pair.close
9280 && prev_chars.starts_with(pair.start.as_str())
9281 && next_chars.starts_with(pair.end.as_str())
9282 {
9283 bracket_pair = Some(pair.clone());
9284 break;
9285 }
9286 }
9287 if let Some(pair) = bracket_pair {
9288 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9289 let autoclose_enabled =
9290 self.use_autoclose && snapshot_settings.use_autoclose;
9291 if autoclose_enabled {
9292 let start = snapshot.anchor_after(selection_head);
9293 let end = snapshot.anchor_after(selection_head);
9294 self.autoclose_regions.push(AutocloseRegion {
9295 selection_id: selection.id,
9296 range: start..end,
9297 pair,
9298 });
9299 }
9300 }
9301 }
9302 }
9303 }
9304 Ok(())
9305 }
9306
9307 pub fn move_to_next_snippet_tabstop(
9308 &mut self,
9309 window: &mut Window,
9310 cx: &mut Context<Self>,
9311 ) -> bool {
9312 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9313 }
9314
9315 pub fn move_to_prev_snippet_tabstop(
9316 &mut self,
9317 window: &mut Window,
9318 cx: &mut Context<Self>,
9319 ) -> bool {
9320 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9321 }
9322
9323 pub fn move_to_snippet_tabstop(
9324 &mut self,
9325 bias: Bias,
9326 window: &mut Window,
9327 cx: &mut Context<Self>,
9328 ) -> bool {
9329 if let Some(mut snippet) = self.snippet_stack.pop() {
9330 match bias {
9331 Bias::Left => {
9332 if snippet.active_index > 0 {
9333 snippet.active_index -= 1;
9334 } else {
9335 self.snippet_stack.push(snippet);
9336 return false;
9337 }
9338 }
9339 Bias::Right => {
9340 if snippet.active_index + 1 < snippet.ranges.len() {
9341 snippet.active_index += 1;
9342 } else {
9343 self.snippet_stack.push(snippet);
9344 return false;
9345 }
9346 }
9347 }
9348 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9349 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9350 // Reverse order so that the first range is the newest created selection.
9351 // Completions will use it and autoscroll will prioritize it.
9352 s.select_ranges(current_ranges.iter().rev().cloned())
9353 });
9354
9355 if let Some(choices) = &snippet.choices[snippet.active_index] {
9356 if let Some(selection) = current_ranges.first() {
9357 self.show_snippet_choices(&choices, selection.clone(), cx);
9358 }
9359 }
9360
9361 // If snippet state is not at the last tabstop, push it back on the stack
9362 if snippet.active_index + 1 < snippet.ranges.len() {
9363 self.snippet_stack.push(snippet);
9364 }
9365 return true;
9366 }
9367 }
9368
9369 false
9370 }
9371
9372 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9373 self.transact(window, cx, |this, window, cx| {
9374 this.select_all(&SelectAll, window, cx);
9375 this.insert("", window, cx);
9376 });
9377 }
9378
9379 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9380 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9381 self.transact(window, cx, |this, window, cx| {
9382 this.select_autoclose_pair(window, cx);
9383 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9384 if !this.linked_edit_ranges.is_empty() {
9385 let selections = this.selections.all::<MultiBufferPoint>(cx);
9386 let snapshot = this.buffer.read(cx).snapshot(cx);
9387
9388 for selection in selections.iter() {
9389 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9390 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9391 if selection_start.buffer_id != selection_end.buffer_id {
9392 continue;
9393 }
9394 if let Some(ranges) =
9395 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9396 {
9397 for (buffer, entries) in ranges {
9398 linked_ranges.entry(buffer).or_default().extend(entries);
9399 }
9400 }
9401 }
9402 }
9403
9404 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9405 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9406 for selection in &mut selections {
9407 if selection.is_empty() {
9408 let old_head = selection.head();
9409 let mut new_head =
9410 movement::left(&display_map, old_head.to_display_point(&display_map))
9411 .to_point(&display_map);
9412 if let Some((buffer, line_buffer_range)) = display_map
9413 .buffer_snapshot
9414 .buffer_line_for_row(MultiBufferRow(old_head.row))
9415 {
9416 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9417 let indent_len = match indent_size.kind {
9418 IndentKind::Space => {
9419 buffer.settings_at(line_buffer_range.start, cx).tab_size
9420 }
9421 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9422 };
9423 if old_head.column <= indent_size.len && old_head.column > 0 {
9424 let indent_len = indent_len.get();
9425 new_head = cmp::min(
9426 new_head,
9427 MultiBufferPoint::new(
9428 old_head.row,
9429 ((old_head.column - 1) / indent_len) * indent_len,
9430 ),
9431 );
9432 }
9433 }
9434
9435 selection.set_head(new_head, SelectionGoal::None);
9436 }
9437 }
9438
9439 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9440 s.select(selections)
9441 });
9442 this.insert("", window, cx);
9443 let empty_str: Arc<str> = Arc::from("");
9444 for (buffer, edits) in linked_ranges {
9445 let snapshot = buffer.read(cx).snapshot();
9446 use text::ToPoint as TP;
9447
9448 let edits = edits
9449 .into_iter()
9450 .map(|range| {
9451 let end_point = TP::to_point(&range.end, &snapshot);
9452 let mut start_point = TP::to_point(&range.start, &snapshot);
9453
9454 if end_point == start_point {
9455 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9456 .saturating_sub(1);
9457 start_point =
9458 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9459 };
9460
9461 (start_point..end_point, empty_str.clone())
9462 })
9463 .sorted_by_key(|(range, _)| range.start)
9464 .collect::<Vec<_>>();
9465 buffer.update(cx, |this, cx| {
9466 this.edit(edits, None, cx);
9467 })
9468 }
9469 this.refresh_inline_completion(true, false, window, cx);
9470 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9471 });
9472 }
9473
9474 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9475 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9476 self.transact(window, cx, |this, window, cx| {
9477 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9478 s.move_with(|map, selection| {
9479 if selection.is_empty() {
9480 let cursor = movement::right(map, selection.head());
9481 selection.end = cursor;
9482 selection.reversed = true;
9483 selection.goal = SelectionGoal::None;
9484 }
9485 })
9486 });
9487 this.insert("", window, cx);
9488 this.refresh_inline_completion(true, false, window, cx);
9489 });
9490 }
9491
9492 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9493 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9494 if self.move_to_prev_snippet_tabstop(window, cx) {
9495 return;
9496 }
9497 self.outdent(&Outdent, window, cx);
9498 }
9499
9500 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9501 if self.move_to_next_snippet_tabstop(window, cx) {
9502 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9503 return;
9504 }
9505 if self.read_only(cx) {
9506 return;
9507 }
9508 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9509 let mut selections = self.selections.all_adjusted(cx);
9510 let buffer = self.buffer.read(cx);
9511 let snapshot = buffer.snapshot(cx);
9512 let rows_iter = selections.iter().map(|s| s.head().row);
9513 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9514
9515 let has_some_cursor_in_whitespace = selections
9516 .iter()
9517 .filter(|selection| selection.is_empty())
9518 .any(|selection| {
9519 let cursor = selection.head();
9520 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9521 cursor.column < current_indent.len
9522 });
9523
9524 let mut edits = Vec::new();
9525 let mut prev_edited_row = 0;
9526 let mut row_delta = 0;
9527 for selection in &mut selections {
9528 if selection.start.row != prev_edited_row {
9529 row_delta = 0;
9530 }
9531 prev_edited_row = selection.end.row;
9532
9533 // If the selection is non-empty, then increase the indentation of the selected lines.
9534 if !selection.is_empty() {
9535 row_delta =
9536 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9537 continue;
9538 }
9539
9540 let cursor = selection.head();
9541 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9542 if let Some(suggested_indent) =
9543 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9544 {
9545 // Don't do anything if already at suggested indent
9546 // and there is any other cursor which is not
9547 if has_some_cursor_in_whitespace
9548 && cursor.column == current_indent.len
9549 && current_indent.len == suggested_indent.len
9550 {
9551 continue;
9552 }
9553
9554 // Adjust line and move cursor to suggested indent
9555 // if cursor is not at suggested indent
9556 if cursor.column < suggested_indent.len
9557 && cursor.column <= current_indent.len
9558 && current_indent.len <= suggested_indent.len
9559 {
9560 selection.start = Point::new(cursor.row, suggested_indent.len);
9561 selection.end = selection.start;
9562 if row_delta == 0 {
9563 edits.extend(Buffer::edit_for_indent_size_adjustment(
9564 cursor.row,
9565 current_indent,
9566 suggested_indent,
9567 ));
9568 row_delta = suggested_indent.len - current_indent.len;
9569 }
9570 continue;
9571 }
9572
9573 // If current indent is more than suggested indent
9574 // only move cursor to current indent and skip indent
9575 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9576 selection.start = Point::new(cursor.row, current_indent.len);
9577 selection.end = selection.start;
9578 continue;
9579 }
9580 }
9581
9582 // Otherwise, insert a hard or soft tab.
9583 let settings = buffer.language_settings_at(cursor, cx);
9584 let tab_size = if settings.hard_tabs {
9585 IndentSize::tab()
9586 } else {
9587 let tab_size = settings.tab_size.get();
9588 let indent_remainder = snapshot
9589 .text_for_range(Point::new(cursor.row, 0)..cursor)
9590 .flat_map(str::chars)
9591 .fold(row_delta % tab_size, |counter: u32, c| {
9592 if c == '\t' {
9593 0
9594 } else {
9595 (counter + 1) % tab_size
9596 }
9597 });
9598
9599 let chars_to_next_tab_stop = tab_size - indent_remainder;
9600 IndentSize::spaces(chars_to_next_tab_stop)
9601 };
9602 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9603 selection.end = selection.start;
9604 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9605 row_delta += tab_size.len;
9606 }
9607
9608 self.transact(window, cx, |this, window, cx| {
9609 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9610 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9611 s.select(selections)
9612 });
9613 this.refresh_inline_completion(true, false, window, cx);
9614 });
9615 }
9616
9617 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9618 if self.read_only(cx) {
9619 return;
9620 }
9621 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9622 let mut selections = self.selections.all::<Point>(cx);
9623 let mut prev_edited_row = 0;
9624 let mut row_delta = 0;
9625 let mut edits = Vec::new();
9626 let buffer = self.buffer.read(cx);
9627 let snapshot = buffer.snapshot(cx);
9628 for selection in &mut selections {
9629 if selection.start.row != prev_edited_row {
9630 row_delta = 0;
9631 }
9632 prev_edited_row = selection.end.row;
9633
9634 row_delta =
9635 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9636 }
9637
9638 self.transact(window, cx, |this, window, cx| {
9639 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9640 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9641 s.select(selections)
9642 });
9643 });
9644 }
9645
9646 fn indent_selection(
9647 buffer: &MultiBuffer,
9648 snapshot: &MultiBufferSnapshot,
9649 selection: &mut Selection<Point>,
9650 edits: &mut Vec<(Range<Point>, String)>,
9651 delta_for_start_row: u32,
9652 cx: &App,
9653 ) -> u32 {
9654 let settings = buffer.language_settings_at(selection.start, cx);
9655 let tab_size = settings.tab_size.get();
9656 let indent_kind = if settings.hard_tabs {
9657 IndentKind::Tab
9658 } else {
9659 IndentKind::Space
9660 };
9661 let mut start_row = selection.start.row;
9662 let mut end_row = selection.end.row + 1;
9663
9664 // If a selection ends at the beginning of a line, don't indent
9665 // that last line.
9666 if selection.end.column == 0 && selection.end.row > selection.start.row {
9667 end_row -= 1;
9668 }
9669
9670 // Avoid re-indenting a row that has already been indented by a
9671 // previous selection, but still update this selection's column
9672 // to reflect that indentation.
9673 if delta_for_start_row > 0 {
9674 start_row += 1;
9675 selection.start.column += delta_for_start_row;
9676 if selection.end.row == selection.start.row {
9677 selection.end.column += delta_for_start_row;
9678 }
9679 }
9680
9681 let mut delta_for_end_row = 0;
9682 let has_multiple_rows = start_row + 1 != end_row;
9683 for row in start_row..end_row {
9684 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9685 let indent_delta = match (current_indent.kind, indent_kind) {
9686 (IndentKind::Space, IndentKind::Space) => {
9687 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9688 IndentSize::spaces(columns_to_next_tab_stop)
9689 }
9690 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9691 (_, IndentKind::Tab) => IndentSize::tab(),
9692 };
9693
9694 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9695 0
9696 } else {
9697 selection.start.column
9698 };
9699 let row_start = Point::new(row, start);
9700 edits.push((
9701 row_start..row_start,
9702 indent_delta.chars().collect::<String>(),
9703 ));
9704
9705 // Update this selection's endpoints to reflect the indentation.
9706 if row == selection.start.row {
9707 selection.start.column += indent_delta.len;
9708 }
9709 if row == selection.end.row {
9710 selection.end.column += indent_delta.len;
9711 delta_for_end_row = indent_delta.len;
9712 }
9713 }
9714
9715 if selection.start.row == selection.end.row {
9716 delta_for_start_row + delta_for_end_row
9717 } else {
9718 delta_for_end_row
9719 }
9720 }
9721
9722 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9723 if self.read_only(cx) {
9724 return;
9725 }
9726 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9727 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9728 let selections = self.selections.all::<Point>(cx);
9729 let mut deletion_ranges = Vec::new();
9730 let mut last_outdent = None;
9731 {
9732 let buffer = self.buffer.read(cx);
9733 let snapshot = buffer.snapshot(cx);
9734 for selection in &selections {
9735 let settings = buffer.language_settings_at(selection.start, cx);
9736 let tab_size = settings.tab_size.get();
9737 let mut rows = selection.spanned_rows(false, &display_map);
9738
9739 // Avoid re-outdenting a row that has already been outdented by a
9740 // previous selection.
9741 if let Some(last_row) = last_outdent {
9742 if last_row == rows.start {
9743 rows.start = rows.start.next_row();
9744 }
9745 }
9746 let has_multiple_rows = rows.len() > 1;
9747 for row in rows.iter_rows() {
9748 let indent_size = snapshot.indent_size_for_line(row);
9749 if indent_size.len > 0 {
9750 let deletion_len = match indent_size.kind {
9751 IndentKind::Space => {
9752 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9753 if columns_to_prev_tab_stop == 0 {
9754 tab_size
9755 } else {
9756 columns_to_prev_tab_stop
9757 }
9758 }
9759 IndentKind::Tab => 1,
9760 };
9761 let start = if has_multiple_rows
9762 || deletion_len > selection.start.column
9763 || indent_size.len < selection.start.column
9764 {
9765 0
9766 } else {
9767 selection.start.column - deletion_len
9768 };
9769 deletion_ranges.push(
9770 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9771 );
9772 last_outdent = Some(row);
9773 }
9774 }
9775 }
9776 }
9777
9778 self.transact(window, cx, |this, window, cx| {
9779 this.buffer.update(cx, |buffer, cx| {
9780 let empty_str: Arc<str> = Arc::default();
9781 buffer.edit(
9782 deletion_ranges
9783 .into_iter()
9784 .map(|range| (range, empty_str.clone())),
9785 None,
9786 cx,
9787 );
9788 });
9789 let selections = this.selections.all::<usize>(cx);
9790 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9791 s.select(selections)
9792 });
9793 });
9794 }
9795
9796 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9797 if self.read_only(cx) {
9798 return;
9799 }
9800 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9801 let selections = self
9802 .selections
9803 .all::<usize>(cx)
9804 .into_iter()
9805 .map(|s| s.range());
9806
9807 self.transact(window, cx, |this, window, cx| {
9808 this.buffer.update(cx, |buffer, cx| {
9809 buffer.autoindent_ranges(selections, cx);
9810 });
9811 let selections = this.selections.all::<usize>(cx);
9812 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9813 s.select(selections)
9814 });
9815 });
9816 }
9817
9818 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9819 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9821 let selections = self.selections.all::<Point>(cx);
9822
9823 let mut new_cursors = Vec::new();
9824 let mut edit_ranges = Vec::new();
9825 let mut selections = selections.iter().peekable();
9826 while let Some(selection) = selections.next() {
9827 let mut rows = selection.spanned_rows(false, &display_map);
9828 let goal_display_column = selection.head().to_display_point(&display_map).column();
9829
9830 // Accumulate contiguous regions of rows that we want to delete.
9831 while let Some(next_selection) = selections.peek() {
9832 let next_rows = next_selection.spanned_rows(false, &display_map);
9833 if next_rows.start <= rows.end {
9834 rows.end = next_rows.end;
9835 selections.next().unwrap();
9836 } else {
9837 break;
9838 }
9839 }
9840
9841 let buffer = &display_map.buffer_snapshot;
9842 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9843 let edit_end;
9844 let cursor_buffer_row;
9845 if buffer.max_point().row >= rows.end.0 {
9846 // If there's a line after the range, delete the \n from the end of the row range
9847 // and position the cursor on the next line.
9848 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9849 cursor_buffer_row = rows.end;
9850 } else {
9851 // If there isn't a line after the range, delete the \n from the line before the
9852 // start of the row range and position the cursor there.
9853 edit_start = edit_start.saturating_sub(1);
9854 edit_end = buffer.len();
9855 cursor_buffer_row = rows.start.previous_row();
9856 }
9857
9858 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9859 *cursor.column_mut() =
9860 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9861
9862 new_cursors.push((
9863 selection.id,
9864 buffer.anchor_after(cursor.to_point(&display_map)),
9865 ));
9866 edit_ranges.push(edit_start..edit_end);
9867 }
9868
9869 self.transact(window, cx, |this, window, cx| {
9870 let buffer = this.buffer.update(cx, |buffer, cx| {
9871 let empty_str: Arc<str> = Arc::default();
9872 buffer.edit(
9873 edit_ranges
9874 .into_iter()
9875 .map(|range| (range, empty_str.clone())),
9876 None,
9877 cx,
9878 );
9879 buffer.snapshot(cx)
9880 });
9881 let new_selections = new_cursors
9882 .into_iter()
9883 .map(|(id, cursor)| {
9884 let cursor = cursor.to_point(&buffer);
9885 Selection {
9886 id,
9887 start: cursor,
9888 end: cursor,
9889 reversed: false,
9890 goal: SelectionGoal::None,
9891 }
9892 })
9893 .collect();
9894
9895 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9896 s.select(new_selections);
9897 });
9898 });
9899 }
9900
9901 pub fn join_lines_impl(
9902 &mut self,
9903 insert_whitespace: bool,
9904 window: &mut Window,
9905 cx: &mut Context<Self>,
9906 ) {
9907 if self.read_only(cx) {
9908 return;
9909 }
9910 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9911 for selection in self.selections.all::<Point>(cx) {
9912 let start = MultiBufferRow(selection.start.row);
9913 // Treat single line selections as if they include the next line. Otherwise this action
9914 // would do nothing for single line selections individual cursors.
9915 let end = if selection.start.row == selection.end.row {
9916 MultiBufferRow(selection.start.row + 1)
9917 } else {
9918 MultiBufferRow(selection.end.row)
9919 };
9920
9921 if let Some(last_row_range) = row_ranges.last_mut() {
9922 if start <= last_row_range.end {
9923 last_row_range.end = end;
9924 continue;
9925 }
9926 }
9927 row_ranges.push(start..end);
9928 }
9929
9930 let snapshot = self.buffer.read(cx).snapshot(cx);
9931 let mut cursor_positions = Vec::new();
9932 for row_range in &row_ranges {
9933 let anchor = snapshot.anchor_before(Point::new(
9934 row_range.end.previous_row().0,
9935 snapshot.line_len(row_range.end.previous_row()),
9936 ));
9937 cursor_positions.push(anchor..anchor);
9938 }
9939
9940 self.transact(window, cx, |this, window, cx| {
9941 for row_range in row_ranges.into_iter().rev() {
9942 for row in row_range.iter_rows().rev() {
9943 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9944 let next_line_row = row.next_row();
9945 let indent = snapshot.indent_size_for_line(next_line_row);
9946 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9947
9948 let replace =
9949 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9950 " "
9951 } else {
9952 ""
9953 };
9954
9955 this.buffer.update(cx, |buffer, cx| {
9956 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9957 });
9958 }
9959 }
9960
9961 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9962 s.select_anchor_ranges(cursor_positions)
9963 });
9964 });
9965 }
9966
9967 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9968 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9969 self.join_lines_impl(true, window, cx);
9970 }
9971
9972 pub fn sort_lines_case_sensitive(
9973 &mut self,
9974 _: &SortLinesCaseSensitive,
9975 window: &mut Window,
9976 cx: &mut Context<Self>,
9977 ) {
9978 self.manipulate_lines(window, cx, |lines| lines.sort())
9979 }
9980
9981 pub fn sort_lines_case_insensitive(
9982 &mut self,
9983 _: &SortLinesCaseInsensitive,
9984 window: &mut Window,
9985 cx: &mut Context<Self>,
9986 ) {
9987 self.manipulate_lines(window, cx, |lines| {
9988 lines.sort_by_key(|line| line.to_lowercase())
9989 })
9990 }
9991
9992 pub fn unique_lines_case_insensitive(
9993 &mut self,
9994 _: &UniqueLinesCaseInsensitive,
9995 window: &mut Window,
9996 cx: &mut Context<Self>,
9997 ) {
9998 self.manipulate_lines(window, cx, |lines| {
9999 let mut seen = HashSet::default();
10000 lines.retain(|line| seen.insert(line.to_lowercase()));
10001 })
10002 }
10003
10004 pub fn unique_lines_case_sensitive(
10005 &mut self,
10006 _: &UniqueLinesCaseSensitive,
10007 window: &mut Window,
10008 cx: &mut Context<Self>,
10009 ) {
10010 self.manipulate_lines(window, cx, |lines| {
10011 let mut seen = HashSet::default();
10012 lines.retain(|line| seen.insert(*line));
10013 })
10014 }
10015
10016 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10017 let Some(project) = self.project.clone() else {
10018 return;
10019 };
10020 self.reload(project, window, cx)
10021 .detach_and_notify_err(window, cx);
10022 }
10023
10024 pub fn restore_file(
10025 &mut self,
10026 _: &::git::RestoreFile,
10027 window: &mut Window,
10028 cx: &mut Context<Self>,
10029 ) {
10030 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10031 let mut buffer_ids = HashSet::default();
10032 let snapshot = self.buffer().read(cx).snapshot(cx);
10033 for selection in self.selections.all::<usize>(cx) {
10034 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10035 }
10036
10037 let buffer = self.buffer().read(cx);
10038 let ranges = buffer_ids
10039 .into_iter()
10040 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10041 .collect::<Vec<_>>();
10042
10043 self.restore_hunks_in_ranges(ranges, window, cx);
10044 }
10045
10046 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10047 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10048 let selections = self
10049 .selections
10050 .all(cx)
10051 .into_iter()
10052 .map(|s| s.range())
10053 .collect();
10054 self.restore_hunks_in_ranges(selections, window, cx);
10055 }
10056
10057 pub fn restore_hunks_in_ranges(
10058 &mut self,
10059 ranges: Vec<Range<Point>>,
10060 window: &mut Window,
10061 cx: &mut Context<Editor>,
10062 ) {
10063 let mut revert_changes = HashMap::default();
10064 let chunk_by = self
10065 .snapshot(window, cx)
10066 .hunks_for_ranges(ranges)
10067 .into_iter()
10068 .chunk_by(|hunk| hunk.buffer_id);
10069 for (buffer_id, hunks) in &chunk_by {
10070 let hunks = hunks.collect::<Vec<_>>();
10071 for hunk in &hunks {
10072 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10073 }
10074 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10075 }
10076 drop(chunk_by);
10077 if !revert_changes.is_empty() {
10078 self.transact(window, cx, |editor, window, cx| {
10079 editor.restore(revert_changes, window, cx);
10080 });
10081 }
10082 }
10083
10084 pub fn open_active_item_in_terminal(
10085 &mut self,
10086 _: &OpenInTerminal,
10087 window: &mut Window,
10088 cx: &mut Context<Self>,
10089 ) {
10090 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10091 let project_path = buffer.read(cx).project_path(cx)?;
10092 let project = self.project.as_ref()?.read(cx);
10093 let entry = project.entry_for_path(&project_path, cx)?;
10094 let parent = match &entry.canonical_path {
10095 Some(canonical_path) => canonical_path.to_path_buf(),
10096 None => project.absolute_path(&project_path, cx)?,
10097 }
10098 .parent()?
10099 .to_path_buf();
10100 Some(parent)
10101 }) {
10102 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10103 }
10104 }
10105
10106 fn set_breakpoint_context_menu(
10107 &mut self,
10108 display_row: DisplayRow,
10109 position: Option<Anchor>,
10110 clicked_point: gpui::Point<Pixels>,
10111 window: &mut Window,
10112 cx: &mut Context<Self>,
10113 ) {
10114 if !cx.has_flag::<DebuggerFeatureFlag>() {
10115 return;
10116 }
10117 let source = self
10118 .buffer
10119 .read(cx)
10120 .snapshot(cx)
10121 .anchor_before(Point::new(display_row.0, 0u32));
10122
10123 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10124
10125 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10126 self,
10127 source,
10128 clicked_point,
10129 context_menu,
10130 window,
10131 cx,
10132 );
10133 }
10134
10135 fn add_edit_breakpoint_block(
10136 &mut self,
10137 anchor: Anchor,
10138 breakpoint: &Breakpoint,
10139 edit_action: BreakpointPromptEditAction,
10140 window: &mut Window,
10141 cx: &mut Context<Self>,
10142 ) {
10143 let weak_editor = cx.weak_entity();
10144 let bp_prompt = cx.new(|cx| {
10145 BreakpointPromptEditor::new(
10146 weak_editor,
10147 anchor,
10148 breakpoint.clone(),
10149 edit_action,
10150 window,
10151 cx,
10152 )
10153 });
10154
10155 let height = bp_prompt.update(cx, |this, cx| {
10156 this.prompt
10157 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10158 });
10159 let cloned_prompt = bp_prompt.clone();
10160 let blocks = vec![BlockProperties {
10161 style: BlockStyle::Sticky,
10162 placement: BlockPlacement::Above(anchor),
10163 height: Some(height),
10164 render: Arc::new(move |cx| {
10165 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10166 cloned_prompt.clone().into_any_element()
10167 }),
10168 priority: 0,
10169 render_in_minimap: true,
10170 }];
10171
10172 let focus_handle = bp_prompt.focus_handle(cx);
10173 window.focus(&focus_handle);
10174
10175 let block_ids = self.insert_blocks(blocks, None, cx);
10176 bp_prompt.update(cx, |prompt, _| {
10177 prompt.add_block_ids(block_ids);
10178 });
10179 }
10180
10181 pub(crate) fn breakpoint_at_row(
10182 &self,
10183 row: u32,
10184 window: &mut Window,
10185 cx: &mut Context<Self>,
10186 ) -> Option<(Anchor, Breakpoint)> {
10187 let snapshot = self.snapshot(window, cx);
10188 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10189
10190 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10191 }
10192
10193 pub(crate) fn breakpoint_at_anchor(
10194 &self,
10195 breakpoint_position: Anchor,
10196 snapshot: &EditorSnapshot,
10197 cx: &mut Context<Self>,
10198 ) -> Option<(Anchor, Breakpoint)> {
10199 let project = self.project.clone()?;
10200
10201 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10202 snapshot
10203 .buffer_snapshot
10204 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10205 })?;
10206
10207 let enclosing_excerpt = breakpoint_position.excerpt_id;
10208 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10209 let buffer_snapshot = buffer.read(cx).snapshot();
10210
10211 let row = buffer_snapshot
10212 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10213 .row;
10214
10215 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10216 let anchor_end = snapshot
10217 .buffer_snapshot
10218 .anchor_after(Point::new(row, line_len));
10219
10220 let bp = self
10221 .breakpoint_store
10222 .as_ref()?
10223 .read_with(cx, |breakpoint_store, cx| {
10224 breakpoint_store
10225 .breakpoints(
10226 &buffer,
10227 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10228 &buffer_snapshot,
10229 cx,
10230 )
10231 .next()
10232 .and_then(|(bp, _)| {
10233 let breakpoint_row = buffer_snapshot
10234 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10235 .row;
10236
10237 if breakpoint_row == row {
10238 snapshot
10239 .buffer_snapshot
10240 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10241 .map(|position| (position, bp.bp.clone()))
10242 } else {
10243 None
10244 }
10245 })
10246 });
10247 bp
10248 }
10249
10250 pub fn edit_log_breakpoint(
10251 &mut self,
10252 _: &EditLogBreakpoint,
10253 window: &mut Window,
10254 cx: &mut Context<Self>,
10255 ) {
10256 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10257 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10258 message: None,
10259 state: BreakpointState::Enabled,
10260 condition: None,
10261 hit_condition: None,
10262 });
10263
10264 self.add_edit_breakpoint_block(
10265 anchor,
10266 &breakpoint,
10267 BreakpointPromptEditAction::Log,
10268 window,
10269 cx,
10270 );
10271 }
10272 }
10273
10274 fn breakpoints_at_cursors(
10275 &self,
10276 window: &mut Window,
10277 cx: &mut Context<Self>,
10278 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10279 let snapshot = self.snapshot(window, cx);
10280 let cursors = self
10281 .selections
10282 .disjoint_anchors()
10283 .into_iter()
10284 .map(|selection| {
10285 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10286
10287 let breakpoint_position = self
10288 .breakpoint_at_row(cursor_position.row, window, cx)
10289 .map(|bp| bp.0)
10290 .unwrap_or_else(|| {
10291 snapshot
10292 .display_snapshot
10293 .buffer_snapshot
10294 .anchor_after(Point::new(cursor_position.row, 0))
10295 });
10296
10297 let breakpoint = self
10298 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10299 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10300
10301 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10302 })
10303 // 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.
10304 .collect::<HashMap<Anchor, _>>();
10305
10306 cursors.into_iter().collect()
10307 }
10308
10309 pub fn enable_breakpoint(
10310 &mut self,
10311 _: &crate::actions::EnableBreakpoint,
10312 window: &mut Window,
10313 cx: &mut Context<Self>,
10314 ) {
10315 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10316 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10317 continue;
10318 };
10319 self.edit_breakpoint_at_anchor(
10320 anchor,
10321 breakpoint,
10322 BreakpointEditAction::InvertState,
10323 cx,
10324 );
10325 }
10326 }
10327
10328 pub fn disable_breakpoint(
10329 &mut self,
10330 _: &crate::actions::DisableBreakpoint,
10331 window: &mut Window,
10332 cx: &mut Context<Self>,
10333 ) {
10334 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10335 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10336 continue;
10337 };
10338 self.edit_breakpoint_at_anchor(
10339 anchor,
10340 breakpoint,
10341 BreakpointEditAction::InvertState,
10342 cx,
10343 );
10344 }
10345 }
10346
10347 pub fn toggle_breakpoint(
10348 &mut self,
10349 _: &crate::actions::ToggleBreakpoint,
10350 window: &mut Window,
10351 cx: &mut Context<Self>,
10352 ) {
10353 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10354 if let Some(breakpoint) = breakpoint {
10355 self.edit_breakpoint_at_anchor(
10356 anchor,
10357 breakpoint,
10358 BreakpointEditAction::Toggle,
10359 cx,
10360 );
10361 } else {
10362 self.edit_breakpoint_at_anchor(
10363 anchor,
10364 Breakpoint::new_standard(),
10365 BreakpointEditAction::Toggle,
10366 cx,
10367 );
10368 }
10369 }
10370 }
10371
10372 pub fn edit_breakpoint_at_anchor(
10373 &mut self,
10374 breakpoint_position: Anchor,
10375 breakpoint: Breakpoint,
10376 edit_action: BreakpointEditAction,
10377 cx: &mut Context<Self>,
10378 ) {
10379 let Some(breakpoint_store) = &self.breakpoint_store else {
10380 return;
10381 };
10382
10383 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10384 if breakpoint_position == Anchor::min() {
10385 self.buffer()
10386 .read(cx)
10387 .excerpt_buffer_ids()
10388 .into_iter()
10389 .next()
10390 } else {
10391 None
10392 }
10393 }) else {
10394 return;
10395 };
10396
10397 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10398 return;
10399 };
10400
10401 breakpoint_store.update(cx, |breakpoint_store, cx| {
10402 breakpoint_store.toggle_breakpoint(
10403 buffer,
10404 BreakpointWithPosition {
10405 position: breakpoint_position.text_anchor,
10406 bp: breakpoint,
10407 },
10408 edit_action,
10409 cx,
10410 );
10411 });
10412
10413 cx.notify();
10414 }
10415
10416 #[cfg(any(test, feature = "test-support"))]
10417 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10418 self.breakpoint_store.clone()
10419 }
10420
10421 pub fn prepare_restore_change(
10422 &self,
10423 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10424 hunk: &MultiBufferDiffHunk,
10425 cx: &mut App,
10426 ) -> Option<()> {
10427 if hunk.is_created_file() {
10428 return None;
10429 }
10430 let buffer = self.buffer.read(cx);
10431 let diff = buffer.diff_for(hunk.buffer_id)?;
10432 let buffer = buffer.buffer(hunk.buffer_id)?;
10433 let buffer = buffer.read(cx);
10434 let original_text = diff
10435 .read(cx)
10436 .base_text()
10437 .as_rope()
10438 .slice(hunk.diff_base_byte_range.clone());
10439 let buffer_snapshot = buffer.snapshot();
10440 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10441 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10442 probe
10443 .0
10444 .start
10445 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10446 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10447 }) {
10448 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10449 Some(())
10450 } else {
10451 None
10452 }
10453 }
10454
10455 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10456 self.manipulate_lines(window, cx, |lines| lines.reverse())
10457 }
10458
10459 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10460 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10461 }
10462
10463 fn manipulate_lines<Fn>(
10464 &mut self,
10465 window: &mut Window,
10466 cx: &mut Context<Self>,
10467 mut callback: Fn,
10468 ) where
10469 Fn: FnMut(&mut Vec<&str>),
10470 {
10471 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10472
10473 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10474 let buffer = self.buffer.read(cx).snapshot(cx);
10475
10476 let mut edits = Vec::new();
10477
10478 let selections = self.selections.all::<Point>(cx);
10479 let mut selections = selections.iter().peekable();
10480 let mut contiguous_row_selections = Vec::new();
10481 let mut new_selections = Vec::new();
10482 let mut added_lines = 0;
10483 let mut removed_lines = 0;
10484
10485 while let Some(selection) = selections.next() {
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 let start_point = Point::new(start_row.0, 0);
10494 let end_point = Point::new(
10495 end_row.previous_row().0,
10496 buffer.line_len(end_row.previous_row()),
10497 );
10498 let text = buffer
10499 .text_for_range(start_point..end_point)
10500 .collect::<String>();
10501
10502 let mut lines = text.split('\n').collect_vec();
10503
10504 let lines_before = lines.len();
10505 callback(&mut lines);
10506 let lines_after = lines.len();
10507
10508 edits.push((start_point..end_point, lines.join("\n")));
10509
10510 // Selections must change based on added and removed line count
10511 let start_row =
10512 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10513 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10514 new_selections.push(Selection {
10515 id: selection.id,
10516 start: start_row,
10517 end: end_row,
10518 goal: SelectionGoal::None,
10519 reversed: selection.reversed,
10520 });
10521
10522 if lines_after > lines_before {
10523 added_lines += lines_after - lines_before;
10524 } else if lines_before > lines_after {
10525 removed_lines += lines_before - lines_after;
10526 }
10527 }
10528
10529 self.transact(window, cx, |this, window, cx| {
10530 let buffer = this.buffer.update(cx, |buffer, cx| {
10531 buffer.edit(edits, None, cx);
10532 buffer.snapshot(cx)
10533 });
10534
10535 // Recalculate offsets on newly edited buffer
10536 let new_selections = new_selections
10537 .iter()
10538 .map(|s| {
10539 let start_point = Point::new(s.start.0, 0);
10540 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10541 Selection {
10542 id: s.id,
10543 start: buffer.point_to_offset(start_point),
10544 end: buffer.point_to_offset(end_point),
10545 goal: s.goal,
10546 reversed: s.reversed,
10547 }
10548 })
10549 .collect();
10550
10551 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10552 s.select(new_selections);
10553 });
10554
10555 this.request_autoscroll(Autoscroll::fit(), cx);
10556 });
10557 }
10558
10559 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10560 self.manipulate_text(window, cx, |text| {
10561 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10562 if has_upper_case_characters {
10563 text.to_lowercase()
10564 } else {
10565 text.to_uppercase()
10566 }
10567 })
10568 }
10569
10570 pub fn convert_to_upper_case(
10571 &mut self,
10572 _: &ConvertToUpperCase,
10573 window: &mut Window,
10574 cx: &mut Context<Self>,
10575 ) {
10576 self.manipulate_text(window, cx, |text| text.to_uppercase())
10577 }
10578
10579 pub fn convert_to_lower_case(
10580 &mut self,
10581 _: &ConvertToLowerCase,
10582 window: &mut Window,
10583 cx: &mut Context<Self>,
10584 ) {
10585 self.manipulate_text(window, cx, |text| text.to_lowercase())
10586 }
10587
10588 pub fn convert_to_title_case(
10589 &mut self,
10590 _: &ConvertToTitleCase,
10591 window: &mut Window,
10592 cx: &mut Context<Self>,
10593 ) {
10594 self.manipulate_text(window, cx, |text| {
10595 text.split('\n')
10596 .map(|line| line.to_case(Case::Title))
10597 .join("\n")
10598 })
10599 }
10600
10601 pub fn convert_to_snake_case(
10602 &mut self,
10603 _: &ConvertToSnakeCase,
10604 window: &mut Window,
10605 cx: &mut Context<Self>,
10606 ) {
10607 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10608 }
10609
10610 pub fn convert_to_kebab_case(
10611 &mut self,
10612 _: &ConvertToKebabCase,
10613 window: &mut Window,
10614 cx: &mut Context<Self>,
10615 ) {
10616 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10617 }
10618
10619 pub fn convert_to_upper_camel_case(
10620 &mut self,
10621 _: &ConvertToUpperCamelCase,
10622 window: &mut Window,
10623 cx: &mut Context<Self>,
10624 ) {
10625 self.manipulate_text(window, cx, |text| {
10626 text.split('\n')
10627 .map(|line| line.to_case(Case::UpperCamel))
10628 .join("\n")
10629 })
10630 }
10631
10632 pub fn convert_to_lower_camel_case(
10633 &mut self,
10634 _: &ConvertToLowerCamelCase,
10635 window: &mut Window,
10636 cx: &mut Context<Self>,
10637 ) {
10638 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10639 }
10640
10641 pub fn convert_to_opposite_case(
10642 &mut self,
10643 _: &ConvertToOppositeCase,
10644 window: &mut Window,
10645 cx: &mut Context<Self>,
10646 ) {
10647 self.manipulate_text(window, cx, |text| {
10648 text.chars()
10649 .fold(String::with_capacity(text.len()), |mut t, c| {
10650 if c.is_uppercase() {
10651 t.extend(c.to_lowercase());
10652 } else {
10653 t.extend(c.to_uppercase());
10654 }
10655 t
10656 })
10657 })
10658 }
10659
10660 pub fn convert_to_rot13(
10661 &mut self,
10662 _: &ConvertToRot13,
10663 window: &mut Window,
10664 cx: &mut Context<Self>,
10665 ) {
10666 self.manipulate_text(window, cx, |text| {
10667 text.chars()
10668 .map(|c| match c {
10669 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10670 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10671 _ => c,
10672 })
10673 .collect()
10674 })
10675 }
10676
10677 pub fn convert_to_rot47(
10678 &mut self,
10679 _: &ConvertToRot47,
10680 window: &mut Window,
10681 cx: &mut Context<Self>,
10682 ) {
10683 self.manipulate_text(window, cx, |text| {
10684 text.chars()
10685 .map(|c| {
10686 let code_point = c as u32;
10687 if code_point >= 33 && code_point <= 126 {
10688 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10689 }
10690 c
10691 })
10692 .collect()
10693 })
10694 }
10695
10696 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10697 where
10698 Fn: FnMut(&str) -> String,
10699 {
10700 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10701 let buffer = self.buffer.read(cx).snapshot(cx);
10702
10703 let mut new_selections = Vec::new();
10704 let mut edits = Vec::new();
10705 let mut selection_adjustment = 0i32;
10706
10707 for selection in self.selections.all::<usize>(cx) {
10708 let selection_is_empty = selection.is_empty();
10709
10710 let (start, end) = if selection_is_empty {
10711 let word_range = movement::surrounding_word(
10712 &display_map,
10713 selection.start.to_display_point(&display_map),
10714 );
10715 let start = word_range.start.to_offset(&display_map, Bias::Left);
10716 let end = word_range.end.to_offset(&display_map, Bias::Left);
10717 (start, end)
10718 } else {
10719 (selection.start, selection.end)
10720 };
10721
10722 let text = buffer.text_for_range(start..end).collect::<String>();
10723 let old_length = text.len() as i32;
10724 let text = callback(&text);
10725
10726 new_selections.push(Selection {
10727 start: (start as i32 - selection_adjustment) as usize,
10728 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10729 goal: SelectionGoal::None,
10730 ..selection
10731 });
10732
10733 selection_adjustment += old_length - text.len() as i32;
10734
10735 edits.push((start..end, text));
10736 }
10737
10738 self.transact(window, cx, |this, window, cx| {
10739 this.buffer.update(cx, |buffer, cx| {
10740 buffer.edit(edits, None, cx);
10741 });
10742
10743 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10744 s.select(new_selections);
10745 });
10746
10747 this.request_autoscroll(Autoscroll::fit(), cx);
10748 });
10749 }
10750
10751 pub fn move_selection_on_drop(
10752 &mut self,
10753 selection: &Selection<Anchor>,
10754 target: DisplayPoint,
10755 is_cut: bool,
10756 window: &mut Window,
10757 cx: &mut Context<Self>,
10758 ) {
10759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10760 let buffer = &display_map.buffer_snapshot;
10761 let mut edits = Vec::new();
10762 let insert_point = display_map
10763 .clip_point(target, Bias::Left)
10764 .to_point(&display_map);
10765 let text = buffer
10766 .text_for_range(selection.start..selection.end)
10767 .collect::<String>();
10768 if is_cut {
10769 edits.push(((selection.start..selection.end), String::new()));
10770 }
10771 let insert_anchor = buffer.anchor_before(insert_point);
10772 edits.push(((insert_anchor..insert_anchor), text));
10773 let last_edit_start = insert_anchor.bias_left(buffer);
10774 let last_edit_end = insert_anchor.bias_right(buffer);
10775 self.transact(window, cx, |this, window, cx| {
10776 this.buffer.update(cx, |buffer, cx| {
10777 buffer.edit(edits, None, cx);
10778 });
10779 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10780 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10781 });
10782 });
10783 }
10784
10785 pub fn clear_selection_drag_state(&mut self) {
10786 self.selection_drag_state = SelectionDragState::None;
10787 }
10788
10789 pub fn duplicate(
10790 &mut self,
10791 upwards: bool,
10792 whole_lines: bool,
10793 window: &mut Window,
10794 cx: &mut Context<Self>,
10795 ) {
10796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10797
10798 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10799 let buffer = &display_map.buffer_snapshot;
10800 let selections = self.selections.all::<Point>(cx);
10801
10802 let mut edits = Vec::new();
10803 let mut selections_iter = selections.iter().peekable();
10804 while let Some(selection) = selections_iter.next() {
10805 let mut rows = selection.spanned_rows(false, &display_map);
10806 // duplicate line-wise
10807 if whole_lines || selection.start == selection.end {
10808 // Avoid duplicating the same lines twice.
10809 while let Some(next_selection) = selections_iter.peek() {
10810 let next_rows = next_selection.spanned_rows(false, &display_map);
10811 if next_rows.start < rows.end {
10812 rows.end = next_rows.end;
10813 selections_iter.next().unwrap();
10814 } else {
10815 break;
10816 }
10817 }
10818
10819 // Copy the text from the selected row region and splice it either at the start
10820 // or end of the region.
10821 let start = Point::new(rows.start.0, 0);
10822 let end = Point::new(
10823 rows.end.previous_row().0,
10824 buffer.line_len(rows.end.previous_row()),
10825 );
10826 let text = buffer
10827 .text_for_range(start..end)
10828 .chain(Some("\n"))
10829 .collect::<String>();
10830 let insert_location = if upwards {
10831 Point::new(rows.end.0, 0)
10832 } else {
10833 start
10834 };
10835 edits.push((insert_location..insert_location, text));
10836 } else {
10837 // duplicate character-wise
10838 let start = selection.start;
10839 let end = selection.end;
10840 let text = buffer.text_for_range(start..end).collect::<String>();
10841 edits.push((selection.end..selection.end, text));
10842 }
10843 }
10844
10845 self.transact(window, cx, |this, _, cx| {
10846 this.buffer.update(cx, |buffer, cx| {
10847 buffer.edit(edits, None, cx);
10848 });
10849
10850 this.request_autoscroll(Autoscroll::fit(), cx);
10851 });
10852 }
10853
10854 pub fn duplicate_line_up(
10855 &mut self,
10856 _: &DuplicateLineUp,
10857 window: &mut Window,
10858 cx: &mut Context<Self>,
10859 ) {
10860 self.duplicate(true, true, window, cx);
10861 }
10862
10863 pub fn duplicate_line_down(
10864 &mut self,
10865 _: &DuplicateLineDown,
10866 window: &mut Window,
10867 cx: &mut Context<Self>,
10868 ) {
10869 self.duplicate(false, true, window, cx);
10870 }
10871
10872 pub fn duplicate_selection(
10873 &mut self,
10874 _: &DuplicateSelection,
10875 window: &mut Window,
10876 cx: &mut Context<Self>,
10877 ) {
10878 self.duplicate(false, false, window, cx);
10879 }
10880
10881 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10883
10884 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10885 let buffer = self.buffer.read(cx).snapshot(cx);
10886
10887 let mut edits = Vec::new();
10888 let mut unfold_ranges = Vec::new();
10889 let mut refold_creases = Vec::new();
10890
10891 let selections = self.selections.all::<Point>(cx);
10892 let mut selections = selections.iter().peekable();
10893 let mut contiguous_row_selections = Vec::new();
10894 let mut new_selections = Vec::new();
10895
10896 while let Some(selection) = selections.next() {
10897 // Find all the selections that span a contiguous row range
10898 let (start_row, end_row) = consume_contiguous_rows(
10899 &mut contiguous_row_selections,
10900 selection,
10901 &display_map,
10902 &mut selections,
10903 );
10904
10905 // Move the text spanned by the row range to be before the line preceding the row range
10906 if start_row.0 > 0 {
10907 let range_to_move = Point::new(
10908 start_row.previous_row().0,
10909 buffer.line_len(start_row.previous_row()),
10910 )
10911 ..Point::new(
10912 end_row.previous_row().0,
10913 buffer.line_len(end_row.previous_row()),
10914 );
10915 let insertion_point = display_map
10916 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10917 .0;
10918
10919 // Don't move lines across excerpts
10920 if buffer
10921 .excerpt_containing(insertion_point..range_to_move.end)
10922 .is_some()
10923 {
10924 let text = buffer
10925 .text_for_range(range_to_move.clone())
10926 .flat_map(|s| s.chars())
10927 .skip(1)
10928 .chain(['\n'])
10929 .collect::<String>();
10930
10931 edits.push((
10932 buffer.anchor_after(range_to_move.start)
10933 ..buffer.anchor_before(range_to_move.end),
10934 String::new(),
10935 ));
10936 let insertion_anchor = buffer.anchor_after(insertion_point);
10937 edits.push((insertion_anchor..insertion_anchor, text));
10938
10939 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10940
10941 // Move selections up
10942 new_selections.extend(contiguous_row_selections.drain(..).map(
10943 |mut selection| {
10944 selection.start.row -= row_delta;
10945 selection.end.row -= row_delta;
10946 selection
10947 },
10948 ));
10949
10950 // Move folds up
10951 unfold_ranges.push(range_to_move.clone());
10952 for fold in display_map.folds_in_range(
10953 buffer.anchor_before(range_to_move.start)
10954 ..buffer.anchor_after(range_to_move.end),
10955 ) {
10956 let mut start = fold.range.start.to_point(&buffer);
10957 let mut end = fold.range.end.to_point(&buffer);
10958 start.row -= row_delta;
10959 end.row -= row_delta;
10960 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10961 }
10962 }
10963 }
10964
10965 // If we didn't move line(s), preserve the existing selections
10966 new_selections.append(&mut contiguous_row_selections);
10967 }
10968
10969 self.transact(window, cx, |this, window, cx| {
10970 this.unfold_ranges(&unfold_ranges, true, true, cx);
10971 this.buffer.update(cx, |buffer, cx| {
10972 for (range, text) in edits {
10973 buffer.edit([(range, text)], None, cx);
10974 }
10975 });
10976 this.fold_creases(refold_creases, true, window, cx);
10977 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10978 s.select(new_selections);
10979 })
10980 });
10981 }
10982
10983 pub fn move_line_down(
10984 &mut self,
10985 _: &MoveLineDown,
10986 window: &mut Window,
10987 cx: &mut Context<Self>,
10988 ) {
10989 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10990
10991 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10992 let buffer = self.buffer.read(cx).snapshot(cx);
10993
10994 let mut edits = Vec::new();
10995 let mut unfold_ranges = Vec::new();
10996 let mut refold_creases = Vec::new();
10997
10998 let selections = self.selections.all::<Point>(cx);
10999 let mut selections = selections.iter().peekable();
11000 let mut contiguous_row_selections = Vec::new();
11001 let mut new_selections = Vec::new();
11002
11003 while let Some(selection) = selections.next() {
11004 // Find all the selections that span a contiguous row range
11005 let (start_row, end_row) = consume_contiguous_rows(
11006 &mut contiguous_row_selections,
11007 selection,
11008 &display_map,
11009 &mut selections,
11010 );
11011
11012 // Move the text spanned by the row range to be after the last line of the row range
11013 if end_row.0 <= buffer.max_point().row {
11014 let range_to_move =
11015 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11016 let insertion_point = display_map
11017 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11018 .0;
11019
11020 // Don't move lines across excerpt boundaries
11021 if buffer
11022 .excerpt_containing(range_to_move.start..insertion_point)
11023 .is_some()
11024 {
11025 let mut text = String::from("\n");
11026 text.extend(buffer.text_for_range(range_to_move.clone()));
11027 text.pop(); // Drop trailing newline
11028 edits.push((
11029 buffer.anchor_after(range_to_move.start)
11030 ..buffer.anchor_before(range_to_move.end),
11031 String::new(),
11032 ));
11033 let insertion_anchor = buffer.anchor_after(insertion_point);
11034 edits.push((insertion_anchor..insertion_anchor, text));
11035
11036 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11037
11038 // Move selections down
11039 new_selections.extend(contiguous_row_selections.drain(..).map(
11040 |mut selection| {
11041 selection.start.row += row_delta;
11042 selection.end.row += row_delta;
11043 selection
11044 },
11045 ));
11046
11047 // Move folds down
11048 unfold_ranges.push(range_to_move.clone());
11049 for fold in display_map.folds_in_range(
11050 buffer.anchor_before(range_to_move.start)
11051 ..buffer.anchor_after(range_to_move.end),
11052 ) {
11053 let mut start = fold.range.start.to_point(&buffer);
11054 let mut end = fold.range.end.to_point(&buffer);
11055 start.row += row_delta;
11056 end.row += row_delta;
11057 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11058 }
11059 }
11060 }
11061
11062 // If we didn't move line(s), preserve the existing selections
11063 new_selections.append(&mut contiguous_row_selections);
11064 }
11065
11066 self.transact(window, cx, |this, window, cx| {
11067 this.unfold_ranges(&unfold_ranges, true, true, cx);
11068 this.buffer.update(cx, |buffer, cx| {
11069 for (range, text) in edits {
11070 buffer.edit([(range, text)], None, cx);
11071 }
11072 });
11073 this.fold_creases(refold_creases, true, window, cx);
11074 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11075 s.select(new_selections)
11076 });
11077 });
11078 }
11079
11080 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11082 let text_layout_details = &self.text_layout_details(window);
11083 self.transact(window, cx, |this, window, cx| {
11084 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11085 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11086 s.move_with(|display_map, selection| {
11087 if !selection.is_empty() {
11088 return;
11089 }
11090
11091 let mut head = selection.head();
11092 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11093 if head.column() == display_map.line_len(head.row()) {
11094 transpose_offset = display_map
11095 .buffer_snapshot
11096 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11097 }
11098
11099 if transpose_offset == 0 {
11100 return;
11101 }
11102
11103 *head.column_mut() += 1;
11104 head = display_map.clip_point(head, Bias::Right);
11105 let goal = SelectionGoal::HorizontalPosition(
11106 display_map
11107 .x_for_display_point(head, text_layout_details)
11108 .into(),
11109 );
11110 selection.collapse_to(head, goal);
11111
11112 let transpose_start = display_map
11113 .buffer_snapshot
11114 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11115 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11116 let transpose_end = display_map
11117 .buffer_snapshot
11118 .clip_offset(transpose_offset + 1, Bias::Right);
11119 if let Some(ch) =
11120 display_map.buffer_snapshot.chars_at(transpose_start).next()
11121 {
11122 edits.push((transpose_start..transpose_offset, String::new()));
11123 edits.push((transpose_end..transpose_end, ch.to_string()));
11124 }
11125 }
11126 });
11127 edits
11128 });
11129 this.buffer
11130 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11131 let selections = this.selections.all::<usize>(cx);
11132 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11133 s.select(selections);
11134 });
11135 });
11136 }
11137
11138 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11139 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11140 self.rewrap_impl(RewrapOptions::default(), cx)
11141 }
11142
11143 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11144 let buffer = self.buffer.read(cx).snapshot(cx);
11145 let selections = self.selections.all::<Point>(cx);
11146
11147 // Shrink and split selections to respect paragraph boundaries.
11148 let ranges = selections.into_iter().flat_map(|selection| {
11149 let language_settings = buffer.language_settings_at(selection.head(), cx);
11150 let language_scope = buffer.language_scope_at(selection.head());
11151
11152 let Some(start_row) = (selection.start.row..=selection.end.row)
11153 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11154 else {
11155 return vec![];
11156 };
11157 let Some(end_row) = (selection.start.row..=selection.end.row)
11158 .rev()
11159 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11160 else {
11161 return vec![];
11162 };
11163
11164 let mut row = start_row;
11165 let mut ranges = Vec::new();
11166 while let Some(blank_row) =
11167 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11168 {
11169 let next_paragraph_start = (blank_row + 1..=end_row)
11170 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11171 .unwrap();
11172 ranges.push((
11173 language_settings.clone(),
11174 language_scope.clone(),
11175 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11176 ));
11177 row = next_paragraph_start;
11178 }
11179 ranges.push((
11180 language_settings.clone(),
11181 language_scope.clone(),
11182 Point::new(row, 0)..Point::new(end_row, 0),
11183 ));
11184
11185 ranges
11186 });
11187
11188 let mut edits = Vec::new();
11189 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11190
11191 for (language_settings, language_scope, range) in ranges {
11192 let mut start_row = range.start.row;
11193 let mut end_row = range.end.row;
11194
11195 // Skip selections that overlap with a range that has already been rewrapped.
11196 let selection_range = start_row..end_row;
11197 if rewrapped_row_ranges
11198 .iter()
11199 .any(|range| range.overlaps(&selection_range))
11200 {
11201 continue;
11202 }
11203
11204 let tab_size = language_settings.tab_size;
11205
11206 // Since not all lines in the selection may be at the same indent
11207 // level, choose the indent size that is the most common between all
11208 // of the lines.
11209 //
11210 // If there is a tie, we use the deepest indent.
11211 let (indent_size, indent_end) = {
11212 let mut indent_size_occurrences = HashMap::default();
11213 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11214
11215 for row in start_row..=end_row {
11216 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11217 rows_by_indent_size.entry(indent).or_default().push(row);
11218 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11219 }
11220
11221 let indent_size = indent_size_occurrences
11222 .into_iter()
11223 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11224 .map(|(indent, _)| indent)
11225 .unwrap_or_default();
11226 let row = rows_by_indent_size[&indent_size][0];
11227 let indent_end = Point::new(row, indent_size.len);
11228
11229 (indent_size, indent_end)
11230 };
11231
11232 let mut line_prefix = indent_size.chars().collect::<String>();
11233
11234 let mut inside_comment = false;
11235 if let Some(comment_prefix) = language_scope.and_then(|language| {
11236 language
11237 .line_comment_prefixes()
11238 .iter()
11239 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11240 .cloned()
11241 }) {
11242 line_prefix.push_str(&comment_prefix);
11243 inside_comment = true;
11244 }
11245
11246 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11247 RewrapBehavior::InComments => inside_comment,
11248 RewrapBehavior::InSelections => !range.is_empty(),
11249 RewrapBehavior::Anywhere => true,
11250 };
11251
11252 let should_rewrap = options.override_language_settings
11253 || allow_rewrap_based_on_language
11254 || self.hard_wrap.is_some();
11255 if !should_rewrap {
11256 continue;
11257 }
11258
11259 if range.is_empty() {
11260 'expand_upwards: while start_row > 0 {
11261 let prev_row = start_row - 1;
11262 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11263 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11264 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11265 {
11266 start_row = prev_row;
11267 } else {
11268 break 'expand_upwards;
11269 }
11270 }
11271
11272 'expand_downwards: while end_row < buffer.max_point().row {
11273 let next_row = end_row + 1;
11274 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11275 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11276 && !buffer.is_line_blank(MultiBufferRow(next_row))
11277 {
11278 end_row = next_row;
11279 } else {
11280 break 'expand_downwards;
11281 }
11282 }
11283 }
11284
11285 let start = Point::new(start_row, 0);
11286 let start_offset = start.to_offset(&buffer);
11287 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11288 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11289 let Some(lines_without_prefixes) = selection_text
11290 .lines()
11291 .map(|line| {
11292 line.strip_prefix(&line_prefix)
11293 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11294 .with_context(|| {
11295 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11296 })
11297 })
11298 .collect::<Result<Vec<_>, _>>()
11299 .log_err()
11300 else {
11301 continue;
11302 };
11303
11304 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11305 buffer
11306 .language_settings_at(Point::new(start_row, 0), cx)
11307 .preferred_line_length as usize
11308 });
11309 let wrapped_text = wrap_with_prefix(
11310 line_prefix,
11311 lines_without_prefixes.join("\n"),
11312 wrap_column,
11313 tab_size,
11314 options.preserve_existing_whitespace,
11315 );
11316
11317 // TODO: should always use char-based diff while still supporting cursor behavior that
11318 // matches vim.
11319 let mut diff_options = DiffOptions::default();
11320 if options.override_language_settings {
11321 diff_options.max_word_diff_len = 0;
11322 diff_options.max_word_diff_line_count = 0;
11323 } else {
11324 diff_options.max_word_diff_len = usize::MAX;
11325 diff_options.max_word_diff_line_count = usize::MAX;
11326 }
11327
11328 for (old_range, new_text) in
11329 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11330 {
11331 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11332 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11333 edits.push((edit_start..edit_end, new_text));
11334 }
11335
11336 rewrapped_row_ranges.push(start_row..=end_row);
11337 }
11338
11339 self.buffer
11340 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11341 }
11342
11343 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11344 let mut text = String::new();
11345 let buffer = self.buffer.read(cx).snapshot(cx);
11346 let mut selections = self.selections.all::<Point>(cx);
11347 let mut clipboard_selections = Vec::with_capacity(selections.len());
11348 {
11349 let max_point = buffer.max_point();
11350 let mut is_first = true;
11351 for selection in &mut selections {
11352 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11353 if is_entire_line {
11354 selection.start = Point::new(selection.start.row, 0);
11355 if !selection.is_empty() && selection.end.column == 0 {
11356 selection.end = cmp::min(max_point, selection.end);
11357 } else {
11358 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11359 }
11360 selection.goal = SelectionGoal::None;
11361 }
11362 if is_first {
11363 is_first = false;
11364 } else {
11365 text += "\n";
11366 }
11367 let mut len = 0;
11368 for chunk in buffer.text_for_range(selection.start..selection.end) {
11369 text.push_str(chunk);
11370 len += chunk.len();
11371 }
11372 clipboard_selections.push(ClipboardSelection {
11373 len,
11374 is_entire_line,
11375 first_line_indent: buffer
11376 .indent_size_for_line(MultiBufferRow(selection.start.row))
11377 .len,
11378 });
11379 }
11380 }
11381
11382 self.transact(window, cx, |this, window, cx| {
11383 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11384 s.select(selections);
11385 });
11386 this.insert("", window, cx);
11387 });
11388 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11389 }
11390
11391 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11392 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11393 let item = self.cut_common(window, cx);
11394 cx.write_to_clipboard(item);
11395 }
11396
11397 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11399 self.change_selections(None, window, cx, |s| {
11400 s.move_with(|snapshot, sel| {
11401 if sel.is_empty() {
11402 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11403 }
11404 });
11405 });
11406 let item = self.cut_common(window, cx);
11407 cx.set_global(KillRing(item))
11408 }
11409
11410 pub fn kill_ring_yank(
11411 &mut self,
11412 _: &KillRingYank,
11413 window: &mut Window,
11414 cx: &mut Context<Self>,
11415 ) {
11416 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11417 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11418 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11419 (kill_ring.text().to_string(), kill_ring.metadata_json())
11420 } else {
11421 return;
11422 }
11423 } else {
11424 return;
11425 };
11426 self.do_paste(&text, metadata, false, window, cx);
11427 }
11428
11429 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11430 self.do_copy(true, cx);
11431 }
11432
11433 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11434 self.do_copy(false, cx);
11435 }
11436
11437 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11438 let selections = self.selections.all::<Point>(cx);
11439 let buffer = self.buffer.read(cx).read(cx);
11440 let mut text = String::new();
11441
11442 let mut clipboard_selections = Vec::with_capacity(selections.len());
11443 {
11444 let max_point = buffer.max_point();
11445 let mut is_first = true;
11446 for selection in &selections {
11447 let mut start = selection.start;
11448 let mut end = selection.end;
11449 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11450 if is_entire_line {
11451 start = Point::new(start.row, 0);
11452 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11453 }
11454
11455 let mut trimmed_selections = Vec::new();
11456 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11457 let row = MultiBufferRow(start.row);
11458 let first_indent = buffer.indent_size_for_line(row);
11459 if first_indent.len == 0 || start.column > first_indent.len {
11460 trimmed_selections.push(start..end);
11461 } else {
11462 trimmed_selections.push(
11463 Point::new(row.0, first_indent.len)
11464 ..Point::new(row.0, buffer.line_len(row)),
11465 );
11466 for row in start.row + 1..=end.row {
11467 let mut line_len = buffer.line_len(MultiBufferRow(row));
11468 if row == end.row {
11469 line_len = end.column;
11470 }
11471 if line_len == 0 {
11472 trimmed_selections
11473 .push(Point::new(row, 0)..Point::new(row, line_len));
11474 continue;
11475 }
11476 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11477 if row_indent_size.len >= first_indent.len {
11478 trimmed_selections.push(
11479 Point::new(row, first_indent.len)..Point::new(row, line_len),
11480 );
11481 } else {
11482 trimmed_selections.clear();
11483 trimmed_selections.push(start..end);
11484 break;
11485 }
11486 }
11487 }
11488 } else {
11489 trimmed_selections.push(start..end);
11490 }
11491
11492 for trimmed_range in trimmed_selections {
11493 if is_first {
11494 is_first = false;
11495 } else {
11496 text += "\n";
11497 }
11498 let mut len = 0;
11499 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11500 text.push_str(chunk);
11501 len += chunk.len();
11502 }
11503 clipboard_selections.push(ClipboardSelection {
11504 len,
11505 is_entire_line,
11506 first_line_indent: buffer
11507 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11508 .len,
11509 });
11510 }
11511 }
11512 }
11513
11514 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11515 text,
11516 clipboard_selections,
11517 ));
11518 }
11519
11520 pub fn do_paste(
11521 &mut self,
11522 text: &String,
11523 clipboard_selections: Option<Vec<ClipboardSelection>>,
11524 handle_entire_lines: bool,
11525 window: &mut Window,
11526 cx: &mut Context<Self>,
11527 ) {
11528 if self.read_only(cx) {
11529 return;
11530 }
11531
11532 let clipboard_text = Cow::Borrowed(text);
11533
11534 self.transact(window, cx, |this, window, cx| {
11535 if let Some(mut clipboard_selections) = clipboard_selections {
11536 let old_selections = this.selections.all::<usize>(cx);
11537 let all_selections_were_entire_line =
11538 clipboard_selections.iter().all(|s| s.is_entire_line);
11539 let first_selection_indent_column =
11540 clipboard_selections.first().map(|s| s.first_line_indent);
11541 if clipboard_selections.len() != old_selections.len() {
11542 clipboard_selections.drain(..);
11543 }
11544 let cursor_offset = this.selections.last::<usize>(cx).head();
11545 let mut auto_indent_on_paste = true;
11546
11547 this.buffer.update(cx, |buffer, cx| {
11548 let snapshot = buffer.read(cx);
11549 auto_indent_on_paste = snapshot
11550 .language_settings_at(cursor_offset, cx)
11551 .auto_indent_on_paste;
11552
11553 let mut start_offset = 0;
11554 let mut edits = Vec::new();
11555 let mut original_indent_columns = Vec::new();
11556 for (ix, selection) in old_selections.iter().enumerate() {
11557 let to_insert;
11558 let entire_line;
11559 let original_indent_column;
11560 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11561 let end_offset = start_offset + clipboard_selection.len;
11562 to_insert = &clipboard_text[start_offset..end_offset];
11563 entire_line = clipboard_selection.is_entire_line;
11564 start_offset = end_offset + 1;
11565 original_indent_column = Some(clipboard_selection.first_line_indent);
11566 } else {
11567 to_insert = clipboard_text.as_str();
11568 entire_line = all_selections_were_entire_line;
11569 original_indent_column = first_selection_indent_column
11570 }
11571
11572 // If the corresponding selection was empty when this slice of the
11573 // clipboard text was written, then the entire line containing the
11574 // selection was copied. If this selection is also currently empty,
11575 // then paste the line before the current line of the buffer.
11576 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11577 let column = selection.start.to_point(&snapshot).column as usize;
11578 let line_start = selection.start - column;
11579 line_start..line_start
11580 } else {
11581 selection.range()
11582 };
11583
11584 edits.push((range, to_insert));
11585 original_indent_columns.push(original_indent_column);
11586 }
11587 drop(snapshot);
11588
11589 buffer.edit(
11590 edits,
11591 if auto_indent_on_paste {
11592 Some(AutoindentMode::Block {
11593 original_indent_columns,
11594 })
11595 } else {
11596 None
11597 },
11598 cx,
11599 );
11600 });
11601
11602 let selections = this.selections.all::<usize>(cx);
11603 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11604 s.select(selections)
11605 });
11606 } else {
11607 this.insert(&clipboard_text, window, cx);
11608 }
11609 });
11610 }
11611
11612 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11613 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11614 if let Some(item) = cx.read_from_clipboard() {
11615 let entries = item.entries();
11616
11617 match entries.first() {
11618 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11619 // of all the pasted entries.
11620 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11621 .do_paste(
11622 clipboard_string.text(),
11623 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11624 true,
11625 window,
11626 cx,
11627 ),
11628 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11629 }
11630 }
11631 }
11632
11633 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11634 if self.read_only(cx) {
11635 return;
11636 }
11637
11638 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11639
11640 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11641 if let Some((selections, _)) =
11642 self.selection_history.transaction(transaction_id).cloned()
11643 {
11644 self.change_selections(None, window, cx, |s| {
11645 s.select_anchors(selections.to_vec());
11646 });
11647 } else {
11648 log::error!(
11649 "No entry in selection_history found for undo. \
11650 This may correspond to a bug where undo does not update the selection. \
11651 If this is occurring, please add details to \
11652 https://github.com/zed-industries/zed/issues/22692"
11653 );
11654 }
11655 self.request_autoscroll(Autoscroll::fit(), cx);
11656 self.unmark_text(window, cx);
11657 self.refresh_inline_completion(true, false, window, cx);
11658 cx.emit(EditorEvent::Edited { transaction_id });
11659 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11660 }
11661 }
11662
11663 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11664 if self.read_only(cx) {
11665 return;
11666 }
11667
11668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11669
11670 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11671 if let Some((_, Some(selections))) =
11672 self.selection_history.transaction(transaction_id).cloned()
11673 {
11674 self.change_selections(None, window, cx, |s| {
11675 s.select_anchors(selections.to_vec());
11676 });
11677 } else {
11678 log::error!(
11679 "No entry in selection_history found for redo. \
11680 This may correspond to a bug where undo does not update the selection. \
11681 If this is occurring, please add details to \
11682 https://github.com/zed-industries/zed/issues/22692"
11683 );
11684 }
11685 self.request_autoscroll(Autoscroll::fit(), cx);
11686 self.unmark_text(window, cx);
11687 self.refresh_inline_completion(true, false, window, cx);
11688 cx.emit(EditorEvent::Edited { transaction_id });
11689 }
11690 }
11691
11692 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11693 self.buffer
11694 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11695 }
11696
11697 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11698 self.buffer
11699 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11700 }
11701
11702 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11703 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11704 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11705 s.move_with(|map, selection| {
11706 let cursor = if selection.is_empty() {
11707 movement::left(map, selection.start)
11708 } else {
11709 selection.start
11710 };
11711 selection.collapse_to(cursor, SelectionGoal::None);
11712 });
11713 })
11714 }
11715
11716 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11717 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11718 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11719 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11720 })
11721 }
11722
11723 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11724 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11725 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11726 s.move_with(|map, selection| {
11727 let cursor = if selection.is_empty() {
11728 movement::right(map, selection.end)
11729 } else {
11730 selection.end
11731 };
11732 selection.collapse_to(cursor, SelectionGoal::None)
11733 });
11734 })
11735 }
11736
11737 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11738 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11739 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11740 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11741 })
11742 }
11743
11744 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11745 if self.take_rename(true, window, cx).is_some() {
11746 return;
11747 }
11748
11749 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11750 cx.propagate();
11751 return;
11752 }
11753
11754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11755
11756 let text_layout_details = &self.text_layout_details(window);
11757 let selection_count = self.selections.count();
11758 let first_selection = self.selections.first_anchor();
11759
11760 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11761 s.move_with(|map, selection| {
11762 if !selection.is_empty() {
11763 selection.goal = SelectionGoal::None;
11764 }
11765 let (cursor, goal) = movement::up(
11766 map,
11767 selection.start,
11768 selection.goal,
11769 false,
11770 text_layout_details,
11771 );
11772 selection.collapse_to(cursor, goal);
11773 });
11774 });
11775
11776 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11777 {
11778 cx.propagate();
11779 }
11780 }
11781
11782 pub fn move_up_by_lines(
11783 &mut self,
11784 action: &MoveUpByLines,
11785 window: &mut Window,
11786 cx: &mut Context<Self>,
11787 ) {
11788 if self.take_rename(true, window, cx).is_some() {
11789 return;
11790 }
11791
11792 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11793 cx.propagate();
11794 return;
11795 }
11796
11797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11798
11799 let text_layout_details = &self.text_layout_details(window);
11800
11801 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11802 s.move_with(|map, selection| {
11803 if !selection.is_empty() {
11804 selection.goal = SelectionGoal::None;
11805 }
11806 let (cursor, goal) = movement::up_by_rows(
11807 map,
11808 selection.start,
11809 action.lines,
11810 selection.goal,
11811 false,
11812 text_layout_details,
11813 );
11814 selection.collapse_to(cursor, goal);
11815 });
11816 })
11817 }
11818
11819 pub fn move_down_by_lines(
11820 &mut self,
11821 action: &MoveDownByLines,
11822 window: &mut Window,
11823 cx: &mut Context<Self>,
11824 ) {
11825 if self.take_rename(true, window, cx).is_some() {
11826 return;
11827 }
11828
11829 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11830 cx.propagate();
11831 return;
11832 }
11833
11834 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11835
11836 let text_layout_details = &self.text_layout_details(window);
11837
11838 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11839 s.move_with(|map, selection| {
11840 if !selection.is_empty() {
11841 selection.goal = SelectionGoal::None;
11842 }
11843 let (cursor, goal) = movement::down_by_rows(
11844 map,
11845 selection.start,
11846 action.lines,
11847 selection.goal,
11848 false,
11849 text_layout_details,
11850 );
11851 selection.collapse_to(cursor, goal);
11852 });
11853 })
11854 }
11855
11856 pub fn select_down_by_lines(
11857 &mut self,
11858 action: &SelectDownByLines,
11859 window: &mut Window,
11860 cx: &mut Context<Self>,
11861 ) {
11862 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11863 let text_layout_details = &self.text_layout_details(window);
11864 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11865 s.move_heads_with(|map, head, goal| {
11866 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11867 })
11868 })
11869 }
11870
11871 pub fn select_up_by_lines(
11872 &mut self,
11873 action: &SelectUpByLines,
11874 window: &mut Window,
11875 cx: &mut Context<Self>,
11876 ) {
11877 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11878 let text_layout_details = &self.text_layout_details(window);
11879 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11880 s.move_heads_with(|map, head, goal| {
11881 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11882 })
11883 })
11884 }
11885
11886 pub fn select_page_up(
11887 &mut self,
11888 _: &SelectPageUp,
11889 window: &mut Window,
11890 cx: &mut Context<Self>,
11891 ) {
11892 let Some(row_count) = self.visible_row_count() else {
11893 return;
11894 };
11895
11896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11897
11898 let text_layout_details = &self.text_layout_details(window);
11899
11900 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11901 s.move_heads_with(|map, head, goal| {
11902 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11903 })
11904 })
11905 }
11906
11907 pub fn move_page_up(
11908 &mut self,
11909 action: &MovePageUp,
11910 window: &mut Window,
11911 cx: &mut Context<Self>,
11912 ) {
11913 if self.take_rename(true, window, cx).is_some() {
11914 return;
11915 }
11916
11917 if self
11918 .context_menu
11919 .borrow_mut()
11920 .as_mut()
11921 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11922 .unwrap_or(false)
11923 {
11924 return;
11925 }
11926
11927 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11928 cx.propagate();
11929 return;
11930 }
11931
11932 let Some(row_count) = self.visible_row_count() else {
11933 return;
11934 };
11935
11936 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11937
11938 let autoscroll = if action.center_cursor {
11939 Autoscroll::center()
11940 } else {
11941 Autoscroll::fit()
11942 };
11943
11944 let text_layout_details = &self.text_layout_details(window);
11945
11946 self.change_selections(Some(autoscroll), window, cx, |s| {
11947 s.move_with(|map, selection| {
11948 if !selection.is_empty() {
11949 selection.goal = SelectionGoal::None;
11950 }
11951 let (cursor, goal) = movement::up_by_rows(
11952 map,
11953 selection.end,
11954 row_count,
11955 selection.goal,
11956 false,
11957 text_layout_details,
11958 );
11959 selection.collapse_to(cursor, goal);
11960 });
11961 });
11962 }
11963
11964 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11965 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11966 let text_layout_details = &self.text_layout_details(window);
11967 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11968 s.move_heads_with(|map, head, goal| {
11969 movement::up(map, head, goal, false, text_layout_details)
11970 })
11971 })
11972 }
11973
11974 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11975 self.take_rename(true, window, cx);
11976
11977 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11978 cx.propagate();
11979 return;
11980 }
11981
11982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11983
11984 let text_layout_details = &self.text_layout_details(window);
11985 let selection_count = self.selections.count();
11986 let first_selection = self.selections.first_anchor();
11987
11988 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11989 s.move_with(|map, selection| {
11990 if !selection.is_empty() {
11991 selection.goal = SelectionGoal::None;
11992 }
11993 let (cursor, goal) = movement::down(
11994 map,
11995 selection.end,
11996 selection.goal,
11997 false,
11998 text_layout_details,
11999 );
12000 selection.collapse_to(cursor, goal);
12001 });
12002 });
12003
12004 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12005 {
12006 cx.propagate();
12007 }
12008 }
12009
12010 pub fn select_page_down(
12011 &mut self,
12012 _: &SelectPageDown,
12013 window: &mut Window,
12014 cx: &mut Context<Self>,
12015 ) {
12016 let Some(row_count) = self.visible_row_count() else {
12017 return;
12018 };
12019
12020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12021
12022 let text_layout_details = &self.text_layout_details(window);
12023
12024 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12025 s.move_heads_with(|map, head, goal| {
12026 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12027 })
12028 })
12029 }
12030
12031 pub fn move_page_down(
12032 &mut self,
12033 action: &MovePageDown,
12034 window: &mut Window,
12035 cx: &mut Context<Self>,
12036 ) {
12037 if self.take_rename(true, window, cx).is_some() {
12038 return;
12039 }
12040
12041 if self
12042 .context_menu
12043 .borrow_mut()
12044 .as_mut()
12045 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12046 .unwrap_or(false)
12047 {
12048 return;
12049 }
12050
12051 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12052 cx.propagate();
12053 return;
12054 }
12055
12056 let Some(row_count) = self.visible_row_count() else {
12057 return;
12058 };
12059
12060 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12061
12062 let autoscroll = if action.center_cursor {
12063 Autoscroll::center()
12064 } else {
12065 Autoscroll::fit()
12066 };
12067
12068 let text_layout_details = &self.text_layout_details(window);
12069 self.change_selections(Some(autoscroll), window, cx, |s| {
12070 s.move_with(|map, selection| {
12071 if !selection.is_empty() {
12072 selection.goal = SelectionGoal::None;
12073 }
12074 let (cursor, goal) = movement::down_by_rows(
12075 map,
12076 selection.end,
12077 row_count,
12078 selection.goal,
12079 false,
12080 text_layout_details,
12081 );
12082 selection.collapse_to(cursor, goal);
12083 });
12084 });
12085 }
12086
12087 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12089 let text_layout_details = &self.text_layout_details(window);
12090 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12091 s.move_heads_with(|map, head, goal| {
12092 movement::down(map, head, goal, false, text_layout_details)
12093 })
12094 });
12095 }
12096
12097 pub fn context_menu_first(
12098 &mut self,
12099 _: &ContextMenuFirst,
12100 window: &mut Window,
12101 cx: &mut Context<Self>,
12102 ) {
12103 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12104 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12105 }
12106 }
12107
12108 pub fn context_menu_prev(
12109 &mut self,
12110 _: &ContextMenuPrevious,
12111 window: &mut Window,
12112 cx: &mut Context<Self>,
12113 ) {
12114 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12115 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12116 }
12117 }
12118
12119 pub fn context_menu_next(
12120 &mut self,
12121 _: &ContextMenuNext,
12122 window: &mut Window,
12123 cx: &mut Context<Self>,
12124 ) {
12125 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12126 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12127 }
12128 }
12129
12130 pub fn context_menu_last(
12131 &mut self,
12132 _: &ContextMenuLast,
12133 window: &mut Window,
12134 cx: &mut Context<Self>,
12135 ) {
12136 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12137 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12138 }
12139 }
12140
12141 pub fn move_to_previous_word_start(
12142 &mut self,
12143 _: &MoveToPreviousWordStart,
12144 window: &mut Window,
12145 cx: &mut Context<Self>,
12146 ) {
12147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12148 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12149 s.move_cursors_with(|map, head, _| {
12150 (
12151 movement::previous_word_start(map, head),
12152 SelectionGoal::None,
12153 )
12154 });
12155 })
12156 }
12157
12158 pub fn move_to_previous_subword_start(
12159 &mut self,
12160 _: &MoveToPreviousSubwordStart,
12161 window: &mut Window,
12162 cx: &mut Context<Self>,
12163 ) {
12164 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12165 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12166 s.move_cursors_with(|map, head, _| {
12167 (
12168 movement::previous_subword_start(map, head),
12169 SelectionGoal::None,
12170 )
12171 });
12172 })
12173 }
12174
12175 pub fn select_to_previous_word_start(
12176 &mut self,
12177 _: &SelectToPreviousWordStart,
12178 window: &mut Window,
12179 cx: &mut Context<Self>,
12180 ) {
12181 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12182 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12183 s.move_heads_with(|map, head, _| {
12184 (
12185 movement::previous_word_start(map, head),
12186 SelectionGoal::None,
12187 )
12188 });
12189 })
12190 }
12191
12192 pub fn select_to_previous_subword_start(
12193 &mut self,
12194 _: &SelectToPreviousSubwordStart,
12195 window: &mut Window,
12196 cx: &mut Context<Self>,
12197 ) {
12198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12199 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12200 s.move_heads_with(|map, head, _| {
12201 (
12202 movement::previous_subword_start(map, head),
12203 SelectionGoal::None,
12204 )
12205 });
12206 })
12207 }
12208
12209 pub fn delete_to_previous_word_start(
12210 &mut self,
12211 action: &DeleteToPreviousWordStart,
12212 window: &mut Window,
12213 cx: &mut Context<Self>,
12214 ) {
12215 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12216 self.transact(window, cx, |this, window, cx| {
12217 this.select_autoclose_pair(window, cx);
12218 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12219 s.move_with(|map, selection| {
12220 if selection.is_empty() {
12221 let cursor = if action.ignore_newlines {
12222 movement::previous_word_start(map, selection.head())
12223 } else {
12224 movement::previous_word_start_or_newline(map, selection.head())
12225 };
12226 selection.set_head(cursor, SelectionGoal::None);
12227 }
12228 });
12229 });
12230 this.insert("", window, cx);
12231 });
12232 }
12233
12234 pub fn delete_to_previous_subword_start(
12235 &mut self,
12236 _: &DeleteToPreviousSubwordStart,
12237 window: &mut Window,
12238 cx: &mut Context<Self>,
12239 ) {
12240 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12241 self.transact(window, cx, |this, window, cx| {
12242 this.select_autoclose_pair(window, cx);
12243 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12244 s.move_with(|map, selection| {
12245 if selection.is_empty() {
12246 let cursor = movement::previous_subword_start(map, selection.head());
12247 selection.set_head(cursor, SelectionGoal::None);
12248 }
12249 });
12250 });
12251 this.insert("", window, cx);
12252 });
12253 }
12254
12255 pub fn move_to_next_word_end(
12256 &mut self,
12257 _: &MoveToNextWordEnd,
12258 window: &mut Window,
12259 cx: &mut Context<Self>,
12260 ) {
12261 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12262 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12263 s.move_cursors_with(|map, head, _| {
12264 (movement::next_word_end(map, head), SelectionGoal::None)
12265 });
12266 })
12267 }
12268
12269 pub fn move_to_next_subword_end(
12270 &mut self,
12271 _: &MoveToNextSubwordEnd,
12272 window: &mut Window,
12273 cx: &mut Context<Self>,
12274 ) {
12275 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12276 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12277 s.move_cursors_with(|map, head, _| {
12278 (movement::next_subword_end(map, head), SelectionGoal::None)
12279 });
12280 })
12281 }
12282
12283 pub fn select_to_next_word_end(
12284 &mut self,
12285 _: &SelectToNextWordEnd,
12286 window: &mut Window,
12287 cx: &mut Context<Self>,
12288 ) {
12289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12290 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12291 s.move_heads_with(|map, head, _| {
12292 (movement::next_word_end(map, head), SelectionGoal::None)
12293 });
12294 })
12295 }
12296
12297 pub fn select_to_next_subword_end(
12298 &mut self,
12299 _: &SelectToNextSubwordEnd,
12300 window: &mut Window,
12301 cx: &mut Context<Self>,
12302 ) {
12303 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12304 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12305 s.move_heads_with(|map, head, _| {
12306 (movement::next_subword_end(map, head), SelectionGoal::None)
12307 });
12308 })
12309 }
12310
12311 pub fn delete_to_next_word_end(
12312 &mut self,
12313 action: &DeleteToNextWordEnd,
12314 window: &mut Window,
12315 cx: &mut Context<Self>,
12316 ) {
12317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12318 self.transact(window, cx, |this, window, cx| {
12319 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12320 s.move_with(|map, selection| {
12321 if selection.is_empty() {
12322 let cursor = if action.ignore_newlines {
12323 movement::next_word_end(map, selection.head())
12324 } else {
12325 movement::next_word_end_or_newline(map, selection.head())
12326 };
12327 selection.set_head(cursor, SelectionGoal::None);
12328 }
12329 });
12330 });
12331 this.insert("", window, cx);
12332 });
12333 }
12334
12335 pub fn delete_to_next_subword_end(
12336 &mut self,
12337 _: &DeleteToNextSubwordEnd,
12338 window: &mut Window,
12339 cx: &mut Context<Self>,
12340 ) {
12341 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12342 self.transact(window, cx, |this, window, cx| {
12343 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12344 s.move_with(|map, selection| {
12345 if selection.is_empty() {
12346 let cursor = movement::next_subword_end(map, selection.head());
12347 selection.set_head(cursor, SelectionGoal::None);
12348 }
12349 });
12350 });
12351 this.insert("", window, cx);
12352 });
12353 }
12354
12355 pub fn move_to_beginning_of_line(
12356 &mut self,
12357 action: &MoveToBeginningOfLine,
12358 window: &mut Window,
12359 cx: &mut Context<Self>,
12360 ) {
12361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12362 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12363 s.move_cursors_with(|map, head, _| {
12364 (
12365 movement::indented_line_beginning(
12366 map,
12367 head,
12368 action.stop_at_soft_wraps,
12369 action.stop_at_indent,
12370 ),
12371 SelectionGoal::None,
12372 )
12373 });
12374 })
12375 }
12376
12377 pub fn select_to_beginning_of_line(
12378 &mut self,
12379 action: &SelectToBeginningOfLine,
12380 window: &mut Window,
12381 cx: &mut Context<Self>,
12382 ) {
12383 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12384 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12385 s.move_heads_with(|map, head, _| {
12386 (
12387 movement::indented_line_beginning(
12388 map,
12389 head,
12390 action.stop_at_soft_wraps,
12391 action.stop_at_indent,
12392 ),
12393 SelectionGoal::None,
12394 )
12395 });
12396 });
12397 }
12398
12399 pub fn delete_to_beginning_of_line(
12400 &mut self,
12401 action: &DeleteToBeginningOfLine,
12402 window: &mut Window,
12403 cx: &mut Context<Self>,
12404 ) {
12405 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12406 self.transact(window, cx, |this, window, cx| {
12407 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12408 s.move_with(|_, selection| {
12409 selection.reversed = true;
12410 });
12411 });
12412
12413 this.select_to_beginning_of_line(
12414 &SelectToBeginningOfLine {
12415 stop_at_soft_wraps: false,
12416 stop_at_indent: action.stop_at_indent,
12417 },
12418 window,
12419 cx,
12420 );
12421 this.backspace(&Backspace, window, cx);
12422 });
12423 }
12424
12425 pub fn move_to_end_of_line(
12426 &mut self,
12427 action: &MoveToEndOfLine,
12428 window: &mut Window,
12429 cx: &mut Context<Self>,
12430 ) {
12431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12432 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12433 s.move_cursors_with(|map, head, _| {
12434 (
12435 movement::line_end(map, head, action.stop_at_soft_wraps),
12436 SelectionGoal::None,
12437 )
12438 });
12439 })
12440 }
12441
12442 pub fn select_to_end_of_line(
12443 &mut self,
12444 action: &SelectToEndOfLine,
12445 window: &mut Window,
12446 cx: &mut Context<Self>,
12447 ) {
12448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12449 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12450 s.move_heads_with(|map, head, _| {
12451 (
12452 movement::line_end(map, head, action.stop_at_soft_wraps),
12453 SelectionGoal::None,
12454 )
12455 });
12456 })
12457 }
12458
12459 pub fn delete_to_end_of_line(
12460 &mut self,
12461 _: &DeleteToEndOfLine,
12462 window: &mut Window,
12463 cx: &mut Context<Self>,
12464 ) {
12465 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12466 self.transact(window, cx, |this, window, cx| {
12467 this.select_to_end_of_line(
12468 &SelectToEndOfLine {
12469 stop_at_soft_wraps: false,
12470 },
12471 window,
12472 cx,
12473 );
12474 this.delete(&Delete, window, cx);
12475 });
12476 }
12477
12478 pub fn cut_to_end_of_line(
12479 &mut self,
12480 _: &CutToEndOfLine,
12481 window: &mut Window,
12482 cx: &mut Context<Self>,
12483 ) {
12484 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12485 self.transact(window, cx, |this, window, cx| {
12486 this.select_to_end_of_line(
12487 &SelectToEndOfLine {
12488 stop_at_soft_wraps: false,
12489 },
12490 window,
12491 cx,
12492 );
12493 this.cut(&Cut, window, cx);
12494 });
12495 }
12496
12497 pub fn move_to_start_of_paragraph(
12498 &mut self,
12499 _: &MoveToStartOfParagraph,
12500 window: &mut Window,
12501 cx: &mut Context<Self>,
12502 ) {
12503 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12504 cx.propagate();
12505 return;
12506 }
12507 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12508 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12509 s.move_with(|map, selection| {
12510 selection.collapse_to(
12511 movement::start_of_paragraph(map, selection.head(), 1),
12512 SelectionGoal::None,
12513 )
12514 });
12515 })
12516 }
12517
12518 pub fn move_to_end_of_paragraph(
12519 &mut self,
12520 _: &MoveToEndOfParagraph,
12521 window: &mut Window,
12522 cx: &mut Context<Self>,
12523 ) {
12524 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12525 cx.propagate();
12526 return;
12527 }
12528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12529 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12530 s.move_with(|map, selection| {
12531 selection.collapse_to(
12532 movement::end_of_paragraph(map, selection.head(), 1),
12533 SelectionGoal::None,
12534 )
12535 });
12536 })
12537 }
12538
12539 pub fn select_to_start_of_paragraph(
12540 &mut self,
12541 _: &SelectToStartOfParagraph,
12542 window: &mut Window,
12543 cx: &mut Context<Self>,
12544 ) {
12545 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12546 cx.propagate();
12547 return;
12548 }
12549 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12550 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12551 s.move_heads_with(|map, head, _| {
12552 (
12553 movement::start_of_paragraph(map, head, 1),
12554 SelectionGoal::None,
12555 )
12556 });
12557 })
12558 }
12559
12560 pub fn select_to_end_of_paragraph(
12561 &mut self,
12562 _: &SelectToEndOfParagraph,
12563 window: &mut Window,
12564 cx: &mut Context<Self>,
12565 ) {
12566 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12567 cx.propagate();
12568 return;
12569 }
12570 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12571 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12572 s.move_heads_with(|map, head, _| {
12573 (
12574 movement::end_of_paragraph(map, head, 1),
12575 SelectionGoal::None,
12576 )
12577 });
12578 })
12579 }
12580
12581 pub fn move_to_start_of_excerpt(
12582 &mut self,
12583 _: &MoveToStartOfExcerpt,
12584 window: &mut Window,
12585 cx: &mut Context<Self>,
12586 ) {
12587 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12588 cx.propagate();
12589 return;
12590 }
12591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12592 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12593 s.move_with(|map, selection| {
12594 selection.collapse_to(
12595 movement::start_of_excerpt(
12596 map,
12597 selection.head(),
12598 workspace::searchable::Direction::Prev,
12599 ),
12600 SelectionGoal::None,
12601 )
12602 });
12603 })
12604 }
12605
12606 pub fn move_to_start_of_next_excerpt(
12607 &mut self,
12608 _: &MoveToStartOfNextExcerpt,
12609 window: &mut Window,
12610 cx: &mut Context<Self>,
12611 ) {
12612 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12613 cx.propagate();
12614 return;
12615 }
12616
12617 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12618 s.move_with(|map, selection| {
12619 selection.collapse_to(
12620 movement::start_of_excerpt(
12621 map,
12622 selection.head(),
12623 workspace::searchable::Direction::Next,
12624 ),
12625 SelectionGoal::None,
12626 )
12627 });
12628 })
12629 }
12630
12631 pub fn move_to_end_of_excerpt(
12632 &mut self,
12633 _: &MoveToEndOfExcerpt,
12634 window: &mut Window,
12635 cx: &mut Context<Self>,
12636 ) {
12637 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12638 cx.propagate();
12639 return;
12640 }
12641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12642 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12643 s.move_with(|map, selection| {
12644 selection.collapse_to(
12645 movement::end_of_excerpt(
12646 map,
12647 selection.head(),
12648 workspace::searchable::Direction::Next,
12649 ),
12650 SelectionGoal::None,
12651 )
12652 });
12653 })
12654 }
12655
12656 pub fn move_to_end_of_previous_excerpt(
12657 &mut self,
12658 _: &MoveToEndOfPreviousExcerpt,
12659 window: &mut Window,
12660 cx: &mut Context<Self>,
12661 ) {
12662 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12663 cx.propagate();
12664 return;
12665 }
12666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12667 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12668 s.move_with(|map, selection| {
12669 selection.collapse_to(
12670 movement::end_of_excerpt(
12671 map,
12672 selection.head(),
12673 workspace::searchable::Direction::Prev,
12674 ),
12675 SelectionGoal::None,
12676 )
12677 });
12678 })
12679 }
12680
12681 pub fn select_to_start_of_excerpt(
12682 &mut self,
12683 _: &SelectToStartOfExcerpt,
12684 window: &mut Window,
12685 cx: &mut Context<Self>,
12686 ) {
12687 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12688 cx.propagate();
12689 return;
12690 }
12691 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12692 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12693 s.move_heads_with(|map, head, _| {
12694 (
12695 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12696 SelectionGoal::None,
12697 )
12698 });
12699 })
12700 }
12701
12702 pub fn select_to_start_of_next_excerpt(
12703 &mut self,
12704 _: &SelectToStartOfNextExcerpt,
12705 window: &mut Window,
12706 cx: &mut Context<Self>,
12707 ) {
12708 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12709 cx.propagate();
12710 return;
12711 }
12712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12713 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12714 s.move_heads_with(|map, head, _| {
12715 (
12716 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12717 SelectionGoal::None,
12718 )
12719 });
12720 })
12721 }
12722
12723 pub fn select_to_end_of_excerpt(
12724 &mut self,
12725 _: &SelectToEndOfExcerpt,
12726 window: &mut Window,
12727 cx: &mut Context<Self>,
12728 ) {
12729 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12730 cx.propagate();
12731 return;
12732 }
12733 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12734 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12735 s.move_heads_with(|map, head, _| {
12736 (
12737 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12738 SelectionGoal::None,
12739 )
12740 });
12741 })
12742 }
12743
12744 pub fn select_to_end_of_previous_excerpt(
12745 &mut self,
12746 _: &SelectToEndOfPreviousExcerpt,
12747 window: &mut Window,
12748 cx: &mut Context<Self>,
12749 ) {
12750 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12751 cx.propagate();
12752 return;
12753 }
12754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12755 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12756 s.move_heads_with(|map, head, _| {
12757 (
12758 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12759 SelectionGoal::None,
12760 )
12761 });
12762 })
12763 }
12764
12765 pub fn move_to_beginning(
12766 &mut self,
12767 _: &MoveToBeginning,
12768 window: &mut Window,
12769 cx: &mut Context<Self>,
12770 ) {
12771 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12772 cx.propagate();
12773 return;
12774 }
12775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12776 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12777 s.select_ranges(vec![0..0]);
12778 });
12779 }
12780
12781 pub fn select_to_beginning(
12782 &mut self,
12783 _: &SelectToBeginning,
12784 window: &mut Window,
12785 cx: &mut Context<Self>,
12786 ) {
12787 let mut selection = self.selections.last::<Point>(cx);
12788 selection.set_head(Point::zero(), SelectionGoal::None);
12789 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12790 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12791 s.select(vec![selection]);
12792 });
12793 }
12794
12795 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12796 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12797 cx.propagate();
12798 return;
12799 }
12800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12801 let cursor = self.buffer.read(cx).read(cx).len();
12802 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12803 s.select_ranges(vec![cursor..cursor])
12804 });
12805 }
12806
12807 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12808 self.nav_history = nav_history;
12809 }
12810
12811 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12812 self.nav_history.as_ref()
12813 }
12814
12815 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12816 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12817 }
12818
12819 fn push_to_nav_history(
12820 &mut self,
12821 cursor_anchor: Anchor,
12822 new_position: Option<Point>,
12823 is_deactivate: bool,
12824 cx: &mut Context<Self>,
12825 ) {
12826 if let Some(nav_history) = self.nav_history.as_mut() {
12827 let buffer = self.buffer.read(cx).read(cx);
12828 let cursor_position = cursor_anchor.to_point(&buffer);
12829 let scroll_state = self.scroll_manager.anchor();
12830 let scroll_top_row = scroll_state.top_row(&buffer);
12831 drop(buffer);
12832
12833 if let Some(new_position) = new_position {
12834 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12835 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12836 return;
12837 }
12838 }
12839
12840 nav_history.push(
12841 Some(NavigationData {
12842 cursor_anchor,
12843 cursor_position,
12844 scroll_anchor: scroll_state,
12845 scroll_top_row,
12846 }),
12847 cx,
12848 );
12849 cx.emit(EditorEvent::PushedToNavHistory {
12850 anchor: cursor_anchor,
12851 is_deactivate,
12852 })
12853 }
12854 }
12855
12856 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12858 let buffer = self.buffer.read(cx).snapshot(cx);
12859 let mut selection = self.selections.first::<usize>(cx);
12860 selection.set_head(buffer.len(), SelectionGoal::None);
12861 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12862 s.select(vec![selection]);
12863 });
12864 }
12865
12866 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12868 let end = self.buffer.read(cx).read(cx).len();
12869 self.change_selections(None, window, cx, |s| {
12870 s.select_ranges(vec![0..end]);
12871 });
12872 }
12873
12874 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12876 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12877 let mut selections = self.selections.all::<Point>(cx);
12878 let max_point = display_map.buffer_snapshot.max_point();
12879 for selection in &mut selections {
12880 let rows = selection.spanned_rows(true, &display_map);
12881 selection.start = Point::new(rows.start.0, 0);
12882 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12883 selection.reversed = false;
12884 }
12885 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12886 s.select(selections);
12887 });
12888 }
12889
12890 pub fn split_selection_into_lines(
12891 &mut self,
12892 _: &SplitSelectionIntoLines,
12893 window: &mut Window,
12894 cx: &mut Context<Self>,
12895 ) {
12896 let selections = self
12897 .selections
12898 .all::<Point>(cx)
12899 .into_iter()
12900 .map(|selection| selection.start..selection.end)
12901 .collect::<Vec<_>>();
12902 self.unfold_ranges(&selections, true, true, cx);
12903
12904 let mut new_selection_ranges = Vec::new();
12905 {
12906 let buffer = self.buffer.read(cx).read(cx);
12907 for selection in selections {
12908 for row in selection.start.row..selection.end.row {
12909 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12910 new_selection_ranges.push(cursor..cursor);
12911 }
12912
12913 let is_multiline_selection = selection.start.row != selection.end.row;
12914 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12915 // so this action feels more ergonomic when paired with other selection operations
12916 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12917 if !should_skip_last {
12918 new_selection_ranges.push(selection.end..selection.end);
12919 }
12920 }
12921 }
12922 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12923 s.select_ranges(new_selection_ranges);
12924 });
12925 }
12926
12927 pub fn add_selection_above(
12928 &mut self,
12929 _: &AddSelectionAbove,
12930 window: &mut Window,
12931 cx: &mut Context<Self>,
12932 ) {
12933 self.add_selection(true, window, cx);
12934 }
12935
12936 pub fn add_selection_below(
12937 &mut self,
12938 _: &AddSelectionBelow,
12939 window: &mut Window,
12940 cx: &mut Context<Self>,
12941 ) {
12942 self.add_selection(false, window, cx);
12943 }
12944
12945 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12947
12948 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12949 let all_selections = self.selections.all::<Point>(cx);
12950 let text_layout_details = self.text_layout_details(window);
12951
12952 let (mut columnar_selections, new_selections_to_columnarize) = {
12953 if let Some(state) = self.add_selections_state.as_ref() {
12954 let columnar_selection_ids: HashSet<_> = state
12955 .groups
12956 .iter()
12957 .flat_map(|group| group.stack.iter())
12958 .copied()
12959 .collect();
12960
12961 all_selections
12962 .into_iter()
12963 .partition(|s| columnar_selection_ids.contains(&s.id))
12964 } else {
12965 (Vec::new(), all_selections)
12966 }
12967 };
12968
12969 let mut state = self
12970 .add_selections_state
12971 .take()
12972 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12973
12974 for selection in new_selections_to_columnarize {
12975 let range = selection.display_range(&display_map).sorted();
12976 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12977 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12978 let positions = start_x.min(end_x)..start_x.max(end_x);
12979 let mut stack = Vec::new();
12980 for row in range.start.row().0..=range.end.row().0 {
12981 if let Some(selection) = self.selections.build_columnar_selection(
12982 &display_map,
12983 DisplayRow(row),
12984 &positions,
12985 selection.reversed,
12986 &text_layout_details,
12987 ) {
12988 stack.push(selection.id);
12989 columnar_selections.push(selection);
12990 }
12991 }
12992 if !stack.is_empty() {
12993 if above {
12994 stack.reverse();
12995 }
12996 state.groups.push(AddSelectionsGroup { above, stack });
12997 }
12998 }
12999
13000 let mut final_selections = Vec::new();
13001 let end_row = if above {
13002 DisplayRow(0)
13003 } else {
13004 display_map.max_point().row()
13005 };
13006
13007 let mut last_added_item_per_group = HashMap::default();
13008 for group in state.groups.iter_mut() {
13009 if let Some(last_id) = group.stack.last() {
13010 last_added_item_per_group.insert(*last_id, group);
13011 }
13012 }
13013
13014 for selection in columnar_selections {
13015 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13016 if above == group.above {
13017 let range = selection.display_range(&display_map).sorted();
13018 debug_assert_eq!(range.start.row(), range.end.row());
13019 let mut row = range.start.row();
13020 let positions =
13021 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13022 px(start)..px(end)
13023 } else {
13024 let start_x =
13025 display_map.x_for_display_point(range.start, &text_layout_details);
13026 let end_x =
13027 display_map.x_for_display_point(range.end, &text_layout_details);
13028 start_x.min(end_x)..start_x.max(end_x)
13029 };
13030
13031 let mut maybe_new_selection = None;
13032 while row != end_row {
13033 if above {
13034 row.0 -= 1;
13035 } else {
13036 row.0 += 1;
13037 }
13038 if let Some(new_selection) = self.selections.build_columnar_selection(
13039 &display_map,
13040 row,
13041 &positions,
13042 selection.reversed,
13043 &text_layout_details,
13044 ) {
13045 maybe_new_selection = Some(new_selection);
13046 break;
13047 }
13048 }
13049
13050 if let Some(new_selection) = maybe_new_selection {
13051 group.stack.push(new_selection.id);
13052 if above {
13053 final_selections.push(new_selection);
13054 final_selections.push(selection);
13055 } else {
13056 final_selections.push(selection);
13057 final_selections.push(new_selection);
13058 }
13059 } else {
13060 final_selections.push(selection);
13061 }
13062 } else {
13063 group.stack.pop();
13064 }
13065 } else {
13066 final_selections.push(selection);
13067 }
13068 }
13069
13070 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13071 s.select(final_selections);
13072 });
13073
13074 let final_selection_ids: HashSet<_> = self
13075 .selections
13076 .all::<Point>(cx)
13077 .iter()
13078 .map(|s| s.id)
13079 .collect();
13080 state.groups.retain_mut(|group| {
13081 // selections might get merged above so we remove invalid items from stacks
13082 group.stack.retain(|id| final_selection_ids.contains(id));
13083
13084 // single selection in stack can be treated as initial state
13085 group.stack.len() > 1
13086 });
13087
13088 if !state.groups.is_empty() {
13089 self.add_selections_state = Some(state);
13090 }
13091 }
13092
13093 fn select_match_ranges(
13094 &mut self,
13095 range: Range<usize>,
13096 reversed: bool,
13097 replace_newest: bool,
13098 auto_scroll: Option<Autoscroll>,
13099 window: &mut Window,
13100 cx: &mut Context<Editor>,
13101 ) {
13102 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13103 self.change_selections(auto_scroll, window, cx, |s| {
13104 if replace_newest {
13105 s.delete(s.newest_anchor().id);
13106 }
13107 if reversed {
13108 s.insert_range(range.end..range.start);
13109 } else {
13110 s.insert_range(range);
13111 }
13112 });
13113 }
13114
13115 pub fn select_next_match_internal(
13116 &mut self,
13117 display_map: &DisplaySnapshot,
13118 replace_newest: bool,
13119 autoscroll: Option<Autoscroll>,
13120 window: &mut Window,
13121 cx: &mut Context<Self>,
13122 ) -> Result<()> {
13123 let buffer = &display_map.buffer_snapshot;
13124 let mut selections = self.selections.all::<usize>(cx);
13125 if let Some(mut select_next_state) = self.select_next_state.take() {
13126 let query = &select_next_state.query;
13127 if !select_next_state.done {
13128 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13129 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13130 let mut next_selected_range = None;
13131
13132 let bytes_after_last_selection =
13133 buffer.bytes_in_range(last_selection.end..buffer.len());
13134 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13135 let query_matches = query
13136 .stream_find_iter(bytes_after_last_selection)
13137 .map(|result| (last_selection.end, result))
13138 .chain(
13139 query
13140 .stream_find_iter(bytes_before_first_selection)
13141 .map(|result| (0, result)),
13142 );
13143
13144 for (start_offset, query_match) in query_matches {
13145 let query_match = query_match.unwrap(); // can only fail due to I/O
13146 let offset_range =
13147 start_offset + query_match.start()..start_offset + query_match.end();
13148 let display_range = offset_range.start.to_display_point(display_map)
13149 ..offset_range.end.to_display_point(display_map);
13150
13151 if !select_next_state.wordwise
13152 || (!movement::is_inside_word(display_map, display_range.start)
13153 && !movement::is_inside_word(display_map, display_range.end))
13154 {
13155 // TODO: This is n^2, because we might check all the selections
13156 if !selections
13157 .iter()
13158 .any(|selection| selection.range().overlaps(&offset_range))
13159 {
13160 next_selected_range = Some(offset_range);
13161 break;
13162 }
13163 }
13164 }
13165
13166 if let Some(next_selected_range) = next_selected_range {
13167 self.select_match_ranges(
13168 next_selected_range,
13169 last_selection.reversed,
13170 replace_newest,
13171 autoscroll,
13172 window,
13173 cx,
13174 );
13175 } else {
13176 select_next_state.done = true;
13177 }
13178 }
13179
13180 self.select_next_state = Some(select_next_state);
13181 } else {
13182 let mut only_carets = true;
13183 let mut same_text_selected = true;
13184 let mut selected_text = None;
13185
13186 let mut selections_iter = selections.iter().peekable();
13187 while let Some(selection) = selections_iter.next() {
13188 if selection.start != selection.end {
13189 only_carets = false;
13190 }
13191
13192 if same_text_selected {
13193 if selected_text.is_none() {
13194 selected_text =
13195 Some(buffer.text_for_range(selection.range()).collect::<String>());
13196 }
13197
13198 if let Some(next_selection) = selections_iter.peek() {
13199 if next_selection.range().len() == selection.range().len() {
13200 let next_selected_text = buffer
13201 .text_for_range(next_selection.range())
13202 .collect::<String>();
13203 if Some(next_selected_text) != selected_text {
13204 same_text_selected = false;
13205 selected_text = None;
13206 }
13207 } else {
13208 same_text_selected = false;
13209 selected_text = None;
13210 }
13211 }
13212 }
13213 }
13214
13215 if only_carets {
13216 for selection in &mut selections {
13217 let word_range = movement::surrounding_word(
13218 display_map,
13219 selection.start.to_display_point(display_map),
13220 );
13221 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13222 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13223 selection.goal = SelectionGoal::None;
13224 selection.reversed = false;
13225 self.select_match_ranges(
13226 selection.start..selection.end,
13227 selection.reversed,
13228 replace_newest,
13229 autoscroll,
13230 window,
13231 cx,
13232 );
13233 }
13234
13235 if selections.len() == 1 {
13236 let selection = selections
13237 .last()
13238 .expect("ensured that there's only one selection");
13239 let query = buffer
13240 .text_for_range(selection.start..selection.end)
13241 .collect::<String>();
13242 let is_empty = query.is_empty();
13243 let select_state = SelectNextState {
13244 query: AhoCorasick::new(&[query])?,
13245 wordwise: true,
13246 done: is_empty,
13247 };
13248 self.select_next_state = Some(select_state);
13249 } else {
13250 self.select_next_state = None;
13251 }
13252 } else if let Some(selected_text) = selected_text {
13253 self.select_next_state = Some(SelectNextState {
13254 query: AhoCorasick::new(&[selected_text])?,
13255 wordwise: false,
13256 done: false,
13257 });
13258 self.select_next_match_internal(
13259 display_map,
13260 replace_newest,
13261 autoscroll,
13262 window,
13263 cx,
13264 )?;
13265 }
13266 }
13267 Ok(())
13268 }
13269
13270 pub fn select_all_matches(
13271 &mut self,
13272 _action: &SelectAllMatches,
13273 window: &mut Window,
13274 cx: &mut Context<Self>,
13275 ) -> Result<()> {
13276 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13277
13278 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13279
13280 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13281 let Some(select_next_state) = self.select_next_state.as_mut() else {
13282 return Ok(());
13283 };
13284 if select_next_state.done {
13285 return Ok(());
13286 }
13287
13288 let mut new_selections = Vec::new();
13289
13290 let reversed = self.selections.oldest::<usize>(cx).reversed;
13291 let buffer = &display_map.buffer_snapshot;
13292 let query_matches = select_next_state
13293 .query
13294 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13295
13296 for query_match in query_matches.into_iter() {
13297 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13298 let offset_range = if reversed {
13299 query_match.end()..query_match.start()
13300 } else {
13301 query_match.start()..query_match.end()
13302 };
13303 let display_range = offset_range.start.to_display_point(&display_map)
13304 ..offset_range.end.to_display_point(&display_map);
13305
13306 if !select_next_state.wordwise
13307 || (!movement::is_inside_word(&display_map, display_range.start)
13308 && !movement::is_inside_word(&display_map, display_range.end))
13309 {
13310 new_selections.push(offset_range.start..offset_range.end);
13311 }
13312 }
13313
13314 select_next_state.done = true;
13315 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13316 self.change_selections(None, window, cx, |selections| {
13317 selections.select_ranges(new_selections)
13318 });
13319
13320 Ok(())
13321 }
13322
13323 pub fn select_next(
13324 &mut self,
13325 action: &SelectNext,
13326 window: &mut Window,
13327 cx: &mut Context<Self>,
13328 ) -> Result<()> {
13329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13330 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13331 self.select_next_match_internal(
13332 &display_map,
13333 action.replace_newest,
13334 Some(Autoscroll::newest()),
13335 window,
13336 cx,
13337 )?;
13338 Ok(())
13339 }
13340
13341 pub fn select_previous(
13342 &mut self,
13343 action: &SelectPrevious,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) -> Result<()> {
13347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13348 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13349 let buffer = &display_map.buffer_snapshot;
13350 let mut selections = self.selections.all::<usize>(cx);
13351 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13352 let query = &select_prev_state.query;
13353 if !select_prev_state.done {
13354 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13355 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13356 let mut next_selected_range = None;
13357 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13358 let bytes_before_last_selection =
13359 buffer.reversed_bytes_in_range(0..last_selection.start);
13360 let bytes_after_first_selection =
13361 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13362 let query_matches = query
13363 .stream_find_iter(bytes_before_last_selection)
13364 .map(|result| (last_selection.start, result))
13365 .chain(
13366 query
13367 .stream_find_iter(bytes_after_first_selection)
13368 .map(|result| (buffer.len(), result)),
13369 );
13370 for (end_offset, query_match) in query_matches {
13371 let query_match = query_match.unwrap(); // can only fail due to I/O
13372 let offset_range =
13373 end_offset - query_match.end()..end_offset - query_match.start();
13374 let display_range = offset_range.start.to_display_point(&display_map)
13375 ..offset_range.end.to_display_point(&display_map);
13376
13377 if !select_prev_state.wordwise
13378 || (!movement::is_inside_word(&display_map, display_range.start)
13379 && !movement::is_inside_word(&display_map, display_range.end))
13380 {
13381 next_selected_range = Some(offset_range);
13382 break;
13383 }
13384 }
13385
13386 if let Some(next_selected_range) = next_selected_range {
13387 self.select_match_ranges(
13388 next_selected_range,
13389 last_selection.reversed,
13390 action.replace_newest,
13391 Some(Autoscroll::newest()),
13392 window,
13393 cx,
13394 );
13395 } else {
13396 select_prev_state.done = true;
13397 }
13398 }
13399
13400 self.select_prev_state = Some(select_prev_state);
13401 } else {
13402 let mut only_carets = true;
13403 let mut same_text_selected = true;
13404 let mut selected_text = None;
13405
13406 let mut selections_iter = selections.iter().peekable();
13407 while let Some(selection) = selections_iter.next() {
13408 if selection.start != selection.end {
13409 only_carets = false;
13410 }
13411
13412 if same_text_selected {
13413 if selected_text.is_none() {
13414 selected_text =
13415 Some(buffer.text_for_range(selection.range()).collect::<String>());
13416 }
13417
13418 if let Some(next_selection) = selections_iter.peek() {
13419 if next_selection.range().len() == selection.range().len() {
13420 let next_selected_text = buffer
13421 .text_for_range(next_selection.range())
13422 .collect::<String>();
13423 if Some(next_selected_text) != selected_text {
13424 same_text_selected = false;
13425 selected_text = None;
13426 }
13427 } else {
13428 same_text_selected = false;
13429 selected_text = None;
13430 }
13431 }
13432 }
13433 }
13434
13435 if only_carets {
13436 for selection in &mut selections {
13437 let word_range = movement::surrounding_word(
13438 &display_map,
13439 selection.start.to_display_point(&display_map),
13440 );
13441 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13442 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13443 selection.goal = SelectionGoal::None;
13444 selection.reversed = false;
13445 self.select_match_ranges(
13446 selection.start..selection.end,
13447 selection.reversed,
13448 action.replace_newest,
13449 Some(Autoscroll::newest()),
13450 window,
13451 cx,
13452 );
13453 }
13454 if selections.len() == 1 {
13455 let selection = selections
13456 .last()
13457 .expect("ensured that there's only one selection");
13458 let query = buffer
13459 .text_for_range(selection.start..selection.end)
13460 .collect::<String>();
13461 let is_empty = query.is_empty();
13462 let select_state = SelectNextState {
13463 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13464 wordwise: true,
13465 done: is_empty,
13466 };
13467 self.select_prev_state = Some(select_state);
13468 } else {
13469 self.select_prev_state = None;
13470 }
13471 } else if let Some(selected_text) = selected_text {
13472 self.select_prev_state = Some(SelectNextState {
13473 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13474 wordwise: false,
13475 done: false,
13476 });
13477 self.select_previous(action, window, cx)?;
13478 }
13479 }
13480 Ok(())
13481 }
13482
13483 pub fn find_next_match(
13484 &mut self,
13485 _: &FindNextMatch,
13486 window: &mut Window,
13487 cx: &mut Context<Self>,
13488 ) -> Result<()> {
13489 let selections = self.selections.disjoint_anchors();
13490 match selections.first() {
13491 Some(first) if selections.len() >= 2 => {
13492 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13493 s.select_ranges([first.range()]);
13494 });
13495 }
13496 _ => self.select_next(
13497 &SelectNext {
13498 replace_newest: true,
13499 },
13500 window,
13501 cx,
13502 )?,
13503 }
13504 Ok(())
13505 }
13506
13507 pub fn find_previous_match(
13508 &mut self,
13509 _: &FindPreviousMatch,
13510 window: &mut Window,
13511 cx: &mut Context<Self>,
13512 ) -> Result<()> {
13513 let selections = self.selections.disjoint_anchors();
13514 match selections.last() {
13515 Some(last) if selections.len() >= 2 => {
13516 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13517 s.select_ranges([last.range()]);
13518 });
13519 }
13520 _ => self.select_previous(
13521 &SelectPrevious {
13522 replace_newest: true,
13523 },
13524 window,
13525 cx,
13526 )?,
13527 }
13528 Ok(())
13529 }
13530
13531 pub fn toggle_comments(
13532 &mut self,
13533 action: &ToggleComments,
13534 window: &mut Window,
13535 cx: &mut Context<Self>,
13536 ) {
13537 if self.read_only(cx) {
13538 return;
13539 }
13540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13541 let text_layout_details = &self.text_layout_details(window);
13542 self.transact(window, cx, |this, window, cx| {
13543 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13544 let mut edits = Vec::new();
13545 let mut selection_edit_ranges = Vec::new();
13546 let mut last_toggled_row = None;
13547 let snapshot = this.buffer.read(cx).read(cx);
13548 let empty_str: Arc<str> = Arc::default();
13549 let mut suffixes_inserted = Vec::new();
13550 let ignore_indent = action.ignore_indent;
13551
13552 fn comment_prefix_range(
13553 snapshot: &MultiBufferSnapshot,
13554 row: MultiBufferRow,
13555 comment_prefix: &str,
13556 comment_prefix_whitespace: &str,
13557 ignore_indent: bool,
13558 ) -> Range<Point> {
13559 let indent_size = if ignore_indent {
13560 0
13561 } else {
13562 snapshot.indent_size_for_line(row).len
13563 };
13564
13565 let start = Point::new(row.0, indent_size);
13566
13567 let mut line_bytes = snapshot
13568 .bytes_in_range(start..snapshot.max_point())
13569 .flatten()
13570 .copied();
13571
13572 // If this line currently begins with the line comment prefix, then record
13573 // the range containing the prefix.
13574 if line_bytes
13575 .by_ref()
13576 .take(comment_prefix.len())
13577 .eq(comment_prefix.bytes())
13578 {
13579 // Include any whitespace that matches the comment prefix.
13580 let matching_whitespace_len = line_bytes
13581 .zip(comment_prefix_whitespace.bytes())
13582 .take_while(|(a, b)| a == b)
13583 .count() as u32;
13584 let end = Point::new(
13585 start.row,
13586 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13587 );
13588 start..end
13589 } else {
13590 start..start
13591 }
13592 }
13593
13594 fn comment_suffix_range(
13595 snapshot: &MultiBufferSnapshot,
13596 row: MultiBufferRow,
13597 comment_suffix: &str,
13598 comment_suffix_has_leading_space: bool,
13599 ) -> Range<Point> {
13600 let end = Point::new(row.0, snapshot.line_len(row));
13601 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13602
13603 let mut line_end_bytes = snapshot
13604 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13605 .flatten()
13606 .copied();
13607
13608 let leading_space_len = if suffix_start_column > 0
13609 && line_end_bytes.next() == Some(b' ')
13610 && comment_suffix_has_leading_space
13611 {
13612 1
13613 } else {
13614 0
13615 };
13616
13617 // If this line currently begins with the line comment prefix, then record
13618 // the range containing the prefix.
13619 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13620 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13621 start..end
13622 } else {
13623 end..end
13624 }
13625 }
13626
13627 // TODO: Handle selections that cross excerpts
13628 for selection in &mut selections {
13629 let start_column = snapshot
13630 .indent_size_for_line(MultiBufferRow(selection.start.row))
13631 .len;
13632 let language = if let Some(language) =
13633 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13634 {
13635 language
13636 } else {
13637 continue;
13638 };
13639
13640 selection_edit_ranges.clear();
13641
13642 // If multiple selections contain a given row, avoid processing that
13643 // row more than once.
13644 let mut start_row = MultiBufferRow(selection.start.row);
13645 if last_toggled_row == Some(start_row) {
13646 start_row = start_row.next_row();
13647 }
13648 let end_row =
13649 if selection.end.row > selection.start.row && selection.end.column == 0 {
13650 MultiBufferRow(selection.end.row - 1)
13651 } else {
13652 MultiBufferRow(selection.end.row)
13653 };
13654 last_toggled_row = Some(end_row);
13655
13656 if start_row > end_row {
13657 continue;
13658 }
13659
13660 // If the language has line comments, toggle those.
13661 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13662
13663 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13664 if ignore_indent {
13665 full_comment_prefixes = full_comment_prefixes
13666 .into_iter()
13667 .map(|s| Arc::from(s.trim_end()))
13668 .collect();
13669 }
13670
13671 if !full_comment_prefixes.is_empty() {
13672 let first_prefix = full_comment_prefixes
13673 .first()
13674 .expect("prefixes is non-empty");
13675 let prefix_trimmed_lengths = full_comment_prefixes
13676 .iter()
13677 .map(|p| p.trim_end_matches(' ').len())
13678 .collect::<SmallVec<[usize; 4]>>();
13679
13680 let mut all_selection_lines_are_comments = true;
13681
13682 for row in start_row.0..=end_row.0 {
13683 let row = MultiBufferRow(row);
13684 if start_row < end_row && snapshot.is_line_blank(row) {
13685 continue;
13686 }
13687
13688 let prefix_range = full_comment_prefixes
13689 .iter()
13690 .zip(prefix_trimmed_lengths.iter().copied())
13691 .map(|(prefix, trimmed_prefix_len)| {
13692 comment_prefix_range(
13693 snapshot.deref(),
13694 row,
13695 &prefix[..trimmed_prefix_len],
13696 &prefix[trimmed_prefix_len..],
13697 ignore_indent,
13698 )
13699 })
13700 .max_by_key(|range| range.end.column - range.start.column)
13701 .expect("prefixes is non-empty");
13702
13703 if prefix_range.is_empty() {
13704 all_selection_lines_are_comments = false;
13705 }
13706
13707 selection_edit_ranges.push(prefix_range);
13708 }
13709
13710 if all_selection_lines_are_comments {
13711 edits.extend(
13712 selection_edit_ranges
13713 .iter()
13714 .cloned()
13715 .map(|range| (range, empty_str.clone())),
13716 );
13717 } else {
13718 let min_column = selection_edit_ranges
13719 .iter()
13720 .map(|range| range.start.column)
13721 .min()
13722 .unwrap_or(0);
13723 edits.extend(selection_edit_ranges.iter().map(|range| {
13724 let position = Point::new(range.start.row, min_column);
13725 (position..position, first_prefix.clone())
13726 }));
13727 }
13728 } else if let Some((full_comment_prefix, comment_suffix)) =
13729 language.block_comment_delimiters()
13730 {
13731 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13732 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13733 let prefix_range = comment_prefix_range(
13734 snapshot.deref(),
13735 start_row,
13736 comment_prefix,
13737 comment_prefix_whitespace,
13738 ignore_indent,
13739 );
13740 let suffix_range = comment_suffix_range(
13741 snapshot.deref(),
13742 end_row,
13743 comment_suffix.trim_start_matches(' '),
13744 comment_suffix.starts_with(' '),
13745 );
13746
13747 if prefix_range.is_empty() || suffix_range.is_empty() {
13748 edits.push((
13749 prefix_range.start..prefix_range.start,
13750 full_comment_prefix.clone(),
13751 ));
13752 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13753 suffixes_inserted.push((end_row, comment_suffix.len()));
13754 } else {
13755 edits.push((prefix_range, empty_str.clone()));
13756 edits.push((suffix_range, empty_str.clone()));
13757 }
13758 } else {
13759 continue;
13760 }
13761 }
13762
13763 drop(snapshot);
13764 this.buffer.update(cx, |buffer, cx| {
13765 buffer.edit(edits, None, cx);
13766 });
13767
13768 // Adjust selections so that they end before any comment suffixes that
13769 // were inserted.
13770 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13771 let mut selections = this.selections.all::<Point>(cx);
13772 let snapshot = this.buffer.read(cx).read(cx);
13773 for selection in &mut selections {
13774 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13775 match row.cmp(&MultiBufferRow(selection.end.row)) {
13776 Ordering::Less => {
13777 suffixes_inserted.next();
13778 continue;
13779 }
13780 Ordering::Greater => break,
13781 Ordering::Equal => {
13782 if selection.end.column == snapshot.line_len(row) {
13783 if selection.is_empty() {
13784 selection.start.column -= suffix_len as u32;
13785 }
13786 selection.end.column -= suffix_len as u32;
13787 }
13788 break;
13789 }
13790 }
13791 }
13792 }
13793
13794 drop(snapshot);
13795 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13796 s.select(selections)
13797 });
13798
13799 let selections = this.selections.all::<Point>(cx);
13800 let selections_on_single_row = selections.windows(2).all(|selections| {
13801 selections[0].start.row == selections[1].start.row
13802 && selections[0].end.row == selections[1].end.row
13803 && selections[0].start.row == selections[0].end.row
13804 });
13805 let selections_selecting = selections
13806 .iter()
13807 .any(|selection| selection.start != selection.end);
13808 let advance_downwards = action.advance_downwards
13809 && selections_on_single_row
13810 && !selections_selecting
13811 && !matches!(this.mode, EditorMode::SingleLine { .. });
13812
13813 if advance_downwards {
13814 let snapshot = this.buffer.read(cx).snapshot(cx);
13815
13816 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13817 s.move_cursors_with(|display_snapshot, display_point, _| {
13818 let mut point = display_point.to_point(display_snapshot);
13819 point.row += 1;
13820 point = snapshot.clip_point(point, Bias::Left);
13821 let display_point = point.to_display_point(display_snapshot);
13822 let goal = SelectionGoal::HorizontalPosition(
13823 display_snapshot
13824 .x_for_display_point(display_point, text_layout_details)
13825 .into(),
13826 );
13827 (display_point, goal)
13828 })
13829 });
13830 }
13831 });
13832 }
13833
13834 pub fn select_enclosing_symbol(
13835 &mut self,
13836 _: &SelectEnclosingSymbol,
13837 window: &mut Window,
13838 cx: &mut Context<Self>,
13839 ) {
13840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13841
13842 let buffer = self.buffer.read(cx).snapshot(cx);
13843 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13844
13845 fn update_selection(
13846 selection: &Selection<usize>,
13847 buffer_snap: &MultiBufferSnapshot,
13848 ) -> Option<Selection<usize>> {
13849 let cursor = selection.head();
13850 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13851 for symbol in symbols.iter().rev() {
13852 let start = symbol.range.start.to_offset(buffer_snap);
13853 let end = symbol.range.end.to_offset(buffer_snap);
13854 let new_range = start..end;
13855 if start < selection.start || end > selection.end {
13856 return Some(Selection {
13857 id: selection.id,
13858 start: new_range.start,
13859 end: new_range.end,
13860 goal: SelectionGoal::None,
13861 reversed: selection.reversed,
13862 });
13863 }
13864 }
13865 None
13866 }
13867
13868 let mut selected_larger_symbol = false;
13869 let new_selections = old_selections
13870 .iter()
13871 .map(|selection| match update_selection(selection, &buffer) {
13872 Some(new_selection) => {
13873 if new_selection.range() != selection.range() {
13874 selected_larger_symbol = true;
13875 }
13876 new_selection
13877 }
13878 None => selection.clone(),
13879 })
13880 .collect::<Vec<_>>();
13881
13882 if selected_larger_symbol {
13883 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13884 s.select(new_selections);
13885 });
13886 }
13887 }
13888
13889 pub fn select_larger_syntax_node(
13890 &mut self,
13891 _: &SelectLargerSyntaxNode,
13892 window: &mut Window,
13893 cx: &mut Context<Self>,
13894 ) {
13895 let Some(visible_row_count) = self.visible_row_count() else {
13896 return;
13897 };
13898 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13899 if old_selections.is_empty() {
13900 return;
13901 }
13902
13903 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13904
13905 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13906 let buffer = self.buffer.read(cx).snapshot(cx);
13907
13908 let mut selected_larger_node = false;
13909 let mut new_selections = old_selections
13910 .iter()
13911 .map(|selection| {
13912 let old_range = selection.start..selection.end;
13913
13914 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13915 // manually select word at selection
13916 if ["string_content", "inline"].contains(&node.kind()) {
13917 let word_range = {
13918 let display_point = buffer
13919 .offset_to_point(old_range.start)
13920 .to_display_point(&display_map);
13921 let Range { start, end } =
13922 movement::surrounding_word(&display_map, display_point);
13923 start.to_point(&display_map).to_offset(&buffer)
13924 ..end.to_point(&display_map).to_offset(&buffer)
13925 };
13926 // ignore if word is already selected
13927 if !word_range.is_empty() && old_range != word_range {
13928 let last_word_range = {
13929 let display_point = buffer
13930 .offset_to_point(old_range.end)
13931 .to_display_point(&display_map);
13932 let Range { start, end } =
13933 movement::surrounding_word(&display_map, display_point);
13934 start.to_point(&display_map).to_offset(&buffer)
13935 ..end.to_point(&display_map).to_offset(&buffer)
13936 };
13937 // only select word if start and end point belongs to same word
13938 if word_range == last_word_range {
13939 selected_larger_node = true;
13940 return Selection {
13941 id: selection.id,
13942 start: word_range.start,
13943 end: word_range.end,
13944 goal: SelectionGoal::None,
13945 reversed: selection.reversed,
13946 };
13947 }
13948 }
13949 }
13950 }
13951
13952 let mut new_range = old_range.clone();
13953 while let Some((_node, containing_range)) =
13954 buffer.syntax_ancestor(new_range.clone())
13955 {
13956 new_range = match containing_range {
13957 MultiOrSingleBufferOffsetRange::Single(_) => break,
13958 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13959 };
13960 if !display_map.intersects_fold(new_range.start)
13961 && !display_map.intersects_fold(new_range.end)
13962 {
13963 break;
13964 }
13965 }
13966
13967 selected_larger_node |= new_range != old_range;
13968 Selection {
13969 id: selection.id,
13970 start: new_range.start,
13971 end: new_range.end,
13972 goal: SelectionGoal::None,
13973 reversed: selection.reversed,
13974 }
13975 })
13976 .collect::<Vec<_>>();
13977
13978 if !selected_larger_node {
13979 return; // don't put this call in the history
13980 }
13981
13982 // scroll based on transformation done to the last selection created by the user
13983 let (last_old, last_new) = old_selections
13984 .last()
13985 .zip(new_selections.last().cloned())
13986 .expect("old_selections isn't empty");
13987
13988 // revert selection
13989 let is_selection_reversed = {
13990 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13991 new_selections.last_mut().expect("checked above").reversed =
13992 should_newest_selection_be_reversed;
13993 should_newest_selection_be_reversed
13994 };
13995
13996 if selected_larger_node {
13997 self.select_syntax_node_history.disable_clearing = true;
13998 self.change_selections(None, window, cx, |s| {
13999 s.select(new_selections.clone());
14000 });
14001 self.select_syntax_node_history.disable_clearing = false;
14002 }
14003
14004 let start_row = last_new.start.to_display_point(&display_map).row().0;
14005 let end_row = last_new.end.to_display_point(&display_map).row().0;
14006 let selection_height = end_row - start_row + 1;
14007 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14008
14009 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14010 let scroll_behavior = if fits_on_the_screen {
14011 self.request_autoscroll(Autoscroll::fit(), cx);
14012 SelectSyntaxNodeScrollBehavior::FitSelection
14013 } else if is_selection_reversed {
14014 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14015 SelectSyntaxNodeScrollBehavior::CursorTop
14016 } else {
14017 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14018 SelectSyntaxNodeScrollBehavior::CursorBottom
14019 };
14020
14021 self.select_syntax_node_history.push((
14022 old_selections,
14023 scroll_behavior,
14024 is_selection_reversed,
14025 ));
14026 }
14027
14028 pub fn select_smaller_syntax_node(
14029 &mut self,
14030 _: &SelectSmallerSyntaxNode,
14031 window: &mut Window,
14032 cx: &mut Context<Self>,
14033 ) {
14034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14035
14036 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14037 self.select_syntax_node_history.pop()
14038 {
14039 if let Some(selection) = selections.last_mut() {
14040 selection.reversed = is_selection_reversed;
14041 }
14042
14043 self.select_syntax_node_history.disable_clearing = true;
14044 self.change_selections(None, window, cx, |s| {
14045 s.select(selections.to_vec());
14046 });
14047 self.select_syntax_node_history.disable_clearing = false;
14048
14049 match scroll_behavior {
14050 SelectSyntaxNodeScrollBehavior::CursorTop => {
14051 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14052 }
14053 SelectSyntaxNodeScrollBehavior::FitSelection => {
14054 self.request_autoscroll(Autoscroll::fit(), cx);
14055 }
14056 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14057 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14058 }
14059 }
14060 }
14061 }
14062
14063 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14064 if !EditorSettings::get_global(cx).gutter.runnables {
14065 self.clear_tasks();
14066 return Task::ready(());
14067 }
14068 let project = self.project.as_ref().map(Entity::downgrade);
14069 let task_sources = self.lsp_task_sources(cx);
14070 let multi_buffer = self.buffer.downgrade();
14071 cx.spawn_in(window, async move |editor, cx| {
14072 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14073 let Some(project) = project.and_then(|p| p.upgrade()) else {
14074 return;
14075 };
14076 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14077 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14078 }) else {
14079 return;
14080 };
14081
14082 let hide_runnables = project
14083 .update(cx, |project, cx| {
14084 // Do not display any test indicators in non-dev server remote projects.
14085 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14086 })
14087 .unwrap_or(true);
14088 if hide_runnables {
14089 return;
14090 }
14091 let new_rows =
14092 cx.background_spawn({
14093 let snapshot = display_snapshot.clone();
14094 async move {
14095 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14096 }
14097 })
14098 .await;
14099 let Ok(lsp_tasks) =
14100 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14101 else {
14102 return;
14103 };
14104 let lsp_tasks = lsp_tasks.await;
14105
14106 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14107 lsp_tasks
14108 .into_iter()
14109 .flat_map(|(kind, tasks)| {
14110 tasks.into_iter().filter_map(move |(location, task)| {
14111 Some((kind.clone(), location?, task))
14112 })
14113 })
14114 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14115 let buffer = location.target.buffer;
14116 let buffer_snapshot = buffer.read(cx).snapshot();
14117 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14118 |(excerpt_id, snapshot, _)| {
14119 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14120 display_snapshot
14121 .buffer_snapshot
14122 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14123 } else {
14124 None
14125 }
14126 },
14127 );
14128 if let Some(offset) = offset {
14129 let task_buffer_range =
14130 location.target.range.to_point(&buffer_snapshot);
14131 let context_buffer_range =
14132 task_buffer_range.to_offset(&buffer_snapshot);
14133 let context_range = BufferOffset(context_buffer_range.start)
14134 ..BufferOffset(context_buffer_range.end);
14135
14136 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14137 .or_insert_with(|| RunnableTasks {
14138 templates: Vec::new(),
14139 offset,
14140 column: task_buffer_range.start.column,
14141 extra_variables: HashMap::default(),
14142 context_range,
14143 })
14144 .templates
14145 .push((kind, task.original_task().clone()));
14146 }
14147
14148 acc
14149 })
14150 }) else {
14151 return;
14152 };
14153
14154 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14155 buffer.language_settings(cx).tasks.prefer_lsp
14156 }) else {
14157 return;
14158 };
14159
14160 let rows = Self::runnable_rows(
14161 project,
14162 display_snapshot,
14163 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14164 new_rows,
14165 cx.clone(),
14166 )
14167 .await;
14168 editor
14169 .update(cx, |editor, _| {
14170 editor.clear_tasks();
14171 for (key, mut value) in rows {
14172 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14173 value.templates.extend(lsp_tasks.templates);
14174 }
14175
14176 editor.insert_tasks(key, value);
14177 }
14178 for (key, value) in lsp_tasks_by_rows {
14179 editor.insert_tasks(key, value);
14180 }
14181 })
14182 .ok();
14183 })
14184 }
14185 fn fetch_runnable_ranges(
14186 snapshot: &DisplaySnapshot,
14187 range: Range<Anchor>,
14188 ) -> Vec<language::RunnableRange> {
14189 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14190 }
14191
14192 fn runnable_rows(
14193 project: Entity<Project>,
14194 snapshot: DisplaySnapshot,
14195 prefer_lsp: bool,
14196 runnable_ranges: Vec<RunnableRange>,
14197 cx: AsyncWindowContext,
14198 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14199 cx.spawn(async move |cx| {
14200 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14201 for mut runnable in runnable_ranges {
14202 let Some(tasks) = cx
14203 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14204 .ok()
14205 else {
14206 continue;
14207 };
14208 let mut tasks = tasks.await;
14209
14210 if prefer_lsp {
14211 tasks.retain(|(task_kind, _)| {
14212 !matches!(task_kind, TaskSourceKind::Language { .. })
14213 });
14214 }
14215 if tasks.is_empty() {
14216 continue;
14217 }
14218
14219 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14220 let Some(row) = snapshot
14221 .buffer_snapshot
14222 .buffer_line_for_row(MultiBufferRow(point.row))
14223 .map(|(_, range)| range.start.row)
14224 else {
14225 continue;
14226 };
14227
14228 let context_range =
14229 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14230 runnable_rows.push((
14231 (runnable.buffer_id, row),
14232 RunnableTasks {
14233 templates: tasks,
14234 offset: snapshot
14235 .buffer_snapshot
14236 .anchor_before(runnable.run_range.start),
14237 context_range,
14238 column: point.column,
14239 extra_variables: runnable.extra_captures,
14240 },
14241 ));
14242 }
14243 runnable_rows
14244 })
14245 }
14246
14247 fn templates_with_tags(
14248 project: &Entity<Project>,
14249 runnable: &mut Runnable,
14250 cx: &mut App,
14251 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14252 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14253 let (worktree_id, file) = project
14254 .buffer_for_id(runnable.buffer, cx)
14255 .and_then(|buffer| buffer.read(cx).file())
14256 .map(|file| (file.worktree_id(cx), file.clone()))
14257 .unzip();
14258
14259 (
14260 project.task_store().read(cx).task_inventory().cloned(),
14261 worktree_id,
14262 file,
14263 )
14264 });
14265
14266 let tags = mem::take(&mut runnable.tags);
14267 let language = runnable.language.clone();
14268 cx.spawn(async move |cx| {
14269 let mut templates_with_tags = Vec::new();
14270 if let Some(inventory) = inventory {
14271 for RunnableTag(tag) in tags {
14272 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14273 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14274 }) else {
14275 return templates_with_tags;
14276 };
14277 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14278 move |(_, template)| {
14279 template.tags.iter().any(|source_tag| source_tag == &tag)
14280 },
14281 ));
14282 }
14283 }
14284 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14285
14286 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14287 // Strongest source wins; if we have worktree tag binding, prefer that to
14288 // global and language bindings;
14289 // if we have a global binding, prefer that to language binding.
14290 let first_mismatch = templates_with_tags
14291 .iter()
14292 .position(|(tag_source, _)| tag_source != leading_tag_source);
14293 if let Some(index) = first_mismatch {
14294 templates_with_tags.truncate(index);
14295 }
14296 }
14297
14298 templates_with_tags
14299 })
14300 }
14301
14302 pub fn move_to_enclosing_bracket(
14303 &mut self,
14304 _: &MoveToEnclosingBracket,
14305 window: &mut Window,
14306 cx: &mut Context<Self>,
14307 ) {
14308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14309 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14310 s.move_offsets_with(|snapshot, selection| {
14311 let Some(enclosing_bracket_ranges) =
14312 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14313 else {
14314 return;
14315 };
14316
14317 let mut best_length = usize::MAX;
14318 let mut best_inside = false;
14319 let mut best_in_bracket_range = false;
14320 let mut best_destination = None;
14321 for (open, close) in enclosing_bracket_ranges {
14322 let close = close.to_inclusive();
14323 let length = close.end() - open.start;
14324 let inside = selection.start >= open.end && selection.end <= *close.start();
14325 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14326 || close.contains(&selection.head());
14327
14328 // If best is next to a bracket and current isn't, skip
14329 if !in_bracket_range && best_in_bracket_range {
14330 continue;
14331 }
14332
14333 // Prefer smaller lengths unless best is inside and current isn't
14334 if length > best_length && (best_inside || !inside) {
14335 continue;
14336 }
14337
14338 best_length = length;
14339 best_inside = inside;
14340 best_in_bracket_range = in_bracket_range;
14341 best_destination = Some(
14342 if close.contains(&selection.start) && close.contains(&selection.end) {
14343 if inside { open.end } else { open.start }
14344 } else if inside {
14345 *close.start()
14346 } else {
14347 *close.end()
14348 },
14349 );
14350 }
14351
14352 if let Some(destination) = best_destination {
14353 selection.collapse_to(destination, SelectionGoal::None);
14354 }
14355 })
14356 });
14357 }
14358
14359 pub fn undo_selection(
14360 &mut self,
14361 _: &UndoSelection,
14362 window: &mut Window,
14363 cx: &mut Context<Self>,
14364 ) {
14365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14366 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14367 self.selection_history.mode = SelectionHistoryMode::Undoing;
14368 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14369 this.end_selection(window, cx);
14370 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14371 s.select_anchors(entry.selections.to_vec())
14372 });
14373 });
14374 self.selection_history.mode = SelectionHistoryMode::Normal;
14375
14376 self.select_next_state = entry.select_next_state;
14377 self.select_prev_state = entry.select_prev_state;
14378 self.add_selections_state = entry.add_selections_state;
14379 }
14380 }
14381
14382 pub fn redo_selection(
14383 &mut self,
14384 _: &RedoSelection,
14385 window: &mut Window,
14386 cx: &mut Context<Self>,
14387 ) {
14388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14389 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14390 self.selection_history.mode = SelectionHistoryMode::Redoing;
14391 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14392 this.end_selection(window, cx);
14393 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14394 s.select_anchors(entry.selections.to_vec())
14395 });
14396 });
14397 self.selection_history.mode = SelectionHistoryMode::Normal;
14398
14399 self.select_next_state = entry.select_next_state;
14400 self.select_prev_state = entry.select_prev_state;
14401 self.add_selections_state = entry.add_selections_state;
14402 }
14403 }
14404
14405 pub fn expand_excerpts(
14406 &mut self,
14407 action: &ExpandExcerpts,
14408 _: &mut Window,
14409 cx: &mut Context<Self>,
14410 ) {
14411 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14412 }
14413
14414 pub fn expand_excerpts_down(
14415 &mut self,
14416 action: &ExpandExcerptsDown,
14417 _: &mut Window,
14418 cx: &mut Context<Self>,
14419 ) {
14420 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14421 }
14422
14423 pub fn expand_excerpts_up(
14424 &mut self,
14425 action: &ExpandExcerptsUp,
14426 _: &mut Window,
14427 cx: &mut Context<Self>,
14428 ) {
14429 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14430 }
14431
14432 pub fn expand_excerpts_for_direction(
14433 &mut self,
14434 lines: u32,
14435 direction: ExpandExcerptDirection,
14436
14437 cx: &mut Context<Self>,
14438 ) {
14439 let selections = self.selections.disjoint_anchors();
14440
14441 let lines = if lines == 0 {
14442 EditorSettings::get_global(cx).expand_excerpt_lines
14443 } else {
14444 lines
14445 };
14446
14447 self.buffer.update(cx, |buffer, cx| {
14448 let snapshot = buffer.snapshot(cx);
14449 let mut excerpt_ids = selections
14450 .iter()
14451 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14452 .collect::<Vec<_>>();
14453 excerpt_ids.sort();
14454 excerpt_ids.dedup();
14455 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14456 })
14457 }
14458
14459 pub fn expand_excerpt(
14460 &mut self,
14461 excerpt: ExcerptId,
14462 direction: ExpandExcerptDirection,
14463 window: &mut Window,
14464 cx: &mut Context<Self>,
14465 ) {
14466 let current_scroll_position = self.scroll_position(cx);
14467 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14468 let mut should_scroll_up = false;
14469
14470 if direction == ExpandExcerptDirection::Down {
14471 let multi_buffer = self.buffer.read(cx);
14472 let snapshot = multi_buffer.snapshot(cx);
14473 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14474 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14475 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14476 let buffer_snapshot = buffer.read(cx).snapshot();
14477 let excerpt_end_row =
14478 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14479 let last_row = buffer_snapshot.max_point().row;
14480 let lines_below = last_row.saturating_sub(excerpt_end_row);
14481 should_scroll_up = lines_below >= lines_to_expand;
14482 }
14483 }
14484 }
14485 }
14486
14487 self.buffer.update(cx, |buffer, cx| {
14488 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14489 });
14490
14491 if should_scroll_up {
14492 let new_scroll_position =
14493 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14494 self.set_scroll_position(new_scroll_position, window, cx);
14495 }
14496 }
14497
14498 pub fn go_to_singleton_buffer_point(
14499 &mut self,
14500 point: Point,
14501 window: &mut Window,
14502 cx: &mut Context<Self>,
14503 ) {
14504 self.go_to_singleton_buffer_range(point..point, window, cx);
14505 }
14506
14507 pub fn go_to_singleton_buffer_range(
14508 &mut self,
14509 range: Range<Point>,
14510 window: &mut Window,
14511 cx: &mut Context<Self>,
14512 ) {
14513 let multibuffer = self.buffer().read(cx);
14514 let Some(buffer) = multibuffer.as_singleton() else {
14515 return;
14516 };
14517 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14518 return;
14519 };
14520 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14521 return;
14522 };
14523 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14524 s.select_anchor_ranges([start..end])
14525 });
14526 }
14527
14528 pub fn go_to_diagnostic(
14529 &mut self,
14530 _: &GoToDiagnostic,
14531 window: &mut Window,
14532 cx: &mut Context<Self>,
14533 ) {
14534 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14535 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14536 }
14537
14538 pub fn go_to_prev_diagnostic(
14539 &mut self,
14540 _: &GoToPreviousDiagnostic,
14541 window: &mut Window,
14542 cx: &mut Context<Self>,
14543 ) {
14544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14545 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14546 }
14547
14548 pub fn go_to_diagnostic_impl(
14549 &mut self,
14550 direction: Direction,
14551 window: &mut Window,
14552 cx: &mut Context<Self>,
14553 ) {
14554 let buffer = self.buffer.read(cx).snapshot(cx);
14555 let selection = self.selections.newest::<usize>(cx);
14556
14557 let mut active_group_id = None;
14558 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14559 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14560 active_group_id = Some(active_group.group_id);
14561 }
14562 }
14563
14564 fn filtered(
14565 snapshot: EditorSnapshot,
14566 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14567 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14568 diagnostics
14569 .filter(|entry| entry.range.start != entry.range.end)
14570 .filter(|entry| !entry.diagnostic.is_unnecessary)
14571 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14572 }
14573
14574 let snapshot = self.snapshot(window, cx);
14575 let before = filtered(
14576 snapshot.clone(),
14577 buffer
14578 .diagnostics_in_range(0..selection.start)
14579 .filter(|entry| entry.range.start <= selection.start),
14580 );
14581 let after = filtered(
14582 snapshot,
14583 buffer
14584 .diagnostics_in_range(selection.start..buffer.len())
14585 .filter(|entry| entry.range.start >= selection.start),
14586 );
14587
14588 let mut found: Option<DiagnosticEntry<usize>> = None;
14589 if direction == Direction::Prev {
14590 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14591 {
14592 for diagnostic in prev_diagnostics.into_iter().rev() {
14593 if diagnostic.range.start != selection.start
14594 || active_group_id
14595 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14596 {
14597 found = Some(diagnostic);
14598 break 'outer;
14599 }
14600 }
14601 }
14602 } else {
14603 for diagnostic in after.chain(before) {
14604 if diagnostic.range.start != selection.start
14605 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14606 {
14607 found = Some(diagnostic);
14608 break;
14609 }
14610 }
14611 }
14612 let Some(next_diagnostic) = found else {
14613 return;
14614 };
14615
14616 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14617 return;
14618 };
14619 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14620 s.select_ranges(vec![
14621 next_diagnostic.range.start..next_diagnostic.range.start,
14622 ])
14623 });
14624 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14625 self.refresh_inline_completion(false, true, window, cx);
14626 }
14627
14628 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14630 let snapshot = self.snapshot(window, cx);
14631 let selection = self.selections.newest::<Point>(cx);
14632 self.go_to_hunk_before_or_after_position(
14633 &snapshot,
14634 selection.head(),
14635 Direction::Next,
14636 window,
14637 cx,
14638 );
14639 }
14640
14641 pub fn go_to_hunk_before_or_after_position(
14642 &mut self,
14643 snapshot: &EditorSnapshot,
14644 position: Point,
14645 direction: Direction,
14646 window: &mut Window,
14647 cx: &mut Context<Editor>,
14648 ) {
14649 let row = if direction == Direction::Next {
14650 self.hunk_after_position(snapshot, position)
14651 .map(|hunk| hunk.row_range.start)
14652 } else {
14653 self.hunk_before_position(snapshot, position)
14654 };
14655
14656 if let Some(row) = row {
14657 let destination = Point::new(row.0, 0);
14658 let autoscroll = Autoscroll::center();
14659
14660 self.unfold_ranges(&[destination..destination], false, false, cx);
14661 self.change_selections(Some(autoscroll), window, cx, |s| {
14662 s.select_ranges([destination..destination]);
14663 });
14664 }
14665 }
14666
14667 fn hunk_after_position(
14668 &mut self,
14669 snapshot: &EditorSnapshot,
14670 position: Point,
14671 ) -> Option<MultiBufferDiffHunk> {
14672 snapshot
14673 .buffer_snapshot
14674 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14675 .find(|hunk| hunk.row_range.start.0 > position.row)
14676 .or_else(|| {
14677 snapshot
14678 .buffer_snapshot
14679 .diff_hunks_in_range(Point::zero()..position)
14680 .find(|hunk| hunk.row_range.end.0 < position.row)
14681 })
14682 }
14683
14684 fn go_to_prev_hunk(
14685 &mut self,
14686 _: &GoToPreviousHunk,
14687 window: &mut Window,
14688 cx: &mut Context<Self>,
14689 ) {
14690 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14691 let snapshot = self.snapshot(window, cx);
14692 let selection = self.selections.newest::<Point>(cx);
14693 self.go_to_hunk_before_or_after_position(
14694 &snapshot,
14695 selection.head(),
14696 Direction::Prev,
14697 window,
14698 cx,
14699 );
14700 }
14701
14702 fn hunk_before_position(
14703 &mut self,
14704 snapshot: &EditorSnapshot,
14705 position: Point,
14706 ) -> Option<MultiBufferRow> {
14707 snapshot
14708 .buffer_snapshot
14709 .diff_hunk_before(position)
14710 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14711 }
14712
14713 fn go_to_next_change(
14714 &mut self,
14715 _: &GoToNextChange,
14716 window: &mut Window,
14717 cx: &mut Context<Self>,
14718 ) {
14719 if let Some(selections) = self
14720 .change_list
14721 .next_change(1, Direction::Next)
14722 .map(|s| s.to_vec())
14723 {
14724 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14725 let map = s.display_map();
14726 s.select_display_ranges(selections.iter().map(|a| {
14727 let point = a.to_display_point(&map);
14728 point..point
14729 }))
14730 })
14731 }
14732 }
14733
14734 fn go_to_previous_change(
14735 &mut self,
14736 _: &GoToPreviousChange,
14737 window: &mut Window,
14738 cx: &mut Context<Self>,
14739 ) {
14740 if let Some(selections) = self
14741 .change_list
14742 .next_change(1, Direction::Prev)
14743 .map(|s| s.to_vec())
14744 {
14745 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14746 let map = s.display_map();
14747 s.select_display_ranges(selections.iter().map(|a| {
14748 let point = a.to_display_point(&map);
14749 point..point
14750 }))
14751 })
14752 }
14753 }
14754
14755 fn go_to_line<T: 'static>(
14756 &mut self,
14757 position: Anchor,
14758 highlight_color: Option<Hsla>,
14759 window: &mut Window,
14760 cx: &mut Context<Self>,
14761 ) {
14762 let snapshot = self.snapshot(window, cx).display_snapshot;
14763 let position = position.to_point(&snapshot.buffer_snapshot);
14764 let start = snapshot
14765 .buffer_snapshot
14766 .clip_point(Point::new(position.row, 0), Bias::Left);
14767 let end = start + Point::new(1, 0);
14768 let start = snapshot.buffer_snapshot.anchor_before(start);
14769 let end = snapshot.buffer_snapshot.anchor_before(end);
14770
14771 self.highlight_rows::<T>(
14772 start..end,
14773 highlight_color
14774 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14775 Default::default(),
14776 cx,
14777 );
14778
14779 if self.buffer.read(cx).is_singleton() {
14780 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14781 }
14782 }
14783
14784 pub fn go_to_definition(
14785 &mut self,
14786 _: &GoToDefinition,
14787 window: &mut Window,
14788 cx: &mut Context<Self>,
14789 ) -> Task<Result<Navigated>> {
14790 let definition =
14791 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14792 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14793 cx.spawn_in(window, async move |editor, cx| {
14794 if definition.await? == Navigated::Yes {
14795 return Ok(Navigated::Yes);
14796 }
14797 match fallback_strategy {
14798 GoToDefinitionFallback::None => Ok(Navigated::No),
14799 GoToDefinitionFallback::FindAllReferences => {
14800 match editor.update_in(cx, |editor, window, cx| {
14801 editor.find_all_references(&FindAllReferences, window, cx)
14802 })? {
14803 Some(references) => references.await,
14804 None => Ok(Navigated::No),
14805 }
14806 }
14807 }
14808 })
14809 }
14810
14811 pub fn go_to_declaration(
14812 &mut self,
14813 _: &GoToDeclaration,
14814 window: &mut Window,
14815 cx: &mut Context<Self>,
14816 ) -> Task<Result<Navigated>> {
14817 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14818 }
14819
14820 pub fn go_to_declaration_split(
14821 &mut self,
14822 _: &GoToDeclaration,
14823 window: &mut Window,
14824 cx: &mut Context<Self>,
14825 ) -> Task<Result<Navigated>> {
14826 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14827 }
14828
14829 pub fn go_to_implementation(
14830 &mut self,
14831 _: &GoToImplementation,
14832 window: &mut Window,
14833 cx: &mut Context<Self>,
14834 ) -> Task<Result<Navigated>> {
14835 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14836 }
14837
14838 pub fn go_to_implementation_split(
14839 &mut self,
14840 _: &GoToImplementationSplit,
14841 window: &mut Window,
14842 cx: &mut Context<Self>,
14843 ) -> Task<Result<Navigated>> {
14844 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14845 }
14846
14847 pub fn go_to_type_definition(
14848 &mut self,
14849 _: &GoToTypeDefinition,
14850 window: &mut Window,
14851 cx: &mut Context<Self>,
14852 ) -> Task<Result<Navigated>> {
14853 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14854 }
14855
14856 pub fn go_to_definition_split(
14857 &mut self,
14858 _: &GoToDefinitionSplit,
14859 window: &mut Window,
14860 cx: &mut Context<Self>,
14861 ) -> Task<Result<Navigated>> {
14862 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14863 }
14864
14865 pub fn go_to_type_definition_split(
14866 &mut self,
14867 _: &GoToTypeDefinitionSplit,
14868 window: &mut Window,
14869 cx: &mut Context<Self>,
14870 ) -> Task<Result<Navigated>> {
14871 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14872 }
14873
14874 fn go_to_definition_of_kind(
14875 &mut self,
14876 kind: GotoDefinitionKind,
14877 split: bool,
14878 window: &mut Window,
14879 cx: &mut Context<Self>,
14880 ) -> Task<Result<Navigated>> {
14881 let Some(provider) = self.semantics_provider.clone() else {
14882 return Task::ready(Ok(Navigated::No));
14883 };
14884 let head = self.selections.newest::<usize>(cx).head();
14885 let buffer = self.buffer.read(cx);
14886 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14887 text_anchor
14888 } else {
14889 return Task::ready(Ok(Navigated::No));
14890 };
14891
14892 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14893 return Task::ready(Ok(Navigated::No));
14894 };
14895
14896 cx.spawn_in(window, async move |editor, cx| {
14897 let definitions = definitions.await?;
14898 let navigated = editor
14899 .update_in(cx, |editor, window, cx| {
14900 editor.navigate_to_hover_links(
14901 Some(kind),
14902 definitions
14903 .into_iter()
14904 .filter(|location| {
14905 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14906 })
14907 .map(HoverLink::Text)
14908 .collect::<Vec<_>>(),
14909 split,
14910 window,
14911 cx,
14912 )
14913 })?
14914 .await?;
14915 anyhow::Ok(navigated)
14916 })
14917 }
14918
14919 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14920 let selection = self.selections.newest_anchor();
14921 let head = selection.head();
14922 let tail = selection.tail();
14923
14924 let Some((buffer, start_position)) =
14925 self.buffer.read(cx).text_anchor_for_position(head, cx)
14926 else {
14927 return;
14928 };
14929
14930 let end_position = if head != tail {
14931 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14932 return;
14933 };
14934 Some(pos)
14935 } else {
14936 None
14937 };
14938
14939 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14940 let url = if let Some(end_pos) = end_position {
14941 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14942 } else {
14943 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14944 };
14945
14946 if let Some(url) = url {
14947 editor.update(cx, |_, cx| {
14948 cx.open_url(&url);
14949 })
14950 } else {
14951 Ok(())
14952 }
14953 });
14954
14955 url_finder.detach();
14956 }
14957
14958 pub fn open_selected_filename(
14959 &mut self,
14960 _: &OpenSelectedFilename,
14961 window: &mut Window,
14962 cx: &mut Context<Self>,
14963 ) {
14964 let Some(workspace) = self.workspace() else {
14965 return;
14966 };
14967
14968 let position = self.selections.newest_anchor().head();
14969
14970 let Some((buffer, buffer_position)) =
14971 self.buffer.read(cx).text_anchor_for_position(position, cx)
14972 else {
14973 return;
14974 };
14975
14976 let project = self.project.clone();
14977
14978 cx.spawn_in(window, async move |_, cx| {
14979 let result = find_file(&buffer, project, buffer_position, cx).await;
14980
14981 if let Some((_, path)) = result {
14982 workspace
14983 .update_in(cx, |workspace, window, cx| {
14984 workspace.open_resolved_path(path, window, cx)
14985 })?
14986 .await?;
14987 }
14988 anyhow::Ok(())
14989 })
14990 .detach();
14991 }
14992
14993 pub(crate) fn navigate_to_hover_links(
14994 &mut self,
14995 kind: Option<GotoDefinitionKind>,
14996 mut definitions: Vec<HoverLink>,
14997 split: bool,
14998 window: &mut Window,
14999 cx: &mut Context<Editor>,
15000 ) -> Task<Result<Navigated>> {
15001 // If there is one definition, just open it directly
15002 if definitions.len() == 1 {
15003 let definition = definitions.pop().unwrap();
15004
15005 enum TargetTaskResult {
15006 Location(Option<Location>),
15007 AlreadyNavigated,
15008 }
15009
15010 let target_task = match definition {
15011 HoverLink::Text(link) => {
15012 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15013 }
15014 HoverLink::InlayHint(lsp_location, server_id) => {
15015 let computation =
15016 self.compute_target_location(lsp_location, server_id, window, cx);
15017 cx.background_spawn(async move {
15018 let location = computation.await?;
15019 Ok(TargetTaskResult::Location(location))
15020 })
15021 }
15022 HoverLink::Url(url) => {
15023 cx.open_url(&url);
15024 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15025 }
15026 HoverLink::File(path) => {
15027 if let Some(workspace) = self.workspace() {
15028 cx.spawn_in(window, async move |_, cx| {
15029 workspace
15030 .update_in(cx, |workspace, window, cx| {
15031 workspace.open_resolved_path(path, window, cx)
15032 })?
15033 .await
15034 .map(|_| TargetTaskResult::AlreadyNavigated)
15035 })
15036 } else {
15037 Task::ready(Ok(TargetTaskResult::Location(None)))
15038 }
15039 }
15040 };
15041 cx.spawn_in(window, async move |editor, cx| {
15042 let target = match target_task.await.context("target resolution task")? {
15043 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15044 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15045 TargetTaskResult::Location(Some(target)) => target,
15046 };
15047
15048 editor.update_in(cx, |editor, window, cx| {
15049 let Some(workspace) = editor.workspace() else {
15050 return Navigated::No;
15051 };
15052 let pane = workspace.read(cx).active_pane().clone();
15053
15054 let range = target.range.to_point(target.buffer.read(cx));
15055 let range = editor.range_for_match(&range);
15056 let range = collapse_multiline_range(range);
15057
15058 if !split
15059 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15060 {
15061 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15062 } else {
15063 window.defer(cx, move |window, cx| {
15064 let target_editor: Entity<Self> =
15065 workspace.update(cx, |workspace, cx| {
15066 let pane = if split {
15067 workspace.adjacent_pane(window, cx)
15068 } else {
15069 workspace.active_pane().clone()
15070 };
15071
15072 workspace.open_project_item(
15073 pane,
15074 target.buffer.clone(),
15075 true,
15076 true,
15077 window,
15078 cx,
15079 )
15080 });
15081 target_editor.update(cx, |target_editor, cx| {
15082 // When selecting a definition in a different buffer, disable the nav history
15083 // to avoid creating a history entry at the previous cursor location.
15084 pane.update(cx, |pane, _| pane.disable_history());
15085 target_editor.go_to_singleton_buffer_range(range, window, cx);
15086 pane.update(cx, |pane, _| pane.enable_history());
15087 });
15088 });
15089 }
15090 Navigated::Yes
15091 })
15092 })
15093 } else if !definitions.is_empty() {
15094 cx.spawn_in(window, async move |editor, cx| {
15095 let (title, location_tasks, workspace) = editor
15096 .update_in(cx, |editor, window, cx| {
15097 let tab_kind = match kind {
15098 Some(GotoDefinitionKind::Implementation) => "Implementations",
15099 _ => "Definitions",
15100 };
15101 let title = definitions
15102 .iter()
15103 .find_map(|definition| match definition {
15104 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15105 let buffer = origin.buffer.read(cx);
15106 format!(
15107 "{} for {}",
15108 tab_kind,
15109 buffer
15110 .text_for_range(origin.range.clone())
15111 .collect::<String>()
15112 )
15113 }),
15114 HoverLink::InlayHint(_, _) => None,
15115 HoverLink::Url(_) => None,
15116 HoverLink::File(_) => None,
15117 })
15118 .unwrap_or(tab_kind.to_string());
15119 let location_tasks = definitions
15120 .into_iter()
15121 .map(|definition| match definition {
15122 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15123 HoverLink::InlayHint(lsp_location, server_id) => editor
15124 .compute_target_location(lsp_location, server_id, window, cx),
15125 HoverLink::Url(_) => Task::ready(Ok(None)),
15126 HoverLink::File(_) => Task::ready(Ok(None)),
15127 })
15128 .collect::<Vec<_>>();
15129 (title, location_tasks, editor.workspace().clone())
15130 })
15131 .context("location tasks preparation")?;
15132
15133 let locations = future::join_all(location_tasks)
15134 .await
15135 .into_iter()
15136 .filter_map(|location| location.transpose())
15137 .collect::<Result<_>>()
15138 .context("location tasks")?;
15139
15140 let Some(workspace) = workspace else {
15141 return Ok(Navigated::No);
15142 };
15143 let opened = workspace
15144 .update_in(cx, |workspace, window, cx| {
15145 Self::open_locations_in_multibuffer(
15146 workspace,
15147 locations,
15148 title,
15149 split,
15150 MultibufferSelectionMode::First,
15151 window,
15152 cx,
15153 )
15154 })
15155 .ok();
15156
15157 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15158 })
15159 } else {
15160 Task::ready(Ok(Navigated::No))
15161 }
15162 }
15163
15164 fn compute_target_location(
15165 &self,
15166 lsp_location: lsp::Location,
15167 server_id: LanguageServerId,
15168 window: &mut Window,
15169 cx: &mut Context<Self>,
15170 ) -> Task<anyhow::Result<Option<Location>>> {
15171 let Some(project) = self.project.clone() else {
15172 return Task::ready(Ok(None));
15173 };
15174
15175 cx.spawn_in(window, async move |editor, cx| {
15176 let location_task = editor.update(cx, |_, cx| {
15177 project.update(cx, |project, cx| {
15178 let language_server_name = project
15179 .language_server_statuses(cx)
15180 .find(|(id, _)| server_id == *id)
15181 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15182 language_server_name.map(|language_server_name| {
15183 project.open_local_buffer_via_lsp(
15184 lsp_location.uri.clone(),
15185 server_id,
15186 language_server_name,
15187 cx,
15188 )
15189 })
15190 })
15191 })?;
15192 let location = match location_task {
15193 Some(task) => Some({
15194 let target_buffer_handle = task.await.context("open local buffer")?;
15195 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15196 let target_start = target_buffer
15197 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15198 let target_end = target_buffer
15199 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15200 target_buffer.anchor_after(target_start)
15201 ..target_buffer.anchor_before(target_end)
15202 })?;
15203 Location {
15204 buffer: target_buffer_handle,
15205 range,
15206 }
15207 }),
15208 None => None,
15209 };
15210 Ok(location)
15211 })
15212 }
15213
15214 pub fn find_all_references(
15215 &mut self,
15216 _: &FindAllReferences,
15217 window: &mut Window,
15218 cx: &mut Context<Self>,
15219 ) -> Option<Task<Result<Navigated>>> {
15220 let selection = self.selections.newest::<usize>(cx);
15221 let multi_buffer = self.buffer.read(cx);
15222 let head = selection.head();
15223
15224 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15225 let head_anchor = multi_buffer_snapshot.anchor_at(
15226 head,
15227 if head < selection.tail() {
15228 Bias::Right
15229 } else {
15230 Bias::Left
15231 },
15232 );
15233
15234 match self
15235 .find_all_references_task_sources
15236 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15237 {
15238 Ok(_) => {
15239 log::info!(
15240 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15241 );
15242 return None;
15243 }
15244 Err(i) => {
15245 self.find_all_references_task_sources.insert(i, head_anchor);
15246 }
15247 }
15248
15249 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15250 let workspace = self.workspace()?;
15251 let project = workspace.read(cx).project().clone();
15252 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15253 Some(cx.spawn_in(window, async move |editor, cx| {
15254 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15255 if let Ok(i) = editor
15256 .find_all_references_task_sources
15257 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15258 {
15259 editor.find_all_references_task_sources.remove(i);
15260 }
15261 });
15262
15263 let locations = references.await?;
15264 if locations.is_empty() {
15265 return anyhow::Ok(Navigated::No);
15266 }
15267
15268 workspace.update_in(cx, |workspace, window, cx| {
15269 let title = locations
15270 .first()
15271 .as_ref()
15272 .map(|location| {
15273 let buffer = location.buffer.read(cx);
15274 format!(
15275 "References to `{}`",
15276 buffer
15277 .text_for_range(location.range.clone())
15278 .collect::<String>()
15279 )
15280 })
15281 .unwrap();
15282 Self::open_locations_in_multibuffer(
15283 workspace,
15284 locations,
15285 title,
15286 false,
15287 MultibufferSelectionMode::First,
15288 window,
15289 cx,
15290 );
15291 Navigated::Yes
15292 })
15293 }))
15294 }
15295
15296 /// Opens a multibuffer with the given project locations in it
15297 pub fn open_locations_in_multibuffer(
15298 workspace: &mut Workspace,
15299 mut locations: Vec<Location>,
15300 title: String,
15301 split: bool,
15302 multibuffer_selection_mode: MultibufferSelectionMode,
15303 window: &mut Window,
15304 cx: &mut Context<Workspace>,
15305 ) {
15306 // If there are multiple definitions, open them in a multibuffer
15307 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15308 let mut locations = locations.into_iter().peekable();
15309 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15310 let capability = workspace.project().read(cx).capability();
15311
15312 let excerpt_buffer = cx.new(|cx| {
15313 let mut multibuffer = MultiBuffer::new(capability);
15314 while let Some(location) = locations.next() {
15315 let buffer = location.buffer.read(cx);
15316 let mut ranges_for_buffer = Vec::new();
15317 let range = location.range.to_point(buffer);
15318 ranges_for_buffer.push(range.clone());
15319
15320 while let Some(next_location) = locations.peek() {
15321 if next_location.buffer == location.buffer {
15322 ranges_for_buffer.push(next_location.range.to_point(buffer));
15323 locations.next();
15324 } else {
15325 break;
15326 }
15327 }
15328
15329 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15330 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15331 PathKey::for_buffer(&location.buffer, cx),
15332 location.buffer.clone(),
15333 ranges_for_buffer,
15334 DEFAULT_MULTIBUFFER_CONTEXT,
15335 cx,
15336 );
15337 ranges.extend(new_ranges)
15338 }
15339
15340 multibuffer.with_title(title)
15341 });
15342
15343 let editor = cx.new(|cx| {
15344 Editor::for_multibuffer(
15345 excerpt_buffer,
15346 Some(workspace.project().clone()),
15347 window,
15348 cx,
15349 )
15350 });
15351 editor.update(cx, |editor, cx| {
15352 match multibuffer_selection_mode {
15353 MultibufferSelectionMode::First => {
15354 if let Some(first_range) = ranges.first() {
15355 editor.change_selections(None, window, cx, |selections| {
15356 selections.clear_disjoint();
15357 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15358 });
15359 }
15360 editor.highlight_background::<Self>(
15361 &ranges,
15362 |theme| theme.colors().editor_highlighted_line_background,
15363 cx,
15364 );
15365 }
15366 MultibufferSelectionMode::All => {
15367 editor.change_selections(None, window, cx, |selections| {
15368 selections.clear_disjoint();
15369 selections.select_anchor_ranges(ranges);
15370 });
15371 }
15372 }
15373 editor.register_buffers_with_language_servers(cx);
15374 });
15375
15376 let item = Box::new(editor);
15377 let item_id = item.item_id();
15378
15379 if split {
15380 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15381 } else {
15382 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15383 let (preview_item_id, preview_item_idx) =
15384 workspace.active_pane().read_with(cx, |pane, _| {
15385 (pane.preview_item_id(), pane.preview_item_idx())
15386 });
15387
15388 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15389
15390 if let Some(preview_item_id) = preview_item_id {
15391 workspace.active_pane().update(cx, |pane, cx| {
15392 pane.remove_item(preview_item_id, false, false, window, cx);
15393 });
15394 }
15395 } else {
15396 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15397 }
15398 }
15399 workspace.active_pane().update(cx, |pane, cx| {
15400 pane.set_preview_item_id(Some(item_id), cx);
15401 });
15402 }
15403
15404 pub fn rename(
15405 &mut self,
15406 _: &Rename,
15407 window: &mut Window,
15408 cx: &mut Context<Self>,
15409 ) -> Option<Task<Result<()>>> {
15410 use language::ToOffset as _;
15411
15412 let provider = self.semantics_provider.clone()?;
15413 let selection = self.selections.newest_anchor().clone();
15414 let (cursor_buffer, cursor_buffer_position) = self
15415 .buffer
15416 .read(cx)
15417 .text_anchor_for_position(selection.head(), cx)?;
15418 let (tail_buffer, cursor_buffer_position_end) = self
15419 .buffer
15420 .read(cx)
15421 .text_anchor_for_position(selection.tail(), cx)?;
15422 if tail_buffer != cursor_buffer {
15423 return None;
15424 }
15425
15426 let snapshot = cursor_buffer.read(cx).snapshot();
15427 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15428 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15429 let prepare_rename = provider
15430 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15431 .unwrap_or_else(|| Task::ready(Ok(None)));
15432 drop(snapshot);
15433
15434 Some(cx.spawn_in(window, async move |this, cx| {
15435 let rename_range = if let Some(range) = prepare_rename.await? {
15436 Some(range)
15437 } else {
15438 this.update(cx, |this, cx| {
15439 let buffer = this.buffer.read(cx).snapshot(cx);
15440 let mut buffer_highlights = this
15441 .document_highlights_for_position(selection.head(), &buffer)
15442 .filter(|highlight| {
15443 highlight.start.excerpt_id == selection.head().excerpt_id
15444 && highlight.end.excerpt_id == selection.head().excerpt_id
15445 });
15446 buffer_highlights
15447 .next()
15448 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15449 })?
15450 };
15451 if let Some(rename_range) = rename_range {
15452 this.update_in(cx, |this, window, cx| {
15453 let snapshot = cursor_buffer.read(cx).snapshot();
15454 let rename_buffer_range = rename_range.to_offset(&snapshot);
15455 let cursor_offset_in_rename_range =
15456 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15457 let cursor_offset_in_rename_range_end =
15458 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15459
15460 this.take_rename(false, window, cx);
15461 let buffer = this.buffer.read(cx).read(cx);
15462 let cursor_offset = selection.head().to_offset(&buffer);
15463 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15464 let rename_end = rename_start + rename_buffer_range.len();
15465 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15466 let mut old_highlight_id = None;
15467 let old_name: Arc<str> = buffer
15468 .chunks(rename_start..rename_end, true)
15469 .map(|chunk| {
15470 if old_highlight_id.is_none() {
15471 old_highlight_id = chunk.syntax_highlight_id;
15472 }
15473 chunk.text
15474 })
15475 .collect::<String>()
15476 .into();
15477
15478 drop(buffer);
15479
15480 // Position the selection in the rename editor so that it matches the current selection.
15481 this.show_local_selections = false;
15482 let rename_editor = cx.new(|cx| {
15483 let mut editor = Editor::single_line(window, cx);
15484 editor.buffer.update(cx, |buffer, cx| {
15485 buffer.edit([(0..0, old_name.clone())], None, cx)
15486 });
15487 let rename_selection_range = match cursor_offset_in_rename_range
15488 .cmp(&cursor_offset_in_rename_range_end)
15489 {
15490 Ordering::Equal => {
15491 editor.select_all(&SelectAll, window, cx);
15492 return editor;
15493 }
15494 Ordering::Less => {
15495 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15496 }
15497 Ordering::Greater => {
15498 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15499 }
15500 };
15501 if rename_selection_range.end > old_name.len() {
15502 editor.select_all(&SelectAll, window, cx);
15503 } else {
15504 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15505 s.select_ranges([rename_selection_range]);
15506 });
15507 }
15508 editor
15509 });
15510 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15511 if e == &EditorEvent::Focused {
15512 cx.emit(EditorEvent::FocusedIn)
15513 }
15514 })
15515 .detach();
15516
15517 let write_highlights = this
15518 .clear_background_highlights::<DocumentHighlightWrite>(cx)
15519 .unwrap_or_default();
15520 let read_highlights = this
15521 .clear_background_highlights::<DocumentHighlightRead>(cx)
15522 .unwrap_or_default();
15523 let ranges = write_highlights
15524 .iter()
15525 .chain(read_highlights.iter())
15526 .cloned()
15527 .map(|highlight| {
15528 (
15529 highlight.range,
15530 HighlightStyle {
15531 fade_out: Some(0.6),
15532 ..Default::default()
15533 },
15534 )
15535 })
15536 .collect();
15537
15538 this.highlight_text::<Rename>(ranges, cx);
15539 let rename_focus_handle = rename_editor.focus_handle(cx);
15540 window.focus(&rename_focus_handle);
15541 let block_id = this.insert_blocks(
15542 [BlockProperties {
15543 style: BlockStyle::Flex,
15544 placement: BlockPlacement::Below(range.start),
15545 height: Some(1),
15546 render: Arc::new({
15547 let rename_editor = rename_editor.clone();
15548 move |cx: &mut BlockContext| {
15549 let mut text_style = cx.editor_style.text.clone();
15550 if let Some(highlight_style) = old_highlight_id
15551 .and_then(|h| h.style(&cx.editor_style.syntax))
15552 {
15553 text_style = text_style.highlight(highlight_style);
15554 }
15555 div()
15556 .block_mouse_except_scroll()
15557 .pl(cx.anchor_x)
15558 .child(EditorElement::new(
15559 &rename_editor,
15560 EditorStyle {
15561 background: cx.theme().system().transparent,
15562 local_player: cx.editor_style.local_player,
15563 text: text_style,
15564 scrollbar_width: cx.editor_style.scrollbar_width,
15565 syntax: cx.editor_style.syntax.clone(),
15566 status: cx.editor_style.status.clone(),
15567 inlay_hints_style: HighlightStyle {
15568 font_weight: Some(FontWeight::BOLD),
15569 ..make_inlay_hints_style(cx.app)
15570 },
15571 inline_completion_styles: make_suggestion_styles(
15572 cx.app,
15573 ),
15574 ..EditorStyle::default()
15575 },
15576 ))
15577 .into_any_element()
15578 }
15579 }),
15580 priority: 0,
15581 render_in_minimap: true,
15582 }],
15583 Some(Autoscroll::fit()),
15584 cx,
15585 )[0];
15586 this.pending_rename = Some(RenameState {
15587 range,
15588 old_name,
15589 editor: rename_editor,
15590 block_id,
15591 });
15592 })?;
15593 }
15594
15595 Ok(())
15596 }))
15597 }
15598
15599 pub fn confirm_rename(
15600 &mut self,
15601 _: &ConfirmRename,
15602 window: &mut Window,
15603 cx: &mut Context<Self>,
15604 ) -> Option<Task<Result<()>>> {
15605 let rename = self.take_rename(false, window, cx)?;
15606 let workspace = self.workspace()?.downgrade();
15607 let (buffer, start) = self
15608 .buffer
15609 .read(cx)
15610 .text_anchor_for_position(rename.range.start, cx)?;
15611 let (end_buffer, _) = self
15612 .buffer
15613 .read(cx)
15614 .text_anchor_for_position(rename.range.end, cx)?;
15615 if buffer != end_buffer {
15616 return None;
15617 }
15618
15619 let old_name = rename.old_name;
15620 let new_name = rename.editor.read(cx).text(cx);
15621
15622 let rename = self.semantics_provider.as_ref()?.perform_rename(
15623 &buffer,
15624 start,
15625 new_name.clone(),
15626 cx,
15627 )?;
15628
15629 Some(cx.spawn_in(window, async move |editor, cx| {
15630 let project_transaction = rename.await?;
15631 Self::open_project_transaction(
15632 &editor,
15633 workspace,
15634 project_transaction,
15635 format!("Rename: {} → {}", old_name, new_name),
15636 cx,
15637 )
15638 .await?;
15639
15640 editor.update(cx, |editor, cx| {
15641 editor.refresh_document_highlights(cx);
15642 })?;
15643 Ok(())
15644 }))
15645 }
15646
15647 fn take_rename(
15648 &mut self,
15649 moving_cursor: bool,
15650 window: &mut Window,
15651 cx: &mut Context<Self>,
15652 ) -> Option<RenameState> {
15653 let rename = self.pending_rename.take()?;
15654 if rename.editor.focus_handle(cx).is_focused(window) {
15655 window.focus(&self.focus_handle);
15656 }
15657
15658 self.remove_blocks(
15659 [rename.block_id].into_iter().collect(),
15660 Some(Autoscroll::fit()),
15661 cx,
15662 );
15663 self.clear_highlights::<Rename>(cx);
15664 self.show_local_selections = true;
15665
15666 if moving_cursor {
15667 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15668 editor.selections.newest::<usize>(cx).head()
15669 });
15670
15671 // Update the selection to match the position of the selection inside
15672 // the rename editor.
15673 let snapshot = self.buffer.read(cx).read(cx);
15674 let rename_range = rename.range.to_offset(&snapshot);
15675 let cursor_in_editor = snapshot
15676 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15677 .min(rename_range.end);
15678 drop(snapshot);
15679
15680 self.change_selections(None, window, cx, |s| {
15681 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15682 });
15683 } else {
15684 self.refresh_document_highlights(cx);
15685 }
15686
15687 Some(rename)
15688 }
15689
15690 pub fn pending_rename(&self) -> Option<&RenameState> {
15691 self.pending_rename.as_ref()
15692 }
15693
15694 fn format(
15695 &mut self,
15696 _: &Format,
15697 window: &mut Window,
15698 cx: &mut Context<Self>,
15699 ) -> Option<Task<Result<()>>> {
15700 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15701
15702 let project = match &self.project {
15703 Some(project) => project.clone(),
15704 None => return None,
15705 };
15706
15707 Some(self.perform_format(
15708 project,
15709 FormatTrigger::Manual,
15710 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15711 window,
15712 cx,
15713 ))
15714 }
15715
15716 fn format_selections(
15717 &mut self,
15718 _: &FormatSelections,
15719 window: &mut Window,
15720 cx: &mut Context<Self>,
15721 ) -> Option<Task<Result<()>>> {
15722 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15723
15724 let project = match &self.project {
15725 Some(project) => project.clone(),
15726 None => return None,
15727 };
15728
15729 let ranges = self
15730 .selections
15731 .all_adjusted(cx)
15732 .into_iter()
15733 .map(|selection| selection.range())
15734 .collect_vec();
15735
15736 Some(self.perform_format(
15737 project,
15738 FormatTrigger::Manual,
15739 FormatTarget::Ranges(ranges),
15740 window,
15741 cx,
15742 ))
15743 }
15744
15745 fn perform_format(
15746 &mut self,
15747 project: Entity<Project>,
15748 trigger: FormatTrigger,
15749 target: FormatTarget,
15750 window: &mut Window,
15751 cx: &mut Context<Self>,
15752 ) -> Task<Result<()>> {
15753 let buffer = self.buffer.clone();
15754 let (buffers, target) = match target {
15755 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15756 FormatTarget::Ranges(selection_ranges) => {
15757 let multi_buffer = buffer.read(cx);
15758 let snapshot = multi_buffer.read(cx);
15759 let mut buffers = HashSet::default();
15760 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15761 BTreeMap::new();
15762 for selection_range in selection_ranges {
15763 for (buffer, buffer_range, _) in
15764 snapshot.range_to_buffer_ranges(selection_range)
15765 {
15766 let buffer_id = buffer.remote_id();
15767 let start = buffer.anchor_before(buffer_range.start);
15768 let end = buffer.anchor_after(buffer_range.end);
15769 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15770 buffer_id_to_ranges
15771 .entry(buffer_id)
15772 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15773 .or_insert_with(|| vec![start..end]);
15774 }
15775 }
15776 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15777 }
15778 };
15779
15780 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15781 let selections_prev = transaction_id_prev
15782 .and_then(|transaction_id_prev| {
15783 // default to selections as they were after the last edit, if we have them,
15784 // instead of how they are now.
15785 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15786 // will take you back to where you made the last edit, instead of staying where you scrolled
15787 self.selection_history
15788 .transaction(transaction_id_prev)
15789 .map(|t| t.0.clone())
15790 })
15791 .unwrap_or_else(|| {
15792 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15793 self.selections.disjoint_anchors()
15794 });
15795
15796 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15797 let format = project.update(cx, |project, cx| {
15798 project.format(buffers, target, true, trigger, cx)
15799 });
15800
15801 cx.spawn_in(window, async move |editor, cx| {
15802 let transaction = futures::select_biased! {
15803 transaction = format.log_err().fuse() => transaction,
15804 () = timeout => {
15805 log::warn!("timed out waiting for formatting");
15806 None
15807 }
15808 };
15809
15810 buffer
15811 .update(cx, |buffer, cx| {
15812 if let Some(transaction) = transaction {
15813 if !buffer.is_singleton() {
15814 buffer.push_transaction(&transaction.0, cx);
15815 }
15816 }
15817 cx.notify();
15818 })
15819 .ok();
15820
15821 if let Some(transaction_id_now) =
15822 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15823 {
15824 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15825 if has_new_transaction {
15826 _ = editor.update(cx, |editor, _| {
15827 editor
15828 .selection_history
15829 .insert_transaction(transaction_id_now, selections_prev);
15830 });
15831 }
15832 }
15833
15834 Ok(())
15835 })
15836 }
15837
15838 fn organize_imports(
15839 &mut self,
15840 _: &OrganizeImports,
15841 window: &mut Window,
15842 cx: &mut Context<Self>,
15843 ) -> Option<Task<Result<()>>> {
15844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15845 let project = match &self.project {
15846 Some(project) => project.clone(),
15847 None => return None,
15848 };
15849 Some(self.perform_code_action_kind(
15850 project,
15851 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15852 window,
15853 cx,
15854 ))
15855 }
15856
15857 fn perform_code_action_kind(
15858 &mut self,
15859 project: Entity<Project>,
15860 kind: CodeActionKind,
15861 window: &mut Window,
15862 cx: &mut Context<Self>,
15863 ) -> Task<Result<()>> {
15864 let buffer = self.buffer.clone();
15865 let buffers = buffer.read(cx).all_buffers();
15866 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15867 let apply_action = project.update(cx, |project, cx| {
15868 project.apply_code_action_kind(buffers, kind, true, cx)
15869 });
15870 cx.spawn_in(window, async move |_, cx| {
15871 let transaction = futures::select_biased! {
15872 () = timeout => {
15873 log::warn!("timed out waiting for executing code action");
15874 None
15875 }
15876 transaction = apply_action.log_err().fuse() => transaction,
15877 };
15878 buffer
15879 .update(cx, |buffer, cx| {
15880 // check if we need this
15881 if let Some(transaction) = transaction {
15882 if !buffer.is_singleton() {
15883 buffer.push_transaction(&transaction.0, cx);
15884 }
15885 }
15886 cx.notify();
15887 })
15888 .ok();
15889 Ok(())
15890 })
15891 }
15892
15893 fn restart_language_server(
15894 &mut self,
15895 _: &RestartLanguageServer,
15896 _: &mut Window,
15897 cx: &mut Context<Self>,
15898 ) {
15899 if let Some(project) = self.project.clone() {
15900 self.buffer.update(cx, |multi_buffer, cx| {
15901 project.update(cx, |project, cx| {
15902 project.restart_language_servers_for_buffers(
15903 multi_buffer.all_buffers().into_iter().collect(),
15904 cx,
15905 );
15906 });
15907 })
15908 }
15909 }
15910
15911 fn stop_language_server(
15912 &mut self,
15913 _: &StopLanguageServer,
15914 _: &mut Window,
15915 cx: &mut Context<Self>,
15916 ) {
15917 if let Some(project) = self.project.clone() {
15918 self.buffer.update(cx, |multi_buffer, cx| {
15919 project.update(cx, |project, cx| {
15920 project.stop_language_servers_for_buffers(
15921 multi_buffer.all_buffers().into_iter().collect(),
15922 cx,
15923 );
15924 cx.emit(project::Event::RefreshInlayHints);
15925 });
15926 });
15927 }
15928 }
15929
15930 fn cancel_language_server_work(
15931 workspace: &mut Workspace,
15932 _: &actions::CancelLanguageServerWork,
15933 _: &mut Window,
15934 cx: &mut Context<Workspace>,
15935 ) {
15936 let project = workspace.project();
15937 let buffers = workspace
15938 .active_item(cx)
15939 .and_then(|item| item.act_as::<Editor>(cx))
15940 .map_or(HashSet::default(), |editor| {
15941 editor.read(cx).buffer.read(cx).all_buffers()
15942 });
15943 project.update(cx, |project, cx| {
15944 project.cancel_language_server_work_for_buffers(buffers, cx);
15945 });
15946 }
15947
15948 fn show_character_palette(
15949 &mut self,
15950 _: &ShowCharacterPalette,
15951 window: &mut Window,
15952 _: &mut Context<Self>,
15953 ) {
15954 window.show_character_palette();
15955 }
15956
15957 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15958 if self.mode.is_minimap() {
15959 return;
15960 }
15961
15962 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15963 let buffer = self.buffer.read(cx).snapshot(cx);
15964 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15965 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15966 let is_valid = buffer
15967 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15968 .any(|entry| {
15969 entry.diagnostic.is_primary
15970 && !entry.range.is_empty()
15971 && entry.range.start == primary_range_start
15972 && entry.diagnostic.message == active_diagnostics.active_message
15973 });
15974
15975 if !is_valid {
15976 self.dismiss_diagnostics(cx);
15977 }
15978 }
15979 }
15980
15981 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15982 match &self.active_diagnostics {
15983 ActiveDiagnostic::Group(group) => Some(group),
15984 _ => None,
15985 }
15986 }
15987
15988 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15989 self.dismiss_diagnostics(cx);
15990 self.active_diagnostics = ActiveDiagnostic::All;
15991 }
15992
15993 fn activate_diagnostics(
15994 &mut self,
15995 buffer_id: BufferId,
15996 diagnostic: DiagnosticEntry<usize>,
15997 window: &mut Window,
15998 cx: &mut Context<Self>,
15999 ) {
16000 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16001 return;
16002 }
16003 self.dismiss_diagnostics(cx);
16004 let snapshot = self.snapshot(window, cx);
16005 let buffer = self.buffer.read(cx).snapshot(cx);
16006 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16007 return;
16008 };
16009
16010 let diagnostic_group = buffer
16011 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16012 .collect::<Vec<_>>();
16013
16014 let blocks =
16015 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16016
16017 let blocks = self.display_map.update(cx, |display_map, cx| {
16018 display_map.insert_blocks(blocks, cx).into_iter().collect()
16019 });
16020 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16021 active_range: buffer.anchor_before(diagnostic.range.start)
16022 ..buffer.anchor_after(diagnostic.range.end),
16023 active_message: diagnostic.diagnostic.message.clone(),
16024 group_id: diagnostic.diagnostic.group_id,
16025 blocks,
16026 });
16027 cx.notify();
16028 }
16029
16030 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16031 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16032 return;
16033 };
16034
16035 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16036 if let ActiveDiagnostic::Group(group) = prev {
16037 self.display_map.update(cx, |display_map, cx| {
16038 display_map.remove_blocks(group.blocks, cx);
16039 });
16040 cx.notify();
16041 }
16042 }
16043
16044 /// Disable inline diagnostics rendering for this editor.
16045 pub fn disable_inline_diagnostics(&mut self) {
16046 self.inline_diagnostics_enabled = false;
16047 self.inline_diagnostics_update = Task::ready(());
16048 self.inline_diagnostics.clear();
16049 }
16050
16051 pub fn diagnostics_enabled(&self) -> bool {
16052 self.mode.is_full()
16053 }
16054
16055 pub fn inline_diagnostics_enabled(&self) -> bool {
16056 self.diagnostics_enabled() && self.inline_diagnostics_enabled
16057 }
16058
16059 pub fn show_inline_diagnostics(&self) -> bool {
16060 self.show_inline_diagnostics
16061 }
16062
16063 pub fn toggle_inline_diagnostics(
16064 &mut self,
16065 _: &ToggleInlineDiagnostics,
16066 window: &mut Window,
16067 cx: &mut Context<Editor>,
16068 ) {
16069 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16070 self.refresh_inline_diagnostics(false, window, cx);
16071 }
16072
16073 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16074 self.diagnostics_max_severity = severity;
16075 self.display_map.update(cx, |display_map, _| {
16076 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16077 });
16078 }
16079
16080 pub fn toggle_diagnostics(
16081 &mut self,
16082 _: &ToggleDiagnostics,
16083 window: &mut Window,
16084 cx: &mut Context<Editor>,
16085 ) {
16086 if !self.diagnostics_enabled() {
16087 return;
16088 }
16089
16090 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16091 EditorSettings::get_global(cx)
16092 .diagnostics_max_severity
16093 .filter(|severity| severity != &DiagnosticSeverity::Off)
16094 .unwrap_or(DiagnosticSeverity::Hint)
16095 } else {
16096 DiagnosticSeverity::Off
16097 };
16098 self.set_max_diagnostics_severity(new_severity, cx);
16099 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16100 self.active_diagnostics = ActiveDiagnostic::None;
16101 self.inline_diagnostics_update = Task::ready(());
16102 self.inline_diagnostics.clear();
16103 } else {
16104 self.refresh_inline_diagnostics(false, window, cx);
16105 }
16106
16107 cx.notify();
16108 }
16109
16110 pub fn toggle_minimap(
16111 &mut self,
16112 _: &ToggleMinimap,
16113 window: &mut Window,
16114 cx: &mut Context<Editor>,
16115 ) {
16116 if self.supports_minimap(cx) {
16117 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16118 }
16119 }
16120
16121 fn refresh_inline_diagnostics(
16122 &mut self,
16123 debounce: bool,
16124 window: &mut Window,
16125 cx: &mut Context<Self>,
16126 ) {
16127 let max_severity = ProjectSettings::get_global(cx)
16128 .diagnostics
16129 .inline
16130 .max_severity
16131 .unwrap_or(self.diagnostics_max_severity);
16132
16133 if !self.inline_diagnostics_enabled()
16134 || !self.show_inline_diagnostics
16135 || max_severity == DiagnosticSeverity::Off
16136 {
16137 self.inline_diagnostics_update = Task::ready(());
16138 self.inline_diagnostics.clear();
16139 return;
16140 }
16141
16142 let debounce_ms = ProjectSettings::get_global(cx)
16143 .diagnostics
16144 .inline
16145 .update_debounce_ms;
16146 let debounce = if debounce && debounce_ms > 0 {
16147 Some(Duration::from_millis(debounce_ms))
16148 } else {
16149 None
16150 };
16151 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16152 if let Some(debounce) = debounce {
16153 cx.background_executor().timer(debounce).await;
16154 }
16155 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16156 editor
16157 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16158 .ok()
16159 }) else {
16160 return;
16161 };
16162
16163 let new_inline_diagnostics = cx
16164 .background_spawn(async move {
16165 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16166 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16167 let message = diagnostic_entry
16168 .diagnostic
16169 .message
16170 .split_once('\n')
16171 .map(|(line, _)| line)
16172 .map(SharedString::new)
16173 .unwrap_or_else(|| {
16174 SharedString::from(diagnostic_entry.diagnostic.message)
16175 });
16176 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16177 let (Ok(i) | Err(i)) = inline_diagnostics
16178 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16179 inline_diagnostics.insert(
16180 i,
16181 (
16182 start_anchor,
16183 InlineDiagnostic {
16184 message,
16185 group_id: diagnostic_entry.diagnostic.group_id,
16186 start: diagnostic_entry.range.start.to_point(&snapshot),
16187 is_primary: diagnostic_entry.diagnostic.is_primary,
16188 severity: diagnostic_entry.diagnostic.severity,
16189 },
16190 ),
16191 );
16192 }
16193 inline_diagnostics
16194 })
16195 .await;
16196
16197 editor
16198 .update(cx, |editor, cx| {
16199 editor.inline_diagnostics = new_inline_diagnostics;
16200 cx.notify();
16201 })
16202 .ok();
16203 });
16204 }
16205
16206 fn pull_diagnostics(
16207 &mut self,
16208 buffer_id: Option<BufferId>,
16209 window: &Window,
16210 cx: &mut Context<Self>,
16211 ) -> Option<()> {
16212 if !self.mode().is_full() {
16213 return None;
16214 }
16215 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16216 .diagnostics
16217 .lsp_pull_diagnostics;
16218 if !pull_diagnostics_settings.enabled {
16219 return None;
16220 }
16221 let project = self.project.as_ref()?.downgrade();
16222 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16223 let mut buffers = self.buffer.read(cx).all_buffers();
16224 if let Some(buffer_id) = buffer_id {
16225 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16226 }
16227
16228 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16229 cx.background_executor().timer(debounce).await;
16230
16231 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16232 buffers
16233 .into_iter()
16234 .flat_map(|buffer| {
16235 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16236 })
16237 .collect::<FuturesUnordered<_>>()
16238 }) else {
16239 return;
16240 };
16241
16242 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16243 match pull_task {
16244 Ok(()) => {
16245 if editor
16246 .update_in(cx, |editor, window, cx| {
16247 editor.update_diagnostics_state(window, cx);
16248 })
16249 .is_err()
16250 {
16251 return;
16252 }
16253 }
16254 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16255 }
16256 }
16257 });
16258
16259 Some(())
16260 }
16261
16262 pub fn set_selections_from_remote(
16263 &mut self,
16264 selections: Vec<Selection<Anchor>>,
16265 pending_selection: Option<Selection<Anchor>>,
16266 window: &mut Window,
16267 cx: &mut Context<Self>,
16268 ) {
16269 let old_cursor_position = self.selections.newest_anchor().head();
16270 self.selections.change_with(cx, |s| {
16271 s.select_anchors(selections);
16272 if let Some(pending_selection) = pending_selection {
16273 s.set_pending(pending_selection, SelectMode::Character);
16274 } else {
16275 s.clear_pending();
16276 }
16277 });
16278 self.selections_did_change(
16279 false,
16280 &old_cursor_position,
16281 SelectionEffects::default(),
16282 window,
16283 cx,
16284 );
16285 }
16286
16287 pub fn transact(
16288 &mut self,
16289 window: &mut Window,
16290 cx: &mut Context<Self>,
16291 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16292 ) -> Option<TransactionId> {
16293 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16294 this.start_transaction_at(Instant::now(), window, cx);
16295 update(this, window, cx);
16296 this.end_transaction_at(Instant::now(), cx)
16297 })
16298 }
16299
16300 pub fn start_transaction_at(
16301 &mut self,
16302 now: Instant,
16303 window: &mut Window,
16304 cx: &mut Context<Self>,
16305 ) {
16306 self.end_selection(window, cx);
16307 if let Some(tx_id) = self
16308 .buffer
16309 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16310 {
16311 self.selection_history
16312 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16313 cx.emit(EditorEvent::TransactionBegun {
16314 transaction_id: tx_id,
16315 })
16316 }
16317 }
16318
16319 pub fn end_transaction_at(
16320 &mut self,
16321 now: Instant,
16322 cx: &mut Context<Self>,
16323 ) -> Option<TransactionId> {
16324 if let Some(transaction_id) = self
16325 .buffer
16326 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16327 {
16328 if let Some((_, end_selections)) =
16329 self.selection_history.transaction_mut(transaction_id)
16330 {
16331 *end_selections = Some(self.selections.disjoint_anchors());
16332 } else {
16333 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16334 }
16335
16336 cx.emit(EditorEvent::Edited { transaction_id });
16337 Some(transaction_id)
16338 } else {
16339 None
16340 }
16341 }
16342
16343 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16344 if self.selection_mark_mode {
16345 self.change_selections(None, window, cx, |s| {
16346 s.move_with(|_, sel| {
16347 sel.collapse_to(sel.head(), SelectionGoal::None);
16348 });
16349 })
16350 }
16351 self.selection_mark_mode = true;
16352 cx.notify();
16353 }
16354
16355 pub fn swap_selection_ends(
16356 &mut self,
16357 _: &actions::SwapSelectionEnds,
16358 window: &mut Window,
16359 cx: &mut Context<Self>,
16360 ) {
16361 self.change_selections(None, window, cx, |s| {
16362 s.move_with(|_, sel| {
16363 if sel.start != sel.end {
16364 sel.reversed = !sel.reversed
16365 }
16366 });
16367 });
16368 self.request_autoscroll(Autoscroll::newest(), cx);
16369 cx.notify();
16370 }
16371
16372 pub fn toggle_fold(
16373 &mut self,
16374 _: &actions::ToggleFold,
16375 window: &mut Window,
16376 cx: &mut Context<Self>,
16377 ) {
16378 if self.is_singleton(cx) {
16379 let selection = self.selections.newest::<Point>(cx);
16380
16381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16382 let range = if selection.is_empty() {
16383 let point = selection.head().to_display_point(&display_map);
16384 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16385 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16386 .to_point(&display_map);
16387 start..end
16388 } else {
16389 selection.range()
16390 };
16391 if display_map.folds_in_range(range).next().is_some() {
16392 self.unfold_lines(&Default::default(), window, cx)
16393 } else {
16394 self.fold(&Default::default(), window, cx)
16395 }
16396 } else {
16397 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16398 let buffer_ids: HashSet<_> = self
16399 .selections
16400 .disjoint_anchor_ranges()
16401 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16402 .collect();
16403
16404 let should_unfold = buffer_ids
16405 .iter()
16406 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16407
16408 for buffer_id in buffer_ids {
16409 if should_unfold {
16410 self.unfold_buffer(buffer_id, cx);
16411 } else {
16412 self.fold_buffer(buffer_id, cx);
16413 }
16414 }
16415 }
16416 }
16417
16418 pub fn toggle_fold_recursive(
16419 &mut self,
16420 _: &actions::ToggleFoldRecursive,
16421 window: &mut Window,
16422 cx: &mut Context<Self>,
16423 ) {
16424 let selection = self.selections.newest::<Point>(cx);
16425
16426 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16427 let range = if selection.is_empty() {
16428 let point = selection.head().to_display_point(&display_map);
16429 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16430 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16431 .to_point(&display_map);
16432 start..end
16433 } else {
16434 selection.range()
16435 };
16436 if display_map.folds_in_range(range).next().is_some() {
16437 self.unfold_recursive(&Default::default(), window, cx)
16438 } else {
16439 self.fold_recursive(&Default::default(), window, cx)
16440 }
16441 }
16442
16443 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16444 if self.is_singleton(cx) {
16445 let mut to_fold = Vec::new();
16446 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16447 let selections = self.selections.all_adjusted(cx);
16448
16449 for selection in selections {
16450 let range = selection.range().sorted();
16451 let buffer_start_row = range.start.row;
16452
16453 if range.start.row != range.end.row {
16454 let mut found = false;
16455 let mut row = range.start.row;
16456 while row <= range.end.row {
16457 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16458 {
16459 found = true;
16460 row = crease.range().end.row + 1;
16461 to_fold.push(crease);
16462 } else {
16463 row += 1
16464 }
16465 }
16466 if found {
16467 continue;
16468 }
16469 }
16470
16471 for row in (0..=range.start.row).rev() {
16472 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16473 if crease.range().end.row >= buffer_start_row {
16474 to_fold.push(crease);
16475 if row <= range.start.row {
16476 break;
16477 }
16478 }
16479 }
16480 }
16481 }
16482
16483 self.fold_creases(to_fold, true, window, cx);
16484 } else {
16485 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16486 let buffer_ids = self
16487 .selections
16488 .disjoint_anchor_ranges()
16489 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16490 .collect::<HashSet<_>>();
16491 for buffer_id in buffer_ids {
16492 self.fold_buffer(buffer_id, cx);
16493 }
16494 }
16495 }
16496
16497 fn fold_at_level(
16498 &mut self,
16499 fold_at: &FoldAtLevel,
16500 window: &mut Window,
16501 cx: &mut Context<Self>,
16502 ) {
16503 if !self.buffer.read(cx).is_singleton() {
16504 return;
16505 }
16506
16507 let fold_at_level = fold_at.0;
16508 let snapshot = self.buffer.read(cx).snapshot(cx);
16509 let mut to_fold = Vec::new();
16510 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16511
16512 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16513 while start_row < end_row {
16514 match self
16515 .snapshot(window, cx)
16516 .crease_for_buffer_row(MultiBufferRow(start_row))
16517 {
16518 Some(crease) => {
16519 let nested_start_row = crease.range().start.row + 1;
16520 let nested_end_row = crease.range().end.row;
16521
16522 if current_level < fold_at_level {
16523 stack.push((nested_start_row, nested_end_row, current_level + 1));
16524 } else if current_level == fold_at_level {
16525 to_fold.push(crease);
16526 }
16527
16528 start_row = nested_end_row + 1;
16529 }
16530 None => start_row += 1,
16531 }
16532 }
16533 }
16534
16535 self.fold_creases(to_fold, true, window, cx);
16536 }
16537
16538 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16539 if self.buffer.read(cx).is_singleton() {
16540 let mut fold_ranges = Vec::new();
16541 let snapshot = self.buffer.read(cx).snapshot(cx);
16542
16543 for row in 0..snapshot.max_row().0 {
16544 if let Some(foldable_range) = self
16545 .snapshot(window, cx)
16546 .crease_for_buffer_row(MultiBufferRow(row))
16547 {
16548 fold_ranges.push(foldable_range);
16549 }
16550 }
16551
16552 self.fold_creases(fold_ranges, true, window, cx);
16553 } else {
16554 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16555 editor
16556 .update_in(cx, |editor, _, cx| {
16557 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16558 editor.fold_buffer(buffer_id, cx);
16559 }
16560 })
16561 .ok();
16562 });
16563 }
16564 }
16565
16566 pub fn fold_function_bodies(
16567 &mut self,
16568 _: &actions::FoldFunctionBodies,
16569 window: &mut Window,
16570 cx: &mut Context<Self>,
16571 ) {
16572 let snapshot = self.buffer.read(cx).snapshot(cx);
16573
16574 let ranges = snapshot
16575 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16576 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16577 .collect::<Vec<_>>();
16578
16579 let creases = ranges
16580 .into_iter()
16581 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16582 .collect();
16583
16584 self.fold_creases(creases, true, window, cx);
16585 }
16586
16587 pub fn fold_recursive(
16588 &mut self,
16589 _: &actions::FoldRecursive,
16590 window: &mut Window,
16591 cx: &mut Context<Self>,
16592 ) {
16593 let mut to_fold = Vec::new();
16594 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16595 let selections = self.selections.all_adjusted(cx);
16596
16597 for selection in selections {
16598 let range = selection.range().sorted();
16599 let buffer_start_row = range.start.row;
16600
16601 if range.start.row != range.end.row {
16602 let mut found = false;
16603 for row in range.start.row..=range.end.row {
16604 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16605 found = true;
16606 to_fold.push(crease);
16607 }
16608 }
16609 if found {
16610 continue;
16611 }
16612 }
16613
16614 for row in (0..=range.start.row).rev() {
16615 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16616 if crease.range().end.row >= buffer_start_row {
16617 to_fold.push(crease);
16618 } else {
16619 break;
16620 }
16621 }
16622 }
16623 }
16624
16625 self.fold_creases(to_fold, true, window, cx);
16626 }
16627
16628 pub fn fold_at(
16629 &mut self,
16630 buffer_row: MultiBufferRow,
16631 window: &mut Window,
16632 cx: &mut Context<Self>,
16633 ) {
16634 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16635
16636 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16637 let autoscroll = self
16638 .selections
16639 .all::<Point>(cx)
16640 .iter()
16641 .any(|selection| crease.range().overlaps(&selection.range()));
16642
16643 self.fold_creases(vec![crease], autoscroll, window, cx);
16644 }
16645 }
16646
16647 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16648 if self.is_singleton(cx) {
16649 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16650 let buffer = &display_map.buffer_snapshot;
16651 let selections = self.selections.all::<Point>(cx);
16652 let ranges = selections
16653 .iter()
16654 .map(|s| {
16655 let range = s.display_range(&display_map).sorted();
16656 let mut start = range.start.to_point(&display_map);
16657 let mut end = range.end.to_point(&display_map);
16658 start.column = 0;
16659 end.column = buffer.line_len(MultiBufferRow(end.row));
16660 start..end
16661 })
16662 .collect::<Vec<_>>();
16663
16664 self.unfold_ranges(&ranges, true, true, cx);
16665 } else {
16666 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16667 let buffer_ids = self
16668 .selections
16669 .disjoint_anchor_ranges()
16670 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16671 .collect::<HashSet<_>>();
16672 for buffer_id in buffer_ids {
16673 self.unfold_buffer(buffer_id, cx);
16674 }
16675 }
16676 }
16677
16678 pub fn unfold_recursive(
16679 &mut self,
16680 _: &UnfoldRecursive,
16681 _window: &mut Window,
16682 cx: &mut Context<Self>,
16683 ) {
16684 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16685 let selections = self.selections.all::<Point>(cx);
16686 let ranges = selections
16687 .iter()
16688 .map(|s| {
16689 let mut range = s.display_range(&display_map).sorted();
16690 *range.start.column_mut() = 0;
16691 *range.end.column_mut() = display_map.line_len(range.end.row());
16692 let start = range.start.to_point(&display_map);
16693 let end = range.end.to_point(&display_map);
16694 start..end
16695 })
16696 .collect::<Vec<_>>();
16697
16698 self.unfold_ranges(&ranges, true, true, cx);
16699 }
16700
16701 pub fn unfold_at(
16702 &mut self,
16703 buffer_row: MultiBufferRow,
16704 _window: &mut Window,
16705 cx: &mut Context<Self>,
16706 ) {
16707 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16708
16709 let intersection_range = Point::new(buffer_row.0, 0)
16710 ..Point::new(
16711 buffer_row.0,
16712 display_map.buffer_snapshot.line_len(buffer_row),
16713 );
16714
16715 let autoscroll = self
16716 .selections
16717 .all::<Point>(cx)
16718 .iter()
16719 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16720
16721 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16722 }
16723
16724 pub fn unfold_all(
16725 &mut self,
16726 _: &actions::UnfoldAll,
16727 _window: &mut Window,
16728 cx: &mut Context<Self>,
16729 ) {
16730 if self.buffer.read(cx).is_singleton() {
16731 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16732 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16733 } else {
16734 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16735 editor
16736 .update(cx, |editor, cx| {
16737 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16738 editor.unfold_buffer(buffer_id, cx);
16739 }
16740 })
16741 .ok();
16742 });
16743 }
16744 }
16745
16746 pub fn fold_selected_ranges(
16747 &mut self,
16748 _: &FoldSelectedRanges,
16749 window: &mut Window,
16750 cx: &mut Context<Self>,
16751 ) {
16752 let selections = self.selections.all_adjusted(cx);
16753 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16754 let ranges = selections
16755 .into_iter()
16756 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16757 .collect::<Vec<_>>();
16758 self.fold_creases(ranges, true, window, cx);
16759 }
16760
16761 pub fn fold_ranges<T: ToOffset + Clone>(
16762 &mut self,
16763 ranges: Vec<Range<T>>,
16764 auto_scroll: bool,
16765 window: &mut Window,
16766 cx: &mut Context<Self>,
16767 ) {
16768 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16769 let ranges = ranges
16770 .into_iter()
16771 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16772 .collect::<Vec<_>>();
16773 self.fold_creases(ranges, auto_scroll, window, cx);
16774 }
16775
16776 pub fn fold_creases<T: ToOffset + Clone>(
16777 &mut self,
16778 creases: Vec<Crease<T>>,
16779 auto_scroll: bool,
16780 _window: &mut Window,
16781 cx: &mut Context<Self>,
16782 ) {
16783 if creases.is_empty() {
16784 return;
16785 }
16786
16787 let mut buffers_affected = HashSet::default();
16788 let multi_buffer = self.buffer().read(cx);
16789 for crease in &creases {
16790 if let Some((_, buffer, _)) =
16791 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16792 {
16793 buffers_affected.insert(buffer.read(cx).remote_id());
16794 };
16795 }
16796
16797 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16798
16799 if auto_scroll {
16800 self.request_autoscroll(Autoscroll::fit(), cx);
16801 }
16802
16803 cx.notify();
16804
16805 self.scrollbar_marker_state.dirty = true;
16806 self.folds_did_change(cx);
16807 }
16808
16809 /// Removes any folds whose ranges intersect any of the given ranges.
16810 pub fn unfold_ranges<T: ToOffset + Clone>(
16811 &mut self,
16812 ranges: &[Range<T>],
16813 inclusive: bool,
16814 auto_scroll: bool,
16815 cx: &mut Context<Self>,
16816 ) {
16817 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16818 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16819 });
16820 self.folds_did_change(cx);
16821 }
16822
16823 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16824 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16825 return;
16826 }
16827 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16828 self.display_map.update(cx, |display_map, cx| {
16829 display_map.fold_buffers([buffer_id], cx)
16830 });
16831 cx.emit(EditorEvent::BufferFoldToggled {
16832 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16833 folded: true,
16834 });
16835 cx.notify();
16836 }
16837
16838 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16839 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16840 return;
16841 }
16842 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16843 self.display_map.update(cx, |display_map, cx| {
16844 display_map.unfold_buffers([buffer_id], cx);
16845 });
16846 cx.emit(EditorEvent::BufferFoldToggled {
16847 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16848 folded: false,
16849 });
16850 cx.notify();
16851 }
16852
16853 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16854 self.display_map.read(cx).is_buffer_folded(buffer)
16855 }
16856
16857 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16858 self.display_map.read(cx).folded_buffers()
16859 }
16860
16861 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16862 self.display_map.update(cx, |display_map, cx| {
16863 display_map.disable_header_for_buffer(buffer_id, cx);
16864 });
16865 cx.notify();
16866 }
16867
16868 /// Removes any folds with the given ranges.
16869 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16870 &mut self,
16871 ranges: &[Range<T>],
16872 type_id: TypeId,
16873 auto_scroll: bool,
16874 cx: &mut Context<Self>,
16875 ) {
16876 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16877 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16878 });
16879 self.folds_did_change(cx);
16880 }
16881
16882 fn remove_folds_with<T: ToOffset + Clone>(
16883 &mut self,
16884 ranges: &[Range<T>],
16885 auto_scroll: bool,
16886 cx: &mut Context<Self>,
16887 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16888 ) {
16889 if ranges.is_empty() {
16890 return;
16891 }
16892
16893 let mut buffers_affected = HashSet::default();
16894 let multi_buffer = self.buffer().read(cx);
16895 for range in ranges {
16896 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16897 buffers_affected.insert(buffer.read(cx).remote_id());
16898 };
16899 }
16900
16901 self.display_map.update(cx, update);
16902
16903 if auto_scroll {
16904 self.request_autoscroll(Autoscroll::fit(), cx);
16905 }
16906
16907 cx.notify();
16908 self.scrollbar_marker_state.dirty = true;
16909 self.active_indent_guides_state.dirty = true;
16910 }
16911
16912 pub fn update_fold_widths(
16913 &mut self,
16914 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16915 cx: &mut Context<Self>,
16916 ) -> bool {
16917 self.display_map
16918 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16919 }
16920
16921 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16922 self.display_map.read(cx).fold_placeholder.clone()
16923 }
16924
16925 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16926 self.buffer.update(cx, |buffer, cx| {
16927 buffer.set_all_diff_hunks_expanded(cx);
16928 });
16929 }
16930
16931 pub fn expand_all_diff_hunks(
16932 &mut self,
16933 _: &ExpandAllDiffHunks,
16934 _window: &mut Window,
16935 cx: &mut Context<Self>,
16936 ) {
16937 self.buffer.update(cx, |buffer, cx| {
16938 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16939 });
16940 }
16941
16942 pub fn toggle_selected_diff_hunks(
16943 &mut self,
16944 _: &ToggleSelectedDiffHunks,
16945 _window: &mut Window,
16946 cx: &mut Context<Self>,
16947 ) {
16948 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16949 self.toggle_diff_hunks_in_ranges(ranges, cx);
16950 }
16951
16952 pub fn diff_hunks_in_ranges<'a>(
16953 &'a self,
16954 ranges: &'a [Range<Anchor>],
16955 buffer: &'a MultiBufferSnapshot,
16956 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16957 ranges.iter().flat_map(move |range| {
16958 let end_excerpt_id = range.end.excerpt_id;
16959 let range = range.to_point(buffer);
16960 let mut peek_end = range.end;
16961 if range.end.row < buffer.max_row().0 {
16962 peek_end = Point::new(range.end.row + 1, 0);
16963 }
16964 buffer
16965 .diff_hunks_in_range(range.start..peek_end)
16966 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16967 })
16968 }
16969
16970 pub fn has_stageable_diff_hunks_in_ranges(
16971 &self,
16972 ranges: &[Range<Anchor>],
16973 snapshot: &MultiBufferSnapshot,
16974 ) -> bool {
16975 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16976 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16977 }
16978
16979 pub fn toggle_staged_selected_diff_hunks(
16980 &mut self,
16981 _: &::git::ToggleStaged,
16982 _: &mut Window,
16983 cx: &mut Context<Self>,
16984 ) {
16985 let snapshot = self.buffer.read(cx).snapshot(cx);
16986 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16987 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16988 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16989 }
16990
16991 pub fn set_render_diff_hunk_controls(
16992 &mut self,
16993 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16994 cx: &mut Context<Self>,
16995 ) {
16996 self.render_diff_hunk_controls = render_diff_hunk_controls;
16997 cx.notify();
16998 }
16999
17000 pub fn stage_and_next(
17001 &mut self,
17002 _: &::git::StageAndNext,
17003 window: &mut Window,
17004 cx: &mut Context<Self>,
17005 ) {
17006 self.do_stage_or_unstage_and_next(true, window, cx);
17007 }
17008
17009 pub fn unstage_and_next(
17010 &mut self,
17011 _: &::git::UnstageAndNext,
17012 window: &mut Window,
17013 cx: &mut Context<Self>,
17014 ) {
17015 self.do_stage_or_unstage_and_next(false, window, cx);
17016 }
17017
17018 pub fn stage_or_unstage_diff_hunks(
17019 &mut self,
17020 stage: bool,
17021 ranges: Vec<Range<Anchor>>,
17022 cx: &mut Context<Self>,
17023 ) {
17024 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17025 cx.spawn(async move |this, cx| {
17026 task.await?;
17027 this.update(cx, |this, cx| {
17028 let snapshot = this.buffer.read(cx).snapshot(cx);
17029 let chunk_by = this
17030 .diff_hunks_in_ranges(&ranges, &snapshot)
17031 .chunk_by(|hunk| hunk.buffer_id);
17032 for (buffer_id, hunks) in &chunk_by {
17033 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17034 }
17035 })
17036 })
17037 .detach_and_log_err(cx);
17038 }
17039
17040 fn save_buffers_for_ranges_if_needed(
17041 &mut self,
17042 ranges: &[Range<Anchor>],
17043 cx: &mut Context<Editor>,
17044 ) -> Task<Result<()>> {
17045 let multibuffer = self.buffer.read(cx);
17046 let snapshot = multibuffer.read(cx);
17047 let buffer_ids: HashSet<_> = ranges
17048 .iter()
17049 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17050 .collect();
17051 drop(snapshot);
17052
17053 let mut buffers = HashSet::default();
17054 for buffer_id in buffer_ids {
17055 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17056 let buffer = buffer_entity.read(cx);
17057 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17058 {
17059 buffers.insert(buffer_entity);
17060 }
17061 }
17062 }
17063
17064 if let Some(project) = &self.project {
17065 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17066 } else {
17067 Task::ready(Ok(()))
17068 }
17069 }
17070
17071 fn do_stage_or_unstage_and_next(
17072 &mut self,
17073 stage: bool,
17074 window: &mut Window,
17075 cx: &mut Context<Self>,
17076 ) {
17077 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17078
17079 if ranges.iter().any(|range| range.start != range.end) {
17080 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17081 return;
17082 }
17083
17084 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17085 let snapshot = self.snapshot(window, cx);
17086 let position = self.selections.newest::<Point>(cx).head();
17087 let mut row = snapshot
17088 .buffer_snapshot
17089 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17090 .find(|hunk| hunk.row_range.start.0 > position.row)
17091 .map(|hunk| hunk.row_range.start);
17092
17093 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17094 // Outside of the project diff editor, wrap around to the beginning.
17095 if !all_diff_hunks_expanded {
17096 row = row.or_else(|| {
17097 snapshot
17098 .buffer_snapshot
17099 .diff_hunks_in_range(Point::zero()..position)
17100 .find(|hunk| hunk.row_range.end.0 < position.row)
17101 .map(|hunk| hunk.row_range.start)
17102 });
17103 }
17104
17105 if let Some(row) = row {
17106 let destination = Point::new(row.0, 0);
17107 let autoscroll = Autoscroll::center();
17108
17109 self.unfold_ranges(&[destination..destination], false, false, cx);
17110 self.change_selections(Some(autoscroll), window, cx, |s| {
17111 s.select_ranges([destination..destination]);
17112 });
17113 }
17114 }
17115
17116 fn do_stage_or_unstage(
17117 &self,
17118 stage: bool,
17119 buffer_id: BufferId,
17120 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17121 cx: &mut App,
17122 ) -> Option<()> {
17123 let project = self.project.as_ref()?;
17124 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17125 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17126 let buffer_snapshot = buffer.read(cx).snapshot();
17127 let file_exists = buffer_snapshot
17128 .file()
17129 .is_some_and(|file| file.disk_state().exists());
17130 diff.update(cx, |diff, cx| {
17131 diff.stage_or_unstage_hunks(
17132 stage,
17133 &hunks
17134 .map(|hunk| buffer_diff::DiffHunk {
17135 buffer_range: hunk.buffer_range,
17136 diff_base_byte_range: hunk.diff_base_byte_range,
17137 secondary_status: hunk.secondary_status,
17138 range: Point::zero()..Point::zero(), // unused
17139 })
17140 .collect::<Vec<_>>(),
17141 &buffer_snapshot,
17142 file_exists,
17143 cx,
17144 )
17145 });
17146 None
17147 }
17148
17149 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17150 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17151 self.buffer
17152 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17153 }
17154
17155 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17156 self.buffer.update(cx, |buffer, cx| {
17157 let ranges = vec![Anchor::min()..Anchor::max()];
17158 if !buffer.all_diff_hunks_expanded()
17159 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17160 {
17161 buffer.collapse_diff_hunks(ranges, cx);
17162 true
17163 } else {
17164 false
17165 }
17166 })
17167 }
17168
17169 fn toggle_diff_hunks_in_ranges(
17170 &mut self,
17171 ranges: Vec<Range<Anchor>>,
17172 cx: &mut Context<Editor>,
17173 ) {
17174 self.buffer.update(cx, |buffer, cx| {
17175 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17176 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17177 })
17178 }
17179
17180 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17181 self.buffer.update(cx, |buffer, cx| {
17182 let snapshot = buffer.snapshot(cx);
17183 let excerpt_id = range.end.excerpt_id;
17184 let point_range = range.to_point(&snapshot);
17185 let expand = !buffer.single_hunk_is_expanded(range, cx);
17186 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17187 })
17188 }
17189
17190 pub(crate) fn apply_all_diff_hunks(
17191 &mut self,
17192 _: &ApplyAllDiffHunks,
17193 window: &mut Window,
17194 cx: &mut Context<Self>,
17195 ) {
17196 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17197
17198 let buffers = self.buffer.read(cx).all_buffers();
17199 for branch_buffer in buffers {
17200 branch_buffer.update(cx, |branch_buffer, cx| {
17201 branch_buffer.merge_into_base(Vec::new(), cx);
17202 });
17203 }
17204
17205 if let Some(project) = self.project.clone() {
17206 self.save(
17207 SaveOptions {
17208 format: true,
17209 autosave: false,
17210 },
17211 project,
17212 window,
17213 cx,
17214 )
17215 .detach_and_log_err(cx);
17216 }
17217 }
17218
17219 pub(crate) fn apply_selected_diff_hunks(
17220 &mut self,
17221 _: &ApplyDiffHunk,
17222 window: &mut Window,
17223 cx: &mut Context<Self>,
17224 ) {
17225 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17226 let snapshot = self.snapshot(window, cx);
17227 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17228 let mut ranges_by_buffer = HashMap::default();
17229 self.transact(window, cx, |editor, _window, cx| {
17230 for hunk in hunks {
17231 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17232 ranges_by_buffer
17233 .entry(buffer.clone())
17234 .or_insert_with(Vec::new)
17235 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17236 }
17237 }
17238
17239 for (buffer, ranges) in ranges_by_buffer {
17240 buffer.update(cx, |buffer, cx| {
17241 buffer.merge_into_base(ranges, cx);
17242 });
17243 }
17244 });
17245
17246 if let Some(project) = self.project.clone() {
17247 self.save(
17248 SaveOptions {
17249 format: true,
17250 autosave: false,
17251 },
17252 project,
17253 window,
17254 cx,
17255 )
17256 .detach_and_log_err(cx);
17257 }
17258 }
17259
17260 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17261 if hovered != self.gutter_hovered {
17262 self.gutter_hovered = hovered;
17263 cx.notify();
17264 }
17265 }
17266
17267 pub fn insert_blocks(
17268 &mut self,
17269 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17270 autoscroll: Option<Autoscroll>,
17271 cx: &mut Context<Self>,
17272 ) -> Vec<CustomBlockId> {
17273 let blocks = self
17274 .display_map
17275 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17276 if let Some(autoscroll) = autoscroll {
17277 self.request_autoscroll(autoscroll, cx);
17278 }
17279 cx.notify();
17280 blocks
17281 }
17282
17283 pub fn resize_blocks(
17284 &mut self,
17285 heights: HashMap<CustomBlockId, u32>,
17286 autoscroll: Option<Autoscroll>,
17287 cx: &mut Context<Self>,
17288 ) {
17289 self.display_map
17290 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17291 if let Some(autoscroll) = autoscroll {
17292 self.request_autoscroll(autoscroll, cx);
17293 }
17294 cx.notify();
17295 }
17296
17297 pub fn replace_blocks(
17298 &mut self,
17299 renderers: HashMap<CustomBlockId, RenderBlock>,
17300 autoscroll: Option<Autoscroll>,
17301 cx: &mut Context<Self>,
17302 ) {
17303 self.display_map
17304 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17305 if let Some(autoscroll) = autoscroll {
17306 self.request_autoscroll(autoscroll, cx);
17307 }
17308 cx.notify();
17309 }
17310
17311 pub fn remove_blocks(
17312 &mut self,
17313 block_ids: HashSet<CustomBlockId>,
17314 autoscroll: Option<Autoscroll>,
17315 cx: &mut Context<Self>,
17316 ) {
17317 self.display_map.update(cx, |display_map, cx| {
17318 display_map.remove_blocks(block_ids, cx)
17319 });
17320 if let Some(autoscroll) = autoscroll {
17321 self.request_autoscroll(autoscroll, cx);
17322 }
17323 cx.notify();
17324 }
17325
17326 pub fn row_for_block(
17327 &self,
17328 block_id: CustomBlockId,
17329 cx: &mut Context<Self>,
17330 ) -> Option<DisplayRow> {
17331 self.display_map
17332 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17333 }
17334
17335 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17336 self.focused_block = Some(focused_block);
17337 }
17338
17339 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17340 self.focused_block.take()
17341 }
17342
17343 pub fn insert_creases(
17344 &mut self,
17345 creases: impl IntoIterator<Item = Crease<Anchor>>,
17346 cx: &mut Context<Self>,
17347 ) -> Vec<CreaseId> {
17348 self.display_map
17349 .update(cx, |map, cx| map.insert_creases(creases, cx))
17350 }
17351
17352 pub fn remove_creases(
17353 &mut self,
17354 ids: impl IntoIterator<Item = CreaseId>,
17355 cx: &mut Context<Self>,
17356 ) -> Vec<(CreaseId, Range<Anchor>)> {
17357 self.display_map
17358 .update(cx, |map, cx| map.remove_creases(ids, cx))
17359 }
17360
17361 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17362 self.display_map
17363 .update(cx, |map, cx| map.snapshot(cx))
17364 .longest_row()
17365 }
17366
17367 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17368 self.display_map
17369 .update(cx, |map, cx| map.snapshot(cx))
17370 .max_point()
17371 }
17372
17373 pub fn text(&self, cx: &App) -> String {
17374 self.buffer.read(cx).read(cx).text()
17375 }
17376
17377 pub fn is_empty(&self, cx: &App) -> bool {
17378 self.buffer.read(cx).read(cx).is_empty()
17379 }
17380
17381 pub fn text_option(&self, cx: &App) -> Option<String> {
17382 let text = self.text(cx);
17383 let text = text.trim();
17384
17385 if text.is_empty() {
17386 return None;
17387 }
17388
17389 Some(text.to_string())
17390 }
17391
17392 pub fn set_text(
17393 &mut self,
17394 text: impl Into<Arc<str>>,
17395 window: &mut Window,
17396 cx: &mut Context<Self>,
17397 ) {
17398 self.transact(window, cx, |this, _, cx| {
17399 this.buffer
17400 .read(cx)
17401 .as_singleton()
17402 .expect("you can only call set_text on editors for singleton buffers")
17403 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17404 });
17405 }
17406
17407 pub fn display_text(&self, cx: &mut App) -> String {
17408 self.display_map
17409 .update(cx, |map, cx| map.snapshot(cx))
17410 .text()
17411 }
17412
17413 fn create_minimap(
17414 &self,
17415 minimap_settings: MinimapSettings,
17416 window: &mut Window,
17417 cx: &mut Context<Self>,
17418 ) -> Option<Entity<Self>> {
17419 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17420 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17421 }
17422
17423 fn initialize_new_minimap(
17424 &self,
17425 minimap_settings: MinimapSettings,
17426 window: &mut Window,
17427 cx: &mut Context<Self>,
17428 ) -> Entity<Self> {
17429 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17430
17431 let mut minimap = Editor::new_internal(
17432 EditorMode::Minimap {
17433 parent: cx.weak_entity(),
17434 },
17435 self.buffer.clone(),
17436 self.project.clone(),
17437 Some(self.display_map.clone()),
17438 window,
17439 cx,
17440 );
17441 minimap.scroll_manager.clone_state(&self.scroll_manager);
17442 minimap.set_text_style_refinement(TextStyleRefinement {
17443 font_size: Some(MINIMAP_FONT_SIZE),
17444 font_weight: Some(MINIMAP_FONT_WEIGHT),
17445 ..Default::default()
17446 });
17447 minimap.update_minimap_configuration(minimap_settings, cx);
17448 cx.new(|_| minimap)
17449 }
17450
17451 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17452 let current_line_highlight = minimap_settings
17453 .current_line_highlight
17454 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17455 self.set_current_line_highlight(Some(current_line_highlight));
17456 }
17457
17458 pub fn minimap(&self) -> Option<&Entity<Self>> {
17459 self.minimap
17460 .as_ref()
17461 .filter(|_| self.minimap_visibility.visible())
17462 }
17463
17464 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17465 let mut wrap_guides = smallvec![];
17466
17467 if self.show_wrap_guides == Some(false) {
17468 return wrap_guides;
17469 }
17470
17471 let settings = self.buffer.read(cx).language_settings(cx);
17472 if settings.show_wrap_guides {
17473 match self.soft_wrap_mode(cx) {
17474 SoftWrap::Column(soft_wrap) => {
17475 wrap_guides.push((soft_wrap as usize, true));
17476 }
17477 SoftWrap::Bounded(soft_wrap) => {
17478 wrap_guides.push((soft_wrap as usize, true));
17479 }
17480 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17481 }
17482 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17483 }
17484
17485 wrap_guides
17486 }
17487
17488 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17489 let settings = self.buffer.read(cx).language_settings(cx);
17490 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17491 match mode {
17492 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17493 SoftWrap::None
17494 }
17495 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17496 language_settings::SoftWrap::PreferredLineLength => {
17497 SoftWrap::Column(settings.preferred_line_length)
17498 }
17499 language_settings::SoftWrap::Bounded => {
17500 SoftWrap::Bounded(settings.preferred_line_length)
17501 }
17502 }
17503 }
17504
17505 pub fn set_soft_wrap_mode(
17506 &mut self,
17507 mode: language_settings::SoftWrap,
17508
17509 cx: &mut Context<Self>,
17510 ) {
17511 self.soft_wrap_mode_override = Some(mode);
17512 cx.notify();
17513 }
17514
17515 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17516 self.hard_wrap = hard_wrap;
17517 cx.notify();
17518 }
17519
17520 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17521 self.text_style_refinement = Some(style);
17522 }
17523
17524 /// called by the Element so we know what style we were most recently rendered with.
17525 pub(crate) fn set_style(
17526 &mut self,
17527 style: EditorStyle,
17528 window: &mut Window,
17529 cx: &mut Context<Self>,
17530 ) {
17531 // We intentionally do not inform the display map about the minimap style
17532 // so that wrapping is not recalculated and stays consistent for the editor
17533 // and its linked minimap.
17534 if !self.mode.is_minimap() {
17535 let rem_size = window.rem_size();
17536 self.display_map.update(cx, |map, cx| {
17537 map.set_font(
17538 style.text.font(),
17539 style.text.font_size.to_pixels(rem_size),
17540 cx,
17541 )
17542 });
17543 }
17544 self.style = Some(style);
17545 }
17546
17547 pub fn style(&self) -> Option<&EditorStyle> {
17548 self.style.as_ref()
17549 }
17550
17551 // Called by the element. This method is not designed to be called outside of the editor
17552 // element's layout code because it does not notify when rewrapping is computed synchronously.
17553 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17554 self.display_map
17555 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17556 }
17557
17558 pub fn set_soft_wrap(&mut self) {
17559 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17560 }
17561
17562 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17563 if self.soft_wrap_mode_override.is_some() {
17564 self.soft_wrap_mode_override.take();
17565 } else {
17566 let soft_wrap = match self.soft_wrap_mode(cx) {
17567 SoftWrap::GitDiff => return,
17568 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17569 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17570 language_settings::SoftWrap::None
17571 }
17572 };
17573 self.soft_wrap_mode_override = Some(soft_wrap);
17574 }
17575 cx.notify();
17576 }
17577
17578 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17579 let Some(workspace) = self.workspace() else {
17580 return;
17581 };
17582 let fs = workspace.read(cx).app_state().fs.clone();
17583 let current_show = TabBarSettings::get_global(cx).show;
17584 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17585 setting.show = Some(!current_show);
17586 });
17587 }
17588
17589 pub fn toggle_indent_guides(
17590 &mut self,
17591 _: &ToggleIndentGuides,
17592 _: &mut Window,
17593 cx: &mut Context<Self>,
17594 ) {
17595 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17596 self.buffer
17597 .read(cx)
17598 .language_settings(cx)
17599 .indent_guides
17600 .enabled
17601 });
17602 self.show_indent_guides = Some(!currently_enabled);
17603 cx.notify();
17604 }
17605
17606 fn should_show_indent_guides(&self) -> Option<bool> {
17607 self.show_indent_guides
17608 }
17609
17610 pub fn toggle_line_numbers(
17611 &mut self,
17612 _: &ToggleLineNumbers,
17613 _: &mut Window,
17614 cx: &mut Context<Self>,
17615 ) {
17616 let mut editor_settings = EditorSettings::get_global(cx).clone();
17617 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17618 EditorSettings::override_global(editor_settings, cx);
17619 }
17620
17621 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17622 if let Some(show_line_numbers) = self.show_line_numbers {
17623 return show_line_numbers;
17624 }
17625 EditorSettings::get_global(cx).gutter.line_numbers
17626 }
17627
17628 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17629 self.use_relative_line_numbers
17630 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17631 }
17632
17633 pub fn toggle_relative_line_numbers(
17634 &mut self,
17635 _: &ToggleRelativeLineNumbers,
17636 _: &mut Window,
17637 cx: &mut Context<Self>,
17638 ) {
17639 let is_relative = self.should_use_relative_line_numbers(cx);
17640 self.set_relative_line_number(Some(!is_relative), cx)
17641 }
17642
17643 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17644 self.use_relative_line_numbers = is_relative;
17645 cx.notify();
17646 }
17647
17648 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17649 self.show_gutter = show_gutter;
17650 cx.notify();
17651 }
17652
17653 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17654 self.show_scrollbars = ScrollbarAxes {
17655 horizontal: show,
17656 vertical: show,
17657 };
17658 cx.notify();
17659 }
17660
17661 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17662 self.show_scrollbars.vertical = show;
17663 cx.notify();
17664 }
17665
17666 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17667 self.show_scrollbars.horizontal = show;
17668 cx.notify();
17669 }
17670
17671 pub fn set_minimap_visibility(
17672 &mut self,
17673 minimap_visibility: MinimapVisibility,
17674 window: &mut Window,
17675 cx: &mut Context<Self>,
17676 ) {
17677 if self.minimap_visibility != minimap_visibility {
17678 if minimap_visibility.visible() && self.minimap.is_none() {
17679 let minimap_settings = EditorSettings::get_global(cx).minimap;
17680 self.minimap =
17681 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17682 }
17683 self.minimap_visibility = minimap_visibility;
17684 cx.notify();
17685 }
17686 }
17687
17688 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17689 self.set_show_scrollbars(false, cx);
17690 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17691 }
17692
17693 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17694 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17695 }
17696
17697 /// Normally the text in full mode and auto height editors is padded on the
17698 /// left side by roughly half a character width for improved hit testing.
17699 ///
17700 /// Use this method to disable this for cases where this is not wanted (e.g.
17701 /// if you want to align the editor text with some other text above or below)
17702 /// or if you want to add this padding to single-line editors.
17703 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17704 self.offset_content = offset_content;
17705 cx.notify();
17706 }
17707
17708 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17709 self.show_line_numbers = Some(show_line_numbers);
17710 cx.notify();
17711 }
17712
17713 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17714 self.disable_expand_excerpt_buttons = true;
17715 cx.notify();
17716 }
17717
17718 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17719 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17720 cx.notify();
17721 }
17722
17723 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17724 self.show_code_actions = Some(show_code_actions);
17725 cx.notify();
17726 }
17727
17728 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17729 self.show_runnables = Some(show_runnables);
17730 cx.notify();
17731 }
17732
17733 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17734 self.show_breakpoints = Some(show_breakpoints);
17735 cx.notify();
17736 }
17737
17738 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17739 if self.display_map.read(cx).masked != masked {
17740 self.display_map.update(cx, |map, _| map.masked = masked);
17741 }
17742 cx.notify()
17743 }
17744
17745 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17746 self.show_wrap_guides = Some(show_wrap_guides);
17747 cx.notify();
17748 }
17749
17750 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17751 self.show_indent_guides = Some(show_indent_guides);
17752 cx.notify();
17753 }
17754
17755 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17756 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17757 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17758 if let Some(dir) = file.abs_path(cx).parent() {
17759 return Some(dir.to_owned());
17760 }
17761 }
17762
17763 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17764 return Some(project_path.path.to_path_buf());
17765 }
17766 }
17767
17768 None
17769 }
17770
17771 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17772 self.active_excerpt(cx)?
17773 .1
17774 .read(cx)
17775 .file()
17776 .and_then(|f| f.as_local())
17777 }
17778
17779 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17780 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17781 let buffer = buffer.read(cx);
17782 if let Some(project_path) = buffer.project_path(cx) {
17783 let project = self.project.as_ref()?.read(cx);
17784 project.absolute_path(&project_path, cx)
17785 } else {
17786 buffer
17787 .file()
17788 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17789 }
17790 })
17791 }
17792
17793 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17794 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17795 let project_path = buffer.read(cx).project_path(cx)?;
17796 let project = self.project.as_ref()?.read(cx);
17797 let entry = project.entry_for_path(&project_path, cx)?;
17798 let path = entry.path.to_path_buf();
17799 Some(path)
17800 })
17801 }
17802
17803 pub fn reveal_in_finder(
17804 &mut self,
17805 _: &RevealInFileManager,
17806 _window: &mut Window,
17807 cx: &mut Context<Self>,
17808 ) {
17809 if let Some(target) = self.target_file(cx) {
17810 cx.reveal_path(&target.abs_path(cx));
17811 }
17812 }
17813
17814 pub fn copy_path(
17815 &mut self,
17816 _: &zed_actions::workspace::CopyPath,
17817 _window: &mut Window,
17818 cx: &mut Context<Self>,
17819 ) {
17820 if let Some(path) = self.target_file_abs_path(cx) {
17821 if let Some(path) = path.to_str() {
17822 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17823 }
17824 }
17825 }
17826
17827 pub fn copy_relative_path(
17828 &mut self,
17829 _: &zed_actions::workspace::CopyRelativePath,
17830 _window: &mut Window,
17831 cx: &mut Context<Self>,
17832 ) {
17833 if let Some(path) = self.target_file_path(cx) {
17834 if let Some(path) = path.to_str() {
17835 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17836 }
17837 }
17838 }
17839
17840 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17841 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17842 buffer.read(cx).project_path(cx)
17843 } else {
17844 None
17845 }
17846 }
17847
17848 // Returns true if the editor handled a go-to-line request
17849 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17850 maybe!({
17851 let breakpoint_store = self.breakpoint_store.as_ref()?;
17852
17853 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17854 else {
17855 self.clear_row_highlights::<ActiveDebugLine>();
17856 return None;
17857 };
17858
17859 let position = active_stack_frame.position;
17860 let buffer_id = position.buffer_id?;
17861 let snapshot = self
17862 .project
17863 .as_ref()?
17864 .read(cx)
17865 .buffer_for_id(buffer_id, cx)?
17866 .read(cx)
17867 .snapshot();
17868
17869 let mut handled = false;
17870 for (id, ExcerptRange { context, .. }) in
17871 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17872 {
17873 if context.start.cmp(&position, &snapshot).is_ge()
17874 || context.end.cmp(&position, &snapshot).is_lt()
17875 {
17876 continue;
17877 }
17878 let snapshot = self.buffer.read(cx).snapshot(cx);
17879 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17880
17881 handled = true;
17882 self.clear_row_highlights::<ActiveDebugLine>();
17883
17884 self.go_to_line::<ActiveDebugLine>(
17885 multibuffer_anchor,
17886 Some(cx.theme().colors().editor_debugger_active_line_background),
17887 window,
17888 cx,
17889 );
17890
17891 cx.notify();
17892 }
17893
17894 handled.then_some(())
17895 })
17896 .is_some()
17897 }
17898
17899 pub fn copy_file_name_without_extension(
17900 &mut self,
17901 _: &CopyFileNameWithoutExtension,
17902 _: &mut Window,
17903 cx: &mut Context<Self>,
17904 ) {
17905 if let Some(file) = self.target_file(cx) {
17906 if let Some(file_stem) = file.path().file_stem() {
17907 if let Some(name) = file_stem.to_str() {
17908 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17909 }
17910 }
17911 }
17912 }
17913
17914 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17915 if let Some(file) = self.target_file(cx) {
17916 if let Some(file_name) = file.path().file_name() {
17917 if let Some(name) = file_name.to_str() {
17918 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17919 }
17920 }
17921 }
17922 }
17923
17924 pub fn toggle_git_blame(
17925 &mut self,
17926 _: &::git::Blame,
17927 window: &mut Window,
17928 cx: &mut Context<Self>,
17929 ) {
17930 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17931
17932 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17933 self.start_git_blame(true, window, cx);
17934 }
17935
17936 cx.notify();
17937 }
17938
17939 pub fn toggle_git_blame_inline(
17940 &mut self,
17941 _: &ToggleGitBlameInline,
17942 window: &mut Window,
17943 cx: &mut Context<Self>,
17944 ) {
17945 self.toggle_git_blame_inline_internal(true, window, cx);
17946 cx.notify();
17947 }
17948
17949 pub fn open_git_blame_commit(
17950 &mut self,
17951 _: &OpenGitBlameCommit,
17952 window: &mut Window,
17953 cx: &mut Context<Self>,
17954 ) {
17955 self.open_git_blame_commit_internal(window, cx);
17956 }
17957
17958 fn open_git_blame_commit_internal(
17959 &mut self,
17960 window: &mut Window,
17961 cx: &mut Context<Self>,
17962 ) -> Option<()> {
17963 let blame = self.blame.as_ref()?;
17964 let snapshot = self.snapshot(window, cx);
17965 let cursor = self.selections.newest::<Point>(cx).head();
17966 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17967 let blame_entry = blame
17968 .update(cx, |blame, cx| {
17969 blame
17970 .blame_for_rows(
17971 &[RowInfo {
17972 buffer_id: Some(buffer.remote_id()),
17973 buffer_row: Some(point.row),
17974 ..Default::default()
17975 }],
17976 cx,
17977 )
17978 .next()
17979 })
17980 .flatten()?;
17981 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17982 let repo = blame.read(cx).repository(cx)?;
17983 let workspace = self.workspace()?.downgrade();
17984 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17985 None
17986 }
17987
17988 pub fn git_blame_inline_enabled(&self) -> bool {
17989 self.git_blame_inline_enabled
17990 }
17991
17992 pub fn toggle_selection_menu(
17993 &mut self,
17994 _: &ToggleSelectionMenu,
17995 _: &mut Window,
17996 cx: &mut Context<Self>,
17997 ) {
17998 self.show_selection_menu = self
17999 .show_selection_menu
18000 .map(|show_selections_menu| !show_selections_menu)
18001 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18002
18003 cx.notify();
18004 }
18005
18006 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18007 self.show_selection_menu
18008 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18009 }
18010
18011 fn start_git_blame(
18012 &mut self,
18013 user_triggered: bool,
18014 window: &mut Window,
18015 cx: &mut Context<Self>,
18016 ) {
18017 if let Some(project) = self.project.as_ref() {
18018 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18019 return;
18020 };
18021
18022 if buffer.read(cx).file().is_none() {
18023 return;
18024 }
18025
18026 let focused = self.focus_handle(cx).contains_focused(window, cx);
18027
18028 let project = project.clone();
18029 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18030 self.blame_subscription =
18031 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18032 self.blame = Some(blame);
18033 }
18034 }
18035
18036 fn toggle_git_blame_inline_internal(
18037 &mut self,
18038 user_triggered: bool,
18039 window: &mut Window,
18040 cx: &mut Context<Self>,
18041 ) {
18042 if self.git_blame_inline_enabled {
18043 self.git_blame_inline_enabled = false;
18044 self.show_git_blame_inline = false;
18045 self.show_git_blame_inline_delay_task.take();
18046 } else {
18047 self.git_blame_inline_enabled = true;
18048 self.start_git_blame_inline(user_triggered, window, cx);
18049 }
18050
18051 cx.notify();
18052 }
18053
18054 fn start_git_blame_inline(
18055 &mut self,
18056 user_triggered: bool,
18057 window: &mut Window,
18058 cx: &mut Context<Self>,
18059 ) {
18060 self.start_git_blame(user_triggered, window, cx);
18061
18062 if ProjectSettings::get_global(cx)
18063 .git
18064 .inline_blame_delay()
18065 .is_some()
18066 {
18067 self.start_inline_blame_timer(window, cx);
18068 } else {
18069 self.show_git_blame_inline = true
18070 }
18071 }
18072
18073 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18074 self.blame.as_ref()
18075 }
18076
18077 pub fn show_git_blame_gutter(&self) -> bool {
18078 self.show_git_blame_gutter
18079 }
18080
18081 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18082 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18083 }
18084
18085 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18086 self.show_git_blame_inline
18087 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18088 && !self.newest_selection_head_on_empty_line(cx)
18089 && self.has_blame_entries(cx)
18090 }
18091
18092 fn has_blame_entries(&self, cx: &App) -> bool {
18093 self.blame()
18094 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18095 }
18096
18097 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18098 let cursor_anchor = self.selections.newest_anchor().head();
18099
18100 let snapshot = self.buffer.read(cx).snapshot(cx);
18101 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18102
18103 snapshot.line_len(buffer_row) == 0
18104 }
18105
18106 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18107 let buffer_and_selection = maybe!({
18108 let selection = self.selections.newest::<Point>(cx);
18109 let selection_range = selection.range();
18110
18111 let multi_buffer = self.buffer().read(cx);
18112 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18113 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18114
18115 let (buffer, range, _) = if selection.reversed {
18116 buffer_ranges.first()
18117 } else {
18118 buffer_ranges.last()
18119 }?;
18120
18121 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18122 ..text::ToPoint::to_point(&range.end, &buffer).row;
18123 Some((
18124 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18125 selection,
18126 ))
18127 });
18128
18129 let Some((buffer, selection)) = buffer_and_selection else {
18130 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18131 };
18132
18133 let Some(project) = self.project.as_ref() else {
18134 return Task::ready(Err(anyhow!("editor does not have project")));
18135 };
18136
18137 project.update(cx, |project, cx| {
18138 project.get_permalink_to_line(&buffer, selection, cx)
18139 })
18140 }
18141
18142 pub fn copy_permalink_to_line(
18143 &mut self,
18144 _: &CopyPermalinkToLine,
18145 window: &mut Window,
18146 cx: &mut Context<Self>,
18147 ) {
18148 let permalink_task = self.get_permalink_to_line(cx);
18149 let workspace = self.workspace();
18150
18151 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18152 Ok(permalink) => {
18153 cx.update(|_, cx| {
18154 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18155 })
18156 .ok();
18157 }
18158 Err(err) => {
18159 let message = format!("Failed to copy permalink: {err}");
18160
18161 anyhow::Result::<()>::Err(err).log_err();
18162
18163 if let Some(workspace) = workspace {
18164 workspace
18165 .update_in(cx, |workspace, _, cx| {
18166 struct CopyPermalinkToLine;
18167
18168 workspace.show_toast(
18169 Toast::new(
18170 NotificationId::unique::<CopyPermalinkToLine>(),
18171 message,
18172 ),
18173 cx,
18174 )
18175 })
18176 .ok();
18177 }
18178 }
18179 })
18180 .detach();
18181 }
18182
18183 pub fn copy_file_location(
18184 &mut self,
18185 _: &CopyFileLocation,
18186 _: &mut Window,
18187 cx: &mut Context<Self>,
18188 ) {
18189 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18190 if let Some(file) = self.target_file(cx) {
18191 if let Some(path) = file.path().to_str() {
18192 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18193 }
18194 }
18195 }
18196
18197 pub fn open_permalink_to_line(
18198 &mut self,
18199 _: &OpenPermalinkToLine,
18200 window: &mut Window,
18201 cx: &mut Context<Self>,
18202 ) {
18203 let permalink_task = self.get_permalink_to_line(cx);
18204 let workspace = self.workspace();
18205
18206 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18207 Ok(permalink) => {
18208 cx.update(|_, cx| {
18209 cx.open_url(permalink.as_ref());
18210 })
18211 .ok();
18212 }
18213 Err(err) => {
18214 let message = format!("Failed to open permalink: {err}");
18215
18216 anyhow::Result::<()>::Err(err).log_err();
18217
18218 if let Some(workspace) = workspace {
18219 workspace
18220 .update(cx, |workspace, cx| {
18221 struct OpenPermalinkToLine;
18222
18223 workspace.show_toast(
18224 Toast::new(
18225 NotificationId::unique::<OpenPermalinkToLine>(),
18226 message,
18227 ),
18228 cx,
18229 )
18230 })
18231 .ok();
18232 }
18233 }
18234 })
18235 .detach();
18236 }
18237
18238 pub fn insert_uuid_v4(
18239 &mut self,
18240 _: &InsertUuidV4,
18241 window: &mut Window,
18242 cx: &mut Context<Self>,
18243 ) {
18244 self.insert_uuid(UuidVersion::V4, window, cx);
18245 }
18246
18247 pub fn insert_uuid_v7(
18248 &mut self,
18249 _: &InsertUuidV7,
18250 window: &mut Window,
18251 cx: &mut Context<Self>,
18252 ) {
18253 self.insert_uuid(UuidVersion::V7, window, cx);
18254 }
18255
18256 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18257 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18258 self.transact(window, cx, |this, window, cx| {
18259 let edits = this
18260 .selections
18261 .all::<Point>(cx)
18262 .into_iter()
18263 .map(|selection| {
18264 let uuid = match version {
18265 UuidVersion::V4 => uuid::Uuid::new_v4(),
18266 UuidVersion::V7 => uuid::Uuid::now_v7(),
18267 };
18268
18269 (selection.range(), uuid.to_string())
18270 });
18271 this.edit(edits, cx);
18272 this.refresh_inline_completion(true, false, window, cx);
18273 });
18274 }
18275
18276 pub fn open_selections_in_multibuffer(
18277 &mut self,
18278 _: &OpenSelectionsInMultibuffer,
18279 window: &mut Window,
18280 cx: &mut Context<Self>,
18281 ) {
18282 let multibuffer = self.buffer.read(cx);
18283
18284 let Some(buffer) = multibuffer.as_singleton() else {
18285 return;
18286 };
18287
18288 let Some(workspace) = self.workspace() else {
18289 return;
18290 };
18291
18292 let locations = self
18293 .selections
18294 .disjoint_anchors()
18295 .iter()
18296 .map(|range| Location {
18297 buffer: buffer.clone(),
18298 range: range.start.text_anchor..range.end.text_anchor,
18299 })
18300 .collect::<Vec<_>>();
18301
18302 let title = multibuffer.title(cx).to_string();
18303
18304 cx.spawn_in(window, async move |_, cx| {
18305 workspace.update_in(cx, |workspace, window, cx| {
18306 Self::open_locations_in_multibuffer(
18307 workspace,
18308 locations,
18309 format!("Selections for '{title}'"),
18310 false,
18311 MultibufferSelectionMode::All,
18312 window,
18313 cx,
18314 );
18315 })
18316 })
18317 .detach();
18318 }
18319
18320 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18321 /// last highlight added will be used.
18322 ///
18323 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18324 pub fn highlight_rows<T: 'static>(
18325 &mut self,
18326 range: Range<Anchor>,
18327 color: Hsla,
18328 options: RowHighlightOptions,
18329 cx: &mut Context<Self>,
18330 ) {
18331 let snapshot = self.buffer().read(cx).snapshot(cx);
18332 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18333 let ix = row_highlights.binary_search_by(|highlight| {
18334 Ordering::Equal
18335 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18336 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18337 });
18338
18339 if let Err(mut ix) = ix {
18340 let index = post_inc(&mut self.highlight_order);
18341
18342 // If this range intersects with the preceding highlight, then merge it with
18343 // the preceding highlight. Otherwise insert a new highlight.
18344 let mut merged = false;
18345 if ix > 0 {
18346 let prev_highlight = &mut row_highlights[ix - 1];
18347 if prev_highlight
18348 .range
18349 .end
18350 .cmp(&range.start, &snapshot)
18351 .is_ge()
18352 {
18353 ix -= 1;
18354 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18355 prev_highlight.range.end = range.end;
18356 }
18357 merged = true;
18358 prev_highlight.index = index;
18359 prev_highlight.color = color;
18360 prev_highlight.options = options;
18361 }
18362 }
18363
18364 if !merged {
18365 row_highlights.insert(
18366 ix,
18367 RowHighlight {
18368 range: range.clone(),
18369 index,
18370 color,
18371 options,
18372 type_id: TypeId::of::<T>(),
18373 },
18374 );
18375 }
18376
18377 // If any of the following highlights intersect with this one, merge them.
18378 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18379 let highlight = &row_highlights[ix];
18380 if next_highlight
18381 .range
18382 .start
18383 .cmp(&highlight.range.end, &snapshot)
18384 .is_le()
18385 {
18386 if next_highlight
18387 .range
18388 .end
18389 .cmp(&highlight.range.end, &snapshot)
18390 .is_gt()
18391 {
18392 row_highlights[ix].range.end = next_highlight.range.end;
18393 }
18394 row_highlights.remove(ix + 1);
18395 } else {
18396 break;
18397 }
18398 }
18399 }
18400 }
18401
18402 /// Remove any highlighted row ranges of the given type that intersect the
18403 /// given ranges.
18404 pub fn remove_highlighted_rows<T: 'static>(
18405 &mut self,
18406 ranges_to_remove: Vec<Range<Anchor>>,
18407 cx: &mut Context<Self>,
18408 ) {
18409 let snapshot = self.buffer().read(cx).snapshot(cx);
18410 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18411 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18412 row_highlights.retain(|highlight| {
18413 while let Some(range_to_remove) = ranges_to_remove.peek() {
18414 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18415 Ordering::Less | Ordering::Equal => {
18416 ranges_to_remove.next();
18417 }
18418 Ordering::Greater => {
18419 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18420 Ordering::Less | Ordering::Equal => {
18421 return false;
18422 }
18423 Ordering::Greater => break,
18424 }
18425 }
18426 }
18427 }
18428
18429 true
18430 })
18431 }
18432
18433 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18434 pub fn clear_row_highlights<T: 'static>(&mut self) {
18435 self.highlighted_rows.remove(&TypeId::of::<T>());
18436 }
18437
18438 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18439 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18440 self.highlighted_rows
18441 .get(&TypeId::of::<T>())
18442 .map_or(&[] as &[_], |vec| vec.as_slice())
18443 .iter()
18444 .map(|highlight| (highlight.range.clone(), highlight.color))
18445 }
18446
18447 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18448 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18449 /// Allows to ignore certain kinds of highlights.
18450 pub fn highlighted_display_rows(
18451 &self,
18452 window: &mut Window,
18453 cx: &mut App,
18454 ) -> BTreeMap<DisplayRow, LineHighlight> {
18455 let snapshot = self.snapshot(window, cx);
18456 let mut used_highlight_orders = HashMap::default();
18457 self.highlighted_rows
18458 .iter()
18459 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18460 .fold(
18461 BTreeMap::<DisplayRow, LineHighlight>::new(),
18462 |mut unique_rows, highlight| {
18463 let start = highlight.range.start.to_display_point(&snapshot);
18464 let end = highlight.range.end.to_display_point(&snapshot);
18465 let start_row = start.row().0;
18466 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18467 && end.column() == 0
18468 {
18469 end.row().0.saturating_sub(1)
18470 } else {
18471 end.row().0
18472 };
18473 for row in start_row..=end_row {
18474 let used_index =
18475 used_highlight_orders.entry(row).or_insert(highlight.index);
18476 if highlight.index >= *used_index {
18477 *used_index = highlight.index;
18478 unique_rows.insert(
18479 DisplayRow(row),
18480 LineHighlight {
18481 include_gutter: highlight.options.include_gutter,
18482 border: None,
18483 background: highlight.color.into(),
18484 type_id: Some(highlight.type_id),
18485 },
18486 );
18487 }
18488 }
18489 unique_rows
18490 },
18491 )
18492 }
18493
18494 pub fn highlighted_display_row_for_autoscroll(
18495 &self,
18496 snapshot: &DisplaySnapshot,
18497 ) -> Option<DisplayRow> {
18498 self.highlighted_rows
18499 .values()
18500 .flat_map(|highlighted_rows| highlighted_rows.iter())
18501 .filter_map(|highlight| {
18502 if highlight.options.autoscroll {
18503 Some(highlight.range.start.to_display_point(snapshot).row())
18504 } else {
18505 None
18506 }
18507 })
18508 .min()
18509 }
18510
18511 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18512 self.highlight_background::<SearchWithinRange>(
18513 ranges,
18514 |theme| theme.colors().editor_document_highlight_read_background,
18515 cx,
18516 )
18517 }
18518
18519 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18520 self.breadcrumb_header = Some(new_header);
18521 }
18522
18523 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18524 self.clear_background_highlights::<SearchWithinRange>(cx);
18525 }
18526
18527 pub fn highlight_background<T: 'static>(
18528 &mut self,
18529 ranges: &[Range<Anchor>],
18530 color_fetcher: fn(&Theme) -> Hsla,
18531 cx: &mut Context<Self>,
18532 ) {
18533 let highlights = ranges
18534 .iter()
18535 .map(|range| BackgroundHighlight {
18536 range: range.clone(),
18537 color_fetcher,
18538 })
18539 .collect();
18540 self.background_highlights
18541 .insert(TypeId::of::<T>(), highlights);
18542 self.scrollbar_marker_state.dirty = true;
18543 cx.notify();
18544 }
18545
18546 pub fn highlight_background_ranges<T: 'static>(
18547 &mut self,
18548 background_highlights: Vec<BackgroundHighlight>,
18549 cx: &mut Context<'_, Editor>,
18550 ) {
18551 self.background_highlights
18552 .insert(TypeId::of::<T>(), background_highlights);
18553 self.scrollbar_marker_state.dirty = true;
18554 cx.notify();
18555 }
18556
18557 pub fn clear_background_highlights<T: 'static>(
18558 &mut self,
18559 cx: &mut Context<Self>,
18560 ) -> Option<Vec<BackgroundHighlight>> {
18561 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18562 if !text_highlights.is_empty() {
18563 self.scrollbar_marker_state.dirty = true;
18564 cx.notify();
18565 }
18566 Some(text_highlights)
18567 }
18568
18569 pub fn highlight_gutter<T: 'static>(
18570 &mut self,
18571 ranges: impl Into<Vec<Range<Anchor>>>,
18572 color_fetcher: fn(&App) -> Hsla,
18573 cx: &mut Context<Self>,
18574 ) {
18575 self.gutter_highlights
18576 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18577 cx.notify();
18578 }
18579
18580 pub fn clear_gutter_highlights<T: 'static>(
18581 &mut self,
18582 cx: &mut Context<Self>,
18583 ) -> Option<GutterHighlight> {
18584 cx.notify();
18585 self.gutter_highlights.remove(&TypeId::of::<T>())
18586 }
18587
18588 pub fn insert_gutter_highlight<T: 'static>(
18589 &mut self,
18590 range: Range<Anchor>,
18591 color_fetcher: fn(&App) -> Hsla,
18592 cx: &mut Context<Self>,
18593 ) {
18594 let snapshot = self.buffer().read(cx).snapshot(cx);
18595 let mut highlights = self
18596 .gutter_highlights
18597 .remove(&TypeId::of::<T>())
18598 .map(|(_, highlights)| highlights)
18599 .unwrap_or_default();
18600 let ix = highlights.binary_search_by(|highlight| {
18601 Ordering::Equal
18602 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18603 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18604 });
18605 if let Err(ix) = ix {
18606 highlights.insert(ix, range);
18607 }
18608 self.gutter_highlights
18609 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18610 }
18611
18612 pub fn remove_gutter_highlights<T: 'static>(
18613 &mut self,
18614 ranges_to_remove: Vec<Range<Anchor>>,
18615 cx: &mut Context<Self>,
18616 ) {
18617 let snapshot = self.buffer().read(cx).snapshot(cx);
18618 let Some((color_fetcher, mut gutter_highlights)) =
18619 self.gutter_highlights.remove(&TypeId::of::<T>())
18620 else {
18621 return;
18622 };
18623 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18624 gutter_highlights.retain(|highlight| {
18625 while let Some(range_to_remove) = ranges_to_remove.peek() {
18626 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18627 Ordering::Less | Ordering::Equal => {
18628 ranges_to_remove.next();
18629 }
18630 Ordering::Greater => {
18631 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18632 Ordering::Less | Ordering::Equal => {
18633 return false;
18634 }
18635 Ordering::Greater => break,
18636 }
18637 }
18638 }
18639 }
18640
18641 true
18642 });
18643 self.gutter_highlights
18644 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18645 }
18646
18647 #[cfg(feature = "test-support")]
18648 pub fn all_text_background_highlights(
18649 &self,
18650 window: &mut Window,
18651 cx: &mut Context<Self>,
18652 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18653 let snapshot = self.snapshot(window, cx);
18654 let buffer = &snapshot.buffer_snapshot;
18655 let start = buffer.anchor_before(0);
18656 let end = buffer.anchor_after(buffer.len());
18657 let theme = cx.theme();
18658 self.background_highlights_in_range(start..end, &snapshot, theme)
18659 }
18660
18661 #[cfg(feature = "test-support")]
18662 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18663 let snapshot = self.buffer().read(cx).snapshot(cx);
18664
18665 let highlights = self
18666 .background_highlights
18667 .get(&TypeId::of::<items::BufferSearchHighlights>());
18668
18669 if let Some(highlights) = highlights {
18670 highlights
18671 .iter()
18672 .map(|highlight| {
18673 highlight.range.start.to_point(&snapshot)
18674 ..highlight.range.end.to_point(&snapshot)
18675 })
18676 .collect_vec()
18677 } else {
18678 vec![]
18679 }
18680 }
18681
18682 fn document_highlights_for_position<'a>(
18683 &'a self,
18684 position: Anchor,
18685 buffer: &'a MultiBufferSnapshot,
18686 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18687 let read_highlights = self
18688 .background_highlights
18689 .get(&TypeId::of::<DocumentHighlightRead>());
18690 let write_highlights = self
18691 .background_highlights
18692 .get(&TypeId::of::<DocumentHighlightWrite>());
18693 let left_position = position.bias_left(buffer);
18694 let right_position = position.bias_right(buffer);
18695 read_highlights
18696 .into_iter()
18697 .chain(write_highlights)
18698 .flat_map(move |highlights| {
18699 let start_ix = match highlights.binary_search_by(|probe| {
18700 let cmp = probe.range.end.cmp(&left_position, buffer);
18701 if cmp.is_ge() {
18702 Ordering::Greater
18703 } else {
18704 Ordering::Less
18705 }
18706 }) {
18707 Ok(i) | Err(i) => i,
18708 };
18709
18710 highlights[start_ix..]
18711 .iter()
18712 .take_while(move |highlight| {
18713 highlight.range.start.cmp(&right_position, buffer).is_le()
18714 })
18715 .map(|highlight| &highlight.range)
18716 })
18717 }
18718
18719 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18720 self.background_highlights
18721 .get(&TypeId::of::<T>())
18722 .map_or(false, |highlights| !highlights.is_empty())
18723 }
18724
18725 pub fn background_highlights_in_range(
18726 &self,
18727 search_range: Range<Anchor>,
18728 display_snapshot: &DisplaySnapshot,
18729 theme: &Theme,
18730 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18731 let mut results = Vec::new();
18732 for highlights in self.background_highlights.values() {
18733 let start_ix = match highlights.binary_search_by(|probe| {
18734 let cmp = probe
18735 .range
18736 .end
18737 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18738 if cmp.is_gt() {
18739 Ordering::Greater
18740 } else {
18741 Ordering::Less
18742 }
18743 }) {
18744 Ok(i) | Err(i) => i,
18745 };
18746 for highlight in &highlights[start_ix..] {
18747 if highlight
18748 .range
18749 .start
18750 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18751 .is_ge()
18752 {
18753 break;
18754 }
18755
18756 let start = highlight.range.start.to_display_point(display_snapshot);
18757 let end = highlight.range.end.to_display_point(display_snapshot);
18758 let color = (highlight.color_fetcher)(theme);
18759 results.push((start..end, color))
18760 }
18761 }
18762 results
18763 }
18764
18765 pub fn background_highlight_row_ranges<T: 'static>(
18766 &self,
18767 search_range: Range<Anchor>,
18768 display_snapshot: &DisplaySnapshot,
18769 count: usize,
18770 ) -> Vec<RangeInclusive<DisplayPoint>> {
18771 let mut results = Vec::new();
18772 let Some(highlights) = self.background_highlights.get(&TypeId::of::<T>()) else {
18773 return vec![];
18774 };
18775
18776 let start_ix = match highlights.binary_search_by(|probe| {
18777 let cmp = probe
18778 .range
18779 .end
18780 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18781 if cmp.is_gt() {
18782 Ordering::Greater
18783 } else {
18784 Ordering::Less
18785 }
18786 }) {
18787 Ok(i) | Err(i) => i,
18788 };
18789 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18790 if let (Some(start_display), Some(end_display)) = (start, end) {
18791 results.push(
18792 start_display.to_display_point(display_snapshot)
18793 ..=end_display.to_display_point(display_snapshot),
18794 );
18795 }
18796 };
18797 let mut start_row: Option<Point> = None;
18798 let mut end_row: Option<Point> = None;
18799 if highlights.len() > count {
18800 return Vec::new();
18801 }
18802 for highlight in &highlights[start_ix..] {
18803 if highlight
18804 .range
18805 .start
18806 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18807 .is_ge()
18808 {
18809 break;
18810 }
18811 let end = highlight
18812 .range
18813 .end
18814 .to_point(&display_snapshot.buffer_snapshot);
18815 if let Some(current_row) = &end_row {
18816 if end.row == current_row.row {
18817 continue;
18818 }
18819 }
18820 let start = highlight
18821 .range
18822 .start
18823 .to_point(&display_snapshot.buffer_snapshot);
18824 if start_row.is_none() {
18825 assert_eq!(end_row, None);
18826 start_row = Some(start);
18827 end_row = Some(end);
18828 continue;
18829 }
18830 if let Some(current_end) = end_row.as_mut() {
18831 if start.row > current_end.row + 1 {
18832 push_region(start_row, end_row);
18833 start_row = Some(start);
18834 end_row = Some(end);
18835 } else {
18836 // Merge two hunks.
18837 *current_end = end;
18838 }
18839 } else {
18840 unreachable!();
18841 }
18842 }
18843 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18844 push_region(start_row, end_row);
18845 results
18846 }
18847
18848 pub fn gutter_highlights_in_range(
18849 &self,
18850 search_range: Range<Anchor>,
18851 display_snapshot: &DisplaySnapshot,
18852 cx: &App,
18853 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18854 let mut results = Vec::new();
18855 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18856 let color = color_fetcher(cx);
18857 let start_ix = match ranges.binary_search_by(|probe| {
18858 let cmp = probe
18859 .end
18860 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18861 if cmp.is_gt() {
18862 Ordering::Greater
18863 } else {
18864 Ordering::Less
18865 }
18866 }) {
18867 Ok(i) | Err(i) => i,
18868 };
18869 for range in &ranges[start_ix..] {
18870 if range
18871 .start
18872 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18873 .is_ge()
18874 {
18875 break;
18876 }
18877
18878 let start = range.start.to_display_point(display_snapshot);
18879 let end = range.end.to_display_point(display_snapshot);
18880 results.push((start..end, color))
18881 }
18882 }
18883 results
18884 }
18885
18886 /// Get the text ranges corresponding to the redaction query
18887 pub fn redacted_ranges(
18888 &self,
18889 search_range: Range<Anchor>,
18890 display_snapshot: &DisplaySnapshot,
18891 cx: &App,
18892 ) -> Vec<Range<DisplayPoint>> {
18893 display_snapshot
18894 .buffer_snapshot
18895 .redacted_ranges(search_range, |file| {
18896 if let Some(file) = file {
18897 file.is_private()
18898 && EditorSettings::get(
18899 Some(SettingsLocation {
18900 worktree_id: file.worktree_id(cx),
18901 path: file.path().as_ref(),
18902 }),
18903 cx,
18904 )
18905 .redact_private_values
18906 } else {
18907 false
18908 }
18909 })
18910 .map(|range| {
18911 range.start.to_display_point(display_snapshot)
18912 ..range.end.to_display_point(display_snapshot)
18913 })
18914 .collect()
18915 }
18916
18917 pub fn highlight_text<T: 'static>(
18918 &mut self,
18919 ranges: Vec<(Range<Anchor>, HighlightStyle)>,
18920 cx: &mut Context<Self>,
18921 ) {
18922 self.display_map
18923 .update(cx, |map, _| map.highlight_text(TypeId::of::<T>(), ranges));
18924 cx.notify();
18925 }
18926
18927 pub(crate) fn highlight_inlays<T: 'static>(
18928 &mut self,
18929 highlights: Vec<InlayHighlight>,
18930 style: HighlightStyle,
18931 cx: &mut Context<Self>,
18932 ) {
18933 self.display_map.update(cx, |map, _| {
18934 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18935 });
18936 cx.notify();
18937 }
18938
18939 pub fn text_highlights<'a, T: 'static>(
18940 &'a self,
18941 cx: &'a App,
18942 ) -> Option<&'a [(Range<Anchor>, HighlightStyle)]> {
18943 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18944 }
18945
18946 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18947 let cleared = self
18948 .display_map
18949 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18950 if cleared {
18951 cx.notify();
18952 }
18953 }
18954
18955 pub fn remove_text_highlights<T: 'static>(
18956 &mut self,
18957 cx: &mut Context<Self>,
18958 ) -> Option<Vec<(Range<Anchor>, HighlightStyle)>> {
18959 self.display_map
18960 .update(cx, |map, _| map.remove_text_highlights(TypeId::of::<T>()))
18961 }
18962
18963 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18964 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18965 && self.focus_handle.is_focused(window)
18966 }
18967
18968 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18969 self.show_cursor_when_unfocused = is_enabled;
18970 cx.notify();
18971 }
18972
18973 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18974 cx.notify();
18975 }
18976
18977 fn on_debug_session_event(
18978 &mut self,
18979 _session: Entity<Session>,
18980 event: &SessionEvent,
18981 cx: &mut Context<Self>,
18982 ) {
18983 match event {
18984 SessionEvent::InvalidateInlineValue => {
18985 self.refresh_inline_values(cx);
18986 }
18987 _ => {}
18988 }
18989 }
18990
18991 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18992 let Some(project) = self.project.clone() else {
18993 return;
18994 };
18995
18996 if !self.inline_value_cache.enabled {
18997 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18998 self.splice_inlays(&inlays, Vec::new(), cx);
18999 return;
19000 }
19001
19002 let current_execution_position = self
19003 .highlighted_rows
19004 .get(&TypeId::of::<ActiveDebugLine>())
19005 .and_then(|lines| lines.last().map(|line| line.range.start));
19006
19007 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19008 let inline_values = editor
19009 .update(cx, |editor, cx| {
19010 let Some(current_execution_position) = current_execution_position else {
19011 return Some(Task::ready(Ok(Vec::new())));
19012 };
19013
19014 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19015 let snapshot = buffer.snapshot(cx);
19016
19017 let excerpt = snapshot.excerpt_containing(
19018 current_execution_position..current_execution_position,
19019 )?;
19020
19021 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19022 })?;
19023
19024 let range =
19025 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19026
19027 project.inline_values(buffer, range, cx)
19028 })
19029 .ok()
19030 .flatten()?
19031 .await
19032 .context("refreshing debugger inlays")
19033 .log_err()?;
19034
19035 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19036
19037 for (buffer_id, inline_value) in inline_values
19038 .into_iter()
19039 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19040 {
19041 buffer_inline_values
19042 .entry(buffer_id)
19043 .or_default()
19044 .push(inline_value);
19045 }
19046
19047 editor
19048 .update(cx, |editor, cx| {
19049 let snapshot = editor.buffer.read(cx).snapshot(cx);
19050 let mut new_inlays = Vec::default();
19051
19052 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19053 let buffer_id = buffer_snapshot.remote_id();
19054 buffer_inline_values
19055 .get(&buffer_id)
19056 .into_iter()
19057 .flatten()
19058 .for_each(|hint| {
19059 let inlay = Inlay::debugger_hint(
19060 post_inc(&mut editor.next_inlay_id),
19061 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19062 hint.text(),
19063 );
19064
19065 new_inlays.push(inlay);
19066 });
19067 }
19068
19069 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19070 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19071
19072 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19073 })
19074 .ok()?;
19075 Some(())
19076 });
19077 }
19078
19079 fn on_buffer_event(
19080 &mut self,
19081 multibuffer: &Entity<MultiBuffer>,
19082 event: &multi_buffer::Event,
19083 window: &mut Window,
19084 cx: &mut Context<Self>,
19085 ) {
19086 match event {
19087 multi_buffer::Event::Edited {
19088 singleton_buffer_edited,
19089 edited_buffer,
19090 } => {
19091 self.scrollbar_marker_state.dirty = true;
19092 self.active_indent_guides_state.dirty = true;
19093 self.refresh_active_diagnostics(cx);
19094 self.refresh_code_actions(window, cx);
19095 self.refresh_selected_text_highlights(true, window, cx);
19096 refresh_matching_bracket_highlights(self, window, cx);
19097 if self.has_active_inline_completion() {
19098 self.update_visible_inline_completion(window, cx);
19099 }
19100 if let Some(project) = self.project.as_ref() {
19101 if let Some(edited_buffer) = edited_buffer {
19102 project.update(cx, |project, cx| {
19103 self.registered_buffers
19104 .entry(edited_buffer.read(cx).remote_id())
19105 .or_insert_with(|| {
19106 project
19107 .register_buffer_with_language_servers(&edited_buffer, cx)
19108 });
19109 });
19110 if edited_buffer.read(cx).file().is_some() {
19111 self.pull_diagnostics(
19112 Some(edited_buffer.read(cx).remote_id()),
19113 window,
19114 cx,
19115 );
19116 }
19117 }
19118 }
19119 cx.emit(EditorEvent::BufferEdited);
19120 cx.emit(SearchEvent::MatchesInvalidated);
19121 if *singleton_buffer_edited {
19122 if let Some(buffer) = edited_buffer {
19123 if buffer.read(cx).file().is_none() {
19124 cx.emit(EditorEvent::TitleChanged);
19125 }
19126 }
19127 if let Some(project) = &self.project {
19128 #[allow(clippy::mutable_key_type)]
19129 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19130 multibuffer
19131 .all_buffers()
19132 .into_iter()
19133 .filter_map(|buffer| {
19134 buffer.update(cx, |buffer, cx| {
19135 let language = buffer.language()?;
19136 let should_discard = project.update(cx, |project, cx| {
19137 project.is_local()
19138 && !project.has_language_servers_for(buffer, cx)
19139 });
19140 should_discard.not().then_some(language.clone())
19141 })
19142 })
19143 .collect::<HashSet<_>>()
19144 });
19145 if !languages_affected.is_empty() {
19146 self.refresh_inlay_hints(
19147 InlayHintRefreshReason::BufferEdited(languages_affected),
19148 cx,
19149 );
19150 }
19151 }
19152 }
19153
19154 let Some(project) = &self.project else { return };
19155 let (telemetry, is_via_ssh) = {
19156 let project = project.read(cx);
19157 let telemetry = project.client().telemetry().clone();
19158 let is_via_ssh = project.is_via_ssh();
19159 (telemetry, is_via_ssh)
19160 };
19161 refresh_linked_ranges(self, window, cx);
19162 telemetry.log_edit_event("editor", is_via_ssh);
19163 }
19164 multi_buffer::Event::ExcerptsAdded {
19165 buffer,
19166 predecessor,
19167 excerpts,
19168 } => {
19169 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19170 let buffer_id = buffer.read(cx).remote_id();
19171 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19172 if let Some(project) = &self.project {
19173 update_uncommitted_diff_for_buffer(
19174 cx.entity(),
19175 project,
19176 [buffer.clone()],
19177 self.buffer.clone(),
19178 cx,
19179 )
19180 .detach();
19181 }
19182 }
19183 cx.emit(EditorEvent::ExcerptsAdded {
19184 buffer: buffer.clone(),
19185 predecessor: *predecessor,
19186 excerpts: excerpts.clone(),
19187 });
19188 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19189 }
19190 multi_buffer::Event::ExcerptsRemoved {
19191 ids,
19192 removed_buffer_ids,
19193 } => {
19194 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19195 let buffer = self.buffer.read(cx);
19196 self.registered_buffers
19197 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19198 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19199 cx.emit(EditorEvent::ExcerptsRemoved {
19200 ids: ids.clone(),
19201 removed_buffer_ids: removed_buffer_ids.clone(),
19202 })
19203 }
19204 multi_buffer::Event::ExcerptsEdited {
19205 excerpt_ids,
19206 buffer_ids,
19207 } => {
19208 self.display_map.update(cx, |map, cx| {
19209 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19210 });
19211 cx.emit(EditorEvent::ExcerptsEdited {
19212 ids: excerpt_ids.clone(),
19213 })
19214 }
19215 multi_buffer::Event::ExcerptsExpanded { ids } => {
19216 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19217 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19218 }
19219 multi_buffer::Event::Reparsed(buffer_id) => {
19220 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19221 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19222
19223 cx.emit(EditorEvent::Reparsed(*buffer_id));
19224 }
19225 multi_buffer::Event::DiffHunksToggled => {
19226 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19227 }
19228 multi_buffer::Event::LanguageChanged(buffer_id) => {
19229 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19230 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19231 cx.emit(EditorEvent::Reparsed(*buffer_id));
19232 cx.notify();
19233 }
19234 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19235 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19236 multi_buffer::Event::FileHandleChanged
19237 | multi_buffer::Event::Reloaded
19238 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19239 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19240 multi_buffer::Event::DiagnosticsUpdated => {
19241 self.update_diagnostics_state(window, cx);
19242 }
19243 _ => {}
19244 };
19245 }
19246
19247 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19248 self.refresh_active_diagnostics(cx);
19249 self.refresh_inline_diagnostics(true, window, cx);
19250 self.scrollbar_marker_state.dirty = true;
19251 cx.notify();
19252 }
19253
19254 pub fn start_temporary_diff_override(&mut self) {
19255 self.load_diff_task.take();
19256 self.temporary_diff_override = true;
19257 }
19258
19259 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19260 self.temporary_diff_override = false;
19261 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19262 self.buffer.update(cx, |buffer, cx| {
19263 buffer.set_all_diff_hunks_collapsed(cx);
19264 });
19265
19266 if let Some(project) = self.project.clone() {
19267 self.load_diff_task = Some(
19268 update_uncommitted_diff_for_buffer(
19269 cx.entity(),
19270 &project,
19271 self.buffer.read(cx).all_buffers(),
19272 self.buffer.clone(),
19273 cx,
19274 )
19275 .shared(),
19276 );
19277 }
19278 }
19279
19280 fn on_display_map_changed(
19281 &mut self,
19282 _: Entity<DisplayMap>,
19283 _: &mut Window,
19284 cx: &mut Context<Self>,
19285 ) {
19286 cx.notify();
19287 }
19288
19289 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19290 let new_severity = if self.diagnostics_enabled() {
19291 EditorSettings::get_global(cx)
19292 .diagnostics_max_severity
19293 .unwrap_or(DiagnosticSeverity::Hint)
19294 } else {
19295 DiagnosticSeverity::Off
19296 };
19297 self.set_max_diagnostics_severity(new_severity, cx);
19298 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19299 self.update_edit_prediction_settings(cx);
19300 self.refresh_inline_completion(true, false, window, cx);
19301 self.refresh_inlay_hints(
19302 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19303 self.selections.newest_anchor().head(),
19304 &self.buffer.read(cx).snapshot(cx),
19305 cx,
19306 )),
19307 cx,
19308 );
19309
19310 let old_cursor_shape = self.cursor_shape;
19311
19312 {
19313 let editor_settings = EditorSettings::get_global(cx);
19314 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19315 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19316 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19317 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19318 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19319 }
19320
19321 if old_cursor_shape != self.cursor_shape {
19322 cx.emit(EditorEvent::CursorShapeChanged);
19323 }
19324
19325 let project_settings = ProjectSettings::get_global(cx);
19326 self.serialize_dirty_buffers =
19327 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19328
19329 if self.mode.is_full() {
19330 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19331 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19332 if self.show_inline_diagnostics != show_inline_diagnostics {
19333 self.show_inline_diagnostics = show_inline_diagnostics;
19334 self.refresh_inline_diagnostics(false, window, cx);
19335 }
19336
19337 if self.git_blame_inline_enabled != inline_blame_enabled {
19338 self.toggle_git_blame_inline_internal(false, window, cx);
19339 }
19340
19341 let minimap_settings = EditorSettings::get_global(cx).minimap;
19342 if self.minimap_visibility != MinimapVisibility::Disabled {
19343 if self.minimap_visibility.settings_visibility()
19344 != minimap_settings.minimap_enabled()
19345 {
19346 self.set_minimap_visibility(
19347 MinimapVisibility::for_mode(self.mode(), cx),
19348 window,
19349 cx,
19350 );
19351 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19352 minimap_entity.update(cx, |minimap_editor, cx| {
19353 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19354 })
19355 }
19356 }
19357 }
19358
19359 cx.notify();
19360 }
19361
19362 pub fn set_searchable(&mut self, searchable: bool) {
19363 self.searchable = searchable;
19364 }
19365
19366 pub fn searchable(&self) -> bool {
19367 self.searchable
19368 }
19369
19370 fn open_proposed_changes_editor(
19371 &mut self,
19372 _: &OpenProposedChangesEditor,
19373 window: &mut Window,
19374 cx: &mut Context<Self>,
19375 ) {
19376 let Some(workspace) = self.workspace() else {
19377 cx.propagate();
19378 return;
19379 };
19380
19381 let selections = self.selections.all::<usize>(cx);
19382 let multi_buffer = self.buffer.read(cx);
19383 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19384 let mut new_selections_by_buffer = HashMap::default();
19385 for selection in selections {
19386 for (buffer, range, _) in
19387 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19388 {
19389 let mut range = range.to_point(buffer);
19390 range.start.column = 0;
19391 range.end.column = buffer.line_len(range.end.row);
19392 new_selections_by_buffer
19393 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19394 .or_insert(Vec::new())
19395 .push(range)
19396 }
19397 }
19398
19399 let proposed_changes_buffers = new_selections_by_buffer
19400 .into_iter()
19401 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19402 .collect::<Vec<_>>();
19403 let proposed_changes_editor = cx.new(|cx| {
19404 ProposedChangesEditor::new(
19405 "Proposed changes",
19406 proposed_changes_buffers,
19407 self.project.clone(),
19408 window,
19409 cx,
19410 )
19411 });
19412
19413 window.defer(cx, move |window, cx| {
19414 workspace.update(cx, |workspace, cx| {
19415 workspace.active_pane().update(cx, |pane, cx| {
19416 pane.add_item(
19417 Box::new(proposed_changes_editor),
19418 true,
19419 true,
19420 None,
19421 window,
19422 cx,
19423 );
19424 });
19425 });
19426 });
19427 }
19428
19429 pub fn open_excerpts_in_split(
19430 &mut self,
19431 _: &OpenExcerptsSplit,
19432 window: &mut Window,
19433 cx: &mut Context<Self>,
19434 ) {
19435 self.open_excerpts_common(None, true, window, cx)
19436 }
19437
19438 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19439 self.open_excerpts_common(None, false, window, cx)
19440 }
19441
19442 fn open_excerpts_common(
19443 &mut self,
19444 jump_data: Option<JumpData>,
19445 split: bool,
19446 window: &mut Window,
19447 cx: &mut Context<Self>,
19448 ) {
19449 let Some(workspace) = self.workspace() else {
19450 cx.propagate();
19451 return;
19452 };
19453
19454 if self.buffer.read(cx).is_singleton() {
19455 cx.propagate();
19456 return;
19457 }
19458
19459 let mut new_selections_by_buffer = HashMap::default();
19460 match &jump_data {
19461 Some(JumpData::MultiBufferPoint {
19462 excerpt_id,
19463 position,
19464 anchor,
19465 line_offset_from_top,
19466 }) => {
19467 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19468 if let Some(buffer) = multi_buffer_snapshot
19469 .buffer_id_for_excerpt(*excerpt_id)
19470 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19471 {
19472 let buffer_snapshot = buffer.read(cx).snapshot();
19473 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19474 language::ToPoint::to_point(anchor, &buffer_snapshot)
19475 } else {
19476 buffer_snapshot.clip_point(*position, Bias::Left)
19477 };
19478 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19479 new_selections_by_buffer.insert(
19480 buffer,
19481 (
19482 vec![jump_to_offset..jump_to_offset],
19483 Some(*line_offset_from_top),
19484 ),
19485 );
19486 }
19487 }
19488 Some(JumpData::MultiBufferRow {
19489 row,
19490 line_offset_from_top,
19491 }) => {
19492 let point = MultiBufferPoint::new(row.0, 0);
19493 if let Some((buffer, buffer_point, _)) =
19494 self.buffer.read(cx).point_to_buffer_point(point, cx)
19495 {
19496 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19497 new_selections_by_buffer
19498 .entry(buffer)
19499 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19500 .0
19501 .push(buffer_offset..buffer_offset)
19502 }
19503 }
19504 None => {
19505 let selections = self.selections.all::<usize>(cx);
19506 let multi_buffer = self.buffer.read(cx);
19507 for selection in selections {
19508 for (snapshot, range, _, anchor) in multi_buffer
19509 .snapshot(cx)
19510 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19511 {
19512 if let Some(anchor) = anchor {
19513 // selection is in a deleted hunk
19514 let Some(buffer_id) = anchor.buffer_id else {
19515 continue;
19516 };
19517 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19518 continue;
19519 };
19520 let offset = text::ToOffset::to_offset(
19521 &anchor.text_anchor,
19522 &buffer_handle.read(cx).snapshot(),
19523 );
19524 let range = offset..offset;
19525 new_selections_by_buffer
19526 .entry(buffer_handle)
19527 .or_insert((Vec::new(), None))
19528 .0
19529 .push(range)
19530 } else {
19531 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19532 else {
19533 continue;
19534 };
19535 new_selections_by_buffer
19536 .entry(buffer_handle)
19537 .or_insert((Vec::new(), None))
19538 .0
19539 .push(range)
19540 }
19541 }
19542 }
19543 }
19544 }
19545
19546 new_selections_by_buffer
19547 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19548
19549 if new_selections_by_buffer.is_empty() {
19550 return;
19551 }
19552
19553 // We defer the pane interaction because we ourselves are a workspace item
19554 // and activating a new item causes the pane to call a method on us reentrantly,
19555 // which panics if we're on the stack.
19556 window.defer(cx, move |window, cx| {
19557 workspace.update(cx, |workspace, cx| {
19558 let pane = if split {
19559 workspace.adjacent_pane(window, cx)
19560 } else {
19561 workspace.active_pane().clone()
19562 };
19563
19564 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19565 let editor = buffer
19566 .read(cx)
19567 .file()
19568 .is_none()
19569 .then(|| {
19570 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19571 // so `workspace.open_project_item` will never find them, always opening a new editor.
19572 // Instead, we try to activate the existing editor in the pane first.
19573 let (editor, pane_item_index) =
19574 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19575 let editor = item.downcast::<Editor>()?;
19576 let singleton_buffer =
19577 editor.read(cx).buffer().read(cx).as_singleton()?;
19578 if singleton_buffer == buffer {
19579 Some((editor, i))
19580 } else {
19581 None
19582 }
19583 })?;
19584 pane.update(cx, |pane, cx| {
19585 pane.activate_item(pane_item_index, true, true, window, cx)
19586 });
19587 Some(editor)
19588 })
19589 .flatten()
19590 .unwrap_or_else(|| {
19591 workspace.open_project_item::<Self>(
19592 pane.clone(),
19593 buffer,
19594 true,
19595 true,
19596 window,
19597 cx,
19598 )
19599 });
19600
19601 editor.update(cx, |editor, cx| {
19602 let autoscroll = match scroll_offset {
19603 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19604 None => Autoscroll::newest(),
19605 };
19606 let nav_history = editor.nav_history.take();
19607 editor.change_selections(Some(autoscroll), window, cx, |s| {
19608 s.select_ranges(ranges);
19609 });
19610 editor.nav_history = nav_history;
19611 });
19612 }
19613 })
19614 });
19615 }
19616
19617 // For now, don't allow opening excerpts in buffers that aren't backed by
19618 // regular project files.
19619 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19620 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19621 }
19622
19623 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19624 let snapshot = self.buffer.read(cx).read(cx);
19625 let ranges = self.text_highlights::<InputComposition>(cx)?;
19626 Some(
19627 ranges
19628 .iter()
19629 .map(move |(range, _)| {
19630 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19631 })
19632 .collect(),
19633 )
19634 }
19635
19636 fn selection_replacement_ranges(
19637 &self,
19638 range: Range<OffsetUtf16>,
19639 cx: &mut App,
19640 ) -> Vec<Range<OffsetUtf16>> {
19641 let selections = self.selections.all::<OffsetUtf16>(cx);
19642 let newest_selection = selections
19643 .iter()
19644 .max_by_key(|selection| selection.id)
19645 .unwrap();
19646 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19647 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19648 let snapshot = self.buffer.read(cx).read(cx);
19649 selections
19650 .into_iter()
19651 .map(|mut selection| {
19652 selection.start.0 =
19653 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19654 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19655 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19656 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19657 })
19658 .collect()
19659 }
19660
19661 fn report_editor_event(
19662 &self,
19663 event_type: &'static str,
19664 file_extension: Option<String>,
19665 cx: &App,
19666 ) {
19667 if cfg!(any(test, feature = "test-support")) {
19668 return;
19669 }
19670
19671 let Some(project) = &self.project else { return };
19672
19673 // If None, we are in a file without an extension
19674 let file = self
19675 .buffer
19676 .read(cx)
19677 .as_singleton()
19678 .and_then(|b| b.read(cx).file());
19679 let file_extension = file_extension.or(file
19680 .as_ref()
19681 .and_then(|file| Path::new(file.file_name(cx)).extension())
19682 .and_then(|e| e.to_str())
19683 .map(|a| a.to_string()));
19684
19685 let vim_mode = vim_enabled(cx);
19686
19687 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19688 let copilot_enabled = edit_predictions_provider
19689 == language::language_settings::EditPredictionProvider::Copilot;
19690 let copilot_enabled_for_language = self
19691 .buffer
19692 .read(cx)
19693 .language_settings(cx)
19694 .show_edit_predictions;
19695
19696 let project = project.read(cx);
19697 telemetry::event!(
19698 event_type,
19699 file_extension,
19700 vim_mode,
19701 copilot_enabled,
19702 copilot_enabled_for_language,
19703 edit_predictions_provider,
19704 is_via_ssh = project.is_via_ssh(),
19705 );
19706 }
19707
19708 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19709 /// with each line being an array of {text, highlight} objects.
19710 fn copy_highlight_json(
19711 &mut self,
19712 _: &CopyHighlightJson,
19713 window: &mut Window,
19714 cx: &mut Context<Self>,
19715 ) {
19716 #[derive(Serialize)]
19717 struct Chunk<'a> {
19718 text: String,
19719 highlight: Option<&'a str>,
19720 }
19721
19722 let snapshot = self.buffer.read(cx).snapshot(cx);
19723 let range = self
19724 .selected_text_range(false, window, cx)
19725 .and_then(|selection| {
19726 if selection.range.is_empty() {
19727 None
19728 } else {
19729 Some(selection.range)
19730 }
19731 })
19732 .unwrap_or_else(|| 0..snapshot.len());
19733
19734 let chunks = snapshot.chunks(range, true);
19735 let mut lines = Vec::new();
19736 let mut line: VecDeque<Chunk> = VecDeque::new();
19737
19738 let Some(style) = self.style.as_ref() else {
19739 return;
19740 };
19741
19742 for chunk in chunks {
19743 let highlight = chunk
19744 .syntax_highlight_id
19745 .and_then(|id| id.name(&style.syntax));
19746 let mut chunk_lines = chunk.text.split('\n').peekable();
19747 while let Some(text) = chunk_lines.next() {
19748 let mut merged_with_last_token = false;
19749 if let Some(last_token) = line.back_mut() {
19750 if last_token.highlight == highlight {
19751 last_token.text.push_str(text);
19752 merged_with_last_token = true;
19753 }
19754 }
19755
19756 if !merged_with_last_token {
19757 line.push_back(Chunk {
19758 text: text.into(),
19759 highlight,
19760 });
19761 }
19762
19763 if chunk_lines.peek().is_some() {
19764 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19765 line.pop_front();
19766 }
19767 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19768 line.pop_back();
19769 }
19770
19771 lines.push(mem::take(&mut line));
19772 }
19773 }
19774 }
19775
19776 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19777 return;
19778 };
19779 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19780 }
19781
19782 pub fn open_context_menu(
19783 &mut self,
19784 _: &OpenContextMenu,
19785 window: &mut Window,
19786 cx: &mut Context<Self>,
19787 ) {
19788 self.request_autoscroll(Autoscroll::newest(), cx);
19789 let position = self.selections.newest_display(cx).start;
19790 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19791 }
19792
19793 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19794 &self.inlay_hint_cache
19795 }
19796
19797 pub fn replay_insert_event(
19798 &mut self,
19799 text: &str,
19800 relative_utf16_range: Option<Range<isize>>,
19801 window: &mut Window,
19802 cx: &mut Context<Self>,
19803 ) {
19804 if !self.input_enabled {
19805 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19806 return;
19807 }
19808 if let Some(relative_utf16_range) = relative_utf16_range {
19809 let selections = self.selections.all::<OffsetUtf16>(cx);
19810 self.change_selections(None, window, cx, |s| {
19811 let new_ranges = selections.into_iter().map(|range| {
19812 let start = OffsetUtf16(
19813 range
19814 .head()
19815 .0
19816 .saturating_add_signed(relative_utf16_range.start),
19817 );
19818 let end = OffsetUtf16(
19819 range
19820 .head()
19821 .0
19822 .saturating_add_signed(relative_utf16_range.end),
19823 );
19824 start..end
19825 });
19826 s.select_ranges(new_ranges);
19827 });
19828 }
19829
19830 self.handle_input(text, window, cx);
19831 }
19832
19833 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19834 let Some(provider) = self.semantics_provider.as_ref() else {
19835 return false;
19836 };
19837
19838 let mut supports = false;
19839 self.buffer().update(cx, |this, cx| {
19840 this.for_each_buffer(|buffer| {
19841 supports |= provider.supports_inlay_hints(buffer, cx);
19842 });
19843 });
19844
19845 supports
19846 }
19847
19848 pub fn is_focused(&self, window: &Window) -> bool {
19849 self.focus_handle.is_focused(window)
19850 }
19851
19852 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19853 cx.emit(EditorEvent::Focused);
19854
19855 if let Some(descendant) = self
19856 .last_focused_descendant
19857 .take()
19858 .and_then(|descendant| descendant.upgrade())
19859 {
19860 window.focus(&descendant);
19861 } else {
19862 if let Some(blame) = self.blame.as_ref() {
19863 blame.update(cx, GitBlame::focus)
19864 }
19865
19866 self.blink_manager.update(cx, BlinkManager::enable);
19867 self.show_cursor_names(window, cx);
19868 self.buffer.update(cx, |buffer, cx| {
19869 buffer.finalize_last_transaction(cx);
19870 if self.leader_id.is_none() {
19871 buffer.set_active_selections(
19872 &self.selections.disjoint_anchors(),
19873 self.selections.line_mode,
19874 self.cursor_shape,
19875 cx,
19876 );
19877 }
19878 });
19879 }
19880 }
19881
19882 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19883 cx.emit(EditorEvent::FocusedIn)
19884 }
19885
19886 fn handle_focus_out(
19887 &mut self,
19888 event: FocusOutEvent,
19889 _window: &mut Window,
19890 cx: &mut Context<Self>,
19891 ) {
19892 if event.blurred != self.focus_handle {
19893 self.last_focused_descendant = Some(event.blurred);
19894 }
19895 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19896 }
19897
19898 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19899 self.blink_manager.update(cx, BlinkManager::disable);
19900 self.buffer
19901 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19902
19903 if let Some(blame) = self.blame.as_ref() {
19904 blame.update(cx, GitBlame::blur)
19905 }
19906 if !self.hover_state.focused(window, cx) {
19907 hide_hover(self, cx);
19908 }
19909 if !self
19910 .context_menu
19911 .borrow()
19912 .as_ref()
19913 .is_some_and(|context_menu| context_menu.focused(window, cx))
19914 {
19915 self.hide_context_menu(window, cx);
19916 }
19917 self.discard_inline_completion(false, cx);
19918 cx.emit(EditorEvent::Blurred);
19919 cx.notify();
19920 }
19921
19922 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19923 let mut pending: String = window
19924 .pending_input_keystrokes()
19925 .into_iter()
19926 .flatten()
19927 .filter_map(|keystroke| {
19928 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19929 keystroke.key_char.clone()
19930 } else {
19931 None
19932 }
19933 })
19934 .collect();
19935
19936 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19937 pending = "".to_string();
19938 }
19939
19940 let existing_pending = self.text_highlights::<PendingInput>(cx).map(|ranges| {
19941 ranges
19942 .iter()
19943 .map(|(range, _)| range.clone())
19944 .collect::<Vec<_>>()
19945 });
19946 if existing_pending.is_none() && pending.is_empty() {
19947 return;
19948 }
19949 let transaction =
19950 self.transact(window, cx, |this, window, cx| {
19951 let selections = this.selections.all::<usize>(cx);
19952 let edits = selections
19953 .iter()
19954 .map(|selection| (selection.end..selection.end, pending.clone()));
19955 this.edit(edits, cx);
19956 this.change_selections(None, window, cx, |s| {
19957 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19958 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19959 }));
19960 });
19961 if let Some(existing_ranges) = existing_pending {
19962 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19963 this.edit(edits, cx);
19964 }
19965 });
19966
19967 let snapshot = self.snapshot(window, cx);
19968 let ranges = self
19969 .selections
19970 .all::<usize>(cx)
19971 .into_iter()
19972 .map(|selection| {
19973 (
19974 snapshot.buffer_snapshot.anchor_after(selection.end)
19975 ..snapshot
19976 .buffer_snapshot
19977 .anchor_before(selection.end + pending.len()),
19978 HighlightStyle {
19979 underline: Some(UnderlineStyle {
19980 thickness: px(1.),
19981 color: None,
19982 wavy: false,
19983 }),
19984 ..Default::default()
19985 },
19986 )
19987 })
19988 .collect();
19989
19990 if pending.is_empty() {
19991 self.clear_highlights::<PendingInput>(cx);
19992 } else {
19993 self.highlight_text::<PendingInput>(ranges, cx);
19994 }
19995
19996 self.ime_transaction = self.ime_transaction.or(transaction);
19997 if let Some(transaction) = self.ime_transaction {
19998 self.buffer.update(cx, |buffer, cx| {
19999 buffer.group_until_transaction(transaction, cx);
20000 });
20001 }
20002
20003 if self.text_highlights::<PendingInput>(cx).is_none() {
20004 self.ime_transaction.take();
20005 }
20006 }
20007
20008 pub fn register_action_renderer(
20009 &mut self,
20010 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20011 ) -> Subscription {
20012 let id = self.next_editor_action_id.post_inc();
20013 self.editor_actions
20014 .borrow_mut()
20015 .insert(id, Box::new(listener));
20016
20017 let editor_actions = self.editor_actions.clone();
20018 Subscription::new(move || {
20019 editor_actions.borrow_mut().remove(&id);
20020 })
20021 }
20022
20023 pub fn register_action<A: Action>(
20024 &mut self,
20025 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20026 ) -> Subscription {
20027 let id = self.next_editor_action_id.post_inc();
20028 let listener = Arc::new(listener);
20029 self.editor_actions.borrow_mut().insert(
20030 id,
20031 Box::new(move |_, window, _| {
20032 let listener = listener.clone();
20033 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20034 let action = action.downcast_ref().unwrap();
20035 if phase == DispatchPhase::Bubble {
20036 listener(action, window, cx)
20037 }
20038 })
20039 }),
20040 );
20041
20042 let editor_actions = self.editor_actions.clone();
20043 Subscription::new(move || {
20044 editor_actions.borrow_mut().remove(&id);
20045 })
20046 }
20047
20048 pub fn file_header_size(&self) -> u32 {
20049 FILE_HEADER_HEIGHT
20050 }
20051
20052 pub fn restore(
20053 &mut self,
20054 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20055 window: &mut Window,
20056 cx: &mut Context<Self>,
20057 ) {
20058 let workspace = self.workspace();
20059 let project = self.project.as_ref();
20060 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20061 let mut tasks = Vec::new();
20062 for (buffer_id, changes) in revert_changes {
20063 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20064 buffer.update(cx, |buffer, cx| {
20065 buffer.edit(
20066 changes
20067 .into_iter()
20068 .map(|(range, text)| (range, text.to_string())),
20069 None,
20070 cx,
20071 );
20072 });
20073
20074 if let Some(project) =
20075 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20076 {
20077 project.update(cx, |project, cx| {
20078 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20079 })
20080 }
20081 }
20082 }
20083 tasks
20084 });
20085 cx.spawn_in(window, async move |_, cx| {
20086 for (buffer, task) in save_tasks {
20087 let result = task.await;
20088 if result.is_err() {
20089 let Some(path) = buffer
20090 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20091 .ok()
20092 else {
20093 continue;
20094 };
20095 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20096 let Some(task) = cx
20097 .update_window_entity(&workspace, |workspace, window, cx| {
20098 workspace
20099 .open_path_preview(path, None, false, false, false, window, cx)
20100 })
20101 .ok()
20102 else {
20103 continue;
20104 };
20105 task.await.log_err();
20106 }
20107 }
20108 }
20109 })
20110 .detach();
20111 self.change_selections(None, window, cx, |selections| selections.refresh());
20112 }
20113
20114 pub fn to_pixel_point(
20115 &self,
20116 source: multi_buffer::Anchor,
20117 editor_snapshot: &EditorSnapshot,
20118 window: &mut Window,
20119 ) -> Option<gpui::Point<Pixels>> {
20120 let source_point = source.to_display_point(editor_snapshot);
20121 self.display_to_pixel_point(source_point, editor_snapshot, window)
20122 }
20123
20124 pub fn display_to_pixel_point(
20125 &self,
20126 source: DisplayPoint,
20127 editor_snapshot: &EditorSnapshot,
20128 window: &mut Window,
20129 ) -> Option<gpui::Point<Pixels>> {
20130 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20131 let text_layout_details = self.text_layout_details(window);
20132 let scroll_top = text_layout_details
20133 .scroll_anchor
20134 .scroll_position(editor_snapshot)
20135 .y;
20136
20137 if source.row().as_f32() < scroll_top.floor() {
20138 return None;
20139 }
20140 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20141 let source_y = line_height * (source.row().as_f32() - scroll_top);
20142 Some(gpui::Point::new(source_x, source_y))
20143 }
20144
20145 pub fn has_visible_completions_menu(&self) -> bool {
20146 !self.edit_prediction_preview_is_active()
20147 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20148 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20149 })
20150 }
20151
20152 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20153 if self.mode.is_minimap() {
20154 return;
20155 }
20156 self.addons
20157 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20158 }
20159
20160 pub fn unregister_addon<T: Addon>(&mut self) {
20161 self.addons.remove(&std::any::TypeId::of::<T>());
20162 }
20163
20164 pub fn addon<T: Addon>(&self) -> Option<&T> {
20165 let type_id = std::any::TypeId::of::<T>();
20166 self.addons
20167 .get(&type_id)
20168 .and_then(|item| item.to_any().downcast_ref::<T>())
20169 }
20170
20171 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20172 let type_id = std::any::TypeId::of::<T>();
20173 self.addons
20174 .get_mut(&type_id)
20175 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20176 }
20177
20178 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20179 let text_layout_details = self.text_layout_details(window);
20180 let style = &text_layout_details.editor_style;
20181 let font_id = window.text_system().resolve_font(&style.text.font());
20182 let font_size = style.text.font_size.to_pixels(window.rem_size());
20183 let line_height = style.text.line_height_in_pixels(window.rem_size());
20184 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20185
20186 gpui::Size::new(em_width, line_height)
20187 }
20188
20189 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20190 self.load_diff_task.clone()
20191 }
20192
20193 fn read_metadata_from_db(
20194 &mut self,
20195 item_id: u64,
20196 workspace_id: WorkspaceId,
20197 window: &mut Window,
20198 cx: &mut Context<Editor>,
20199 ) {
20200 if self.is_singleton(cx)
20201 && !self.mode.is_minimap()
20202 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20203 {
20204 let buffer_snapshot = OnceCell::new();
20205
20206 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20207 if !folds.is_empty() {
20208 let snapshot =
20209 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20210 self.fold_ranges(
20211 folds
20212 .into_iter()
20213 .map(|(start, end)| {
20214 snapshot.clip_offset(start, Bias::Left)
20215 ..snapshot.clip_offset(end, Bias::Right)
20216 })
20217 .collect(),
20218 false,
20219 window,
20220 cx,
20221 );
20222 }
20223 }
20224
20225 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20226 if !selections.is_empty() {
20227 let snapshot =
20228 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20229 // skip adding the initial selection to selection history
20230 self.selection_history.mode = SelectionHistoryMode::Skipping;
20231 self.change_selections(None, window, cx, |s| {
20232 s.select_ranges(selections.into_iter().map(|(start, end)| {
20233 snapshot.clip_offset(start, Bias::Left)
20234 ..snapshot.clip_offset(end, Bias::Right)
20235 }));
20236 });
20237 self.selection_history.mode = SelectionHistoryMode::Normal;
20238 }
20239 };
20240 }
20241
20242 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20243 }
20244}
20245
20246fn vim_enabled(cx: &App) -> bool {
20247 cx.global::<SettingsStore>()
20248 .raw_user_settings()
20249 .get("vim_mode")
20250 == Some(&serde_json::Value::Bool(true))
20251}
20252
20253fn process_completion_for_edit(
20254 completion: &Completion,
20255 intent: CompletionIntent,
20256 buffer: &Entity<Buffer>,
20257 cursor_position: &text::Anchor,
20258 cx: &mut Context<Editor>,
20259) -> CompletionEdit {
20260 let buffer = buffer.read(cx);
20261 let buffer_snapshot = buffer.snapshot();
20262 let (snippet, new_text) = if completion.is_snippet() {
20263 // Workaround for typescript language server issues so that methods don't expand within
20264 // strings and functions with type expressions. The previous point is used because the query
20265 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20266 let mut snippet_source = completion.new_text.clone();
20267 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20268 previous_point.column = previous_point.column.saturating_sub(1);
20269 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20270 if scope.prefers_label_for_snippet_in_completion() {
20271 if let Some(label) = completion.label() {
20272 if matches!(
20273 completion.kind(),
20274 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20275 ) {
20276 snippet_source = label;
20277 }
20278 }
20279 }
20280 }
20281 match Snippet::parse(&snippet_source).log_err() {
20282 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20283 None => (None, completion.new_text.clone()),
20284 }
20285 } else {
20286 (None, completion.new_text.clone())
20287 };
20288
20289 let mut range_to_replace = {
20290 let replace_range = &completion.replace_range;
20291 if let CompletionSource::Lsp {
20292 insert_range: Some(insert_range),
20293 ..
20294 } = &completion.source
20295 {
20296 debug_assert_eq!(
20297 insert_range.start, replace_range.start,
20298 "insert_range and replace_range should start at the same position"
20299 );
20300 debug_assert!(
20301 insert_range
20302 .start
20303 .cmp(&cursor_position, &buffer_snapshot)
20304 .is_le(),
20305 "insert_range should start before or at cursor position"
20306 );
20307 debug_assert!(
20308 replace_range
20309 .start
20310 .cmp(&cursor_position, &buffer_snapshot)
20311 .is_le(),
20312 "replace_range should start before or at cursor position"
20313 );
20314 debug_assert!(
20315 insert_range
20316 .end
20317 .cmp(&cursor_position, &buffer_snapshot)
20318 .is_le(),
20319 "insert_range should end before or at cursor position"
20320 );
20321
20322 let should_replace = match intent {
20323 CompletionIntent::CompleteWithInsert => false,
20324 CompletionIntent::CompleteWithReplace => true,
20325 CompletionIntent::Complete | CompletionIntent::Compose => {
20326 let insert_mode =
20327 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20328 .completions
20329 .lsp_insert_mode;
20330 match insert_mode {
20331 LspInsertMode::Insert => false,
20332 LspInsertMode::Replace => true,
20333 LspInsertMode::ReplaceSubsequence => {
20334 let mut text_to_replace = buffer.chars_for_range(
20335 buffer.anchor_before(replace_range.start)
20336 ..buffer.anchor_after(replace_range.end),
20337 );
20338 let mut current_needle = text_to_replace.next();
20339 for haystack_ch in completion.label.text.chars() {
20340 if let Some(needle_ch) = current_needle {
20341 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20342 current_needle = text_to_replace.next();
20343 }
20344 }
20345 }
20346 current_needle.is_none()
20347 }
20348 LspInsertMode::ReplaceSuffix => {
20349 if replace_range
20350 .end
20351 .cmp(&cursor_position, &buffer_snapshot)
20352 .is_gt()
20353 {
20354 let range_after_cursor = *cursor_position..replace_range.end;
20355 let text_after_cursor = buffer
20356 .text_for_range(
20357 buffer.anchor_before(range_after_cursor.start)
20358 ..buffer.anchor_after(range_after_cursor.end),
20359 )
20360 .collect::<String>()
20361 .to_ascii_lowercase();
20362 completion
20363 .label
20364 .text
20365 .to_ascii_lowercase()
20366 .ends_with(&text_after_cursor)
20367 } else {
20368 true
20369 }
20370 }
20371 }
20372 }
20373 };
20374
20375 if should_replace {
20376 replace_range.clone()
20377 } else {
20378 insert_range.clone()
20379 }
20380 } else {
20381 replace_range.clone()
20382 }
20383 };
20384
20385 if range_to_replace
20386 .end
20387 .cmp(&cursor_position, &buffer_snapshot)
20388 .is_lt()
20389 {
20390 range_to_replace.end = *cursor_position;
20391 }
20392
20393 CompletionEdit {
20394 new_text,
20395 replace_range: range_to_replace.to_offset(&buffer),
20396 snippet,
20397 }
20398}
20399
20400struct CompletionEdit {
20401 new_text: String,
20402 replace_range: Range<usize>,
20403 snippet: Option<Snippet>,
20404}
20405
20406fn insert_extra_newline_brackets(
20407 buffer: &MultiBufferSnapshot,
20408 range: Range<usize>,
20409 language: &language::LanguageScope,
20410) -> bool {
20411 let leading_whitespace_len = buffer
20412 .reversed_chars_at(range.start)
20413 .take_while(|c| c.is_whitespace() && *c != '\n')
20414 .map(|c| c.len_utf8())
20415 .sum::<usize>();
20416 let trailing_whitespace_len = buffer
20417 .chars_at(range.end)
20418 .take_while(|c| c.is_whitespace() && *c != '\n')
20419 .map(|c| c.len_utf8())
20420 .sum::<usize>();
20421 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20422
20423 language.brackets().any(|(pair, enabled)| {
20424 let pair_start = pair.start.trim_end();
20425 let pair_end = pair.end.trim_start();
20426
20427 enabled
20428 && pair.newline
20429 && buffer.contains_str_at(range.end, pair_end)
20430 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20431 })
20432}
20433
20434fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20435 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20436 [(buffer, range, _)] => (*buffer, range.clone()),
20437 _ => return false,
20438 };
20439 let pair = {
20440 let mut result: Option<BracketMatch> = None;
20441
20442 for pair in buffer
20443 .all_bracket_ranges(range.clone())
20444 .filter(move |pair| {
20445 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20446 })
20447 {
20448 let len = pair.close_range.end - pair.open_range.start;
20449
20450 if let Some(existing) = &result {
20451 let existing_len = existing.close_range.end - existing.open_range.start;
20452 if len > existing_len {
20453 continue;
20454 }
20455 }
20456
20457 result = Some(pair);
20458 }
20459
20460 result
20461 };
20462 let Some(pair) = pair else {
20463 return false;
20464 };
20465 pair.newline_only
20466 && buffer
20467 .chars_for_range(pair.open_range.end..range.start)
20468 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20469 .all(|c| c.is_whitespace() && c != '\n')
20470}
20471
20472fn update_uncommitted_diff_for_buffer(
20473 editor: Entity<Editor>,
20474 project: &Entity<Project>,
20475 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20476 buffer: Entity<MultiBuffer>,
20477 cx: &mut App,
20478) -> Task<()> {
20479 let mut tasks = Vec::new();
20480 project.update(cx, |project, cx| {
20481 for buffer in buffers {
20482 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20483 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20484 }
20485 }
20486 });
20487 cx.spawn(async move |cx| {
20488 let diffs = future::join_all(tasks).await;
20489 if editor
20490 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20491 .unwrap_or(false)
20492 {
20493 return;
20494 }
20495
20496 buffer
20497 .update(cx, |buffer, cx| {
20498 for diff in diffs.into_iter().flatten() {
20499 buffer.add_diff(diff, cx);
20500 }
20501 })
20502 .ok();
20503 })
20504}
20505
20506fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20507 let tab_size = tab_size.get() as usize;
20508 let mut width = offset;
20509
20510 for ch in text.chars() {
20511 width += if ch == '\t' {
20512 tab_size - (width % tab_size)
20513 } else {
20514 1
20515 };
20516 }
20517
20518 width - offset
20519}
20520
20521#[cfg(test)]
20522mod tests {
20523 use super::*;
20524
20525 #[test]
20526 fn test_string_size_with_expanded_tabs() {
20527 let nz = |val| NonZeroU32::new(val).unwrap();
20528 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20529 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20530 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20531 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20532 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20533 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20534 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20535 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20536 }
20537}
20538
20539/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20540struct WordBreakingTokenizer<'a> {
20541 input: &'a str,
20542}
20543
20544impl<'a> WordBreakingTokenizer<'a> {
20545 fn new(input: &'a str) -> Self {
20546 Self { input }
20547 }
20548}
20549
20550fn is_char_ideographic(ch: char) -> bool {
20551 use unicode_script::Script::*;
20552 use unicode_script::UnicodeScript;
20553 matches!(ch.script(), Han | Tangut | Yi)
20554}
20555
20556fn is_grapheme_ideographic(text: &str) -> bool {
20557 text.chars().any(is_char_ideographic)
20558}
20559
20560fn is_grapheme_whitespace(text: &str) -> bool {
20561 text.chars().any(|x| x.is_whitespace())
20562}
20563
20564fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20565 text.chars().next().map_or(false, |ch| {
20566 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20567 })
20568}
20569
20570#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20571enum WordBreakToken<'a> {
20572 Word { token: &'a str, grapheme_len: usize },
20573 InlineWhitespace { token: &'a str, grapheme_len: usize },
20574 Newline,
20575}
20576
20577impl<'a> Iterator for WordBreakingTokenizer<'a> {
20578 /// Yields a span, the count of graphemes in the token, and whether it was
20579 /// whitespace. Note that it also breaks at word boundaries.
20580 type Item = WordBreakToken<'a>;
20581
20582 fn next(&mut self) -> Option<Self::Item> {
20583 use unicode_segmentation::UnicodeSegmentation;
20584 if self.input.is_empty() {
20585 return None;
20586 }
20587
20588 let mut iter = self.input.graphemes(true).peekable();
20589 let mut offset = 0;
20590 let mut grapheme_len = 0;
20591 if let Some(first_grapheme) = iter.next() {
20592 let is_newline = first_grapheme == "\n";
20593 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20594 offset += first_grapheme.len();
20595 grapheme_len += 1;
20596 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20597 if let Some(grapheme) = iter.peek().copied() {
20598 if should_stay_with_preceding_ideograph(grapheme) {
20599 offset += grapheme.len();
20600 grapheme_len += 1;
20601 }
20602 }
20603 } else {
20604 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20605 let mut next_word_bound = words.peek().copied();
20606 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20607 next_word_bound = words.next();
20608 }
20609 while let Some(grapheme) = iter.peek().copied() {
20610 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20611 break;
20612 };
20613 if is_grapheme_whitespace(grapheme) != is_whitespace
20614 || (grapheme == "\n") != is_newline
20615 {
20616 break;
20617 };
20618 offset += grapheme.len();
20619 grapheme_len += 1;
20620 iter.next();
20621 }
20622 }
20623 let token = &self.input[..offset];
20624 self.input = &self.input[offset..];
20625 if token == "\n" {
20626 Some(WordBreakToken::Newline)
20627 } else if is_whitespace {
20628 Some(WordBreakToken::InlineWhitespace {
20629 token,
20630 grapheme_len,
20631 })
20632 } else {
20633 Some(WordBreakToken::Word {
20634 token,
20635 grapheme_len,
20636 })
20637 }
20638 } else {
20639 None
20640 }
20641 }
20642}
20643
20644#[test]
20645fn test_word_breaking_tokenizer() {
20646 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20647 ("", &[]),
20648 (" ", &[whitespace(" ", 2)]),
20649 ("Ʒ", &[word("Ʒ", 1)]),
20650 ("Ǽ", &[word("Ǽ", 1)]),
20651 ("⋑", &[word("⋑", 1)]),
20652 ("⋑⋑", &[word("⋑⋑", 2)]),
20653 (
20654 "原理,进而",
20655 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20656 ),
20657 (
20658 "hello world",
20659 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20660 ),
20661 (
20662 "hello, world",
20663 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20664 ),
20665 (
20666 " hello world",
20667 &[
20668 whitespace(" ", 2),
20669 word("hello", 5),
20670 whitespace(" ", 1),
20671 word("world", 5),
20672 ],
20673 ),
20674 (
20675 "这是什么 \n 钢笔",
20676 &[
20677 word("这", 1),
20678 word("是", 1),
20679 word("什", 1),
20680 word("么", 1),
20681 whitespace(" ", 1),
20682 newline(),
20683 whitespace(" ", 1),
20684 word("钢", 1),
20685 word("笔", 1),
20686 ],
20687 ),
20688 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20689 ];
20690
20691 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20692 WordBreakToken::Word {
20693 token,
20694 grapheme_len,
20695 }
20696 }
20697
20698 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20699 WordBreakToken::InlineWhitespace {
20700 token,
20701 grapheme_len,
20702 }
20703 }
20704
20705 fn newline() -> WordBreakToken<'static> {
20706 WordBreakToken::Newline
20707 }
20708
20709 for (input, result) in tests {
20710 assert_eq!(
20711 WordBreakingTokenizer::new(input)
20712 .collect::<Vec<_>>()
20713 .as_slice(),
20714 *result,
20715 );
20716 }
20717}
20718
20719fn wrap_with_prefix(
20720 line_prefix: String,
20721 unwrapped_text: String,
20722 wrap_column: usize,
20723 tab_size: NonZeroU32,
20724 preserve_existing_whitespace: bool,
20725) -> String {
20726 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20727 let mut wrapped_text = String::new();
20728 let mut current_line = line_prefix.clone();
20729
20730 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20731 let mut current_line_len = line_prefix_len;
20732 let mut in_whitespace = false;
20733 for token in tokenizer {
20734 let have_preceding_whitespace = in_whitespace;
20735 match token {
20736 WordBreakToken::Word {
20737 token,
20738 grapheme_len,
20739 } => {
20740 in_whitespace = false;
20741 if current_line_len + grapheme_len > wrap_column
20742 && current_line_len != line_prefix_len
20743 {
20744 wrapped_text.push_str(current_line.trim_end());
20745 wrapped_text.push('\n');
20746 current_line.truncate(line_prefix.len());
20747 current_line_len = line_prefix_len;
20748 }
20749 current_line.push_str(token);
20750 current_line_len += grapheme_len;
20751 }
20752 WordBreakToken::InlineWhitespace {
20753 mut token,
20754 mut grapheme_len,
20755 } => {
20756 in_whitespace = true;
20757 if have_preceding_whitespace && !preserve_existing_whitespace {
20758 continue;
20759 }
20760 if !preserve_existing_whitespace {
20761 token = " ";
20762 grapheme_len = 1;
20763 }
20764 if current_line_len + grapheme_len > wrap_column {
20765 wrapped_text.push_str(current_line.trim_end());
20766 wrapped_text.push('\n');
20767 current_line.truncate(line_prefix.len());
20768 current_line_len = line_prefix_len;
20769 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20770 current_line.push_str(token);
20771 current_line_len += grapheme_len;
20772 }
20773 }
20774 WordBreakToken::Newline => {
20775 in_whitespace = true;
20776 if preserve_existing_whitespace {
20777 wrapped_text.push_str(current_line.trim_end());
20778 wrapped_text.push('\n');
20779 current_line.truncate(line_prefix.len());
20780 current_line_len = line_prefix_len;
20781 } else if have_preceding_whitespace {
20782 continue;
20783 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20784 {
20785 wrapped_text.push_str(current_line.trim_end());
20786 wrapped_text.push('\n');
20787 current_line.truncate(line_prefix.len());
20788 current_line_len = line_prefix_len;
20789 } else if current_line_len != line_prefix_len {
20790 current_line.push(' ');
20791 current_line_len += 1;
20792 }
20793 }
20794 }
20795 }
20796
20797 if !current_line.is_empty() {
20798 wrapped_text.push_str(¤t_line);
20799 }
20800 wrapped_text
20801}
20802
20803#[test]
20804fn test_wrap_with_prefix() {
20805 assert_eq!(
20806 wrap_with_prefix(
20807 "# ".to_string(),
20808 "abcdefg".to_string(),
20809 4,
20810 NonZeroU32::new(4).unwrap(),
20811 false,
20812 ),
20813 "# abcdefg"
20814 );
20815 assert_eq!(
20816 wrap_with_prefix(
20817 "".to_string(),
20818 "\thello world".to_string(),
20819 8,
20820 NonZeroU32::new(4).unwrap(),
20821 false,
20822 ),
20823 "hello\nworld"
20824 );
20825 assert_eq!(
20826 wrap_with_prefix(
20827 "// ".to_string(),
20828 "xx \nyy zz aa bb cc".to_string(),
20829 12,
20830 NonZeroU32::new(4).unwrap(),
20831 false,
20832 ),
20833 "// xx yy zz\n// aa bb cc"
20834 );
20835 assert_eq!(
20836 wrap_with_prefix(
20837 String::new(),
20838 "这是什么 \n 钢笔".to_string(),
20839 3,
20840 NonZeroU32::new(4).unwrap(),
20841 false,
20842 ),
20843 "这是什\n么 钢\n笔"
20844 );
20845}
20846
20847pub trait CollaborationHub {
20848 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20849 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20850 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20851}
20852
20853impl CollaborationHub for Entity<Project> {
20854 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20855 self.read(cx).collaborators()
20856 }
20857
20858 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20859 self.read(cx).user_store().read(cx).participant_indices()
20860 }
20861
20862 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20863 let this = self.read(cx);
20864 let user_ids = this.collaborators().values().map(|c| c.user_id);
20865 this.user_store().read(cx).participant_names(user_ids, cx)
20866 }
20867}
20868
20869pub trait SemanticsProvider {
20870 fn hover(
20871 &self,
20872 buffer: &Entity<Buffer>,
20873 position: text::Anchor,
20874 cx: &mut App,
20875 ) -> Option<Task<Vec<project::Hover>>>;
20876
20877 fn inline_values(
20878 &self,
20879 buffer_handle: Entity<Buffer>,
20880 range: Range<text::Anchor>,
20881 cx: &mut App,
20882 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20883
20884 fn inlay_hints(
20885 &self,
20886 buffer_handle: Entity<Buffer>,
20887 range: Range<text::Anchor>,
20888 cx: &mut App,
20889 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20890
20891 fn resolve_inlay_hint(
20892 &self,
20893 hint: InlayHint,
20894 buffer_handle: Entity<Buffer>,
20895 server_id: LanguageServerId,
20896 cx: &mut App,
20897 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20898
20899 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20900
20901 fn document_highlights(
20902 &self,
20903 buffer: &Entity<Buffer>,
20904 position: text::Anchor,
20905 cx: &mut App,
20906 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20907
20908 fn definitions(
20909 &self,
20910 buffer: &Entity<Buffer>,
20911 position: text::Anchor,
20912 kind: GotoDefinitionKind,
20913 cx: &mut App,
20914 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20915
20916 fn range_for_rename(
20917 &self,
20918 buffer: &Entity<Buffer>,
20919 position: text::Anchor,
20920 cx: &mut App,
20921 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20922
20923 fn perform_rename(
20924 &self,
20925 buffer: &Entity<Buffer>,
20926 position: text::Anchor,
20927 new_name: String,
20928 cx: &mut App,
20929 ) -> Option<Task<Result<ProjectTransaction>>>;
20930
20931 fn pull_diagnostics_for_buffer(
20932 &self,
20933 buffer: Entity<Buffer>,
20934 cx: &mut App,
20935 ) -> Task<anyhow::Result<()>>;
20936}
20937
20938pub trait CompletionProvider {
20939 fn completions(
20940 &self,
20941 excerpt_id: ExcerptId,
20942 buffer: &Entity<Buffer>,
20943 buffer_position: text::Anchor,
20944 trigger: CompletionContext,
20945 window: &mut Window,
20946 cx: &mut Context<Editor>,
20947 ) -> Task<Result<Vec<CompletionResponse>>>;
20948
20949 fn resolve_completions(
20950 &self,
20951 _buffer: Entity<Buffer>,
20952 _completion_indices: Vec<usize>,
20953 _completions: Rc<RefCell<Box<[Completion]>>>,
20954 _cx: &mut Context<Editor>,
20955 ) -> Task<Result<bool>> {
20956 Task::ready(Ok(false))
20957 }
20958
20959 fn apply_additional_edits_for_completion(
20960 &self,
20961 _buffer: Entity<Buffer>,
20962 _completions: Rc<RefCell<Box<[Completion]>>>,
20963 _completion_index: usize,
20964 _push_to_history: bool,
20965 _cx: &mut Context<Editor>,
20966 ) -> Task<Result<Option<language::Transaction>>> {
20967 Task::ready(Ok(None))
20968 }
20969
20970 fn is_completion_trigger(
20971 &self,
20972 buffer: &Entity<Buffer>,
20973 position: language::Anchor,
20974 text: &str,
20975 trigger_in_words: bool,
20976 menu_is_open: bool,
20977 cx: &mut Context<Editor>,
20978 ) -> bool;
20979
20980 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20981
20982 fn sort_completions(&self) -> bool {
20983 true
20984 }
20985
20986 fn filter_completions(&self) -> bool {
20987 true
20988 }
20989}
20990
20991pub trait CodeActionProvider {
20992 fn id(&self) -> Arc<str>;
20993
20994 fn code_actions(
20995 &self,
20996 buffer: &Entity<Buffer>,
20997 range: Range<text::Anchor>,
20998 window: &mut Window,
20999 cx: &mut App,
21000 ) -> Task<Result<Vec<CodeAction>>>;
21001
21002 fn apply_code_action(
21003 &self,
21004 buffer_handle: Entity<Buffer>,
21005 action: CodeAction,
21006 excerpt_id: ExcerptId,
21007 push_to_history: bool,
21008 window: &mut Window,
21009 cx: &mut App,
21010 ) -> Task<Result<ProjectTransaction>>;
21011}
21012
21013impl CodeActionProvider for Entity<Project> {
21014 fn id(&self) -> Arc<str> {
21015 "project".into()
21016 }
21017
21018 fn code_actions(
21019 &self,
21020 buffer: &Entity<Buffer>,
21021 range: Range<text::Anchor>,
21022 _window: &mut Window,
21023 cx: &mut App,
21024 ) -> Task<Result<Vec<CodeAction>>> {
21025 self.update(cx, |project, cx| {
21026 let code_lens = project.code_lens(buffer, range.clone(), cx);
21027 let code_actions = project.code_actions(buffer, range, None, cx);
21028 cx.background_spawn(async move {
21029 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21030 Ok(code_lens
21031 .context("code lens fetch")?
21032 .into_iter()
21033 .chain(code_actions.context("code action fetch")?)
21034 .collect())
21035 })
21036 })
21037 }
21038
21039 fn apply_code_action(
21040 &self,
21041 buffer_handle: Entity<Buffer>,
21042 action: CodeAction,
21043 _excerpt_id: ExcerptId,
21044 push_to_history: bool,
21045 _window: &mut Window,
21046 cx: &mut App,
21047 ) -> Task<Result<ProjectTransaction>> {
21048 self.update(cx, |project, cx| {
21049 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21050 })
21051 }
21052}
21053
21054fn snippet_completions(
21055 project: &Project,
21056 buffer: &Entity<Buffer>,
21057 buffer_position: text::Anchor,
21058 cx: &mut App,
21059) -> Task<Result<CompletionResponse>> {
21060 let languages = buffer.read(cx).languages_at(buffer_position);
21061 let snippet_store = project.snippets().read(cx);
21062
21063 let scopes: Vec<_> = languages
21064 .iter()
21065 .filter_map(|language| {
21066 let language_name = language.lsp_id();
21067 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21068
21069 if snippets.is_empty() {
21070 None
21071 } else {
21072 Some((language.default_scope(), snippets))
21073 }
21074 })
21075 .collect();
21076
21077 if scopes.is_empty() {
21078 return Task::ready(Ok(CompletionResponse {
21079 completions: vec![],
21080 is_incomplete: false,
21081 }));
21082 }
21083
21084 let snapshot = buffer.read(cx).text_snapshot();
21085 let chars: String = snapshot
21086 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21087 .collect();
21088 let executor = cx.background_executor().clone();
21089
21090 cx.background_spawn(async move {
21091 let mut is_incomplete = false;
21092 let mut completions: Vec<Completion> = Vec::new();
21093 for (scope, snippets) in scopes.into_iter() {
21094 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21095 let mut last_word = chars
21096 .chars()
21097 .take_while(|c| classifier.is_word(*c))
21098 .collect::<String>();
21099 last_word = last_word.chars().rev().collect();
21100
21101 if last_word.is_empty() {
21102 return Ok(CompletionResponse {
21103 completions: vec![],
21104 is_incomplete: true,
21105 });
21106 }
21107
21108 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21109 let to_lsp = |point: &text::Anchor| {
21110 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21111 point_to_lsp(end)
21112 };
21113 let lsp_end = to_lsp(&buffer_position);
21114
21115 let candidates = snippets
21116 .iter()
21117 .enumerate()
21118 .flat_map(|(ix, snippet)| {
21119 snippet
21120 .prefix
21121 .iter()
21122 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21123 })
21124 .collect::<Vec<StringMatchCandidate>>();
21125
21126 const MAX_RESULTS: usize = 100;
21127 let mut matches = fuzzy::match_strings(
21128 &candidates,
21129 &last_word,
21130 last_word.chars().any(|c| c.is_uppercase()),
21131 MAX_RESULTS,
21132 &Default::default(),
21133 executor.clone(),
21134 )
21135 .await;
21136
21137 if matches.len() >= MAX_RESULTS {
21138 is_incomplete = true;
21139 }
21140
21141 // Remove all candidates where the query's start does not match the start of any word in the candidate
21142 if let Some(query_start) = last_word.chars().next() {
21143 matches.retain(|string_match| {
21144 split_words(&string_match.string).any(|word| {
21145 // Check that the first codepoint of the word as lowercase matches the first
21146 // codepoint of the query as lowercase
21147 word.chars()
21148 .flat_map(|codepoint| codepoint.to_lowercase())
21149 .zip(query_start.to_lowercase())
21150 .all(|(word_cp, query_cp)| word_cp == query_cp)
21151 })
21152 });
21153 }
21154
21155 let matched_strings = matches
21156 .into_iter()
21157 .map(|m| m.string)
21158 .collect::<HashSet<_>>();
21159
21160 completions.extend(snippets.iter().filter_map(|snippet| {
21161 let matching_prefix = snippet
21162 .prefix
21163 .iter()
21164 .find(|prefix| matched_strings.contains(*prefix))?;
21165 let start = as_offset - last_word.len();
21166 let start = snapshot.anchor_before(start);
21167 let range = start..buffer_position;
21168 let lsp_start = to_lsp(&start);
21169 let lsp_range = lsp::Range {
21170 start: lsp_start,
21171 end: lsp_end,
21172 };
21173 Some(Completion {
21174 replace_range: range,
21175 new_text: snippet.body.clone(),
21176 source: CompletionSource::Lsp {
21177 insert_range: None,
21178 server_id: LanguageServerId(usize::MAX),
21179 resolved: true,
21180 lsp_completion: Box::new(lsp::CompletionItem {
21181 label: snippet.prefix.first().unwrap().clone(),
21182 kind: Some(CompletionItemKind::SNIPPET),
21183 label_details: snippet.description.as_ref().map(|description| {
21184 lsp::CompletionItemLabelDetails {
21185 detail: Some(description.clone()),
21186 description: None,
21187 }
21188 }),
21189 insert_text_format: Some(InsertTextFormat::SNIPPET),
21190 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21191 lsp::InsertReplaceEdit {
21192 new_text: snippet.body.clone(),
21193 insert: lsp_range,
21194 replace: lsp_range,
21195 },
21196 )),
21197 filter_text: Some(snippet.body.clone()),
21198 sort_text: Some(char::MAX.to_string()),
21199 ..lsp::CompletionItem::default()
21200 }),
21201 lsp_defaults: None,
21202 },
21203 label: CodeLabel {
21204 text: matching_prefix.clone(),
21205 runs: Vec::new(),
21206 filter_range: 0..matching_prefix.len(),
21207 },
21208 icon_path: None,
21209 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21210 single_line: snippet.name.clone().into(),
21211 plain_text: snippet
21212 .description
21213 .clone()
21214 .map(|description| description.into()),
21215 }),
21216 insert_text_mode: None,
21217 confirm: None,
21218 })
21219 }))
21220 }
21221
21222 Ok(CompletionResponse {
21223 completions,
21224 is_incomplete,
21225 })
21226 })
21227}
21228
21229impl CompletionProvider for Entity<Project> {
21230 fn completions(
21231 &self,
21232 _excerpt_id: ExcerptId,
21233 buffer: &Entity<Buffer>,
21234 buffer_position: text::Anchor,
21235 options: CompletionContext,
21236 _window: &mut Window,
21237 cx: &mut Context<Editor>,
21238 ) -> Task<Result<Vec<CompletionResponse>>> {
21239 self.update(cx, |project, cx| {
21240 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21241 let project_completions = project.completions(buffer, buffer_position, options, cx);
21242 cx.background_spawn(async move {
21243 let mut responses = project_completions.await?;
21244 let snippets = snippets.await?;
21245 if !snippets.completions.is_empty() {
21246 responses.push(snippets);
21247 }
21248 Ok(responses)
21249 })
21250 })
21251 }
21252
21253 fn resolve_completions(
21254 &self,
21255 buffer: Entity<Buffer>,
21256 completion_indices: Vec<usize>,
21257 completions: Rc<RefCell<Box<[Completion]>>>,
21258 cx: &mut Context<Editor>,
21259 ) -> Task<Result<bool>> {
21260 self.update(cx, |project, cx| {
21261 project.lsp_store().update(cx, |lsp_store, cx| {
21262 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21263 })
21264 })
21265 }
21266
21267 fn apply_additional_edits_for_completion(
21268 &self,
21269 buffer: Entity<Buffer>,
21270 completions: Rc<RefCell<Box<[Completion]>>>,
21271 completion_index: usize,
21272 push_to_history: bool,
21273 cx: &mut Context<Editor>,
21274 ) -> Task<Result<Option<language::Transaction>>> {
21275 self.update(cx, |project, cx| {
21276 project.lsp_store().update(cx, |lsp_store, cx| {
21277 lsp_store.apply_additional_edits_for_completion(
21278 buffer,
21279 completions,
21280 completion_index,
21281 push_to_history,
21282 cx,
21283 )
21284 })
21285 })
21286 }
21287
21288 fn is_completion_trigger(
21289 &self,
21290 buffer: &Entity<Buffer>,
21291 position: language::Anchor,
21292 text: &str,
21293 trigger_in_words: bool,
21294 menu_is_open: bool,
21295 cx: &mut Context<Editor>,
21296 ) -> bool {
21297 let mut chars = text.chars();
21298 let char = if let Some(char) = chars.next() {
21299 char
21300 } else {
21301 return false;
21302 };
21303 if chars.next().is_some() {
21304 return false;
21305 }
21306
21307 let buffer = buffer.read(cx);
21308 let snapshot = buffer.snapshot();
21309 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21310 return false;
21311 }
21312 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21313 if trigger_in_words && classifier.is_word(char) {
21314 return true;
21315 }
21316
21317 buffer.completion_triggers().contains(text)
21318 }
21319}
21320
21321impl SemanticsProvider for Entity<Project> {
21322 fn hover(
21323 &self,
21324 buffer: &Entity<Buffer>,
21325 position: text::Anchor,
21326 cx: &mut App,
21327 ) -> Option<Task<Vec<project::Hover>>> {
21328 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21329 }
21330
21331 fn document_highlights(
21332 &self,
21333 buffer: &Entity<Buffer>,
21334 position: text::Anchor,
21335 cx: &mut App,
21336 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21337 Some(self.update(cx, |project, cx| {
21338 project.document_highlights(buffer, position, cx)
21339 }))
21340 }
21341
21342 fn definitions(
21343 &self,
21344 buffer: &Entity<Buffer>,
21345 position: text::Anchor,
21346 kind: GotoDefinitionKind,
21347 cx: &mut App,
21348 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21349 Some(self.update(cx, |project, cx| match kind {
21350 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21351 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21352 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21353 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21354 }))
21355 }
21356
21357 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21358 // TODO: make this work for remote projects
21359 self.update(cx, |project, cx| {
21360 if project
21361 .active_debug_session(cx)
21362 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21363 {
21364 return true;
21365 }
21366
21367 buffer.update(cx, |buffer, cx| {
21368 project.any_language_server_supports_inlay_hints(buffer, cx)
21369 })
21370 })
21371 }
21372
21373 fn inline_values(
21374 &self,
21375 buffer_handle: Entity<Buffer>,
21376
21377 range: Range<text::Anchor>,
21378 cx: &mut App,
21379 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21380 self.update(cx, |project, cx| {
21381 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21382
21383 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21384 })
21385 }
21386
21387 fn inlay_hints(
21388 &self,
21389 buffer_handle: Entity<Buffer>,
21390 range: Range<text::Anchor>,
21391 cx: &mut App,
21392 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21393 Some(self.update(cx, |project, cx| {
21394 project.inlay_hints(buffer_handle, range, cx)
21395 }))
21396 }
21397
21398 fn resolve_inlay_hint(
21399 &self,
21400 hint: InlayHint,
21401 buffer_handle: Entity<Buffer>,
21402 server_id: LanguageServerId,
21403 cx: &mut App,
21404 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21405 Some(self.update(cx, |project, cx| {
21406 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21407 }))
21408 }
21409
21410 fn range_for_rename(
21411 &self,
21412 buffer: &Entity<Buffer>,
21413 position: text::Anchor,
21414 cx: &mut App,
21415 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21416 Some(self.update(cx, |project, cx| {
21417 let buffer = buffer.clone();
21418 let task = project.prepare_rename(buffer.clone(), position, cx);
21419 cx.spawn(async move |_, cx| {
21420 Ok(match task.await? {
21421 PrepareRenameResponse::Success(range) => Some(range),
21422 PrepareRenameResponse::InvalidPosition => None,
21423 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21424 // Fallback on using TreeSitter info to determine identifier range
21425 buffer.read_with(cx, |buffer, _| {
21426 let snapshot = buffer.snapshot();
21427 let (range, kind) = snapshot.surrounding_word(position);
21428 if kind != Some(CharKind::Word) {
21429 return None;
21430 }
21431 Some(
21432 snapshot.anchor_before(range.start)
21433 ..snapshot.anchor_after(range.end),
21434 )
21435 })?
21436 }
21437 })
21438 })
21439 }))
21440 }
21441
21442 fn perform_rename(
21443 &self,
21444 buffer: &Entity<Buffer>,
21445 position: text::Anchor,
21446 new_name: String,
21447 cx: &mut App,
21448 ) -> Option<Task<Result<ProjectTransaction>>> {
21449 Some(self.update(cx, |project, cx| {
21450 project.perform_rename(buffer.clone(), position, new_name, cx)
21451 }))
21452 }
21453
21454 fn pull_diagnostics_for_buffer(
21455 &self,
21456 buffer: Entity<Buffer>,
21457 cx: &mut App,
21458 ) -> Task<anyhow::Result<()>> {
21459 let diagnostics = self.update(cx, |project, cx| {
21460 project
21461 .lsp_store()
21462 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21463 });
21464 let project = self.clone();
21465 cx.spawn(async move |cx| {
21466 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21467 project.update(cx, |project, cx| {
21468 project.lsp_store().update(cx, |lsp_store, cx| {
21469 for diagnostics_set in diagnostics {
21470 let LspPullDiagnostics::Response {
21471 server_id,
21472 uri,
21473 diagnostics,
21474 } = diagnostics_set
21475 else {
21476 continue;
21477 };
21478
21479 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21480 let disk_based_sources = adapter
21481 .as_ref()
21482 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21483 .unwrap_or(&[]);
21484 match diagnostics {
21485 PulledDiagnostics::Unchanged { result_id } => {
21486 lsp_store
21487 .merge_diagnostics(
21488 server_id,
21489 lsp::PublishDiagnosticsParams {
21490 uri: uri.clone(),
21491 diagnostics: Vec::new(),
21492 version: None,
21493 },
21494 Some(result_id),
21495 DiagnosticSourceKind::Pulled,
21496 disk_based_sources,
21497 |_, _| true,
21498 cx,
21499 )
21500 .log_err();
21501 }
21502 PulledDiagnostics::Changed {
21503 diagnostics,
21504 result_id,
21505 } => {
21506 lsp_store
21507 .merge_diagnostics(
21508 server_id,
21509 lsp::PublishDiagnosticsParams {
21510 uri: uri.clone(),
21511 diagnostics,
21512 version: None,
21513 },
21514 result_id,
21515 DiagnosticSourceKind::Pulled,
21516 disk_based_sources,
21517 |old_diagnostic, _| match old_diagnostic.source_kind {
21518 DiagnosticSourceKind::Pulled => false,
21519 DiagnosticSourceKind::Other
21520 | DiagnosticSourceKind::Pushed => true,
21521 },
21522 cx,
21523 )
21524 .log_err();
21525 }
21526 }
21527 }
21528 })
21529 })
21530 })
21531 }
21532}
21533
21534fn inlay_hint_settings(
21535 location: Anchor,
21536 snapshot: &MultiBufferSnapshot,
21537 cx: &mut Context<Editor>,
21538) -> InlayHintSettings {
21539 let file = snapshot.file_at(location);
21540 let language = snapshot.language_at(location).map(|l| l.name());
21541 language_settings(language, file, cx).inlay_hints
21542}
21543
21544fn consume_contiguous_rows(
21545 contiguous_row_selections: &mut Vec<Selection<Point>>,
21546 selection: &Selection<Point>,
21547 display_map: &DisplaySnapshot,
21548 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21549) -> (MultiBufferRow, MultiBufferRow) {
21550 contiguous_row_selections.push(selection.clone());
21551 let start_row = MultiBufferRow(selection.start.row);
21552 let mut end_row = ending_row(selection, display_map);
21553
21554 while let Some(next_selection) = selections.peek() {
21555 if next_selection.start.row <= end_row.0 {
21556 end_row = ending_row(next_selection, display_map);
21557 contiguous_row_selections.push(selections.next().unwrap().clone());
21558 } else {
21559 break;
21560 }
21561 }
21562 (start_row, end_row)
21563}
21564
21565fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21566 if next_selection.end.column > 0 || next_selection.is_empty() {
21567 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21568 } else {
21569 MultiBufferRow(next_selection.end.row)
21570 }
21571}
21572
21573impl EditorSnapshot {
21574 pub fn remote_selections_in_range<'a>(
21575 &'a self,
21576 range: &'a Range<Anchor>,
21577 collaboration_hub: &dyn CollaborationHub,
21578 cx: &'a App,
21579 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21580 let participant_names = collaboration_hub.user_names(cx);
21581 let participant_indices = collaboration_hub.user_participant_indices(cx);
21582 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21583 let collaborators_by_replica_id = collaborators_by_peer_id
21584 .values()
21585 .map(|collaborator| (collaborator.replica_id, collaborator))
21586 .collect::<HashMap<_, _>>();
21587 self.buffer_snapshot
21588 .selections_in_range(range, false)
21589 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21590 if replica_id == AGENT_REPLICA_ID {
21591 Some(RemoteSelection {
21592 replica_id,
21593 selection,
21594 cursor_shape,
21595 line_mode,
21596 collaborator_id: CollaboratorId::Agent,
21597 user_name: Some("Agent".into()),
21598 color: cx.theme().players().agent(),
21599 })
21600 } else {
21601 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21602 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21603 let user_name = participant_names.get(&collaborator.user_id).cloned();
21604 Some(RemoteSelection {
21605 replica_id,
21606 selection,
21607 cursor_shape,
21608 line_mode,
21609 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21610 user_name,
21611 color: if let Some(index) = participant_index {
21612 cx.theme().players().color_for_participant(index.0)
21613 } else {
21614 cx.theme().players().absent()
21615 },
21616 })
21617 }
21618 })
21619 }
21620
21621 pub fn hunks_for_ranges(
21622 &self,
21623 ranges: impl IntoIterator<Item = Range<Point>>,
21624 ) -> Vec<MultiBufferDiffHunk> {
21625 let mut hunks = Vec::new();
21626 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21627 HashMap::default();
21628 for query_range in ranges {
21629 let query_rows =
21630 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21631 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21632 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21633 ) {
21634 // Include deleted hunks that are adjacent to the query range, because
21635 // otherwise they would be missed.
21636 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21637 if hunk.status().is_deleted() {
21638 intersects_range |= hunk.row_range.start == query_rows.end;
21639 intersects_range |= hunk.row_range.end == query_rows.start;
21640 }
21641 if intersects_range {
21642 if !processed_buffer_rows
21643 .entry(hunk.buffer_id)
21644 .or_default()
21645 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21646 {
21647 continue;
21648 }
21649 hunks.push(hunk);
21650 }
21651 }
21652 }
21653
21654 hunks
21655 }
21656
21657 fn display_diff_hunks_for_rows<'a>(
21658 &'a self,
21659 display_rows: Range<DisplayRow>,
21660 folded_buffers: &'a HashSet<BufferId>,
21661 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21662 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21663 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21664
21665 self.buffer_snapshot
21666 .diff_hunks_in_range(buffer_start..buffer_end)
21667 .filter_map(|hunk| {
21668 if folded_buffers.contains(&hunk.buffer_id) {
21669 return None;
21670 }
21671
21672 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21673 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21674
21675 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21676 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21677
21678 let display_hunk = if hunk_display_start.column() != 0 {
21679 DisplayDiffHunk::Folded {
21680 display_row: hunk_display_start.row(),
21681 }
21682 } else {
21683 let mut end_row = hunk_display_end.row();
21684 if hunk_display_end.column() > 0 {
21685 end_row.0 += 1;
21686 }
21687 let is_created_file = hunk.is_created_file();
21688 DisplayDiffHunk::Unfolded {
21689 status: hunk.status(),
21690 diff_base_byte_range: hunk.diff_base_byte_range,
21691 display_row_range: hunk_display_start.row()..end_row,
21692 multi_buffer_range: Anchor::range_in_buffer(
21693 hunk.excerpt_id,
21694 hunk.buffer_id,
21695 hunk.buffer_range,
21696 ),
21697 is_created_file,
21698 }
21699 };
21700
21701 Some(display_hunk)
21702 })
21703 }
21704
21705 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21706 self.display_snapshot.buffer_snapshot.language_at(position)
21707 }
21708
21709 pub fn is_focused(&self) -> bool {
21710 self.is_focused
21711 }
21712
21713 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21714 self.placeholder_text.as_ref()
21715 }
21716
21717 pub fn scroll_position(&self) -> gpui::Point<f32> {
21718 self.scroll_anchor.scroll_position(&self.display_snapshot)
21719 }
21720
21721 fn gutter_dimensions(
21722 &self,
21723 font_id: FontId,
21724 font_size: Pixels,
21725 max_line_number_width: Pixels,
21726 cx: &App,
21727 ) -> Option<GutterDimensions> {
21728 if !self.show_gutter {
21729 return None;
21730 }
21731
21732 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21733 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21734
21735 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21736 matches!(
21737 ProjectSettings::get_global(cx).git.git_gutter,
21738 Some(GitGutterSetting::TrackedFiles)
21739 )
21740 });
21741 let gutter_settings = EditorSettings::get_global(cx).gutter;
21742 let show_line_numbers = self
21743 .show_line_numbers
21744 .unwrap_or(gutter_settings.line_numbers);
21745 let line_gutter_width = if show_line_numbers {
21746 // Avoid flicker-like gutter resizes when the line number gains another digit by
21747 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21748 let min_width_for_number_on_gutter =
21749 ch_advance * gutter_settings.min_line_number_digits as f32;
21750 max_line_number_width.max(min_width_for_number_on_gutter)
21751 } else {
21752 0.0.into()
21753 };
21754
21755 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21756 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21757
21758 let git_blame_entries_width =
21759 self.git_blame_gutter_max_author_length
21760 .map(|max_author_length| {
21761 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21762 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21763
21764 /// The number of characters to dedicate to gaps and margins.
21765 const SPACING_WIDTH: usize = 4;
21766
21767 let max_char_count = max_author_length.min(renderer.max_author_length())
21768 + ::git::SHORT_SHA_LENGTH
21769 + MAX_RELATIVE_TIMESTAMP.len()
21770 + SPACING_WIDTH;
21771
21772 ch_advance * max_char_count
21773 });
21774
21775 let is_singleton = self.buffer_snapshot.is_singleton();
21776
21777 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21778 left_padding += if !is_singleton {
21779 ch_width * 4.0
21780 } else if show_runnables || show_breakpoints {
21781 ch_width * 3.0
21782 } else if show_git_gutter && show_line_numbers {
21783 ch_width * 2.0
21784 } else if show_git_gutter || show_line_numbers {
21785 ch_width
21786 } else {
21787 px(0.)
21788 };
21789
21790 let shows_folds = is_singleton && gutter_settings.folds;
21791
21792 let right_padding = if shows_folds && show_line_numbers {
21793 ch_width * 4.0
21794 } else if shows_folds || (!is_singleton && show_line_numbers) {
21795 ch_width * 3.0
21796 } else if show_line_numbers {
21797 ch_width
21798 } else {
21799 px(0.)
21800 };
21801
21802 Some(GutterDimensions {
21803 left_padding,
21804 right_padding,
21805 width: line_gutter_width + left_padding + right_padding,
21806 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21807 git_blame_entries_width,
21808 })
21809 }
21810
21811 pub fn render_crease_toggle(
21812 &self,
21813 buffer_row: MultiBufferRow,
21814 row_contains_cursor: bool,
21815 editor: Entity<Editor>,
21816 window: &mut Window,
21817 cx: &mut App,
21818 ) -> Option<AnyElement> {
21819 let folded = self.is_line_folded(buffer_row);
21820 let mut is_foldable = false;
21821
21822 if let Some(crease) = self
21823 .crease_snapshot
21824 .query_row(buffer_row, &self.buffer_snapshot)
21825 {
21826 is_foldable = true;
21827 match crease {
21828 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21829 if let Some(render_toggle) = render_toggle {
21830 let toggle_callback =
21831 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21832 if folded {
21833 editor.update(cx, |editor, cx| {
21834 editor.fold_at(buffer_row, window, cx)
21835 });
21836 } else {
21837 editor.update(cx, |editor, cx| {
21838 editor.unfold_at(buffer_row, window, cx)
21839 });
21840 }
21841 });
21842 return Some((render_toggle)(
21843 buffer_row,
21844 folded,
21845 toggle_callback,
21846 window,
21847 cx,
21848 ));
21849 }
21850 }
21851 }
21852 }
21853
21854 is_foldable |= self.starts_indent(buffer_row);
21855
21856 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21857 Some(
21858 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21859 .toggle_state(folded)
21860 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21861 if folded {
21862 this.unfold_at(buffer_row, window, cx);
21863 } else {
21864 this.fold_at(buffer_row, window, cx);
21865 }
21866 }))
21867 .into_any_element(),
21868 )
21869 } else {
21870 None
21871 }
21872 }
21873
21874 pub fn render_crease_trailer(
21875 &self,
21876 buffer_row: MultiBufferRow,
21877 window: &mut Window,
21878 cx: &mut App,
21879 ) -> Option<AnyElement> {
21880 let folded = self.is_line_folded(buffer_row);
21881 if let Crease::Inline { render_trailer, .. } = self
21882 .crease_snapshot
21883 .query_row(buffer_row, &self.buffer_snapshot)?
21884 {
21885 let render_trailer = render_trailer.as_ref()?;
21886 Some(render_trailer(buffer_row, folded, window, cx))
21887 } else {
21888 None
21889 }
21890 }
21891}
21892
21893impl Deref for EditorSnapshot {
21894 type Target = DisplaySnapshot;
21895
21896 fn deref(&self) -> &Self::Target {
21897 &self.display_snapshot
21898 }
21899}
21900
21901#[derive(Clone, Debug, PartialEq, Eq)]
21902pub enum EditorEvent {
21903 InputIgnored {
21904 text: Arc<str>,
21905 },
21906 InputHandled {
21907 utf16_range_to_replace: Option<Range<isize>>,
21908 text: Arc<str>,
21909 },
21910 ExcerptsAdded {
21911 buffer: Entity<Buffer>,
21912 predecessor: ExcerptId,
21913 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21914 },
21915 ExcerptsRemoved {
21916 ids: Vec<ExcerptId>,
21917 removed_buffer_ids: Vec<BufferId>,
21918 },
21919 BufferFoldToggled {
21920 ids: Vec<ExcerptId>,
21921 folded: bool,
21922 },
21923 ExcerptsEdited {
21924 ids: Vec<ExcerptId>,
21925 },
21926 ExcerptsExpanded {
21927 ids: Vec<ExcerptId>,
21928 },
21929 BufferEdited,
21930 Edited {
21931 transaction_id: clock::Lamport,
21932 },
21933 Reparsed(BufferId),
21934 Focused,
21935 FocusedIn,
21936 Blurred,
21937 DirtyChanged,
21938 Saved,
21939 TitleChanged,
21940 DiffBaseChanged,
21941 SelectionsChanged {
21942 local: bool,
21943 },
21944 ScrollPositionChanged {
21945 local: bool,
21946 autoscroll: bool,
21947 },
21948 Closed,
21949 TransactionUndone {
21950 transaction_id: clock::Lamport,
21951 },
21952 TransactionBegun {
21953 transaction_id: clock::Lamport,
21954 },
21955 Reloaded,
21956 CursorShapeChanged,
21957 PushedToNavHistory {
21958 anchor: Anchor,
21959 is_deactivate: bool,
21960 },
21961}
21962
21963impl EventEmitter<EditorEvent> for Editor {}
21964
21965impl Focusable for Editor {
21966 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21967 self.focus_handle.clone()
21968 }
21969}
21970
21971impl Render for Editor {
21972 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21973 let settings = ThemeSettings::get_global(cx);
21974
21975 let mut text_style = match self.mode {
21976 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21977 color: cx.theme().colors().editor_foreground,
21978 font_family: settings.ui_font.family.clone(),
21979 font_features: settings.ui_font.features.clone(),
21980 font_fallbacks: settings.ui_font.fallbacks.clone(),
21981 font_size: rems(0.875).into(),
21982 font_weight: settings.ui_font.weight,
21983 line_height: relative(settings.buffer_line_height.value()),
21984 ..Default::default()
21985 },
21986 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21987 color: cx.theme().colors().editor_foreground,
21988 font_family: settings.buffer_font.family.clone(),
21989 font_features: settings.buffer_font.features.clone(),
21990 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21991 font_size: settings.buffer_font_size(cx).into(),
21992 font_weight: settings.buffer_font.weight,
21993 line_height: relative(settings.buffer_line_height.value()),
21994 ..Default::default()
21995 },
21996 };
21997 if let Some(text_style_refinement) = &self.text_style_refinement {
21998 text_style.refine(text_style_refinement)
21999 }
22000
22001 let background = match self.mode {
22002 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22003 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22004 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22005 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22006 };
22007
22008 EditorElement::new(
22009 &cx.entity(),
22010 EditorStyle {
22011 background,
22012 local_player: cx.theme().players().local(),
22013 text: text_style,
22014 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22015 syntax: cx.theme().syntax().clone(),
22016 status: cx.theme().status().clone(),
22017 inlay_hints_style: make_inlay_hints_style(cx),
22018 inline_completion_styles: make_suggestion_styles(cx),
22019 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22020 show_underlines: !self.mode.is_minimap(),
22021 },
22022 )
22023 }
22024}
22025
22026impl EntityInputHandler for Editor {
22027 fn text_for_range(
22028 &mut self,
22029 range_utf16: Range<usize>,
22030 adjusted_range: &mut Option<Range<usize>>,
22031 _: &mut Window,
22032 cx: &mut Context<Self>,
22033 ) -> Option<String> {
22034 let snapshot = self.buffer.read(cx).read(cx);
22035 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22036 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22037 if (start.0..end.0) != range_utf16 {
22038 adjusted_range.replace(start.0..end.0);
22039 }
22040 Some(snapshot.text_for_range(start..end).collect())
22041 }
22042
22043 fn selected_text_range(
22044 &mut self,
22045 ignore_disabled_input: bool,
22046 _: &mut Window,
22047 cx: &mut Context<Self>,
22048 ) -> Option<UTF16Selection> {
22049 // Prevent the IME menu from appearing when holding down an alphabetic key
22050 // while input is disabled.
22051 if !ignore_disabled_input && !self.input_enabled {
22052 return None;
22053 }
22054
22055 let selection = self.selections.newest::<OffsetUtf16>(cx);
22056 let range = selection.range();
22057
22058 Some(UTF16Selection {
22059 range: range.start.0..range.end.0,
22060 reversed: selection.reversed,
22061 })
22062 }
22063
22064 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22065 let snapshot = self.buffer.read(cx).read(cx);
22066 let (range, _) = self.text_highlights::<InputComposition>(cx)?.first()?;
22067 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22068 }
22069
22070 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22071 self.clear_highlights::<InputComposition>(cx);
22072 self.ime_transaction.take();
22073 }
22074
22075 fn replace_text_in_range(
22076 &mut self,
22077 range_utf16: Option<Range<usize>>,
22078 text: &str,
22079 window: &mut Window,
22080 cx: &mut Context<Self>,
22081 ) {
22082 if !self.input_enabled {
22083 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22084 return;
22085 }
22086
22087 self.transact(window, cx, |this, window, cx| {
22088 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22089 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22090 Some(this.selection_replacement_ranges(range_utf16, cx))
22091 } else {
22092 this.marked_text_ranges(cx)
22093 };
22094
22095 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22096 let newest_selection_id = this.selections.newest_anchor().id;
22097 this.selections
22098 .all::<OffsetUtf16>(cx)
22099 .iter()
22100 .zip(ranges_to_replace.iter())
22101 .find_map(|(selection, range)| {
22102 if selection.id == newest_selection_id {
22103 Some(
22104 (range.start.0 as isize - selection.head().0 as isize)
22105 ..(range.end.0 as isize - selection.head().0 as isize),
22106 )
22107 } else {
22108 None
22109 }
22110 })
22111 });
22112
22113 cx.emit(EditorEvent::InputHandled {
22114 utf16_range_to_replace: range_to_replace,
22115 text: text.into(),
22116 });
22117
22118 if let Some(new_selected_ranges) = new_selected_ranges {
22119 this.change_selections(None, window, cx, |selections| {
22120 selections.select_ranges(new_selected_ranges)
22121 });
22122 this.backspace(&Default::default(), window, cx);
22123 }
22124
22125 this.handle_input(text, window, cx);
22126 });
22127
22128 if let Some(transaction) = self.ime_transaction {
22129 self.buffer.update(cx, |buffer, cx| {
22130 buffer.group_until_transaction(transaction, cx);
22131 });
22132 }
22133
22134 self.unmark_text(window, cx);
22135 }
22136
22137 fn replace_and_mark_text_in_range(
22138 &mut self,
22139 range_utf16: Option<Range<usize>>,
22140 text: &str,
22141 new_selected_range_utf16: Option<Range<usize>>,
22142 window: &mut Window,
22143 cx: &mut Context<Self>,
22144 ) {
22145 if !self.input_enabled {
22146 return;
22147 }
22148
22149 let transaction = self.transact(window, cx, |this, window, cx| {
22150 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22151 let snapshot = this.buffer.read(cx).read(cx);
22152 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22153 for marked_range in &mut marked_ranges {
22154 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22155 marked_range.start.0 += relative_range_utf16.start;
22156 marked_range.start =
22157 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22158 marked_range.end =
22159 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22160 }
22161 }
22162 Some(marked_ranges)
22163 } else if let Some(range_utf16) = range_utf16 {
22164 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22165 Some(this.selection_replacement_ranges(range_utf16, cx))
22166 } else {
22167 None
22168 };
22169
22170 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22171 let newest_selection_id = this.selections.newest_anchor().id;
22172 this.selections
22173 .all::<OffsetUtf16>(cx)
22174 .iter()
22175 .zip(ranges_to_replace.iter())
22176 .find_map(|(selection, range)| {
22177 if selection.id == newest_selection_id {
22178 Some(
22179 (range.start.0 as isize - selection.head().0 as isize)
22180 ..(range.end.0 as isize - selection.head().0 as isize),
22181 )
22182 } else {
22183 None
22184 }
22185 })
22186 });
22187
22188 cx.emit(EditorEvent::InputHandled {
22189 utf16_range_to_replace: range_to_replace,
22190 text: text.into(),
22191 });
22192
22193 if let Some(ranges) = ranges_to_replace {
22194 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22195 }
22196
22197 let marked_ranges = {
22198 let snapshot = this.buffer.read(cx).read(cx);
22199 this.selections
22200 .disjoint_anchors()
22201 .iter()
22202 .map(|selection| {
22203 (
22204 selection.start.bias_left(&snapshot)
22205 ..selection.end.bias_right(&snapshot),
22206 HighlightStyle {
22207 underline: Some(UnderlineStyle {
22208 thickness: px(1.),
22209 color: None,
22210 wavy: false,
22211 }),
22212 ..Default::default()
22213 },
22214 )
22215 })
22216 .collect::<Vec<_>>()
22217 };
22218
22219 if text.is_empty() {
22220 this.unmark_text(window, cx);
22221 } else {
22222 this.highlight_text::<InputComposition>(marked_ranges.clone(), cx);
22223 }
22224
22225 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22226 let use_autoclose = this.use_autoclose;
22227 let use_auto_surround = this.use_auto_surround;
22228 this.set_use_autoclose(false);
22229 this.set_use_auto_surround(false);
22230 this.handle_input(text, window, cx);
22231 this.set_use_autoclose(use_autoclose);
22232 this.set_use_auto_surround(use_auto_surround);
22233
22234 if let Some(new_selected_range) = new_selected_range_utf16 {
22235 let snapshot = this.buffer.read(cx).read(cx);
22236 let new_selected_ranges = marked_ranges
22237 .into_iter()
22238 .map(|(marked_range, _)| {
22239 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22240 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22241 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22242 snapshot.clip_offset_utf16(new_start, Bias::Left)
22243 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22244 })
22245 .collect::<Vec<_>>();
22246
22247 drop(snapshot);
22248 this.change_selections(None, window, cx, |selections| {
22249 selections.select_ranges(new_selected_ranges)
22250 });
22251 }
22252 });
22253
22254 self.ime_transaction = self.ime_transaction.or(transaction);
22255 if let Some(transaction) = self.ime_transaction {
22256 self.buffer.update(cx, |buffer, cx| {
22257 buffer.group_until_transaction(transaction, cx);
22258 });
22259 }
22260
22261 if self.text_highlights::<InputComposition>(cx).is_none() {
22262 self.ime_transaction.take();
22263 }
22264 }
22265
22266 fn bounds_for_range(
22267 &mut self,
22268 range_utf16: Range<usize>,
22269 element_bounds: gpui::Bounds<Pixels>,
22270 window: &mut Window,
22271 cx: &mut Context<Self>,
22272 ) -> Option<gpui::Bounds<Pixels>> {
22273 let text_layout_details = self.text_layout_details(window);
22274 let gpui::Size {
22275 width: em_width,
22276 height: line_height,
22277 } = self.character_size(window);
22278
22279 let snapshot = self.snapshot(window, cx);
22280 let scroll_position = snapshot.scroll_position();
22281 let scroll_left = scroll_position.x * em_width;
22282
22283 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22284 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22285 + self.gutter_dimensions.width
22286 + self.gutter_dimensions.margin;
22287 let y = line_height * (start.row().as_f32() - scroll_position.y);
22288
22289 Some(Bounds {
22290 origin: element_bounds.origin + point(x, y),
22291 size: size(em_width, line_height),
22292 })
22293 }
22294
22295 fn character_index_for_point(
22296 &mut self,
22297 point: gpui::Point<Pixels>,
22298 _window: &mut Window,
22299 _cx: &mut Context<Self>,
22300 ) -> Option<usize> {
22301 let position_map = self.last_position_map.as_ref()?;
22302 if !position_map.text_hitbox.contains(&point) {
22303 return None;
22304 }
22305 let display_point = position_map.point_for_position(point).previous_valid;
22306 let anchor = position_map
22307 .snapshot
22308 .display_point_to_anchor(display_point, Bias::Left);
22309 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22310 Some(utf16_offset.0)
22311 }
22312}
22313
22314trait SelectionExt {
22315 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22316 fn spanned_rows(
22317 &self,
22318 include_end_if_at_line_start: bool,
22319 map: &DisplaySnapshot,
22320 ) -> Range<MultiBufferRow>;
22321}
22322
22323impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22324 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22325 let start = self
22326 .start
22327 .to_point(&map.buffer_snapshot)
22328 .to_display_point(map);
22329 let end = self
22330 .end
22331 .to_point(&map.buffer_snapshot)
22332 .to_display_point(map);
22333 if self.reversed {
22334 end..start
22335 } else {
22336 start..end
22337 }
22338 }
22339
22340 fn spanned_rows(
22341 &self,
22342 include_end_if_at_line_start: bool,
22343 map: &DisplaySnapshot,
22344 ) -> Range<MultiBufferRow> {
22345 let start = self.start.to_point(&map.buffer_snapshot);
22346 let mut end = self.end.to_point(&map.buffer_snapshot);
22347 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22348 end.row -= 1;
22349 }
22350
22351 let buffer_start = map.prev_line_boundary(start).0;
22352 let buffer_end = map.next_line_boundary(end).0;
22353 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22354 }
22355}
22356
22357impl<T: InvalidationRegion> InvalidationStack<T> {
22358 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22359 where
22360 S: Clone + ToOffset,
22361 {
22362 while let Some(region) = self.last() {
22363 let all_selections_inside_invalidation_ranges =
22364 if selections.len() == region.ranges().len() {
22365 selections
22366 .iter()
22367 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22368 .all(|(selection, invalidation_range)| {
22369 let head = selection.head().to_offset(buffer);
22370 invalidation_range.start <= head && invalidation_range.end >= head
22371 })
22372 } else {
22373 false
22374 };
22375
22376 if all_selections_inside_invalidation_ranges {
22377 break;
22378 } else {
22379 self.pop();
22380 }
22381 }
22382 }
22383}
22384
22385impl<T> Default for InvalidationStack<T> {
22386 fn default() -> Self {
22387 Self(Default::default())
22388 }
22389}
22390
22391impl<T> Deref for InvalidationStack<T> {
22392 type Target = Vec<T>;
22393
22394 fn deref(&self) -> &Self::Target {
22395 &self.0
22396 }
22397}
22398
22399impl<T> DerefMut for InvalidationStack<T> {
22400 fn deref_mut(&mut self) -> &mut Self::Target {
22401 &mut self.0
22402 }
22403}
22404
22405impl InvalidationRegion for SnippetState {
22406 fn ranges(&self) -> &[Range<Anchor>] {
22407 &self.ranges[self.active_index]
22408 }
22409}
22410
22411fn inline_completion_edit_text(
22412 current_snapshot: &BufferSnapshot,
22413 edits: &[(Range<Anchor>, String)],
22414 edit_preview: &EditPreview,
22415 include_deletions: bool,
22416 cx: &App,
22417) -> HighlightedText {
22418 let edits = edits
22419 .iter()
22420 .map(|(anchor, text)| {
22421 (
22422 anchor.start.text_anchor..anchor.end.text_anchor,
22423 text.clone(),
22424 )
22425 })
22426 .collect::<Vec<_>>();
22427
22428 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22429}
22430
22431pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22432 match severity {
22433 lsp::DiagnosticSeverity::ERROR => colors.error,
22434 lsp::DiagnosticSeverity::WARNING => colors.warning,
22435 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22436 lsp::DiagnosticSeverity::HINT => colors.info,
22437 _ => colors.ignored,
22438 }
22439}
22440
22441pub fn styled_runs_for_code_label<'a>(
22442 label: &'a CodeLabel,
22443 syntax_theme: &'a theme::SyntaxTheme,
22444) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22445 let fade_out = HighlightStyle {
22446 fade_out: Some(0.35),
22447 ..Default::default()
22448 };
22449
22450 let mut prev_end = label.filter_range.end;
22451 label
22452 .runs
22453 .iter()
22454 .enumerate()
22455 .flat_map(move |(ix, (range, highlight_id))| {
22456 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22457 style
22458 } else {
22459 return Default::default();
22460 };
22461 let mut muted_style = style;
22462 muted_style.highlight(fade_out);
22463
22464 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22465 if range.start >= label.filter_range.end {
22466 if range.start > prev_end {
22467 runs.push((prev_end..range.start, fade_out));
22468 }
22469 runs.push((range.clone(), muted_style));
22470 } else if range.end <= label.filter_range.end {
22471 runs.push((range.clone(), style));
22472 } else {
22473 runs.push((range.start..label.filter_range.end, style));
22474 runs.push((label.filter_range.end..range.end, muted_style));
22475 }
22476 prev_end = cmp::max(prev_end, range.end);
22477
22478 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22479 runs.push((prev_end..label.text.len(), fade_out));
22480 }
22481
22482 runs
22483 })
22484}
22485
22486pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22487 let mut prev_index = 0;
22488 let mut prev_codepoint: Option<char> = None;
22489 text.char_indices()
22490 .chain([(text.len(), '\0')])
22491 .filter_map(move |(index, codepoint)| {
22492 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22493 let is_boundary = index == text.len()
22494 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22495 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22496 if is_boundary {
22497 let chunk = &text[prev_index..index];
22498 prev_index = index;
22499 Some(chunk)
22500 } else {
22501 None
22502 }
22503 })
22504}
22505
22506pub trait RangeToAnchorExt: Sized {
22507 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22508
22509 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22510 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22511 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22512 }
22513}
22514
22515impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22516 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22517 let start_offset = self.start.to_offset(snapshot);
22518 let end_offset = self.end.to_offset(snapshot);
22519 if start_offset == end_offset {
22520 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22521 } else {
22522 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22523 }
22524 }
22525}
22526
22527pub trait RowExt {
22528 fn as_f32(&self) -> f32;
22529
22530 fn next_row(&self) -> Self;
22531
22532 fn previous_row(&self) -> Self;
22533
22534 fn minus(&self, other: Self) -> u32;
22535}
22536
22537impl RowExt for DisplayRow {
22538 fn as_f32(&self) -> f32 {
22539 self.0 as f32
22540 }
22541
22542 fn next_row(&self) -> Self {
22543 Self(self.0 + 1)
22544 }
22545
22546 fn previous_row(&self) -> Self {
22547 Self(self.0.saturating_sub(1))
22548 }
22549
22550 fn minus(&self, other: Self) -> u32 {
22551 self.0 - other.0
22552 }
22553}
22554
22555impl RowExt for MultiBufferRow {
22556 fn as_f32(&self) -> f32 {
22557 self.0 as f32
22558 }
22559
22560 fn next_row(&self) -> Self {
22561 Self(self.0 + 1)
22562 }
22563
22564 fn previous_row(&self) -> Self {
22565 Self(self.0.saturating_sub(1))
22566 }
22567
22568 fn minus(&self, other: Self) -> u32 {
22569 self.0 - other.0
22570 }
22571}
22572
22573trait RowRangeExt {
22574 type Row;
22575
22576 fn len(&self) -> usize;
22577
22578 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22579}
22580
22581impl RowRangeExt for Range<MultiBufferRow> {
22582 type Row = MultiBufferRow;
22583
22584 fn len(&self) -> usize {
22585 (self.end.0 - self.start.0) as usize
22586 }
22587
22588 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22589 (self.start.0..self.end.0).map(MultiBufferRow)
22590 }
22591}
22592
22593impl RowRangeExt for Range<DisplayRow> {
22594 type Row = DisplayRow;
22595
22596 fn len(&self) -> usize {
22597 (self.end.0 - self.start.0) as usize
22598 }
22599
22600 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22601 (self.start.0..self.end.0).map(DisplayRow)
22602 }
22603}
22604
22605/// If select range has more than one line, we
22606/// just point the cursor to range.start.
22607fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22608 if range.start.row == range.end.row {
22609 range
22610 } else {
22611 range.start..range.start
22612 }
22613}
22614pub struct KillRing(ClipboardItem);
22615impl Global for KillRing {}
22616
22617const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22618
22619enum BreakpointPromptEditAction {
22620 Log,
22621 Condition,
22622 HitCondition,
22623}
22624
22625struct BreakpointPromptEditor {
22626 pub(crate) prompt: Entity<Editor>,
22627 editor: WeakEntity<Editor>,
22628 breakpoint_anchor: Anchor,
22629 breakpoint: Breakpoint,
22630 edit_action: BreakpointPromptEditAction,
22631 block_ids: HashSet<CustomBlockId>,
22632 editor_margins: Arc<Mutex<EditorMargins>>,
22633 _subscriptions: Vec<Subscription>,
22634}
22635
22636impl BreakpointPromptEditor {
22637 const MAX_LINES: u8 = 4;
22638
22639 fn new(
22640 editor: WeakEntity<Editor>,
22641 breakpoint_anchor: Anchor,
22642 breakpoint: Breakpoint,
22643 edit_action: BreakpointPromptEditAction,
22644 window: &mut Window,
22645 cx: &mut Context<Self>,
22646 ) -> Self {
22647 let base_text = match edit_action {
22648 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22649 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22650 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22651 }
22652 .map(|msg| msg.to_string())
22653 .unwrap_or_default();
22654
22655 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22656 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22657
22658 let prompt = cx.new(|cx| {
22659 let mut prompt = Editor::new(
22660 EditorMode::AutoHeight {
22661 min_lines: 1,
22662 max_lines: Self::MAX_LINES as usize,
22663 },
22664 buffer,
22665 None,
22666 window,
22667 cx,
22668 );
22669 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22670 prompt.set_show_cursor_when_unfocused(false, cx);
22671 prompt.set_placeholder_text(
22672 match edit_action {
22673 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22674 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22675 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22676 },
22677 cx,
22678 );
22679
22680 prompt
22681 });
22682
22683 Self {
22684 prompt,
22685 editor,
22686 breakpoint_anchor,
22687 breakpoint,
22688 edit_action,
22689 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22690 block_ids: Default::default(),
22691 _subscriptions: vec![],
22692 }
22693 }
22694
22695 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22696 self.block_ids.extend(block_ids)
22697 }
22698
22699 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22700 if let Some(editor) = self.editor.upgrade() {
22701 let message = self
22702 .prompt
22703 .read(cx)
22704 .buffer
22705 .read(cx)
22706 .as_singleton()
22707 .expect("A multi buffer in breakpoint prompt isn't possible")
22708 .read(cx)
22709 .as_rope()
22710 .to_string();
22711
22712 editor.update(cx, |editor, cx| {
22713 editor.edit_breakpoint_at_anchor(
22714 self.breakpoint_anchor,
22715 self.breakpoint.clone(),
22716 match self.edit_action {
22717 BreakpointPromptEditAction::Log => {
22718 BreakpointEditAction::EditLogMessage(message.into())
22719 }
22720 BreakpointPromptEditAction::Condition => {
22721 BreakpointEditAction::EditCondition(message.into())
22722 }
22723 BreakpointPromptEditAction::HitCondition => {
22724 BreakpointEditAction::EditHitCondition(message.into())
22725 }
22726 },
22727 cx,
22728 );
22729
22730 editor.remove_blocks(self.block_ids.clone(), None, cx);
22731 cx.focus_self(window);
22732 });
22733 }
22734 }
22735
22736 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22737 self.editor
22738 .update(cx, |editor, cx| {
22739 editor.remove_blocks(self.block_ids.clone(), None, cx);
22740 window.focus(&editor.focus_handle);
22741 })
22742 .log_err();
22743 }
22744
22745 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22746 let settings = ThemeSettings::get_global(cx);
22747 let text_style = TextStyle {
22748 color: if self.prompt.read(cx).read_only(cx) {
22749 cx.theme().colors().text_disabled
22750 } else {
22751 cx.theme().colors().text
22752 },
22753 font_family: settings.buffer_font.family.clone(),
22754 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22755 font_size: settings.buffer_font_size(cx).into(),
22756 font_weight: settings.buffer_font.weight,
22757 line_height: relative(settings.buffer_line_height.value()),
22758 ..Default::default()
22759 };
22760 EditorElement::new(
22761 &self.prompt,
22762 EditorStyle {
22763 background: cx.theme().colors().editor_background,
22764 local_player: cx.theme().players().local(),
22765 text: text_style,
22766 ..Default::default()
22767 },
22768 )
22769 }
22770}
22771
22772impl Render for BreakpointPromptEditor {
22773 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22774 let editor_margins = *self.editor_margins.lock();
22775 let gutter_dimensions = editor_margins.gutter;
22776 h_flex()
22777 .key_context("Editor")
22778 .bg(cx.theme().colors().editor_background)
22779 .border_y_1()
22780 .border_color(cx.theme().status().info_border)
22781 .size_full()
22782 .py(window.line_height() / 2.5)
22783 .on_action(cx.listener(Self::confirm))
22784 .on_action(cx.listener(Self::cancel))
22785 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22786 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22787 }
22788}
22789
22790impl Focusable for BreakpointPromptEditor {
22791 fn focus_handle(&self, cx: &App) -> FocusHandle {
22792 self.prompt.focus_handle(cx)
22793 }
22794}
22795
22796fn all_edits_insertions_or_deletions(
22797 edits: &Vec<(Range<Anchor>, String)>,
22798 snapshot: &MultiBufferSnapshot,
22799) -> bool {
22800 let mut all_insertions = true;
22801 let mut all_deletions = true;
22802
22803 for (range, new_text) in edits.iter() {
22804 let range_is_empty = range.to_offset(&snapshot).is_empty();
22805 let text_is_empty = new_text.is_empty();
22806
22807 if range_is_empty != text_is_empty {
22808 if range_is_empty {
22809 all_deletions = false;
22810 } else {
22811 all_insertions = false;
22812 }
22813 } else {
22814 return false;
22815 }
22816
22817 if !all_insertions && !all_deletions {
22818 return false;
22819 }
22820 }
22821 all_insertions || all_deletions
22822}
22823
22824struct MissingEditPredictionKeybindingTooltip;
22825
22826impl Render for MissingEditPredictionKeybindingTooltip {
22827 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22828 ui::tooltip_container(window, cx, |container, _, cx| {
22829 container
22830 .flex_shrink_0()
22831 .max_w_80()
22832 .min_h(rems_from_px(124.))
22833 .justify_between()
22834 .child(
22835 v_flex()
22836 .flex_1()
22837 .text_ui_sm(cx)
22838 .child(Label::new("Conflict with Accept Keybinding"))
22839 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22840 )
22841 .child(
22842 h_flex()
22843 .pb_1()
22844 .gap_1()
22845 .items_end()
22846 .w_full()
22847 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22848 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22849 }))
22850 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22851 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22852 })),
22853 )
22854 })
22855 }
22856}
22857
22858#[derive(Debug, Clone, Copy, PartialEq)]
22859pub struct LineHighlight {
22860 pub background: Background,
22861 pub border: Option<gpui::Hsla>,
22862 pub include_gutter: bool,
22863 pub type_id: Option<TypeId>,
22864}
22865
22866fn render_diff_hunk_controls(
22867 row: u32,
22868 status: &DiffHunkStatus,
22869 hunk_range: Range<Anchor>,
22870 is_created_file: bool,
22871 line_height: Pixels,
22872 editor: &Entity<Editor>,
22873 _window: &mut Window,
22874 cx: &mut App,
22875) -> AnyElement {
22876 h_flex()
22877 .h(line_height)
22878 .mr_1()
22879 .gap_1()
22880 .px_0p5()
22881 .pb_1()
22882 .border_x_1()
22883 .border_b_1()
22884 .border_color(cx.theme().colors().border_variant)
22885 .rounded_b_lg()
22886 .bg(cx.theme().colors().editor_background)
22887 .gap_1()
22888 .block_mouse_except_scroll()
22889 .shadow_md()
22890 .child(if status.has_secondary_hunk() {
22891 Button::new(("stage", row as u64), "Stage")
22892 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22893 .tooltip({
22894 let focus_handle = editor.focus_handle(cx);
22895 move |window, cx| {
22896 Tooltip::for_action_in(
22897 "Stage Hunk",
22898 &::git::ToggleStaged,
22899 &focus_handle,
22900 window,
22901 cx,
22902 )
22903 }
22904 })
22905 .on_click({
22906 let editor = editor.clone();
22907 move |_event, _window, cx| {
22908 editor.update(cx, |editor, cx| {
22909 editor.stage_or_unstage_diff_hunks(
22910 true,
22911 vec![hunk_range.start..hunk_range.start],
22912 cx,
22913 );
22914 });
22915 }
22916 })
22917 } else {
22918 Button::new(("unstage", row as u64), "Unstage")
22919 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22920 .tooltip({
22921 let focus_handle = editor.focus_handle(cx);
22922 move |window, cx| {
22923 Tooltip::for_action_in(
22924 "Unstage Hunk",
22925 &::git::ToggleStaged,
22926 &focus_handle,
22927 window,
22928 cx,
22929 )
22930 }
22931 })
22932 .on_click({
22933 let editor = editor.clone();
22934 move |_event, _window, cx| {
22935 editor.update(cx, |editor, cx| {
22936 editor.stage_or_unstage_diff_hunks(
22937 false,
22938 vec![hunk_range.start..hunk_range.start],
22939 cx,
22940 );
22941 });
22942 }
22943 })
22944 })
22945 .child(
22946 Button::new(("restore", row as u64), "Restore")
22947 .tooltip({
22948 let focus_handle = editor.focus_handle(cx);
22949 move |window, cx| {
22950 Tooltip::for_action_in(
22951 "Restore Hunk",
22952 &::git::Restore,
22953 &focus_handle,
22954 window,
22955 cx,
22956 )
22957 }
22958 })
22959 .on_click({
22960 let editor = editor.clone();
22961 move |_event, window, cx| {
22962 editor.update(cx, |editor, cx| {
22963 let snapshot = editor.snapshot(window, cx);
22964 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22965 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22966 });
22967 }
22968 })
22969 .disabled(is_created_file),
22970 )
22971 .when(
22972 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22973 |el| {
22974 el.child(
22975 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22976 .shape(IconButtonShape::Square)
22977 .icon_size(IconSize::Small)
22978 // .disabled(!has_multiple_hunks)
22979 .tooltip({
22980 let focus_handle = editor.focus_handle(cx);
22981 move |window, cx| {
22982 Tooltip::for_action_in(
22983 "Next Hunk",
22984 &GoToHunk,
22985 &focus_handle,
22986 window,
22987 cx,
22988 )
22989 }
22990 })
22991 .on_click({
22992 let editor = editor.clone();
22993 move |_event, window, cx| {
22994 editor.update(cx, |editor, cx| {
22995 let snapshot = editor.snapshot(window, cx);
22996 let position =
22997 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22998 editor.go_to_hunk_before_or_after_position(
22999 &snapshot,
23000 position,
23001 Direction::Next,
23002 window,
23003 cx,
23004 );
23005 editor.expand_selected_diff_hunks(cx);
23006 });
23007 }
23008 }),
23009 )
23010 .child(
23011 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23012 .shape(IconButtonShape::Square)
23013 .icon_size(IconSize::Small)
23014 // .disabled(!has_multiple_hunks)
23015 .tooltip({
23016 let focus_handle = editor.focus_handle(cx);
23017 move |window, cx| {
23018 Tooltip::for_action_in(
23019 "Previous Hunk",
23020 &GoToPreviousHunk,
23021 &focus_handle,
23022 window,
23023 cx,
23024 )
23025 }
23026 })
23027 .on_click({
23028 let editor = editor.clone();
23029 move |_event, window, cx| {
23030 editor.update(cx, |editor, cx| {
23031 let snapshot = editor.snapshot(window, cx);
23032 let point =
23033 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23034 editor.go_to_hunk_before_or_after_position(
23035 &snapshot,
23036 point,
23037 Direction::Prev,
23038 window,
23039 cx,
23040 );
23041 editor.expand_selected_diff_hunks(cx);
23042 });
23043 }
23044 }),
23045 )
23046 },
23047 )
23048 .into_any_element()
23049}