1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_colors;
33mod lsp_ext;
34mod mouse_context_menu;
35pub mod movement;
36mod persistence;
37mod proposed_changes_editor;
38mod rust_analyzer_ext;
39pub mod scroll;
40mod selections_collection;
41pub mod tasks;
42
43#[cfg(test)]
44mod code_completion_tests;
45#[cfg(test)]
46mod editor_tests;
47#[cfg(test)]
48mod inline_completion_tests;
49mod signature_help;
50#[cfg(any(test, feature = "test-support"))]
51pub mod test;
52
53pub(crate) use actions::*;
54pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
55use aho_corasick::AhoCorasick;
56use anyhow::{Context as _, Result, anyhow};
57use blink_manager::BlinkManager;
58use buffer_diff::DiffHunkStatus;
59use client::{Collaborator, ParticipantIndex};
60use clock::{AGENT_REPLICA_ID, ReplicaId};
61use collections::{BTreeMap, HashMap, HashSet, VecDeque};
62use convert_case::{Case, Casing};
63use dap::TelemetrySpawnLocation;
64use display_map::*;
65pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
66pub use editor_settings::{
67 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
68 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowScrollbar,
69};
70use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
71pub use editor_settings_controls::*;
72use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
73pub use element::{
74 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
75};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82use lsp_colors::LspColorData;
83
84use ::git::blame::BlameEntry;
85use ::git::{Restore, blame::ParsedCommitMessage};
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use git::blame::{GitBlame, GlobalBlameRenderer};
91use gpui::{
92 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
93 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
94 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
95 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
96 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
97 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
98 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
99 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
100};
101use highlight_matching_bracket::refresh_matching_bracket_highlights;
102use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
103pub use hover_popover::hover_markdown_style;
104use hover_popover::{HoverState, hide_hover};
105use indent_guides::ActiveIndentGuidesState;
106use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
107pub use inline_completion::Direction;
108use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
109pub use items::MAX_TAB_TITLE_LEN;
110use itertools::Itertools;
111use language::{
112 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
113 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
114 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
115 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, 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, ProjectPath,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 git_store::{GitStoreEvent, RepositoryEvent},
137 project_settings::DiagnosticSeverity,
138};
139
140pub use git::blame::BlameRenderer;
141pub use proposed_changes_editor::{
142 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
143};
144use std::{cell::OnceCell, iter::Peekable, ops::Not};
145use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
146
147pub use lsp::CompletionContext;
148use lsp::{
149 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
150 LanguageServerId, LanguageServerName,
151};
152
153use language::BufferSnapshot;
154pub use lsp_ext::lsp_tasks;
155use movement::TextLayoutDetails;
156pub use multi_buffer::{
157 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
158 RowInfo, ToOffset, ToPoint,
159};
160use multi_buffer::{
161 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
162 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
163};
164use parking_lot::Mutex;
165use project::{
166 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
167 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
168 TaskSourceKind,
169 debugger::breakpoint_store::Breakpoint,
170 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
171 project_settings::{GitGutterSetting, ProjectSettings},
172};
173use rand::prelude::*;
174use rpc::{ErrorExt, proto::*};
175use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
176use selections_collection::{
177 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
178};
179use serde::{Deserialize, Serialize};
180use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
181use smallvec::{SmallVec, smallvec};
182use snippet::Snippet;
183use std::sync::Arc;
184use std::{
185 any::TypeId,
186 borrow::Cow,
187 cell::RefCell,
188 cmp::{self, Ordering, Reverse},
189 mem,
190 num::NonZeroU32,
191 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
192 path::{Path, PathBuf},
193 rc::Rc,
194 time::{Duration, Instant},
195};
196pub use sum_tree::Bias;
197use sum_tree::TreeMap;
198use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
199use theme::{
200 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
201 observe_buffer_font_size_adjustment,
202};
203use ui::{
204 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
205 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
206};
207use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
208use workspace::{
209 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
210 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
211 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
212 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
213 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
214 searchable::SearchEvent,
215};
216
217use crate::{
218 code_context_menus::CompletionsMenuSource,
219 hover_links::{find_url, find_url_from_range},
220};
221use crate::{
222 editor_settings::MultiCursorModifier,
223 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
224};
225
226pub const FILE_HEADER_HEIGHT: u32 = 2;
227pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
228pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
229const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
230const MAX_LINE_LEN: usize = 1024;
231const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
232const MAX_SELECTION_HISTORY_LEN: usize = 1024;
233pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
234#[doc(hidden)]
235pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
236const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
237
238pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
241
242pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
243pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
244pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
245
246pub type RenderDiffHunkControlsFn = Arc<
247 dyn Fn(
248 u32,
249 &DiffHunkStatus,
250 Range<Anchor>,
251 bool,
252 Pixels,
253 &Entity<Editor>,
254 &mut Window,
255 &mut App,
256 ) -> AnyElement,
257>;
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 DebuggerValue(usize),
279 // LSP
280 Hint(usize),
281 Color(usize),
282}
283
284impl InlayId {
285 fn id(&self) -> usize {
286 match self {
287 Self::InlineCompletion(id) => *id,
288 Self::DebuggerValue(id) => *id,
289 Self::Hint(id) => *id,
290 Self::Color(id) => *id,
291 }
292 }
293}
294
295pub enum ActiveDebugLine {}
296pub enum DebugStackFrameLine {}
297enum DocumentHighlightRead {}
298enum DocumentHighlightWrite {}
299enum InputComposition {}
300pub enum PendingInput {}
301enum SelectedTextHighlight {}
302
303pub enum ConflictsOuter {}
304pub enum ConflictsOurs {}
305pub enum ConflictsTheirs {}
306pub enum ConflictsOursMarker {}
307pub enum ConflictsTheirsMarker {}
308
309#[derive(Debug, Copy, Clone, PartialEq, Eq)]
310pub enum Navigated {
311 Yes,
312 No,
313}
314
315impl Navigated {
316 pub fn from_bool(yes: bool) -> Navigated {
317 if yes { Navigated::Yes } else { Navigated::No }
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322enum DisplayDiffHunk {
323 Folded {
324 display_row: DisplayRow,
325 },
326 Unfolded {
327 is_created_file: bool,
328 diff_base_byte_range: Range<usize>,
329 display_row_range: Range<DisplayRow>,
330 multi_buffer_range: Range<Anchor>,
331 status: DiffHunkStatus,
332 },
333}
334
335pub enum HideMouseCursorOrigin {
336 TypingAction,
337 MovementAction,
338}
339
340pub fn init_settings(cx: &mut App) {
341 EditorSettings::register(cx);
342}
343
344pub fn init(cx: &mut App) {
345 init_settings(cx);
346
347 cx.set_global(GlobalBlameRenderer(Arc::new(())));
348
349 workspace::register_project_item::<Editor>(cx);
350 workspace::FollowableViewRegistry::register::<Editor>(cx);
351 workspace::register_serializable_item::<Editor>(cx);
352
353 cx.observe_new(
354 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
355 workspace.register_action(Editor::new_file);
356 workspace.register_action(Editor::new_file_vertical);
357 workspace.register_action(Editor::new_file_horizontal);
358 workspace.register_action(Editor::cancel_language_server_work);
359 },
360 )
361 .detach();
362
363 cx.on_action(move |_: &workspace::NewFile, cx| {
364 let app_state = workspace::AppState::global(cx);
365 if let Some(app_state) = app_state.upgrade() {
366 workspace::open_new(
367 Default::default(),
368 app_state,
369 cx,
370 |workspace, window, cx| {
371 Editor::new_file(workspace, &Default::default(), window, cx)
372 },
373 )
374 .detach();
375 }
376 });
377 cx.on_action(move |_: &workspace::NewWindow, cx| {
378 let app_state = workspace::AppState::global(cx);
379 if let Some(app_state) = app_state.upgrade() {
380 workspace::open_new(
381 Default::default(),
382 app_state,
383 cx,
384 |workspace, window, cx| {
385 cx.activate(true);
386 Editor::new_file(workspace, &Default::default(), window, cx)
387 },
388 )
389 .detach();
390 }
391 });
392}
393
394pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
395 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
396}
397
398pub trait DiagnosticRenderer {
399 fn render_group(
400 &self,
401 diagnostic_group: Vec<DiagnosticEntry<Point>>,
402 buffer_id: BufferId,
403 snapshot: EditorSnapshot,
404 editor: WeakEntity<Editor>,
405 cx: &mut App,
406 ) -> Vec<BlockProperties<Anchor>>;
407
408 fn render_hover(
409 &self,
410 diagnostic_group: Vec<DiagnosticEntry<Point>>,
411 range: Range<Point>,
412 buffer_id: BufferId,
413 cx: &mut App,
414 ) -> Option<Entity<markdown::Markdown>>;
415
416 fn open_link(
417 &self,
418 editor: &mut Editor,
419 link: SharedString,
420 window: &mut Window,
421 cx: &mut Context<Editor>,
422 );
423}
424
425pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
426
427impl GlobalDiagnosticRenderer {
428 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
429 cx.try_global::<Self>().map(|g| g.0.clone())
430 }
431}
432
433impl gpui::Global for GlobalDiagnosticRenderer {}
434pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
435 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
436}
437
438pub struct SearchWithinRange;
439
440trait InvalidationRegion {
441 fn ranges(&self) -> &[Range<Anchor>];
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum SelectPhase {
446 Begin {
447 position: DisplayPoint,
448 add: bool,
449 click_count: usize,
450 },
451 BeginColumnar {
452 position: DisplayPoint,
453 reset: bool,
454 mode: ColumnarMode,
455 goal_column: u32,
456 },
457 Extend {
458 position: DisplayPoint,
459 click_count: usize,
460 },
461 Update {
462 position: DisplayPoint,
463 goal_column: u32,
464 scroll_delta: gpui::Point<f32>,
465 },
466 End,
467}
468
469#[derive(Clone, Debug, PartialEq)]
470pub enum ColumnarMode {
471 FromMouse,
472 FromSelection,
473}
474
475#[derive(Clone, Debug)]
476pub enum SelectMode {
477 Character,
478 Word(Range<Anchor>),
479 Line(Range<Anchor>),
480 All,
481}
482
483#[derive(Clone, PartialEq, Eq, Debug)]
484pub enum EditorMode {
485 SingleLine {
486 auto_width: bool,
487 },
488 AutoHeight {
489 min_lines: usize,
490 max_lines: usize,
491 },
492 Full {
493 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
494 scale_ui_elements_with_buffer_font_size: bool,
495 /// When set to `true`, the editor will render a background for the active line.
496 show_active_line_background: bool,
497 /// When set to `true`, the editor's height will be determined by its content.
498 sized_by_content: bool,
499 },
500 Minimap {
501 parent: WeakEntity<Editor>,
502 },
503}
504
505impl EditorMode {
506 pub fn full() -> Self {
507 Self::Full {
508 scale_ui_elements_with_buffer_font_size: true,
509 show_active_line_background: true,
510 sized_by_content: false,
511 }
512 }
513
514 #[inline]
515 pub fn is_full(&self) -> bool {
516 matches!(self, Self::Full { .. })
517 }
518
519 #[inline]
520 pub fn is_single_line(&self) -> bool {
521 matches!(self, Self::SingleLine { .. })
522 }
523
524 #[inline]
525 fn is_minimap(&self) -> bool {
526 matches!(self, Self::Minimap { .. })
527 }
528}
529
530#[derive(Copy, Clone, Debug)]
531pub enum SoftWrap {
532 /// Prefer not to wrap at all.
533 ///
534 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
535 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
536 GitDiff,
537 /// Prefer a single line generally, unless an overly long line is encountered.
538 None,
539 /// Soft wrap lines that exceed the editor width.
540 EditorWidth,
541 /// Soft wrap lines at the preferred line length.
542 Column(u32),
543 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
544 Bounded(u32),
545}
546
547#[derive(Clone)]
548pub struct EditorStyle {
549 pub background: Hsla,
550 pub local_player: PlayerColor,
551 pub text: TextStyle,
552 pub scrollbar_width: Pixels,
553 pub syntax: Arc<SyntaxTheme>,
554 pub status: StatusColors,
555 pub inlay_hints_style: HighlightStyle,
556 pub inline_completion_styles: InlineCompletionStyles,
557 pub unnecessary_code_fade: f32,
558 pub show_underlines: bool,
559}
560
561impl Default for EditorStyle {
562 fn default() -> Self {
563 Self {
564 background: Hsla::default(),
565 local_player: PlayerColor::default(),
566 text: TextStyle::default(),
567 scrollbar_width: Pixels::default(),
568 syntax: Default::default(),
569 // HACK: Status colors don't have a real default.
570 // We should look into removing the status colors from the editor
571 // style and retrieve them directly from the theme.
572 status: StatusColors::dark(),
573 inlay_hints_style: HighlightStyle::default(),
574 inline_completion_styles: InlineCompletionStyles {
575 insertion: HighlightStyle::default(),
576 whitespace: HighlightStyle::default(),
577 },
578 unnecessary_code_fade: Default::default(),
579 show_underlines: true,
580 }
581 }
582}
583
584pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
585 let show_background = language_settings::language_settings(None, None, cx)
586 .inlay_hints
587 .show_background;
588
589 HighlightStyle {
590 color: Some(cx.theme().status().hint),
591 background_color: show_background.then(|| cx.theme().status().hint_background),
592 ..HighlightStyle::default()
593 }
594}
595
596pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
597 InlineCompletionStyles {
598 insertion: HighlightStyle {
599 color: Some(cx.theme().status().predictive),
600 ..HighlightStyle::default()
601 },
602 whitespace: HighlightStyle {
603 background_color: Some(cx.theme().status().created_background),
604 ..HighlightStyle::default()
605 },
606 }
607}
608
609type CompletionId = usize;
610
611pub(crate) enum EditDisplayMode {
612 TabAccept,
613 DiffPopover,
614 Inline,
615}
616
617enum InlineCompletion {
618 Edit {
619 edits: Vec<(Range<Anchor>, String)>,
620 edit_preview: Option<EditPreview>,
621 display_mode: EditDisplayMode,
622 snapshot: BufferSnapshot,
623 },
624 Move {
625 target: Anchor,
626 snapshot: BufferSnapshot,
627 },
628}
629
630struct InlineCompletionState {
631 inlay_ids: Vec<InlayId>,
632 completion: InlineCompletion,
633 completion_id: Option<SharedString>,
634 invalidation_range: Range<Anchor>,
635}
636
637enum EditPredictionSettings {
638 Disabled,
639 Enabled {
640 show_in_menu: bool,
641 preview_requires_modifier: bool,
642 },
643}
644
645enum InlineCompletionHighlight {}
646
647#[derive(Debug, Clone)]
648struct InlineDiagnostic {
649 message: SharedString,
650 group_id: usize,
651 is_primary: bool,
652 start: Point,
653 severity: lsp::DiagnosticSeverity,
654}
655
656pub enum MenuInlineCompletionsPolicy {
657 Never,
658 ByProvider,
659}
660
661pub enum EditPredictionPreview {
662 /// Modifier is not pressed
663 Inactive { released_too_fast: bool },
664 /// Modifier pressed
665 Active {
666 since: Instant,
667 previous_scroll_position: Option<ScrollAnchor>,
668 },
669}
670
671impl EditPredictionPreview {
672 pub fn released_too_fast(&self) -> bool {
673 match self {
674 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
675 EditPredictionPreview::Active { .. } => false,
676 }
677 }
678
679 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
680 if let EditPredictionPreview::Active {
681 previous_scroll_position,
682 ..
683 } = self
684 {
685 *previous_scroll_position = scroll_position;
686 }
687 }
688}
689
690pub struct ContextMenuOptions {
691 pub min_entries_visible: usize,
692 pub max_entries_visible: usize,
693 pub placement: Option<ContextMenuPlacement>,
694}
695
696#[derive(Debug, Clone, PartialEq, Eq)]
697pub enum ContextMenuPlacement {
698 Above,
699 Below,
700}
701
702#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
703struct EditorActionId(usize);
704
705impl EditorActionId {
706 pub fn post_inc(&mut self) -> Self {
707 let answer = self.0;
708
709 *self = Self(answer + 1);
710
711 Self(answer)
712 }
713}
714
715// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
716// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
717
718type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
719type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
720
721#[derive(Default)]
722struct ScrollbarMarkerState {
723 scrollbar_size: Size<Pixels>,
724 dirty: bool,
725 markers: Arc<[PaintQuad]>,
726 pending_refresh: Option<Task<Result<()>>>,
727}
728
729impl ScrollbarMarkerState {
730 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
731 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
732 }
733}
734
735#[derive(Clone, Copy, PartialEq, Eq)]
736pub enum MinimapVisibility {
737 Disabled,
738 Enabled {
739 /// The configuration currently present in the users settings.
740 setting_configuration: bool,
741 /// Whether to override the currently set visibility from the users setting.
742 toggle_override: bool,
743 },
744}
745
746impl MinimapVisibility {
747 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
748 if mode.is_full() {
749 Self::Enabled {
750 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
751 toggle_override: false,
752 }
753 } else {
754 Self::Disabled
755 }
756 }
757
758 fn hidden(&self) -> Self {
759 match *self {
760 Self::Enabled {
761 setting_configuration,
762 ..
763 } => Self::Enabled {
764 setting_configuration,
765 toggle_override: setting_configuration,
766 },
767 Self::Disabled => Self::Disabled,
768 }
769 }
770
771 fn disabled(&self) -> bool {
772 match *self {
773 Self::Disabled => true,
774 _ => false,
775 }
776 }
777
778 fn settings_visibility(&self) -> bool {
779 match *self {
780 Self::Enabled {
781 setting_configuration,
782 ..
783 } => setting_configuration,
784 _ => false,
785 }
786 }
787
788 fn visible(&self) -> bool {
789 match *self {
790 Self::Enabled {
791 setting_configuration,
792 toggle_override,
793 } => setting_configuration ^ toggle_override,
794 _ => false,
795 }
796 }
797
798 fn toggle_visibility(&self) -> Self {
799 match *self {
800 Self::Enabled {
801 toggle_override,
802 setting_configuration,
803 } => Self::Enabled {
804 setting_configuration,
805 toggle_override: !toggle_override,
806 },
807 Self::Disabled => Self::Disabled,
808 }
809 }
810}
811
812#[derive(Clone, Debug)]
813struct RunnableTasks {
814 templates: Vec<(TaskSourceKind, TaskTemplate)>,
815 offset: multi_buffer::Anchor,
816 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
817 column: u32,
818 // Values of all named captures, including those starting with '_'
819 extra_variables: HashMap<String, String>,
820 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
821 context_range: Range<BufferOffset>,
822}
823
824impl RunnableTasks {
825 fn resolve<'a>(
826 &'a self,
827 cx: &'a task::TaskContext,
828 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
829 self.templates.iter().filter_map(|(kind, template)| {
830 template
831 .resolve_task(&kind.to_id_base(), cx)
832 .map(|task| (kind.clone(), task))
833 })
834 }
835}
836
837#[derive(Clone)]
838pub struct ResolvedTasks {
839 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
840 position: Anchor,
841}
842
843#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
844struct BufferOffset(usize);
845
846// Addons allow storing per-editor state in other crates (e.g. Vim)
847pub trait Addon: 'static {
848 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
849
850 fn render_buffer_header_controls(
851 &self,
852 _: &ExcerptInfo,
853 _: &Window,
854 _: &App,
855 ) -> Option<AnyElement> {
856 None
857 }
858
859 fn to_any(&self) -> &dyn std::any::Any;
860
861 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
862 None
863 }
864}
865
866/// A set of caret positions, registered when the editor was edited.
867pub struct ChangeList {
868 changes: Vec<Vec<Anchor>>,
869 /// Currently "selected" change.
870 position: Option<usize>,
871}
872
873impl ChangeList {
874 pub fn new() -> Self {
875 Self {
876 changes: Vec::new(),
877 position: None,
878 }
879 }
880
881 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
882 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
883 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
884 if self.changes.is_empty() {
885 return None;
886 }
887
888 let prev = self.position.unwrap_or(self.changes.len());
889 let next = if direction == Direction::Prev {
890 prev.saturating_sub(count)
891 } else {
892 (prev + count).min(self.changes.len() - 1)
893 };
894 self.position = Some(next);
895 self.changes.get(next).map(|anchors| anchors.as_slice())
896 }
897
898 /// Adds a new change to the list, resetting the change list position.
899 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
900 self.position.take();
901 if pop_state {
902 self.changes.pop();
903 }
904 self.changes.push(new_positions.clone());
905 }
906
907 pub fn last(&self) -> Option<&[Anchor]> {
908 self.changes.last().map(|anchors| anchors.as_slice())
909 }
910}
911
912#[derive(Clone)]
913struct InlineBlamePopoverState {
914 scroll_handle: ScrollHandle,
915 commit_message: Option<ParsedCommitMessage>,
916 markdown: Entity<Markdown>,
917}
918
919struct InlineBlamePopover {
920 position: gpui::Point<Pixels>,
921 hide_task: Option<Task<()>>,
922 popover_bounds: Option<Bounds<Pixels>>,
923 popover_state: InlineBlamePopoverState,
924}
925
926enum SelectionDragState {
927 /// State when no drag related activity is detected.
928 None,
929 /// State when the mouse is down on a selection that is about to be dragged.
930 ReadyToDrag {
931 selection: Selection<Anchor>,
932 click_position: gpui::Point<Pixels>,
933 mouse_down_time: Instant,
934 },
935 /// State when the mouse is dragging the selection in the editor.
936 Dragging {
937 selection: Selection<Anchor>,
938 drop_cursor: Selection<Anchor>,
939 hide_drop_cursor: bool,
940 },
941}
942
943enum ColumnarSelectionState {
944 FromMouse {
945 selection_tail: Anchor,
946 display_point: Option<DisplayPoint>,
947 },
948 FromSelection {
949 selection_tail: Anchor,
950 },
951}
952
953/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
954/// a breakpoint on them.
955#[derive(Clone, Copy, Debug, PartialEq, Eq)]
956struct PhantomBreakpointIndicator {
957 display_row: DisplayRow,
958 /// There's a small debounce between hovering over the line and showing the indicator.
959 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
960 is_active: bool,
961 collides_with_existing_breakpoint: bool,
962}
963
964/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
965///
966/// See the [module level documentation](self) for more information.
967pub struct Editor {
968 focus_handle: FocusHandle,
969 last_focused_descendant: Option<WeakFocusHandle>,
970 /// The text buffer being edited
971 buffer: Entity<MultiBuffer>,
972 /// Map of how text in the buffer should be displayed.
973 /// Handles soft wraps, folds, fake inlay text insertions, etc.
974 pub display_map: Entity<DisplayMap>,
975 pub selections: SelectionsCollection,
976 pub scroll_manager: ScrollManager,
977 /// When inline assist editors are linked, they all render cursors because
978 /// typing enters text into each of them, even the ones that aren't focused.
979 pub(crate) show_cursor_when_unfocused: bool,
980 columnar_selection_state: Option<ColumnarSelectionState>,
981 add_selections_state: Option<AddSelectionsState>,
982 select_next_state: Option<SelectNextState>,
983 select_prev_state: Option<SelectNextState>,
984 selection_history: SelectionHistory,
985 defer_selection_effects: bool,
986 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
987 autoclose_regions: Vec<AutocloseRegion>,
988 snippet_stack: InvalidationStack<SnippetState>,
989 select_syntax_node_history: SelectSyntaxNodeHistory,
990 ime_transaction: Option<TransactionId>,
991 pub diagnostics_max_severity: DiagnosticSeverity,
992 active_diagnostics: ActiveDiagnostic,
993 show_inline_diagnostics: bool,
994 inline_diagnostics_update: Task<()>,
995 inline_diagnostics_enabled: bool,
996 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
997 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
998 hard_wrap: Option<usize>,
999
1000 // TODO: make this a access method
1001 pub project: Option<Entity<Project>>,
1002 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1003 completion_provider: Option<Rc<dyn CompletionProvider>>,
1004 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1005 blink_manager: Entity<BlinkManager>,
1006 show_cursor_names: bool,
1007 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1008 pub show_local_selections: bool,
1009 mode: EditorMode,
1010 show_breadcrumbs: bool,
1011 show_gutter: bool,
1012 show_scrollbars: ScrollbarAxes,
1013 minimap_visibility: MinimapVisibility,
1014 offset_content: bool,
1015 disable_expand_excerpt_buttons: bool,
1016 show_line_numbers: Option<bool>,
1017 use_relative_line_numbers: Option<bool>,
1018 show_git_diff_gutter: Option<bool>,
1019 show_code_actions: Option<bool>,
1020 show_runnables: Option<bool>,
1021 show_breakpoints: Option<bool>,
1022 show_wrap_guides: Option<bool>,
1023 show_indent_guides: Option<bool>,
1024 placeholder_text: Option<Arc<str>>,
1025 highlight_order: usize,
1026 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1027 background_highlights: TreeMap<HighlightKey, BackgroundHighlight>,
1028 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1029 scrollbar_marker_state: ScrollbarMarkerState,
1030 active_indent_guides_state: ActiveIndentGuidesState,
1031 nav_history: Option<ItemNavHistory>,
1032 context_menu: RefCell<Option<CodeContextMenu>>,
1033 context_menu_options: Option<ContextMenuOptions>,
1034 mouse_context_menu: Option<MouseContextMenu>,
1035 completion_tasks: Vec<(CompletionId, Task<()>)>,
1036 inline_blame_popover: Option<InlineBlamePopover>,
1037 inline_blame_popover_show_task: Option<Task<()>>,
1038 signature_help_state: SignatureHelpState,
1039 auto_signature_help: Option<bool>,
1040 find_all_references_task_sources: Vec<Anchor>,
1041 next_completion_id: CompletionId,
1042 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1043 code_actions_task: Option<Task<Result<()>>>,
1044 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1045 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1046 document_highlights_task: Option<Task<()>>,
1047 linked_editing_range_task: Option<Task<Option<()>>>,
1048 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1049 pending_rename: Option<RenameState>,
1050 searchable: bool,
1051 cursor_shape: CursorShape,
1052 current_line_highlight: Option<CurrentLineHighlight>,
1053 collapse_matches: bool,
1054 autoindent_mode: Option<AutoindentMode>,
1055 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1056 input_enabled: bool,
1057 use_modal_editing: bool,
1058 read_only: bool,
1059 leader_id: Option<CollaboratorId>,
1060 remote_id: Option<ViewId>,
1061 pub hover_state: HoverState,
1062 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1063 gutter_hovered: bool,
1064 hovered_link_state: Option<HoveredLinkState>,
1065 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1066 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1067 active_inline_completion: Option<InlineCompletionState>,
1068 /// Used to prevent flickering as the user types while the menu is open
1069 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1070 edit_prediction_settings: EditPredictionSettings,
1071 inline_completions_hidden_for_vim_mode: bool,
1072 show_inline_completions_override: Option<bool>,
1073 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1074 edit_prediction_preview: EditPredictionPreview,
1075 edit_prediction_indent_conflict: bool,
1076 edit_prediction_requires_modifier_in_indent_conflict: bool,
1077 inlay_hint_cache: InlayHintCache,
1078 next_inlay_id: usize,
1079 _subscriptions: Vec<Subscription>,
1080 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1081 gutter_dimensions: GutterDimensions,
1082 style: Option<EditorStyle>,
1083 text_style_refinement: Option<TextStyleRefinement>,
1084 next_editor_action_id: EditorActionId,
1085 editor_actions: Rc<
1086 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1087 >,
1088 use_autoclose: bool,
1089 use_auto_surround: bool,
1090 auto_replace_emoji_shortcode: bool,
1091 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1092 show_git_blame_gutter: bool,
1093 show_git_blame_inline: bool,
1094 show_git_blame_inline_delay_task: Option<Task<()>>,
1095 git_blame_inline_enabled: bool,
1096 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1097 serialize_dirty_buffers: bool,
1098 show_selection_menu: Option<bool>,
1099 blame: Option<Entity<GitBlame>>,
1100 blame_subscription: Option<Subscription>,
1101 custom_context_menu: Option<
1102 Box<
1103 dyn 'static
1104 + Fn(
1105 &mut Self,
1106 DisplayPoint,
1107 &mut Window,
1108 &mut Context<Self>,
1109 ) -> Option<Entity<ui::ContextMenu>>,
1110 >,
1111 >,
1112 last_bounds: Option<Bounds<Pixels>>,
1113 last_position_map: Option<Rc<PositionMap>>,
1114 expect_bounds_change: Option<Bounds<Pixels>>,
1115 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1116 tasks_update_task: Option<Task<()>>,
1117 breakpoint_store: Option<Entity<BreakpointStore>>,
1118 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1119 hovered_diff_hunk_row: Option<DisplayRow>,
1120 pull_diagnostics_task: Task<()>,
1121 in_project_search: bool,
1122 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1123 breadcrumb_header: Option<String>,
1124 focused_block: Option<FocusedBlock>,
1125 next_scroll_position: NextScrollCursorCenterTopBottom,
1126 addons: HashMap<TypeId, Box<dyn Addon>>,
1127 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1128 load_diff_task: Option<Shared<Task<()>>>,
1129 /// Whether we are temporarily displaying a diff other than git's
1130 temporary_diff_override: bool,
1131 selection_mark_mode: bool,
1132 toggle_fold_multiple_buffers: Task<()>,
1133 _scroll_cursor_center_top_bottom_task: Task<()>,
1134 serialize_selections: Task<()>,
1135 serialize_folds: Task<()>,
1136 mouse_cursor_hidden: bool,
1137 minimap: Option<Entity<Self>>,
1138 hide_mouse_mode: HideMouseMode,
1139 pub change_list: ChangeList,
1140 inline_value_cache: InlineValueCache,
1141 selection_drag_state: SelectionDragState,
1142 drag_and_drop_selection_enabled: bool,
1143 next_color_inlay_id: usize,
1144 colors: Option<LspColorData>,
1145}
1146
1147#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1148enum NextScrollCursorCenterTopBottom {
1149 #[default]
1150 Center,
1151 Top,
1152 Bottom,
1153}
1154
1155impl NextScrollCursorCenterTopBottom {
1156 fn next(&self) -> Self {
1157 match self {
1158 Self::Center => Self::Top,
1159 Self::Top => Self::Bottom,
1160 Self::Bottom => Self::Center,
1161 }
1162 }
1163}
1164
1165#[derive(Clone)]
1166pub struct EditorSnapshot {
1167 pub mode: EditorMode,
1168 show_gutter: bool,
1169 show_line_numbers: Option<bool>,
1170 show_git_diff_gutter: Option<bool>,
1171 show_code_actions: Option<bool>,
1172 show_runnables: Option<bool>,
1173 show_breakpoints: Option<bool>,
1174 git_blame_gutter_max_author_length: Option<usize>,
1175 pub display_snapshot: DisplaySnapshot,
1176 pub placeholder_text: Option<Arc<str>>,
1177 is_focused: bool,
1178 scroll_anchor: ScrollAnchor,
1179 ongoing_scroll: OngoingScroll,
1180 current_line_highlight: CurrentLineHighlight,
1181 gutter_hovered: bool,
1182}
1183
1184#[derive(Default, Debug, Clone, Copy)]
1185pub struct GutterDimensions {
1186 pub left_padding: Pixels,
1187 pub right_padding: Pixels,
1188 pub width: Pixels,
1189 pub margin: Pixels,
1190 pub git_blame_entries_width: Option<Pixels>,
1191}
1192
1193impl GutterDimensions {
1194 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1195 Self {
1196 margin: Self::default_gutter_margin(font_id, font_size, cx),
1197 ..Default::default()
1198 }
1199 }
1200
1201 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1202 -cx.text_system().descent(font_id, font_size)
1203 }
1204 /// The full width of the space taken up by the gutter.
1205 pub fn full_width(&self) -> Pixels {
1206 self.margin + self.width
1207 }
1208
1209 /// The width of the space reserved for the fold indicators,
1210 /// use alongside 'justify_end' and `gutter_width` to
1211 /// right align content with the line numbers
1212 pub fn fold_area_width(&self) -> Pixels {
1213 self.margin + self.right_padding
1214 }
1215}
1216
1217#[derive(Debug)]
1218pub struct RemoteSelection {
1219 pub replica_id: ReplicaId,
1220 pub selection: Selection<Anchor>,
1221 pub cursor_shape: CursorShape,
1222 pub collaborator_id: CollaboratorId,
1223 pub line_mode: bool,
1224 pub user_name: Option<SharedString>,
1225 pub color: PlayerColor,
1226}
1227
1228#[derive(Clone, Debug)]
1229struct SelectionHistoryEntry {
1230 selections: Arc<[Selection<Anchor>]>,
1231 select_next_state: Option<SelectNextState>,
1232 select_prev_state: Option<SelectNextState>,
1233 add_selections_state: Option<AddSelectionsState>,
1234}
1235
1236#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1237enum SelectionHistoryMode {
1238 Normal,
1239 Undoing,
1240 Redoing,
1241 Skipping,
1242}
1243
1244#[derive(Clone, PartialEq, Eq, Hash)]
1245struct HoveredCursor {
1246 replica_id: u16,
1247 selection_id: usize,
1248}
1249
1250impl Default for SelectionHistoryMode {
1251 fn default() -> Self {
1252 Self::Normal
1253 }
1254}
1255
1256#[derive(Debug)]
1257pub struct SelectionEffects {
1258 nav_history: bool,
1259 completions: bool,
1260 scroll: Option<Autoscroll>,
1261}
1262
1263impl Default for SelectionEffects {
1264 fn default() -> Self {
1265 Self {
1266 nav_history: true,
1267 completions: true,
1268 scroll: Some(Autoscroll::fit()),
1269 }
1270 }
1271}
1272impl SelectionEffects {
1273 pub fn scroll(scroll: Autoscroll) -> Self {
1274 Self {
1275 scroll: Some(scroll),
1276 ..Default::default()
1277 }
1278 }
1279
1280 pub fn no_scroll() -> Self {
1281 Self {
1282 scroll: None,
1283 ..Default::default()
1284 }
1285 }
1286
1287 pub fn completions(self, completions: bool) -> Self {
1288 Self {
1289 completions,
1290 ..self
1291 }
1292 }
1293
1294 pub fn nav_history(self, nav_history: bool) -> Self {
1295 Self {
1296 nav_history,
1297 ..self
1298 }
1299 }
1300}
1301
1302struct DeferredSelectionEffectsState {
1303 changed: bool,
1304 effects: SelectionEffects,
1305 old_cursor_position: Anchor,
1306 history_entry: SelectionHistoryEntry,
1307}
1308
1309#[derive(Default)]
1310struct SelectionHistory {
1311 #[allow(clippy::type_complexity)]
1312 selections_by_transaction:
1313 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1314 mode: SelectionHistoryMode,
1315 undo_stack: VecDeque<SelectionHistoryEntry>,
1316 redo_stack: VecDeque<SelectionHistoryEntry>,
1317}
1318
1319impl SelectionHistory {
1320 #[track_caller]
1321 fn insert_transaction(
1322 &mut self,
1323 transaction_id: TransactionId,
1324 selections: Arc<[Selection<Anchor>]>,
1325 ) {
1326 if selections.is_empty() {
1327 log::error!(
1328 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1329 std::panic::Location::caller()
1330 );
1331 return;
1332 }
1333 self.selections_by_transaction
1334 .insert(transaction_id, (selections, None));
1335 }
1336
1337 #[allow(clippy::type_complexity)]
1338 fn transaction(
1339 &self,
1340 transaction_id: TransactionId,
1341 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1342 self.selections_by_transaction.get(&transaction_id)
1343 }
1344
1345 #[allow(clippy::type_complexity)]
1346 fn transaction_mut(
1347 &mut self,
1348 transaction_id: TransactionId,
1349 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1350 self.selections_by_transaction.get_mut(&transaction_id)
1351 }
1352
1353 fn push(&mut self, entry: SelectionHistoryEntry) {
1354 if !entry.selections.is_empty() {
1355 match self.mode {
1356 SelectionHistoryMode::Normal => {
1357 self.push_undo(entry);
1358 self.redo_stack.clear();
1359 }
1360 SelectionHistoryMode::Undoing => self.push_redo(entry),
1361 SelectionHistoryMode::Redoing => self.push_undo(entry),
1362 SelectionHistoryMode::Skipping => {}
1363 }
1364 }
1365 }
1366
1367 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1368 if self
1369 .undo_stack
1370 .back()
1371 .map_or(true, |e| e.selections != entry.selections)
1372 {
1373 self.undo_stack.push_back(entry);
1374 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1375 self.undo_stack.pop_front();
1376 }
1377 }
1378 }
1379
1380 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1381 if self
1382 .redo_stack
1383 .back()
1384 .map_or(true, |e| e.selections != entry.selections)
1385 {
1386 self.redo_stack.push_back(entry);
1387 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1388 self.redo_stack.pop_front();
1389 }
1390 }
1391 }
1392}
1393
1394#[derive(Clone, Copy)]
1395pub struct RowHighlightOptions {
1396 pub autoscroll: bool,
1397 pub include_gutter: bool,
1398}
1399
1400impl Default for RowHighlightOptions {
1401 fn default() -> Self {
1402 Self {
1403 autoscroll: Default::default(),
1404 include_gutter: true,
1405 }
1406 }
1407}
1408
1409struct RowHighlight {
1410 index: usize,
1411 range: Range<Anchor>,
1412 color: Hsla,
1413 options: RowHighlightOptions,
1414 type_id: TypeId,
1415}
1416
1417#[derive(Clone, Debug)]
1418struct AddSelectionsState {
1419 groups: Vec<AddSelectionsGroup>,
1420}
1421
1422#[derive(Clone, Debug)]
1423struct AddSelectionsGroup {
1424 above: bool,
1425 stack: Vec<usize>,
1426}
1427
1428#[derive(Clone)]
1429struct SelectNextState {
1430 query: AhoCorasick,
1431 wordwise: bool,
1432 done: bool,
1433}
1434
1435impl std::fmt::Debug for SelectNextState {
1436 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1437 f.debug_struct(std::any::type_name::<Self>())
1438 .field("wordwise", &self.wordwise)
1439 .field("done", &self.done)
1440 .finish()
1441 }
1442}
1443
1444#[derive(Debug)]
1445struct AutocloseRegion {
1446 selection_id: usize,
1447 range: Range<Anchor>,
1448 pair: BracketPair,
1449}
1450
1451#[derive(Debug)]
1452struct SnippetState {
1453 ranges: Vec<Vec<Range<Anchor>>>,
1454 active_index: usize,
1455 choices: Vec<Option<Vec<String>>>,
1456}
1457
1458#[doc(hidden)]
1459pub struct RenameState {
1460 pub range: Range<Anchor>,
1461 pub old_name: Arc<str>,
1462 pub editor: Entity<Editor>,
1463 block_id: CustomBlockId,
1464}
1465
1466struct InvalidationStack<T>(Vec<T>);
1467
1468struct RegisteredInlineCompletionProvider {
1469 provider: Arc<dyn InlineCompletionProviderHandle>,
1470 _subscription: Subscription,
1471}
1472
1473#[derive(Debug, PartialEq, Eq)]
1474pub struct ActiveDiagnosticGroup {
1475 pub active_range: Range<Anchor>,
1476 pub active_message: String,
1477 pub group_id: usize,
1478 pub blocks: HashSet<CustomBlockId>,
1479}
1480
1481#[derive(Debug, PartialEq, Eq)]
1482
1483pub(crate) enum ActiveDiagnostic {
1484 None,
1485 All,
1486 Group(ActiveDiagnosticGroup),
1487}
1488
1489#[derive(Serialize, Deserialize, Clone, Debug)]
1490pub struct ClipboardSelection {
1491 /// The number of bytes in this selection.
1492 pub len: usize,
1493 /// Whether this was a full-line selection.
1494 pub is_entire_line: bool,
1495 /// The indentation of the first line when this content was originally copied.
1496 pub first_line_indent: u32,
1497}
1498
1499// selections, scroll behavior, was newest selection reversed
1500type SelectSyntaxNodeHistoryState = (
1501 Box<[Selection<usize>]>,
1502 SelectSyntaxNodeScrollBehavior,
1503 bool,
1504);
1505
1506#[derive(Default)]
1507struct SelectSyntaxNodeHistory {
1508 stack: Vec<SelectSyntaxNodeHistoryState>,
1509 // disable temporarily to allow changing selections without losing the stack
1510 pub disable_clearing: bool,
1511}
1512
1513impl SelectSyntaxNodeHistory {
1514 pub fn try_clear(&mut self) {
1515 if !self.disable_clearing {
1516 self.stack.clear();
1517 }
1518 }
1519
1520 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1521 self.stack.push(selection);
1522 }
1523
1524 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1525 self.stack.pop()
1526 }
1527}
1528
1529enum SelectSyntaxNodeScrollBehavior {
1530 CursorTop,
1531 FitSelection,
1532 CursorBottom,
1533}
1534
1535#[derive(Debug)]
1536pub(crate) struct NavigationData {
1537 cursor_anchor: Anchor,
1538 cursor_position: Point,
1539 scroll_anchor: ScrollAnchor,
1540 scroll_top_row: u32,
1541}
1542
1543#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1544pub enum GotoDefinitionKind {
1545 Symbol,
1546 Declaration,
1547 Type,
1548 Implementation,
1549}
1550
1551#[derive(Debug, Clone)]
1552enum InlayHintRefreshReason {
1553 ModifiersChanged(bool),
1554 Toggle(bool),
1555 SettingsChange(InlayHintSettings),
1556 NewLinesShown,
1557 BufferEdited(HashSet<Arc<Language>>),
1558 RefreshRequested,
1559 ExcerptsRemoved(Vec<ExcerptId>),
1560}
1561
1562impl InlayHintRefreshReason {
1563 fn description(&self) -> &'static str {
1564 match self {
1565 Self::ModifiersChanged(_) => "modifiers changed",
1566 Self::Toggle(_) => "toggle",
1567 Self::SettingsChange(_) => "settings change",
1568 Self::NewLinesShown => "new lines shown",
1569 Self::BufferEdited(_) => "buffer edited",
1570 Self::RefreshRequested => "refresh requested",
1571 Self::ExcerptsRemoved(_) => "excerpts removed",
1572 }
1573 }
1574}
1575
1576pub enum FormatTarget {
1577 Buffers(HashSet<Entity<Buffer>>),
1578 Ranges(Vec<Range<MultiBufferPoint>>),
1579}
1580
1581pub(crate) struct FocusedBlock {
1582 id: BlockId,
1583 focus_handle: WeakFocusHandle,
1584}
1585
1586#[derive(Clone)]
1587enum JumpData {
1588 MultiBufferRow {
1589 row: MultiBufferRow,
1590 line_offset_from_top: u32,
1591 },
1592 MultiBufferPoint {
1593 excerpt_id: ExcerptId,
1594 position: Point,
1595 anchor: text::Anchor,
1596 line_offset_from_top: u32,
1597 },
1598}
1599
1600pub enum MultibufferSelectionMode {
1601 First,
1602 All,
1603}
1604
1605#[derive(Clone, Copy, Debug, Default)]
1606pub struct RewrapOptions {
1607 pub override_language_settings: bool,
1608 pub preserve_existing_whitespace: bool,
1609}
1610
1611impl Editor {
1612 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1613 let buffer = cx.new(|cx| Buffer::local("", cx));
1614 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1615 Self::new(
1616 EditorMode::SingleLine { auto_width: false },
1617 buffer,
1618 None,
1619 window,
1620 cx,
1621 )
1622 }
1623
1624 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1625 let buffer = cx.new(|cx| Buffer::local("", cx));
1626 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1627 Self::new(EditorMode::full(), buffer, None, window, cx)
1628 }
1629
1630 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1631 let buffer = cx.new(|cx| Buffer::local("", cx));
1632 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1633 Self::new(
1634 EditorMode::SingleLine { auto_width: true },
1635 buffer,
1636 None,
1637 window,
1638 cx,
1639 )
1640 }
1641
1642 pub fn auto_height(
1643 min_lines: usize,
1644 max_lines: usize,
1645 window: &mut Window,
1646 cx: &mut Context<Self>,
1647 ) -> Self {
1648 let buffer = cx.new(|cx| Buffer::local("", cx));
1649 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1650 Self::new(
1651 EditorMode::AutoHeight {
1652 min_lines,
1653 max_lines,
1654 },
1655 buffer,
1656 None,
1657 window,
1658 cx,
1659 )
1660 }
1661
1662 pub fn for_buffer(
1663 buffer: Entity<Buffer>,
1664 project: Option<Entity<Project>>,
1665 window: &mut Window,
1666 cx: &mut Context<Self>,
1667 ) -> Self {
1668 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1669 Self::new(EditorMode::full(), buffer, project, window, cx)
1670 }
1671
1672 pub fn for_multibuffer(
1673 buffer: Entity<MultiBuffer>,
1674 project: Option<Entity<Project>>,
1675 window: &mut Window,
1676 cx: &mut Context<Self>,
1677 ) -> Self {
1678 Self::new(EditorMode::full(), buffer, project, window, cx)
1679 }
1680
1681 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1682 let mut clone = Self::new(
1683 self.mode.clone(),
1684 self.buffer.clone(),
1685 self.project.clone(),
1686 window,
1687 cx,
1688 );
1689 self.display_map.update(cx, |display_map, cx| {
1690 let snapshot = display_map.snapshot(cx);
1691 clone.display_map.update(cx, |display_map, cx| {
1692 display_map.set_state(&snapshot, cx);
1693 });
1694 });
1695 clone.folds_did_change(cx);
1696 clone.selections.clone_state(&self.selections);
1697 clone.scroll_manager.clone_state(&self.scroll_manager);
1698 clone.searchable = self.searchable;
1699 clone.read_only = self.read_only;
1700 clone
1701 }
1702
1703 pub fn new(
1704 mode: EditorMode,
1705 buffer: Entity<MultiBuffer>,
1706 project: Option<Entity<Project>>,
1707 window: &mut Window,
1708 cx: &mut Context<Self>,
1709 ) -> Self {
1710 Editor::new_internal(mode, buffer, project, None, window, cx)
1711 }
1712
1713 fn new_internal(
1714 mode: EditorMode,
1715 buffer: Entity<MultiBuffer>,
1716 project: Option<Entity<Project>>,
1717 display_map: Option<Entity<DisplayMap>>,
1718 window: &mut Window,
1719 cx: &mut Context<Self>,
1720 ) -> Self {
1721 debug_assert!(
1722 display_map.is_none() || mode.is_minimap(),
1723 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1724 );
1725
1726 let full_mode = mode.is_full();
1727 let diagnostics_max_severity = if full_mode {
1728 EditorSettings::get_global(cx)
1729 .diagnostics_max_severity
1730 .unwrap_or(DiagnosticSeverity::Hint)
1731 } else {
1732 DiagnosticSeverity::Off
1733 };
1734 let style = window.text_style();
1735 let font_size = style.font_size.to_pixels(window.rem_size());
1736 let editor = cx.entity().downgrade();
1737 let fold_placeholder = FoldPlaceholder {
1738 constrain_width: true,
1739 render: Arc::new(move |fold_id, fold_range, cx| {
1740 let editor = editor.clone();
1741 div()
1742 .id(fold_id)
1743 .bg(cx.theme().colors().ghost_element_background)
1744 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1745 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1746 .rounded_xs()
1747 .size_full()
1748 .cursor_pointer()
1749 .child("⋯")
1750 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1751 .on_click(move |_, _window, cx| {
1752 editor
1753 .update(cx, |editor, cx| {
1754 editor.unfold_ranges(
1755 &[fold_range.start..fold_range.end],
1756 true,
1757 false,
1758 cx,
1759 );
1760 cx.stop_propagation();
1761 })
1762 .ok();
1763 })
1764 .into_any()
1765 }),
1766 merge_adjacent: true,
1767 ..FoldPlaceholder::default()
1768 };
1769 let display_map = display_map.unwrap_or_else(|| {
1770 cx.new(|cx| {
1771 DisplayMap::new(
1772 buffer.clone(),
1773 style.font(),
1774 font_size,
1775 None,
1776 FILE_HEADER_HEIGHT,
1777 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1778 fold_placeholder,
1779 diagnostics_max_severity,
1780 cx,
1781 )
1782 })
1783 });
1784
1785 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1786
1787 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1788
1789 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1790 .then(|| language_settings::SoftWrap::None);
1791
1792 let mut project_subscriptions = Vec::new();
1793 if mode.is_full() {
1794 if let Some(project) = project.as_ref() {
1795 project_subscriptions.push(cx.subscribe_in(
1796 project,
1797 window,
1798 |editor, _, event, window, cx| match event {
1799 project::Event::RefreshCodeLens => {
1800 // we always query lens with actions, without storing them, always refreshing them
1801 }
1802 project::Event::RefreshInlayHints => {
1803 editor
1804 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1805 }
1806 project::Event::LanguageServerAdded(server_id, ..)
1807 | project::Event::LanguageServerRemoved(server_id) => {
1808 if editor.tasks_update_task.is_none() {
1809 editor.tasks_update_task =
1810 Some(editor.refresh_runnables(window, cx));
1811 }
1812 editor.update_lsp_data(Some(*server_id), None, window, cx);
1813 }
1814 project::Event::SnippetEdit(id, snippet_edits) => {
1815 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1816 let focus_handle = editor.focus_handle(cx);
1817 if focus_handle.is_focused(window) {
1818 let snapshot = buffer.read(cx).snapshot();
1819 for (range, snippet) in snippet_edits {
1820 let editor_range =
1821 language::range_from_lsp(*range).to_offset(&snapshot);
1822 editor
1823 .insert_snippet(
1824 &[editor_range],
1825 snippet.clone(),
1826 window,
1827 cx,
1828 )
1829 .ok();
1830 }
1831 }
1832 }
1833 }
1834 _ => {}
1835 },
1836 ));
1837 if let Some(task_inventory) = project
1838 .read(cx)
1839 .task_store()
1840 .read(cx)
1841 .task_inventory()
1842 .cloned()
1843 {
1844 project_subscriptions.push(cx.observe_in(
1845 &task_inventory,
1846 window,
1847 |editor, _, window, cx| {
1848 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1849 },
1850 ));
1851 };
1852
1853 project_subscriptions.push(cx.subscribe_in(
1854 &project.read(cx).breakpoint_store(),
1855 window,
1856 |editor, _, event, window, cx| match event {
1857 BreakpointStoreEvent::ClearDebugLines => {
1858 editor.clear_row_highlights::<ActiveDebugLine>();
1859 editor.refresh_inline_values(cx);
1860 }
1861 BreakpointStoreEvent::SetDebugLine => {
1862 if editor.go_to_active_debug_line(window, cx) {
1863 cx.stop_propagation();
1864 }
1865
1866 editor.refresh_inline_values(cx);
1867 }
1868 _ => {}
1869 },
1870 ));
1871 let git_store = project.read(cx).git_store().clone();
1872 let project = project.clone();
1873 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1874 match event {
1875 GitStoreEvent::RepositoryUpdated(
1876 _,
1877 RepositoryEvent::Updated {
1878 new_instance: true, ..
1879 },
1880 _,
1881 ) => {
1882 this.load_diff_task = Some(
1883 update_uncommitted_diff_for_buffer(
1884 cx.entity(),
1885 &project,
1886 this.buffer.read(cx).all_buffers(),
1887 this.buffer.clone(),
1888 cx,
1889 )
1890 .shared(),
1891 );
1892 }
1893 _ => {}
1894 }
1895 }));
1896 }
1897 }
1898
1899 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1900
1901 let inlay_hint_settings =
1902 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1903 let focus_handle = cx.focus_handle();
1904 cx.on_focus(&focus_handle, window, Self::handle_focus)
1905 .detach();
1906 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1907 .detach();
1908 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1909 .detach();
1910 cx.on_blur(&focus_handle, window, Self::handle_blur)
1911 .detach();
1912 cx.observe_pending_input(window, Self::observe_pending_input)
1913 .detach();
1914
1915 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1916 Some(false)
1917 } else {
1918 None
1919 };
1920
1921 let breakpoint_store = match (&mode, project.as_ref()) {
1922 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1923 _ => None,
1924 };
1925
1926 let mut code_action_providers = Vec::new();
1927 let mut load_uncommitted_diff = None;
1928 if let Some(project) = project.clone() {
1929 load_uncommitted_diff = Some(
1930 update_uncommitted_diff_for_buffer(
1931 cx.entity(),
1932 &project,
1933 buffer.read(cx).all_buffers(),
1934 buffer.clone(),
1935 cx,
1936 )
1937 .shared(),
1938 );
1939 code_action_providers.push(Rc::new(project) as Rc<_>);
1940 }
1941
1942 let mut editor = Self {
1943 focus_handle,
1944 show_cursor_when_unfocused: false,
1945 last_focused_descendant: None,
1946 buffer: buffer.clone(),
1947 display_map: display_map.clone(),
1948 selections,
1949 scroll_manager: ScrollManager::new(cx),
1950 columnar_selection_state: None,
1951 add_selections_state: None,
1952 select_next_state: None,
1953 select_prev_state: None,
1954 selection_history: SelectionHistory::default(),
1955 defer_selection_effects: false,
1956 deferred_selection_effects_state: None,
1957 autoclose_regions: Vec::new(),
1958 snippet_stack: InvalidationStack::default(),
1959 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1960 ime_transaction: None,
1961 active_diagnostics: ActiveDiagnostic::None,
1962 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1963 inline_diagnostics_update: Task::ready(()),
1964 inline_diagnostics: Vec::new(),
1965 soft_wrap_mode_override,
1966 diagnostics_max_severity,
1967 hard_wrap: None,
1968 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1969 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1970 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1971 project,
1972 blink_manager: blink_manager.clone(),
1973 show_local_selections: true,
1974 show_scrollbars: ScrollbarAxes {
1975 horizontal: full_mode,
1976 vertical: full_mode,
1977 },
1978 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1979 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1980 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1981 show_gutter: mode.is_full(),
1982 show_line_numbers: None,
1983 use_relative_line_numbers: None,
1984 disable_expand_excerpt_buttons: false,
1985 show_git_diff_gutter: None,
1986 show_code_actions: None,
1987 show_runnables: None,
1988 show_breakpoints: None,
1989 show_wrap_guides: None,
1990 show_indent_guides,
1991 placeholder_text: None,
1992 highlight_order: 0,
1993 highlighted_rows: HashMap::default(),
1994 background_highlights: TreeMap::default(),
1995 gutter_highlights: TreeMap::default(),
1996 scrollbar_marker_state: ScrollbarMarkerState::default(),
1997 active_indent_guides_state: ActiveIndentGuidesState::default(),
1998 nav_history: None,
1999 context_menu: RefCell::new(None),
2000 context_menu_options: None,
2001 mouse_context_menu: None,
2002 completion_tasks: Vec::new(),
2003 inline_blame_popover: None,
2004 inline_blame_popover_show_task: None,
2005 signature_help_state: SignatureHelpState::default(),
2006 auto_signature_help: None,
2007 find_all_references_task_sources: Vec::new(),
2008 next_completion_id: 0,
2009 next_inlay_id: 0,
2010 code_action_providers,
2011 available_code_actions: None,
2012 code_actions_task: None,
2013 quick_selection_highlight_task: None,
2014 debounced_selection_highlight_task: None,
2015 document_highlights_task: None,
2016 linked_editing_range_task: None,
2017 pending_rename: None,
2018 searchable: true,
2019 cursor_shape: EditorSettings::get_global(cx)
2020 .cursor_shape
2021 .unwrap_or_default(),
2022 current_line_highlight: None,
2023 autoindent_mode: Some(AutoindentMode::EachLine),
2024 collapse_matches: false,
2025 workspace: None,
2026 input_enabled: true,
2027 use_modal_editing: mode.is_full(),
2028 read_only: mode.is_minimap(),
2029 use_autoclose: true,
2030 use_auto_surround: true,
2031 auto_replace_emoji_shortcode: false,
2032 jsx_tag_auto_close_enabled_in_any_buffer: false,
2033 leader_id: None,
2034 remote_id: None,
2035 hover_state: HoverState::default(),
2036 pending_mouse_down: None,
2037 hovered_link_state: None,
2038 edit_prediction_provider: None,
2039 active_inline_completion: None,
2040 stale_inline_completion_in_menu: None,
2041 edit_prediction_preview: EditPredictionPreview::Inactive {
2042 released_too_fast: false,
2043 },
2044 inline_diagnostics_enabled: mode.is_full(),
2045 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2046 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2047
2048 gutter_hovered: false,
2049 pixel_position_of_newest_cursor: None,
2050 last_bounds: None,
2051 last_position_map: None,
2052 expect_bounds_change: None,
2053 gutter_dimensions: GutterDimensions::default(),
2054 style: None,
2055 show_cursor_names: false,
2056 hovered_cursors: HashMap::default(),
2057 next_editor_action_id: EditorActionId::default(),
2058 editor_actions: Rc::default(),
2059 inline_completions_hidden_for_vim_mode: false,
2060 show_inline_completions_override: None,
2061 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2062 edit_prediction_settings: EditPredictionSettings::Disabled,
2063 edit_prediction_indent_conflict: false,
2064 edit_prediction_requires_modifier_in_indent_conflict: true,
2065 custom_context_menu: None,
2066 show_git_blame_gutter: false,
2067 show_git_blame_inline: false,
2068 show_selection_menu: None,
2069 show_git_blame_inline_delay_task: None,
2070 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2071 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2072 serialize_dirty_buffers: !mode.is_minimap()
2073 && ProjectSettings::get_global(cx)
2074 .session
2075 .restore_unsaved_buffers,
2076 blame: None,
2077 blame_subscription: None,
2078 tasks: BTreeMap::default(),
2079
2080 breakpoint_store,
2081 gutter_breakpoint_indicator: (None, None),
2082 hovered_diff_hunk_row: None,
2083 _subscriptions: vec![
2084 cx.observe(&buffer, Self::on_buffer_changed),
2085 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2086 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2087 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2088 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2089 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2090 cx.observe_window_activation(window, |editor, window, cx| {
2091 let active = window.is_window_active();
2092 editor.blink_manager.update(cx, |blink_manager, cx| {
2093 if active {
2094 blink_manager.enable(cx);
2095 } else {
2096 blink_manager.disable(cx);
2097 }
2098 });
2099 if active {
2100 editor.show_mouse_cursor(cx);
2101 }
2102 }),
2103 ],
2104 tasks_update_task: None,
2105 pull_diagnostics_task: Task::ready(()),
2106 colors: None,
2107 next_color_inlay_id: 0,
2108 linked_edit_ranges: Default::default(),
2109 in_project_search: false,
2110 previous_search_ranges: None,
2111 breadcrumb_header: None,
2112 focused_block: None,
2113 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2114 addons: HashMap::default(),
2115 registered_buffers: HashMap::default(),
2116 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2117 selection_mark_mode: false,
2118 toggle_fold_multiple_buffers: Task::ready(()),
2119 serialize_selections: Task::ready(()),
2120 serialize_folds: Task::ready(()),
2121 text_style_refinement: None,
2122 load_diff_task: load_uncommitted_diff,
2123 temporary_diff_override: false,
2124 mouse_cursor_hidden: false,
2125 minimap: None,
2126 hide_mouse_mode: EditorSettings::get_global(cx)
2127 .hide_mouse
2128 .unwrap_or_default(),
2129 change_list: ChangeList::new(),
2130 mode,
2131 selection_drag_state: SelectionDragState::None,
2132 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2133 };
2134 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2135 editor
2136 ._subscriptions
2137 .push(cx.observe(breakpoints, |_, _, cx| {
2138 cx.notify();
2139 }));
2140 }
2141 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2142 editor._subscriptions.extend(project_subscriptions);
2143
2144 editor._subscriptions.push(cx.subscribe_in(
2145 &cx.entity(),
2146 window,
2147 |editor, _, e: &EditorEvent, window, cx| match e {
2148 EditorEvent::ScrollPositionChanged { local, .. } => {
2149 if *local {
2150 let new_anchor = editor.scroll_manager.anchor();
2151 let snapshot = editor.snapshot(window, cx);
2152 editor.update_restoration_data(cx, move |data| {
2153 data.scroll_position = (
2154 new_anchor.top_row(&snapshot.buffer_snapshot),
2155 new_anchor.offset,
2156 );
2157 });
2158 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2159 editor.inline_blame_popover.take();
2160 }
2161 }
2162 EditorEvent::Edited { .. } => {
2163 if !vim_enabled(cx) {
2164 let (map, selections) = editor.selections.all_adjusted_display(cx);
2165 let pop_state = editor
2166 .change_list
2167 .last()
2168 .map(|previous| {
2169 previous.len() == selections.len()
2170 && previous.iter().enumerate().all(|(ix, p)| {
2171 p.to_display_point(&map).row()
2172 == selections[ix].head().row()
2173 })
2174 })
2175 .unwrap_or(false);
2176 let new_positions = selections
2177 .into_iter()
2178 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2179 .collect();
2180 editor
2181 .change_list
2182 .push_to_change_list(pop_state, new_positions);
2183 }
2184 }
2185 _ => (),
2186 },
2187 ));
2188
2189 if let Some(dap_store) = editor
2190 .project
2191 .as_ref()
2192 .map(|project| project.read(cx).dap_store())
2193 {
2194 let weak_editor = cx.weak_entity();
2195
2196 editor
2197 ._subscriptions
2198 .push(
2199 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2200 let session_entity = cx.entity();
2201 weak_editor
2202 .update(cx, |editor, cx| {
2203 editor._subscriptions.push(
2204 cx.subscribe(&session_entity, Self::on_debug_session_event),
2205 );
2206 })
2207 .ok();
2208 }),
2209 );
2210
2211 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2212 editor
2213 ._subscriptions
2214 .push(cx.subscribe(&session, Self::on_debug_session_event));
2215 }
2216 }
2217
2218 // skip adding the initial selection to selection history
2219 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2220 editor.end_selection(window, cx);
2221 editor.selection_history.mode = SelectionHistoryMode::Normal;
2222
2223 editor.scroll_manager.show_scrollbars(window, cx);
2224 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2225
2226 if full_mode {
2227 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2228 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2229
2230 if editor.git_blame_inline_enabled {
2231 editor.start_git_blame_inline(false, window, cx);
2232 }
2233
2234 editor.go_to_active_debug_line(window, cx);
2235
2236 if let Some(buffer) = buffer.read(cx).as_singleton() {
2237 if let Some(project) = editor.project.as_ref() {
2238 let handle = project.update(cx, |project, cx| {
2239 project.register_buffer_with_language_servers(&buffer, cx)
2240 });
2241 editor
2242 .registered_buffers
2243 .insert(buffer.read(cx).remote_id(), handle);
2244 }
2245 }
2246
2247 editor.minimap =
2248 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2249 editor.colors = Some(LspColorData::new(cx));
2250 editor.update_lsp_data(None, None, window, cx);
2251 }
2252
2253 editor.report_editor_event("Editor Opened", None, cx);
2254 editor
2255 }
2256
2257 pub fn deploy_mouse_context_menu(
2258 &mut self,
2259 position: gpui::Point<Pixels>,
2260 context_menu: Entity<ContextMenu>,
2261 window: &mut Window,
2262 cx: &mut Context<Self>,
2263 ) {
2264 self.mouse_context_menu = Some(MouseContextMenu::new(
2265 self,
2266 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2267 context_menu,
2268 window,
2269 cx,
2270 ));
2271 }
2272
2273 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2274 self.mouse_context_menu
2275 .as_ref()
2276 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2277 }
2278
2279 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2280 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2281 }
2282
2283 fn key_context_internal(
2284 &self,
2285 has_active_edit_prediction: bool,
2286 window: &Window,
2287 cx: &App,
2288 ) -> KeyContext {
2289 let mut key_context = KeyContext::new_with_defaults();
2290 key_context.add("Editor");
2291 let mode = match self.mode {
2292 EditorMode::SingleLine { .. } => "single_line",
2293 EditorMode::AutoHeight { .. } => "auto_height",
2294 EditorMode::Minimap { .. } => "minimap",
2295 EditorMode::Full { .. } => "full",
2296 };
2297
2298 if EditorSettings::jupyter_enabled(cx) {
2299 key_context.add("jupyter");
2300 }
2301
2302 key_context.set("mode", mode);
2303 if self.pending_rename.is_some() {
2304 key_context.add("renaming");
2305 }
2306
2307 match self.context_menu.borrow().as_ref() {
2308 Some(CodeContextMenu::Completions(_)) => {
2309 key_context.add("menu");
2310 key_context.add("showing_completions");
2311 }
2312 Some(CodeContextMenu::CodeActions(_)) => {
2313 key_context.add("menu");
2314 key_context.add("showing_code_actions")
2315 }
2316 None => {}
2317 }
2318
2319 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2320 if !self.focus_handle(cx).contains_focused(window, cx)
2321 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2322 {
2323 for addon in self.addons.values() {
2324 addon.extend_key_context(&mut key_context, cx)
2325 }
2326 }
2327
2328 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2329 if let Some(extension) = singleton_buffer
2330 .read(cx)
2331 .file()
2332 .and_then(|file| file.path().extension()?.to_str())
2333 {
2334 key_context.set("extension", extension.to_string());
2335 }
2336 } else {
2337 key_context.add("multibuffer");
2338 }
2339
2340 if has_active_edit_prediction {
2341 if self.edit_prediction_in_conflict() {
2342 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2343 } else {
2344 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2345 key_context.add("copilot_suggestion");
2346 }
2347 }
2348
2349 if self.selection_mark_mode {
2350 key_context.add("selection_mode");
2351 }
2352
2353 key_context
2354 }
2355
2356 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2357 if self.mouse_cursor_hidden {
2358 self.mouse_cursor_hidden = false;
2359 cx.notify();
2360 }
2361 }
2362
2363 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2364 let hide_mouse_cursor = match origin {
2365 HideMouseCursorOrigin::TypingAction => {
2366 matches!(
2367 self.hide_mouse_mode,
2368 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2369 )
2370 }
2371 HideMouseCursorOrigin::MovementAction => {
2372 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2373 }
2374 };
2375 if self.mouse_cursor_hidden != hide_mouse_cursor {
2376 self.mouse_cursor_hidden = hide_mouse_cursor;
2377 cx.notify();
2378 }
2379 }
2380
2381 pub fn edit_prediction_in_conflict(&self) -> bool {
2382 if !self.show_edit_predictions_in_menu() {
2383 return false;
2384 }
2385
2386 let showing_completions = self
2387 .context_menu
2388 .borrow()
2389 .as_ref()
2390 .map_or(false, |context| {
2391 matches!(context, CodeContextMenu::Completions(_))
2392 });
2393
2394 showing_completions
2395 || self.edit_prediction_requires_modifier()
2396 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2397 // bindings to insert tab characters.
2398 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2399 }
2400
2401 pub fn accept_edit_prediction_keybind(
2402 &self,
2403 accept_partial: bool,
2404 window: &Window,
2405 cx: &App,
2406 ) -> AcceptEditPredictionBinding {
2407 let key_context = self.key_context_internal(true, window, cx);
2408 let in_conflict = self.edit_prediction_in_conflict();
2409
2410 let bindings = if accept_partial {
2411 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2412 } else {
2413 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2414 };
2415
2416 // TODO: if the binding contains multiple keystrokes, display all of them, not
2417 // just the first one.
2418 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2419 !in_conflict
2420 || binding
2421 .keystrokes()
2422 .first()
2423 .map_or(false, |keystroke| keystroke.modifiers.modified())
2424 }))
2425 }
2426
2427 pub fn new_file(
2428 workspace: &mut Workspace,
2429 _: &workspace::NewFile,
2430 window: &mut Window,
2431 cx: &mut Context<Workspace>,
2432 ) {
2433 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2434 "Failed to create buffer",
2435 window,
2436 cx,
2437 |e, _, _| match e.error_code() {
2438 ErrorCode::RemoteUpgradeRequired => Some(format!(
2439 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2440 e.error_tag("required").unwrap_or("the latest version")
2441 )),
2442 _ => None,
2443 },
2444 );
2445 }
2446
2447 pub fn new_in_workspace(
2448 workspace: &mut Workspace,
2449 window: &mut Window,
2450 cx: &mut Context<Workspace>,
2451 ) -> Task<Result<Entity<Editor>>> {
2452 let project = workspace.project().clone();
2453 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2454
2455 cx.spawn_in(window, async move |workspace, cx| {
2456 let buffer = create.await?;
2457 workspace.update_in(cx, |workspace, window, cx| {
2458 let editor =
2459 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2460 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2461 editor
2462 })
2463 })
2464 }
2465
2466 fn new_file_vertical(
2467 workspace: &mut Workspace,
2468 _: &workspace::NewFileSplitVertical,
2469 window: &mut Window,
2470 cx: &mut Context<Workspace>,
2471 ) {
2472 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2473 }
2474
2475 fn new_file_horizontal(
2476 workspace: &mut Workspace,
2477 _: &workspace::NewFileSplitHorizontal,
2478 window: &mut Window,
2479 cx: &mut Context<Workspace>,
2480 ) {
2481 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2482 }
2483
2484 fn new_file_in_direction(
2485 workspace: &mut Workspace,
2486 direction: SplitDirection,
2487 window: &mut Window,
2488 cx: &mut Context<Workspace>,
2489 ) {
2490 let project = workspace.project().clone();
2491 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2492
2493 cx.spawn_in(window, async move |workspace, cx| {
2494 let buffer = create.await?;
2495 workspace.update_in(cx, move |workspace, window, cx| {
2496 workspace.split_item(
2497 direction,
2498 Box::new(
2499 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2500 ),
2501 window,
2502 cx,
2503 )
2504 })?;
2505 anyhow::Ok(())
2506 })
2507 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2508 match e.error_code() {
2509 ErrorCode::RemoteUpgradeRequired => Some(format!(
2510 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2511 e.error_tag("required").unwrap_or("the latest version")
2512 )),
2513 _ => None,
2514 }
2515 });
2516 }
2517
2518 pub fn leader_id(&self) -> Option<CollaboratorId> {
2519 self.leader_id
2520 }
2521
2522 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2523 &self.buffer
2524 }
2525
2526 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2527 self.workspace.as_ref()?.0.upgrade()
2528 }
2529
2530 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2531 self.buffer().read(cx).title(cx)
2532 }
2533
2534 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2535 let git_blame_gutter_max_author_length = self
2536 .render_git_blame_gutter(cx)
2537 .then(|| {
2538 if let Some(blame) = self.blame.as_ref() {
2539 let max_author_length =
2540 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2541 Some(max_author_length)
2542 } else {
2543 None
2544 }
2545 })
2546 .flatten();
2547
2548 EditorSnapshot {
2549 mode: self.mode.clone(),
2550 show_gutter: self.show_gutter,
2551 show_line_numbers: self.show_line_numbers,
2552 show_git_diff_gutter: self.show_git_diff_gutter,
2553 show_code_actions: self.show_code_actions,
2554 show_runnables: self.show_runnables,
2555 show_breakpoints: self.show_breakpoints,
2556 git_blame_gutter_max_author_length,
2557 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2558 scroll_anchor: self.scroll_manager.anchor(),
2559 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2560 placeholder_text: self.placeholder_text.clone(),
2561 is_focused: self.focus_handle.is_focused(window),
2562 current_line_highlight: self
2563 .current_line_highlight
2564 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2565 gutter_hovered: self.gutter_hovered,
2566 }
2567 }
2568
2569 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2570 self.buffer.read(cx).language_at(point, cx)
2571 }
2572
2573 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2574 self.buffer.read(cx).read(cx).file_at(point).cloned()
2575 }
2576
2577 pub fn active_excerpt(
2578 &self,
2579 cx: &App,
2580 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2581 self.buffer
2582 .read(cx)
2583 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2584 }
2585
2586 pub fn mode(&self) -> &EditorMode {
2587 &self.mode
2588 }
2589
2590 pub fn set_mode(&mut self, mode: EditorMode) {
2591 self.mode = mode;
2592 }
2593
2594 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2595 self.collaboration_hub.as_deref()
2596 }
2597
2598 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2599 self.collaboration_hub = Some(hub);
2600 }
2601
2602 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2603 self.in_project_search = in_project_search;
2604 }
2605
2606 pub fn set_custom_context_menu(
2607 &mut self,
2608 f: impl 'static
2609 + Fn(
2610 &mut Self,
2611 DisplayPoint,
2612 &mut Window,
2613 &mut Context<Self>,
2614 ) -> Option<Entity<ui::ContextMenu>>,
2615 ) {
2616 self.custom_context_menu = Some(Box::new(f))
2617 }
2618
2619 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2620 self.completion_provider = provider;
2621 }
2622
2623 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2624 self.semantics_provider.clone()
2625 }
2626
2627 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2628 self.semantics_provider = provider;
2629 }
2630
2631 pub fn set_edit_prediction_provider<T>(
2632 &mut self,
2633 provider: Option<Entity<T>>,
2634 window: &mut Window,
2635 cx: &mut Context<Self>,
2636 ) where
2637 T: EditPredictionProvider,
2638 {
2639 self.edit_prediction_provider =
2640 provider.map(|provider| RegisteredInlineCompletionProvider {
2641 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2642 if this.focus_handle.is_focused(window) {
2643 this.update_visible_inline_completion(window, cx);
2644 }
2645 }),
2646 provider: Arc::new(provider),
2647 });
2648 self.update_edit_prediction_settings(cx);
2649 self.refresh_inline_completion(false, false, window, cx);
2650 }
2651
2652 pub fn placeholder_text(&self) -> Option<&str> {
2653 self.placeholder_text.as_deref()
2654 }
2655
2656 pub fn set_placeholder_text(
2657 &mut self,
2658 placeholder_text: impl Into<Arc<str>>,
2659 cx: &mut Context<Self>,
2660 ) {
2661 let placeholder_text = Some(placeholder_text.into());
2662 if self.placeholder_text != placeholder_text {
2663 self.placeholder_text = placeholder_text;
2664 cx.notify();
2665 }
2666 }
2667
2668 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2669 self.cursor_shape = cursor_shape;
2670
2671 // Disrupt blink for immediate user feedback that the cursor shape has changed
2672 self.blink_manager.update(cx, BlinkManager::show_cursor);
2673
2674 cx.notify();
2675 }
2676
2677 pub fn set_current_line_highlight(
2678 &mut self,
2679 current_line_highlight: Option<CurrentLineHighlight>,
2680 ) {
2681 self.current_line_highlight = current_line_highlight;
2682 }
2683
2684 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2685 self.collapse_matches = collapse_matches;
2686 }
2687
2688 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2689 let buffers = self.buffer.read(cx).all_buffers();
2690 let Some(project) = self.project.as_ref() else {
2691 return;
2692 };
2693 project.update(cx, |project, cx| {
2694 for buffer in buffers {
2695 self.registered_buffers
2696 .entry(buffer.read(cx).remote_id())
2697 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2698 }
2699 })
2700 }
2701
2702 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2703 if self.collapse_matches {
2704 return range.start..range.start;
2705 }
2706 range.clone()
2707 }
2708
2709 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2710 if self.display_map.read(cx).clip_at_line_ends != clip {
2711 self.display_map
2712 .update(cx, |map, _| map.clip_at_line_ends = clip);
2713 }
2714 }
2715
2716 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2717 self.input_enabled = input_enabled;
2718 }
2719
2720 pub fn set_inline_completions_hidden_for_vim_mode(
2721 &mut self,
2722 hidden: bool,
2723 window: &mut Window,
2724 cx: &mut Context<Self>,
2725 ) {
2726 if hidden != self.inline_completions_hidden_for_vim_mode {
2727 self.inline_completions_hidden_for_vim_mode = hidden;
2728 if hidden {
2729 self.update_visible_inline_completion(window, cx);
2730 } else {
2731 self.refresh_inline_completion(true, false, window, cx);
2732 }
2733 }
2734 }
2735
2736 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2737 self.menu_inline_completions_policy = value;
2738 }
2739
2740 pub fn set_autoindent(&mut self, autoindent: bool) {
2741 if autoindent {
2742 self.autoindent_mode = Some(AutoindentMode::EachLine);
2743 } else {
2744 self.autoindent_mode = None;
2745 }
2746 }
2747
2748 pub fn read_only(&self, cx: &App) -> bool {
2749 self.read_only || self.buffer.read(cx).read_only()
2750 }
2751
2752 pub fn set_read_only(&mut self, read_only: bool) {
2753 self.read_only = read_only;
2754 }
2755
2756 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2757 self.use_autoclose = autoclose;
2758 }
2759
2760 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2761 self.use_auto_surround = auto_surround;
2762 }
2763
2764 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2765 self.auto_replace_emoji_shortcode = auto_replace;
2766 }
2767
2768 pub fn toggle_edit_predictions(
2769 &mut self,
2770 _: &ToggleEditPrediction,
2771 window: &mut Window,
2772 cx: &mut Context<Self>,
2773 ) {
2774 if self.show_inline_completions_override.is_some() {
2775 self.set_show_edit_predictions(None, window, cx);
2776 } else {
2777 let show_edit_predictions = !self.edit_predictions_enabled();
2778 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2779 }
2780 }
2781
2782 pub fn set_show_edit_predictions(
2783 &mut self,
2784 show_edit_predictions: Option<bool>,
2785 window: &mut Window,
2786 cx: &mut Context<Self>,
2787 ) {
2788 self.show_inline_completions_override = show_edit_predictions;
2789 self.update_edit_prediction_settings(cx);
2790
2791 if let Some(false) = show_edit_predictions {
2792 self.discard_inline_completion(false, cx);
2793 } else {
2794 self.refresh_inline_completion(false, true, window, cx);
2795 }
2796 }
2797
2798 fn inline_completions_disabled_in_scope(
2799 &self,
2800 buffer: &Entity<Buffer>,
2801 buffer_position: language::Anchor,
2802 cx: &App,
2803 ) -> bool {
2804 let snapshot = buffer.read(cx).snapshot();
2805 let settings = snapshot.settings_at(buffer_position, cx);
2806
2807 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2808 return false;
2809 };
2810
2811 scope.override_name().map_or(false, |scope_name| {
2812 settings
2813 .edit_predictions_disabled_in
2814 .iter()
2815 .any(|s| s == scope_name)
2816 })
2817 }
2818
2819 pub fn set_use_modal_editing(&mut self, to: bool) {
2820 self.use_modal_editing = to;
2821 }
2822
2823 pub fn use_modal_editing(&self) -> bool {
2824 self.use_modal_editing
2825 }
2826
2827 fn selections_did_change(
2828 &mut self,
2829 local: bool,
2830 old_cursor_position: &Anchor,
2831 effects: SelectionEffects,
2832 window: &mut Window,
2833 cx: &mut Context<Self>,
2834 ) {
2835 window.invalidate_character_coordinates();
2836
2837 // Copy selections to primary selection buffer
2838 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2839 if local {
2840 let selections = self.selections.all::<usize>(cx);
2841 let buffer_handle = self.buffer.read(cx).read(cx);
2842
2843 let mut text = String::new();
2844 for (index, selection) in selections.iter().enumerate() {
2845 let text_for_selection = buffer_handle
2846 .text_for_range(selection.start..selection.end)
2847 .collect::<String>();
2848
2849 text.push_str(&text_for_selection);
2850 if index != selections.len() - 1 {
2851 text.push('\n');
2852 }
2853 }
2854
2855 if !text.is_empty() {
2856 cx.write_to_primary(ClipboardItem::new_string(text));
2857 }
2858 }
2859
2860 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2861 self.buffer.update(cx, |buffer, cx| {
2862 buffer.set_active_selections(
2863 &self.selections.disjoint_anchors(),
2864 self.selections.line_mode,
2865 self.cursor_shape,
2866 cx,
2867 )
2868 });
2869 }
2870 let display_map = self
2871 .display_map
2872 .update(cx, |display_map, cx| display_map.snapshot(cx));
2873 let buffer = &display_map.buffer_snapshot;
2874 if self.selections.count() == 1 {
2875 self.add_selections_state = None;
2876 }
2877 self.select_next_state = None;
2878 self.select_prev_state = None;
2879 self.select_syntax_node_history.try_clear();
2880 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2881 self.snippet_stack
2882 .invalidate(&self.selections.disjoint_anchors(), buffer);
2883 self.take_rename(false, window, cx);
2884
2885 let newest_selection = self.selections.newest_anchor();
2886 let new_cursor_position = newest_selection.head();
2887 let selection_start = newest_selection.start;
2888
2889 if effects.nav_history {
2890 self.push_to_nav_history(
2891 *old_cursor_position,
2892 Some(new_cursor_position.to_point(buffer)),
2893 false,
2894 cx,
2895 );
2896 }
2897
2898 if local {
2899 if let Some(buffer_id) = new_cursor_position.buffer_id {
2900 if !self.registered_buffers.contains_key(&buffer_id) {
2901 if let Some(project) = self.project.as_ref() {
2902 project.update(cx, |project, cx| {
2903 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2904 return;
2905 };
2906 self.registered_buffers.insert(
2907 buffer_id,
2908 project.register_buffer_with_language_servers(&buffer, cx),
2909 );
2910 })
2911 }
2912 }
2913 }
2914
2915 let mut context_menu = self.context_menu.borrow_mut();
2916 let completion_menu = match context_menu.as_ref() {
2917 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2918 Some(CodeContextMenu::CodeActions(_)) => {
2919 *context_menu = None;
2920 None
2921 }
2922 None => None,
2923 };
2924 let completion_position = completion_menu.map(|menu| menu.initial_position);
2925 drop(context_menu);
2926
2927 if effects.completions {
2928 if let Some(completion_position) = completion_position {
2929 let start_offset = selection_start.to_offset(buffer);
2930 let position_matches = start_offset == completion_position.to_offset(buffer);
2931 let continue_showing = if position_matches {
2932 if self.snippet_stack.is_empty() {
2933 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2934 } else {
2935 // Snippet choices can be shown even when the cursor is in whitespace.
2936 // Dismissing the menu with actions like backspace is handled by
2937 // invalidation regions.
2938 true
2939 }
2940 } else {
2941 false
2942 };
2943
2944 if continue_showing {
2945 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2946 } else {
2947 self.hide_context_menu(window, cx);
2948 }
2949 }
2950 }
2951
2952 hide_hover(self, cx);
2953
2954 if old_cursor_position.to_display_point(&display_map).row()
2955 != new_cursor_position.to_display_point(&display_map).row()
2956 {
2957 self.available_code_actions.take();
2958 }
2959 self.refresh_code_actions(window, cx);
2960 self.refresh_document_highlights(cx);
2961 self.refresh_selected_text_highlights(false, window, cx);
2962 refresh_matching_bracket_highlights(self, window, cx);
2963 self.update_visible_inline_completion(window, cx);
2964 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2965 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2966 self.inline_blame_popover.take();
2967 if self.git_blame_inline_enabled {
2968 self.start_inline_blame_timer(window, cx);
2969 }
2970 }
2971
2972 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2973 cx.emit(EditorEvent::SelectionsChanged { local });
2974
2975 let selections = &self.selections.disjoint;
2976 if selections.len() == 1 {
2977 cx.emit(SearchEvent::ActiveMatchChanged)
2978 }
2979 if local {
2980 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2981 let inmemory_selections = selections
2982 .iter()
2983 .map(|s| {
2984 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2985 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2986 })
2987 .collect();
2988 self.update_restoration_data(cx, |data| {
2989 data.selections = inmemory_selections;
2990 });
2991
2992 if WorkspaceSettings::get(None, cx).restore_on_startup
2993 != RestoreOnStartupBehavior::None
2994 {
2995 if let Some(workspace_id) =
2996 self.workspace.as_ref().and_then(|workspace| workspace.1)
2997 {
2998 let snapshot = self.buffer().read(cx).snapshot(cx);
2999 let selections = selections.clone();
3000 let background_executor = cx.background_executor().clone();
3001 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3002 self.serialize_selections = cx.background_spawn(async move {
3003 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3004 let db_selections = selections
3005 .iter()
3006 .map(|selection| {
3007 (
3008 selection.start.to_offset(&snapshot),
3009 selection.end.to_offset(&snapshot),
3010 )
3011 })
3012 .collect();
3013
3014 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3015 .await
3016 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
3017 .log_err();
3018 });
3019 }
3020 }
3021 }
3022 }
3023
3024 cx.notify();
3025 }
3026
3027 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3028 use text::ToOffset as _;
3029 use text::ToPoint as _;
3030
3031 if self.mode.is_minimap()
3032 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3033 {
3034 return;
3035 }
3036
3037 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3038 return;
3039 };
3040
3041 let snapshot = singleton.read(cx).snapshot();
3042 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3043 let display_snapshot = display_map.snapshot(cx);
3044
3045 display_snapshot
3046 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3047 .map(|fold| {
3048 fold.range.start.text_anchor.to_point(&snapshot)
3049 ..fold.range.end.text_anchor.to_point(&snapshot)
3050 })
3051 .collect()
3052 });
3053 self.update_restoration_data(cx, |data| {
3054 data.folds = inmemory_folds;
3055 });
3056
3057 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3058 return;
3059 };
3060 let background_executor = cx.background_executor().clone();
3061 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3062 let db_folds = self.display_map.update(cx, |display_map, cx| {
3063 display_map
3064 .snapshot(cx)
3065 .folds_in_range(0..snapshot.len())
3066 .map(|fold| {
3067 (
3068 fold.range.start.text_anchor.to_offset(&snapshot),
3069 fold.range.end.text_anchor.to_offset(&snapshot),
3070 )
3071 })
3072 .collect()
3073 });
3074 self.serialize_folds = cx.background_spawn(async move {
3075 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3076 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3077 .await
3078 .with_context(|| {
3079 format!(
3080 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3081 )
3082 })
3083 .log_err();
3084 });
3085 }
3086
3087 pub fn sync_selections(
3088 &mut self,
3089 other: Entity<Editor>,
3090 cx: &mut Context<Self>,
3091 ) -> gpui::Subscription {
3092 let other_selections = other.read(cx).selections.disjoint.to_vec();
3093 self.selections.change_with(cx, |selections| {
3094 selections.select_anchors(other_selections);
3095 });
3096
3097 let other_subscription =
3098 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3099 EditorEvent::SelectionsChanged { local: true } => {
3100 let other_selections = other.read(cx).selections.disjoint.to_vec();
3101 if other_selections.is_empty() {
3102 return;
3103 }
3104 this.selections.change_with(cx, |selections| {
3105 selections.select_anchors(other_selections);
3106 });
3107 }
3108 _ => {}
3109 });
3110
3111 let this_subscription =
3112 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3113 EditorEvent::SelectionsChanged { local: true } => {
3114 let these_selections = this.selections.disjoint.to_vec();
3115 if these_selections.is_empty() {
3116 return;
3117 }
3118 other.update(cx, |other_editor, cx| {
3119 other_editor.selections.change_with(cx, |selections| {
3120 selections.select_anchors(these_selections);
3121 })
3122 });
3123 }
3124 _ => {}
3125 });
3126
3127 Subscription::join(other_subscription, this_subscription)
3128 }
3129
3130 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3131 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3132 /// effects of selection change occur at the end of the transaction.
3133 pub fn change_selections<R>(
3134 &mut self,
3135 effects: impl Into<SelectionEffects>,
3136 window: &mut Window,
3137 cx: &mut Context<Self>,
3138 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3139 ) -> R {
3140 let effects = effects.into();
3141 if let Some(state) = &mut self.deferred_selection_effects_state {
3142 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3143 state.effects.completions = effects.completions;
3144 state.effects.nav_history |= effects.nav_history;
3145 let (changed, result) = self.selections.change_with(cx, change);
3146 state.changed |= changed;
3147 return result;
3148 }
3149 let mut state = DeferredSelectionEffectsState {
3150 changed: false,
3151 effects,
3152 old_cursor_position: self.selections.newest_anchor().head(),
3153 history_entry: SelectionHistoryEntry {
3154 selections: self.selections.disjoint_anchors(),
3155 select_next_state: self.select_next_state.clone(),
3156 select_prev_state: self.select_prev_state.clone(),
3157 add_selections_state: self.add_selections_state.clone(),
3158 },
3159 };
3160 let (changed, result) = self.selections.change_with(cx, change);
3161 state.changed = state.changed || changed;
3162 if self.defer_selection_effects {
3163 self.deferred_selection_effects_state = Some(state);
3164 } else {
3165 self.apply_selection_effects(state, window, cx);
3166 }
3167 result
3168 }
3169
3170 /// Defers the effects of selection change, so that the effects of multiple calls to
3171 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3172 /// to selection history and the state of popovers based on selection position aren't
3173 /// erroneously updated.
3174 pub fn with_selection_effects_deferred<R>(
3175 &mut self,
3176 window: &mut Window,
3177 cx: &mut Context<Self>,
3178 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3179 ) -> R {
3180 let already_deferred = self.defer_selection_effects;
3181 self.defer_selection_effects = true;
3182 let result = update(self, window, cx);
3183 if !already_deferred {
3184 self.defer_selection_effects = false;
3185 if let Some(state) = self.deferred_selection_effects_state.take() {
3186 self.apply_selection_effects(state, window, cx);
3187 }
3188 }
3189 result
3190 }
3191
3192 fn apply_selection_effects(
3193 &mut self,
3194 state: DeferredSelectionEffectsState,
3195 window: &mut Window,
3196 cx: &mut Context<Self>,
3197 ) {
3198 if state.changed {
3199 self.selection_history.push(state.history_entry);
3200
3201 if let Some(autoscroll) = state.effects.scroll {
3202 self.request_autoscroll(autoscroll, cx);
3203 }
3204
3205 let old_cursor_position = &state.old_cursor_position;
3206
3207 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3208
3209 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3210 self.show_signature_help(&ShowSignatureHelp, window, cx);
3211 }
3212 }
3213 }
3214
3215 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3216 where
3217 I: IntoIterator<Item = (Range<S>, T)>,
3218 S: ToOffset,
3219 T: Into<Arc<str>>,
3220 {
3221 if self.read_only(cx) {
3222 return;
3223 }
3224
3225 self.buffer
3226 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3227 }
3228
3229 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3230 where
3231 I: IntoIterator<Item = (Range<S>, T)>,
3232 S: ToOffset,
3233 T: Into<Arc<str>>,
3234 {
3235 if self.read_only(cx) {
3236 return;
3237 }
3238
3239 self.buffer.update(cx, |buffer, cx| {
3240 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3241 });
3242 }
3243
3244 pub fn edit_with_block_indent<I, S, T>(
3245 &mut self,
3246 edits: I,
3247 original_indent_columns: Vec<Option<u32>>,
3248 cx: &mut Context<Self>,
3249 ) where
3250 I: IntoIterator<Item = (Range<S>, T)>,
3251 S: ToOffset,
3252 T: Into<Arc<str>>,
3253 {
3254 if self.read_only(cx) {
3255 return;
3256 }
3257
3258 self.buffer.update(cx, |buffer, cx| {
3259 buffer.edit(
3260 edits,
3261 Some(AutoindentMode::Block {
3262 original_indent_columns,
3263 }),
3264 cx,
3265 )
3266 });
3267 }
3268
3269 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3270 self.hide_context_menu(window, cx);
3271
3272 match phase {
3273 SelectPhase::Begin {
3274 position,
3275 add,
3276 click_count,
3277 } => self.begin_selection(position, add, click_count, window, cx),
3278 SelectPhase::BeginColumnar {
3279 position,
3280 goal_column,
3281 reset,
3282 mode,
3283 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3284 SelectPhase::Extend {
3285 position,
3286 click_count,
3287 } => self.extend_selection(position, click_count, window, cx),
3288 SelectPhase::Update {
3289 position,
3290 goal_column,
3291 scroll_delta,
3292 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3293 SelectPhase::End => self.end_selection(window, cx),
3294 }
3295 }
3296
3297 fn extend_selection(
3298 &mut self,
3299 position: DisplayPoint,
3300 click_count: usize,
3301 window: &mut Window,
3302 cx: &mut Context<Self>,
3303 ) {
3304 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3305 let tail = self.selections.newest::<usize>(cx).tail();
3306 self.begin_selection(position, false, click_count, window, cx);
3307
3308 let position = position.to_offset(&display_map, Bias::Left);
3309 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3310
3311 let mut pending_selection = self
3312 .selections
3313 .pending_anchor()
3314 .expect("extend_selection not called with pending selection");
3315 if position >= tail {
3316 pending_selection.start = tail_anchor;
3317 } else {
3318 pending_selection.end = tail_anchor;
3319 pending_selection.reversed = true;
3320 }
3321
3322 let mut pending_mode = self.selections.pending_mode().unwrap();
3323 match &mut pending_mode {
3324 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3325 _ => {}
3326 }
3327
3328 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3329 SelectionEffects::scroll(Autoscroll::fit())
3330 } else {
3331 SelectionEffects::no_scroll()
3332 };
3333
3334 self.change_selections(effects, window, cx, |s| {
3335 s.set_pending(pending_selection, pending_mode)
3336 });
3337 }
3338
3339 fn begin_selection(
3340 &mut self,
3341 position: DisplayPoint,
3342 add: bool,
3343 click_count: usize,
3344 window: &mut Window,
3345 cx: &mut Context<Self>,
3346 ) {
3347 if !self.focus_handle.is_focused(window) {
3348 self.last_focused_descendant = None;
3349 window.focus(&self.focus_handle);
3350 }
3351
3352 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3353 let buffer = &display_map.buffer_snapshot;
3354 let position = display_map.clip_point(position, Bias::Left);
3355
3356 let start;
3357 let end;
3358 let mode;
3359 let mut auto_scroll;
3360 match click_count {
3361 1 => {
3362 start = buffer.anchor_before(position.to_point(&display_map));
3363 end = start;
3364 mode = SelectMode::Character;
3365 auto_scroll = true;
3366 }
3367 2 => {
3368 let range = movement::surrounding_word(&display_map, position);
3369 start = buffer.anchor_before(range.start.to_point(&display_map));
3370 end = buffer.anchor_before(range.end.to_point(&display_map));
3371 mode = SelectMode::Word(start..end);
3372 auto_scroll = true;
3373 }
3374 3 => {
3375 let position = display_map
3376 .clip_point(position, Bias::Left)
3377 .to_point(&display_map);
3378 let line_start = display_map.prev_line_boundary(position).0;
3379 let next_line_start = buffer.clip_point(
3380 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3381 Bias::Left,
3382 );
3383 start = buffer.anchor_before(line_start);
3384 end = buffer.anchor_before(next_line_start);
3385 mode = SelectMode::Line(start..end);
3386 auto_scroll = true;
3387 }
3388 _ => {
3389 start = buffer.anchor_before(0);
3390 end = buffer.anchor_before(buffer.len());
3391 mode = SelectMode::All;
3392 auto_scroll = false;
3393 }
3394 }
3395 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3396
3397 let point_to_delete: Option<usize> = {
3398 let selected_points: Vec<Selection<Point>> =
3399 self.selections.disjoint_in_range(start..end, cx);
3400
3401 if !add || click_count > 1 {
3402 None
3403 } else if !selected_points.is_empty() {
3404 Some(selected_points[0].id)
3405 } else {
3406 let clicked_point_already_selected =
3407 self.selections.disjoint.iter().find(|selection| {
3408 selection.start.to_point(buffer) == start.to_point(buffer)
3409 || selection.end.to_point(buffer) == end.to_point(buffer)
3410 });
3411
3412 clicked_point_already_selected.map(|selection| selection.id)
3413 }
3414 };
3415
3416 let selections_count = self.selections.count();
3417
3418 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3419 if let Some(point_to_delete) = point_to_delete {
3420 s.delete(point_to_delete);
3421
3422 if selections_count == 1 {
3423 s.set_pending_anchor_range(start..end, mode);
3424 }
3425 } else {
3426 if !add {
3427 s.clear_disjoint();
3428 }
3429
3430 s.set_pending_anchor_range(start..end, mode);
3431 }
3432 });
3433 }
3434
3435 fn begin_columnar_selection(
3436 &mut self,
3437 position: DisplayPoint,
3438 goal_column: u32,
3439 reset: bool,
3440 mode: ColumnarMode,
3441 window: &mut Window,
3442 cx: &mut Context<Self>,
3443 ) {
3444 if !self.focus_handle.is_focused(window) {
3445 self.last_focused_descendant = None;
3446 window.focus(&self.focus_handle);
3447 }
3448
3449 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3450
3451 if reset {
3452 let pointer_position = display_map
3453 .buffer_snapshot
3454 .anchor_before(position.to_point(&display_map));
3455
3456 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3457 s.clear_disjoint();
3458 s.set_pending_anchor_range(
3459 pointer_position..pointer_position,
3460 SelectMode::Character,
3461 );
3462 });
3463 };
3464
3465 let tail = self.selections.newest::<Point>(cx).tail();
3466 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3467 self.columnar_selection_state = match mode {
3468 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3469 selection_tail: selection_anchor,
3470 display_point: if reset {
3471 if position.column() != goal_column {
3472 Some(DisplayPoint::new(position.row(), goal_column))
3473 } else {
3474 None
3475 }
3476 } else {
3477 None
3478 },
3479 }),
3480 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3481 selection_tail: selection_anchor,
3482 }),
3483 };
3484
3485 if !reset {
3486 self.select_columns(position, goal_column, &display_map, window, cx);
3487 }
3488 }
3489
3490 fn update_selection(
3491 &mut self,
3492 position: DisplayPoint,
3493 goal_column: u32,
3494 scroll_delta: gpui::Point<f32>,
3495 window: &mut Window,
3496 cx: &mut Context<Self>,
3497 ) {
3498 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3499
3500 if self.columnar_selection_state.is_some() {
3501 self.select_columns(position, goal_column, &display_map, window, cx);
3502 } else if let Some(mut pending) = self.selections.pending_anchor() {
3503 let buffer = self.buffer.read(cx).snapshot(cx);
3504 let head;
3505 let tail;
3506 let mode = self.selections.pending_mode().unwrap();
3507 match &mode {
3508 SelectMode::Character => {
3509 head = position.to_point(&display_map);
3510 tail = pending.tail().to_point(&buffer);
3511 }
3512 SelectMode::Word(original_range) => {
3513 let original_display_range = original_range.start.to_display_point(&display_map)
3514 ..original_range.end.to_display_point(&display_map);
3515 let original_buffer_range = original_display_range.start.to_point(&display_map)
3516 ..original_display_range.end.to_point(&display_map);
3517 if movement::is_inside_word(&display_map, position)
3518 || original_display_range.contains(&position)
3519 {
3520 let word_range = movement::surrounding_word(&display_map, position);
3521 if word_range.start < original_display_range.start {
3522 head = word_range.start.to_point(&display_map);
3523 } else {
3524 head = word_range.end.to_point(&display_map);
3525 }
3526 } else {
3527 head = position.to_point(&display_map);
3528 }
3529
3530 if head <= original_buffer_range.start {
3531 tail = original_buffer_range.end;
3532 } else {
3533 tail = original_buffer_range.start;
3534 }
3535 }
3536 SelectMode::Line(original_range) => {
3537 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3538
3539 let position = display_map
3540 .clip_point(position, Bias::Left)
3541 .to_point(&display_map);
3542 let line_start = display_map.prev_line_boundary(position).0;
3543 let next_line_start = buffer.clip_point(
3544 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3545 Bias::Left,
3546 );
3547
3548 if line_start < original_range.start {
3549 head = line_start
3550 } else {
3551 head = next_line_start
3552 }
3553
3554 if head <= original_range.start {
3555 tail = original_range.end;
3556 } else {
3557 tail = original_range.start;
3558 }
3559 }
3560 SelectMode::All => {
3561 return;
3562 }
3563 };
3564
3565 if head < tail {
3566 pending.start = buffer.anchor_before(head);
3567 pending.end = buffer.anchor_before(tail);
3568 pending.reversed = true;
3569 } else {
3570 pending.start = buffer.anchor_before(tail);
3571 pending.end = buffer.anchor_before(head);
3572 pending.reversed = false;
3573 }
3574
3575 self.change_selections(None, window, cx, |s| {
3576 s.set_pending(pending, mode);
3577 });
3578 } else {
3579 log::error!("update_selection dispatched with no pending selection");
3580 return;
3581 }
3582
3583 self.apply_scroll_delta(scroll_delta, window, cx);
3584 cx.notify();
3585 }
3586
3587 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3588 self.columnar_selection_state.take();
3589 if self.selections.pending_anchor().is_some() {
3590 let selections = self.selections.all::<usize>(cx);
3591 self.change_selections(None, window, cx, |s| {
3592 s.select(selections);
3593 s.clear_pending();
3594 });
3595 }
3596 }
3597
3598 fn select_columns(
3599 &mut self,
3600 head: DisplayPoint,
3601 goal_column: u32,
3602 display_map: &DisplaySnapshot,
3603 window: &mut Window,
3604 cx: &mut Context<Self>,
3605 ) {
3606 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3607 return;
3608 };
3609
3610 let tail = match columnar_state {
3611 ColumnarSelectionState::FromMouse {
3612 selection_tail,
3613 display_point,
3614 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3615 ColumnarSelectionState::FromSelection { selection_tail } => {
3616 selection_tail.to_display_point(&display_map)
3617 }
3618 };
3619
3620 let start_row = cmp::min(tail.row(), head.row());
3621 let end_row = cmp::max(tail.row(), head.row());
3622 let start_column = cmp::min(tail.column(), goal_column);
3623 let end_column = cmp::max(tail.column(), goal_column);
3624 let reversed = start_column < tail.column();
3625
3626 let selection_ranges = (start_row.0..=end_row.0)
3627 .map(DisplayRow)
3628 .filter_map(|row| {
3629 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3630 || start_column <= display_map.line_len(row))
3631 && !display_map.is_block_line(row)
3632 {
3633 let start = display_map
3634 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3635 .to_point(display_map);
3636 let end = display_map
3637 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3638 .to_point(display_map);
3639 if reversed {
3640 Some(end..start)
3641 } else {
3642 Some(start..end)
3643 }
3644 } else {
3645 None
3646 }
3647 })
3648 .collect::<Vec<_>>();
3649
3650 let ranges = match columnar_state {
3651 ColumnarSelectionState::FromMouse { .. } => {
3652 let mut non_empty_ranges = selection_ranges
3653 .iter()
3654 .filter(|selection_range| selection_range.start != selection_range.end)
3655 .peekable();
3656 if non_empty_ranges.peek().is_some() {
3657 non_empty_ranges.cloned().collect()
3658 } else {
3659 selection_ranges
3660 }
3661 }
3662 _ => selection_ranges,
3663 };
3664
3665 self.change_selections(None, window, cx, |s| {
3666 s.select_ranges(ranges);
3667 });
3668 cx.notify();
3669 }
3670
3671 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3672 self.selections
3673 .all_adjusted(cx)
3674 .iter()
3675 .any(|selection| !selection.is_empty())
3676 }
3677
3678 pub fn has_pending_nonempty_selection(&self) -> bool {
3679 let pending_nonempty_selection = match self.selections.pending_anchor() {
3680 Some(Selection { start, end, .. }) => start != end,
3681 None => false,
3682 };
3683
3684 pending_nonempty_selection
3685 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3686 }
3687
3688 pub fn has_pending_selection(&self) -> bool {
3689 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3690 }
3691
3692 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3693 self.selection_mark_mode = false;
3694 self.selection_drag_state = SelectionDragState::None;
3695
3696 if self.clear_expanded_diff_hunks(cx) {
3697 cx.notify();
3698 return;
3699 }
3700 if self.dismiss_menus_and_popups(true, window, cx) {
3701 return;
3702 }
3703
3704 if self.mode.is_full()
3705 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3706 {
3707 return;
3708 }
3709
3710 cx.propagate();
3711 }
3712
3713 pub fn dismiss_menus_and_popups(
3714 &mut self,
3715 is_user_requested: bool,
3716 window: &mut Window,
3717 cx: &mut Context<Self>,
3718 ) -> bool {
3719 if self.take_rename(false, window, cx).is_some() {
3720 return true;
3721 }
3722
3723 if hide_hover(self, cx) {
3724 return true;
3725 }
3726
3727 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3728 return true;
3729 }
3730
3731 if self.hide_context_menu(window, cx).is_some() {
3732 return true;
3733 }
3734
3735 if self.mouse_context_menu.take().is_some() {
3736 return true;
3737 }
3738
3739 if is_user_requested && self.discard_inline_completion(true, cx) {
3740 return true;
3741 }
3742
3743 if self.snippet_stack.pop().is_some() {
3744 return true;
3745 }
3746
3747 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3748 self.dismiss_diagnostics(cx);
3749 return true;
3750 }
3751
3752 false
3753 }
3754
3755 fn linked_editing_ranges_for(
3756 &self,
3757 selection: Range<text::Anchor>,
3758 cx: &App,
3759 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3760 if self.linked_edit_ranges.is_empty() {
3761 return None;
3762 }
3763 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3764 selection.end.buffer_id.and_then(|end_buffer_id| {
3765 if selection.start.buffer_id != Some(end_buffer_id) {
3766 return None;
3767 }
3768 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3769 let snapshot = buffer.read(cx).snapshot();
3770 self.linked_edit_ranges
3771 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3772 .map(|ranges| (ranges, snapshot, buffer))
3773 })?;
3774 use text::ToOffset as TO;
3775 // find offset from the start of current range to current cursor position
3776 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3777
3778 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3779 let start_difference = start_offset - start_byte_offset;
3780 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3781 let end_difference = end_offset - start_byte_offset;
3782 // Current range has associated linked ranges.
3783 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3784 for range in linked_ranges.iter() {
3785 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3786 let end_offset = start_offset + end_difference;
3787 let start_offset = start_offset + start_difference;
3788 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3789 continue;
3790 }
3791 if self.selections.disjoint_anchor_ranges().any(|s| {
3792 if s.start.buffer_id != selection.start.buffer_id
3793 || s.end.buffer_id != selection.end.buffer_id
3794 {
3795 return false;
3796 }
3797 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3798 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3799 }) {
3800 continue;
3801 }
3802 let start = buffer_snapshot.anchor_after(start_offset);
3803 let end = buffer_snapshot.anchor_after(end_offset);
3804 linked_edits
3805 .entry(buffer.clone())
3806 .or_default()
3807 .push(start..end);
3808 }
3809 Some(linked_edits)
3810 }
3811
3812 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3813 let text: Arc<str> = text.into();
3814
3815 if self.read_only(cx) {
3816 return;
3817 }
3818
3819 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3820
3821 let selections = self.selections.all_adjusted(cx);
3822 let mut bracket_inserted = false;
3823 let mut edits = Vec::new();
3824 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3825 let mut new_selections = Vec::with_capacity(selections.len());
3826 let mut new_autoclose_regions = Vec::new();
3827 let snapshot = self.buffer.read(cx).read(cx);
3828 let mut clear_linked_edit_ranges = false;
3829
3830 for (selection, autoclose_region) in
3831 self.selections_with_autoclose_regions(selections, &snapshot)
3832 {
3833 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3834 // Determine if the inserted text matches the opening or closing
3835 // bracket of any of this language's bracket pairs.
3836 let mut bracket_pair = None;
3837 let mut is_bracket_pair_start = false;
3838 let mut is_bracket_pair_end = false;
3839 if !text.is_empty() {
3840 let mut bracket_pair_matching_end = None;
3841 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3842 // and they are removing the character that triggered IME popup.
3843 for (pair, enabled) in scope.brackets() {
3844 if !pair.close && !pair.surround {
3845 continue;
3846 }
3847
3848 if enabled && pair.start.ends_with(text.as_ref()) {
3849 let prefix_len = pair.start.len() - text.len();
3850 let preceding_text_matches_prefix = prefix_len == 0
3851 || (selection.start.column >= (prefix_len as u32)
3852 && snapshot.contains_str_at(
3853 Point::new(
3854 selection.start.row,
3855 selection.start.column - (prefix_len as u32),
3856 ),
3857 &pair.start[..prefix_len],
3858 ));
3859 if preceding_text_matches_prefix {
3860 bracket_pair = Some(pair.clone());
3861 is_bracket_pair_start = true;
3862 break;
3863 }
3864 }
3865 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3866 {
3867 // take first bracket pair matching end, but don't break in case a later bracket
3868 // pair matches start
3869 bracket_pair_matching_end = Some(pair.clone());
3870 }
3871 }
3872 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3873 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3874 is_bracket_pair_end = true;
3875 }
3876 }
3877
3878 if let Some(bracket_pair) = bracket_pair {
3879 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3880 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3881 let auto_surround =
3882 self.use_auto_surround && snapshot_settings.use_auto_surround;
3883 if selection.is_empty() {
3884 if is_bracket_pair_start {
3885 // If the inserted text is a suffix of an opening bracket and the
3886 // selection is preceded by the rest of the opening bracket, then
3887 // insert the closing bracket.
3888 let following_text_allows_autoclose = snapshot
3889 .chars_at(selection.start)
3890 .next()
3891 .map_or(true, |c| scope.should_autoclose_before(c));
3892
3893 let preceding_text_allows_autoclose = selection.start.column == 0
3894 || snapshot.reversed_chars_at(selection.start).next().map_or(
3895 true,
3896 |c| {
3897 bracket_pair.start != bracket_pair.end
3898 || !snapshot
3899 .char_classifier_at(selection.start)
3900 .is_word(c)
3901 },
3902 );
3903
3904 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3905 && bracket_pair.start.len() == 1
3906 {
3907 let target = bracket_pair.start.chars().next().unwrap();
3908 let current_line_count = snapshot
3909 .reversed_chars_at(selection.start)
3910 .take_while(|&c| c != '\n')
3911 .filter(|&c| c == target)
3912 .count();
3913 current_line_count % 2 == 1
3914 } else {
3915 false
3916 };
3917
3918 if autoclose
3919 && bracket_pair.close
3920 && following_text_allows_autoclose
3921 && preceding_text_allows_autoclose
3922 && !is_closing_quote
3923 {
3924 let anchor = snapshot.anchor_before(selection.end);
3925 new_selections.push((selection.map(|_| anchor), text.len()));
3926 new_autoclose_regions.push((
3927 anchor,
3928 text.len(),
3929 selection.id,
3930 bracket_pair.clone(),
3931 ));
3932 edits.push((
3933 selection.range(),
3934 format!("{}{}", text, bracket_pair.end).into(),
3935 ));
3936 bracket_inserted = true;
3937 continue;
3938 }
3939 }
3940
3941 if let Some(region) = autoclose_region {
3942 // If the selection is followed by an auto-inserted closing bracket,
3943 // then don't insert that closing bracket again; just move the selection
3944 // past the closing bracket.
3945 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3946 && text.as_ref() == region.pair.end.as_str();
3947 if should_skip {
3948 let anchor = snapshot.anchor_after(selection.end);
3949 new_selections
3950 .push((selection.map(|_| anchor), region.pair.end.len()));
3951 continue;
3952 }
3953 }
3954
3955 let always_treat_brackets_as_autoclosed = snapshot
3956 .language_settings_at(selection.start, cx)
3957 .always_treat_brackets_as_autoclosed;
3958 if always_treat_brackets_as_autoclosed
3959 && is_bracket_pair_end
3960 && snapshot.contains_str_at(selection.end, text.as_ref())
3961 {
3962 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3963 // and the inserted text is a closing bracket and the selection is followed
3964 // by the closing bracket then move the selection past the closing bracket.
3965 let anchor = snapshot.anchor_after(selection.end);
3966 new_selections.push((selection.map(|_| anchor), text.len()));
3967 continue;
3968 }
3969 }
3970 // If an opening bracket is 1 character long and is typed while
3971 // text is selected, then surround that text with the bracket pair.
3972 else if auto_surround
3973 && bracket_pair.surround
3974 && is_bracket_pair_start
3975 && bracket_pair.start.chars().count() == 1
3976 {
3977 edits.push((selection.start..selection.start, text.clone()));
3978 edits.push((
3979 selection.end..selection.end,
3980 bracket_pair.end.as_str().into(),
3981 ));
3982 bracket_inserted = true;
3983 new_selections.push((
3984 Selection {
3985 id: selection.id,
3986 start: snapshot.anchor_after(selection.start),
3987 end: snapshot.anchor_before(selection.end),
3988 reversed: selection.reversed,
3989 goal: selection.goal,
3990 },
3991 0,
3992 ));
3993 continue;
3994 }
3995 }
3996 }
3997
3998 if self.auto_replace_emoji_shortcode
3999 && selection.is_empty()
4000 && text.as_ref().ends_with(':')
4001 {
4002 if let Some(possible_emoji_short_code) =
4003 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4004 {
4005 if !possible_emoji_short_code.is_empty() {
4006 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
4007 let emoji_shortcode_start = Point::new(
4008 selection.start.row,
4009 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4010 );
4011
4012 // Remove shortcode from buffer
4013 edits.push((
4014 emoji_shortcode_start..selection.start,
4015 "".to_string().into(),
4016 ));
4017 new_selections.push((
4018 Selection {
4019 id: selection.id,
4020 start: snapshot.anchor_after(emoji_shortcode_start),
4021 end: snapshot.anchor_before(selection.start),
4022 reversed: selection.reversed,
4023 goal: selection.goal,
4024 },
4025 0,
4026 ));
4027
4028 // Insert emoji
4029 let selection_start_anchor = snapshot.anchor_after(selection.start);
4030 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4031 edits.push((selection.start..selection.end, emoji.to_string().into()));
4032
4033 continue;
4034 }
4035 }
4036 }
4037 }
4038
4039 // If not handling any auto-close operation, then just replace the selected
4040 // text with the given input and move the selection to the end of the
4041 // newly inserted text.
4042 let anchor = snapshot.anchor_after(selection.end);
4043 if !self.linked_edit_ranges.is_empty() {
4044 let start_anchor = snapshot.anchor_before(selection.start);
4045
4046 let is_word_char = text.chars().next().map_or(true, |char| {
4047 let classifier = snapshot
4048 .char_classifier_at(start_anchor.to_offset(&snapshot))
4049 .ignore_punctuation(true);
4050 classifier.is_word(char)
4051 });
4052
4053 if is_word_char {
4054 if let Some(ranges) = self
4055 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4056 {
4057 for (buffer, edits) in ranges {
4058 linked_edits
4059 .entry(buffer.clone())
4060 .or_default()
4061 .extend(edits.into_iter().map(|range| (range, text.clone())));
4062 }
4063 }
4064 } else {
4065 clear_linked_edit_ranges = true;
4066 }
4067 }
4068
4069 new_selections.push((selection.map(|_| anchor), 0));
4070 edits.push((selection.start..selection.end, text.clone()));
4071 }
4072
4073 drop(snapshot);
4074
4075 self.transact(window, cx, |this, window, cx| {
4076 if clear_linked_edit_ranges {
4077 this.linked_edit_ranges.clear();
4078 }
4079 let initial_buffer_versions =
4080 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4081
4082 this.buffer.update(cx, |buffer, cx| {
4083 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4084 });
4085 for (buffer, edits) in linked_edits {
4086 buffer.update(cx, |buffer, cx| {
4087 let snapshot = buffer.snapshot();
4088 let edits = edits
4089 .into_iter()
4090 .map(|(range, text)| {
4091 use text::ToPoint as TP;
4092 let end_point = TP::to_point(&range.end, &snapshot);
4093 let start_point = TP::to_point(&range.start, &snapshot);
4094 (start_point..end_point, text)
4095 })
4096 .sorted_by_key(|(range, _)| range.start);
4097 buffer.edit(edits, None, cx);
4098 })
4099 }
4100 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4101 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4102 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4103 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4104 .zip(new_selection_deltas)
4105 .map(|(selection, delta)| Selection {
4106 id: selection.id,
4107 start: selection.start + delta,
4108 end: selection.end + delta,
4109 reversed: selection.reversed,
4110 goal: SelectionGoal::None,
4111 })
4112 .collect::<Vec<_>>();
4113
4114 let mut i = 0;
4115 for (position, delta, selection_id, pair) in new_autoclose_regions {
4116 let position = position.to_offset(&map.buffer_snapshot) + delta;
4117 let start = map.buffer_snapshot.anchor_before(position);
4118 let end = map.buffer_snapshot.anchor_after(position);
4119 while let Some(existing_state) = this.autoclose_regions.get(i) {
4120 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4121 Ordering::Less => i += 1,
4122 Ordering::Greater => break,
4123 Ordering::Equal => {
4124 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4125 Ordering::Less => i += 1,
4126 Ordering::Equal => break,
4127 Ordering::Greater => break,
4128 }
4129 }
4130 }
4131 }
4132 this.autoclose_regions.insert(
4133 i,
4134 AutocloseRegion {
4135 selection_id,
4136 range: start..end,
4137 pair,
4138 },
4139 );
4140 }
4141
4142 let had_active_inline_completion = this.has_active_inline_completion();
4143 this.change_selections(
4144 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4145 window,
4146 cx,
4147 |s| s.select(new_selections),
4148 );
4149
4150 if !bracket_inserted {
4151 if let Some(on_type_format_task) =
4152 this.trigger_on_type_formatting(text.to_string(), window, cx)
4153 {
4154 on_type_format_task.detach_and_log_err(cx);
4155 }
4156 }
4157
4158 let editor_settings = EditorSettings::get_global(cx);
4159 if bracket_inserted
4160 && (editor_settings.auto_signature_help
4161 || editor_settings.show_signature_help_after_edits)
4162 {
4163 this.show_signature_help(&ShowSignatureHelp, window, cx);
4164 }
4165
4166 let trigger_in_words =
4167 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4168 if this.hard_wrap.is_some() {
4169 let latest: Range<Point> = this.selections.newest(cx).range();
4170 if latest.is_empty()
4171 && this
4172 .buffer()
4173 .read(cx)
4174 .snapshot(cx)
4175 .line_len(MultiBufferRow(latest.start.row))
4176 == latest.start.column
4177 {
4178 this.rewrap_impl(
4179 RewrapOptions {
4180 override_language_settings: true,
4181 preserve_existing_whitespace: true,
4182 },
4183 cx,
4184 )
4185 }
4186 }
4187 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4188 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4189 this.refresh_inline_completion(true, false, window, cx);
4190 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4191 });
4192 }
4193
4194 fn find_possible_emoji_shortcode_at_position(
4195 snapshot: &MultiBufferSnapshot,
4196 position: Point,
4197 ) -> Option<String> {
4198 let mut chars = Vec::new();
4199 let mut found_colon = false;
4200 for char in snapshot.reversed_chars_at(position).take(100) {
4201 // Found a possible emoji shortcode in the middle of the buffer
4202 if found_colon {
4203 if char.is_whitespace() {
4204 chars.reverse();
4205 return Some(chars.iter().collect());
4206 }
4207 // If the previous character is not a whitespace, we are in the middle of a word
4208 // and we only want to complete the shortcode if the word is made up of other emojis
4209 let mut containing_word = String::new();
4210 for ch in snapshot
4211 .reversed_chars_at(position)
4212 .skip(chars.len() + 1)
4213 .take(100)
4214 {
4215 if ch.is_whitespace() {
4216 break;
4217 }
4218 containing_word.push(ch);
4219 }
4220 let containing_word = containing_word.chars().rev().collect::<String>();
4221 if util::word_consists_of_emojis(containing_word.as_str()) {
4222 chars.reverse();
4223 return Some(chars.iter().collect());
4224 }
4225 }
4226
4227 if char.is_whitespace() || !char.is_ascii() {
4228 return None;
4229 }
4230 if char == ':' {
4231 found_colon = true;
4232 } else {
4233 chars.push(char);
4234 }
4235 }
4236 // Found a possible emoji shortcode at the beginning of the buffer
4237 chars.reverse();
4238 Some(chars.iter().collect())
4239 }
4240
4241 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4242 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4243 self.transact(window, cx, |this, window, cx| {
4244 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4245 let selections = this.selections.all::<usize>(cx);
4246 let multi_buffer = this.buffer.read(cx);
4247 let buffer = multi_buffer.snapshot(cx);
4248 selections
4249 .iter()
4250 .map(|selection| {
4251 let start_point = selection.start.to_point(&buffer);
4252 let mut existing_indent =
4253 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4254 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4255 let start = selection.start;
4256 let end = selection.end;
4257 let selection_is_empty = start == end;
4258 let language_scope = buffer.language_scope_at(start);
4259 let (
4260 comment_delimiter,
4261 doc_delimiter,
4262 insert_extra_newline,
4263 indent_on_newline,
4264 indent_on_extra_newline,
4265 ) = if let Some(language) = &language_scope {
4266 let mut insert_extra_newline =
4267 insert_extra_newline_brackets(&buffer, start..end, language)
4268 || insert_extra_newline_tree_sitter(&buffer, start..end);
4269
4270 // Comment extension on newline is allowed only for cursor selections
4271 let comment_delimiter = maybe!({
4272 if !selection_is_empty {
4273 return None;
4274 }
4275
4276 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4277 return None;
4278 }
4279
4280 let delimiters = language.line_comment_prefixes();
4281 let max_len_of_delimiter =
4282 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4283 let (snapshot, range) =
4284 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4285
4286 let num_of_whitespaces = snapshot
4287 .chars_for_range(range.clone())
4288 .take_while(|c| c.is_whitespace())
4289 .count();
4290 let comment_candidate = snapshot
4291 .chars_for_range(range)
4292 .skip(num_of_whitespaces)
4293 .take(max_len_of_delimiter)
4294 .collect::<String>();
4295 let (delimiter, trimmed_len) = delimiters
4296 .iter()
4297 .filter_map(|delimiter| {
4298 let prefix = delimiter.trim_end();
4299 if comment_candidate.starts_with(prefix) {
4300 Some((delimiter, prefix.len()))
4301 } else {
4302 None
4303 }
4304 })
4305 .max_by_key(|(_, len)| *len)?;
4306
4307 let cursor_is_placed_after_comment_marker =
4308 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4309 if cursor_is_placed_after_comment_marker {
4310 Some(delimiter.clone())
4311 } else {
4312 None
4313 }
4314 });
4315
4316 let mut indent_on_newline = IndentSize::spaces(0);
4317 let mut indent_on_extra_newline = IndentSize::spaces(0);
4318
4319 let doc_delimiter = maybe!({
4320 if !selection_is_empty {
4321 return None;
4322 }
4323
4324 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4325 return None;
4326 }
4327
4328 let DocumentationConfig {
4329 start: start_tag,
4330 end: end_tag,
4331 prefix: delimiter,
4332 tab_size: len,
4333 } = language.documentation()?;
4334
4335 let is_within_block_comment = buffer
4336 .language_scope_at(start_point)
4337 .is_some_and(|scope| scope.override_name() == Some("comment"));
4338 if !is_within_block_comment {
4339 return None;
4340 }
4341
4342 let (snapshot, range) =
4343 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4344
4345 let num_of_whitespaces = snapshot
4346 .chars_for_range(range.clone())
4347 .take_while(|c| c.is_whitespace())
4348 .count();
4349
4350 // 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.
4351 let column = start_point.column;
4352 let cursor_is_after_start_tag = {
4353 let start_tag_len = start_tag.len();
4354 let start_tag_line = snapshot
4355 .chars_for_range(range.clone())
4356 .skip(num_of_whitespaces)
4357 .take(start_tag_len)
4358 .collect::<String>();
4359 if start_tag_line.starts_with(start_tag.as_ref()) {
4360 num_of_whitespaces + start_tag_len <= column as usize
4361 } else {
4362 false
4363 }
4364 };
4365
4366 let cursor_is_after_delimiter = {
4367 let delimiter_trim = delimiter.trim_end();
4368 let delimiter_line = snapshot
4369 .chars_for_range(range.clone())
4370 .skip(num_of_whitespaces)
4371 .take(delimiter_trim.len())
4372 .collect::<String>();
4373 if delimiter_line.starts_with(delimiter_trim) {
4374 num_of_whitespaces + delimiter_trim.len() <= column as usize
4375 } else {
4376 false
4377 }
4378 };
4379
4380 let cursor_is_before_end_tag_if_exists = {
4381 let mut char_position = 0u32;
4382 let mut end_tag_offset = None;
4383
4384 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4385 if let Some(byte_pos) = chunk.find(&**end_tag) {
4386 let chars_before_match =
4387 chunk[..byte_pos].chars().count() as u32;
4388 end_tag_offset =
4389 Some(char_position + chars_before_match);
4390 break 'outer;
4391 }
4392 char_position += chunk.chars().count() as u32;
4393 }
4394
4395 if let Some(end_tag_offset) = end_tag_offset {
4396 let cursor_is_before_end_tag = column <= end_tag_offset;
4397 if cursor_is_after_start_tag {
4398 if cursor_is_before_end_tag {
4399 insert_extra_newline = true;
4400 }
4401 let cursor_is_at_start_of_end_tag =
4402 column == end_tag_offset;
4403 if cursor_is_at_start_of_end_tag {
4404 indent_on_extra_newline.len = (*len).into();
4405 }
4406 }
4407 cursor_is_before_end_tag
4408 } else {
4409 true
4410 }
4411 };
4412
4413 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4414 && cursor_is_before_end_tag_if_exists
4415 {
4416 if cursor_is_after_start_tag {
4417 indent_on_newline.len = (*len).into();
4418 }
4419 Some(delimiter.clone())
4420 } else {
4421 None
4422 }
4423 });
4424
4425 (
4426 comment_delimiter,
4427 doc_delimiter,
4428 insert_extra_newline,
4429 indent_on_newline,
4430 indent_on_extra_newline,
4431 )
4432 } else {
4433 (
4434 None,
4435 None,
4436 false,
4437 IndentSize::default(),
4438 IndentSize::default(),
4439 )
4440 };
4441
4442 let prevent_auto_indent = doc_delimiter.is_some();
4443 let delimiter = comment_delimiter.or(doc_delimiter);
4444
4445 let capacity_for_delimiter =
4446 delimiter.as_deref().map(str::len).unwrap_or_default();
4447 let mut new_text = String::with_capacity(
4448 1 + capacity_for_delimiter
4449 + existing_indent.len as usize
4450 + indent_on_newline.len as usize
4451 + indent_on_extra_newline.len as usize,
4452 );
4453 new_text.push('\n');
4454 new_text.extend(existing_indent.chars());
4455 new_text.extend(indent_on_newline.chars());
4456
4457 if let Some(delimiter) = &delimiter {
4458 new_text.push_str(delimiter);
4459 }
4460
4461 if insert_extra_newline {
4462 new_text.push('\n');
4463 new_text.extend(existing_indent.chars());
4464 new_text.extend(indent_on_extra_newline.chars());
4465 }
4466
4467 let anchor = buffer.anchor_after(end);
4468 let new_selection = selection.map(|_| anchor);
4469 (
4470 ((start..end, new_text), prevent_auto_indent),
4471 (insert_extra_newline, new_selection),
4472 )
4473 })
4474 .unzip()
4475 };
4476
4477 let mut auto_indent_edits = Vec::new();
4478 let mut edits = Vec::new();
4479 for (edit, prevent_auto_indent) in edits_with_flags {
4480 if prevent_auto_indent {
4481 edits.push(edit);
4482 } else {
4483 auto_indent_edits.push(edit);
4484 }
4485 }
4486 if !edits.is_empty() {
4487 this.edit(edits, cx);
4488 }
4489 if !auto_indent_edits.is_empty() {
4490 this.edit_with_autoindent(auto_indent_edits, cx);
4491 }
4492
4493 let buffer = this.buffer.read(cx).snapshot(cx);
4494 let new_selections = selection_info
4495 .into_iter()
4496 .map(|(extra_newline_inserted, new_selection)| {
4497 let mut cursor = new_selection.end.to_point(&buffer);
4498 if extra_newline_inserted {
4499 cursor.row -= 1;
4500 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4501 }
4502 new_selection.map(|_| cursor)
4503 })
4504 .collect();
4505
4506 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4507 s.select(new_selections)
4508 });
4509 this.refresh_inline_completion(true, false, window, cx);
4510 });
4511 }
4512
4513 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4514 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4515
4516 let buffer = self.buffer.read(cx);
4517 let snapshot = buffer.snapshot(cx);
4518
4519 let mut edits = Vec::new();
4520 let mut rows = Vec::new();
4521
4522 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4523 let cursor = selection.head();
4524 let row = cursor.row;
4525
4526 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4527
4528 let newline = "\n".to_string();
4529 edits.push((start_of_line..start_of_line, newline));
4530
4531 rows.push(row + rows_inserted as u32);
4532 }
4533
4534 self.transact(window, cx, |editor, window, cx| {
4535 editor.edit(edits, cx);
4536
4537 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4538 let mut index = 0;
4539 s.move_cursors_with(|map, _, _| {
4540 let row = rows[index];
4541 index += 1;
4542
4543 let point = Point::new(row, 0);
4544 let boundary = map.next_line_boundary(point).1;
4545 let clipped = map.clip_point(boundary, Bias::Left);
4546
4547 (clipped, SelectionGoal::None)
4548 });
4549 });
4550
4551 let mut indent_edits = Vec::new();
4552 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4553 for row in rows {
4554 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4555 for (row, indent) in indents {
4556 if indent.len == 0 {
4557 continue;
4558 }
4559
4560 let text = match indent.kind {
4561 IndentKind::Space => " ".repeat(indent.len as usize),
4562 IndentKind::Tab => "\t".repeat(indent.len as usize),
4563 };
4564 let point = Point::new(row.0, 0);
4565 indent_edits.push((point..point, text));
4566 }
4567 }
4568 editor.edit(indent_edits, cx);
4569 });
4570 }
4571
4572 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4574
4575 let buffer = self.buffer.read(cx);
4576 let snapshot = buffer.snapshot(cx);
4577
4578 let mut edits = Vec::new();
4579 let mut rows = Vec::new();
4580 let mut rows_inserted = 0;
4581
4582 for selection in self.selections.all_adjusted(cx) {
4583 let cursor = selection.head();
4584 let row = cursor.row;
4585
4586 let point = Point::new(row + 1, 0);
4587 let start_of_line = snapshot.clip_point(point, Bias::Left);
4588
4589 let newline = "\n".to_string();
4590 edits.push((start_of_line..start_of_line, newline));
4591
4592 rows_inserted += 1;
4593 rows.push(row + rows_inserted);
4594 }
4595
4596 self.transact(window, cx, |editor, window, cx| {
4597 editor.edit(edits, cx);
4598
4599 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4600 let mut index = 0;
4601 s.move_cursors_with(|map, _, _| {
4602 let row = rows[index];
4603 index += 1;
4604
4605 let point = Point::new(row, 0);
4606 let boundary = map.next_line_boundary(point).1;
4607 let clipped = map.clip_point(boundary, Bias::Left);
4608
4609 (clipped, SelectionGoal::None)
4610 });
4611 });
4612
4613 let mut indent_edits = Vec::new();
4614 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4615 for row in rows {
4616 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4617 for (row, indent) in indents {
4618 if indent.len == 0 {
4619 continue;
4620 }
4621
4622 let text = match indent.kind {
4623 IndentKind::Space => " ".repeat(indent.len as usize),
4624 IndentKind::Tab => "\t".repeat(indent.len as usize),
4625 };
4626 let point = Point::new(row.0, 0);
4627 indent_edits.push((point..point, text));
4628 }
4629 }
4630 editor.edit(indent_edits, cx);
4631 });
4632 }
4633
4634 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4635 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4636 original_indent_columns: Vec::new(),
4637 });
4638 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4639 }
4640
4641 fn insert_with_autoindent_mode(
4642 &mut self,
4643 text: &str,
4644 autoindent_mode: Option<AutoindentMode>,
4645 window: &mut Window,
4646 cx: &mut Context<Self>,
4647 ) {
4648 if self.read_only(cx) {
4649 return;
4650 }
4651
4652 let text: Arc<str> = text.into();
4653 self.transact(window, cx, |this, window, cx| {
4654 let old_selections = this.selections.all_adjusted(cx);
4655 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4656 let anchors = {
4657 let snapshot = buffer.read(cx);
4658 old_selections
4659 .iter()
4660 .map(|s| {
4661 let anchor = snapshot.anchor_after(s.head());
4662 s.map(|_| anchor)
4663 })
4664 .collect::<Vec<_>>()
4665 };
4666 buffer.edit(
4667 old_selections
4668 .iter()
4669 .map(|s| (s.start..s.end, text.clone())),
4670 autoindent_mode,
4671 cx,
4672 );
4673 anchors
4674 });
4675
4676 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4677 s.select_anchors(selection_anchors);
4678 });
4679
4680 cx.notify();
4681 });
4682 }
4683
4684 fn trigger_completion_on_input(
4685 &mut self,
4686 text: &str,
4687 trigger_in_words: bool,
4688 window: &mut Window,
4689 cx: &mut Context<Self>,
4690 ) {
4691 let completions_source = self
4692 .context_menu
4693 .borrow()
4694 .as_ref()
4695 .and_then(|menu| match menu {
4696 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4697 CodeContextMenu::CodeActions(_) => None,
4698 });
4699
4700 match completions_source {
4701 Some(CompletionsMenuSource::Words) => {
4702 self.show_word_completions(&ShowWordCompletions, window, cx)
4703 }
4704 Some(CompletionsMenuSource::Normal)
4705 | Some(CompletionsMenuSource::SnippetChoices)
4706 | None
4707 if self.is_completion_trigger(
4708 text,
4709 trigger_in_words,
4710 completions_source.is_some(),
4711 cx,
4712 ) =>
4713 {
4714 self.show_completions(
4715 &ShowCompletions {
4716 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4717 },
4718 window,
4719 cx,
4720 )
4721 }
4722 _ => {
4723 self.hide_context_menu(window, cx);
4724 }
4725 }
4726 }
4727
4728 fn is_completion_trigger(
4729 &self,
4730 text: &str,
4731 trigger_in_words: bool,
4732 menu_is_open: bool,
4733 cx: &mut Context<Self>,
4734 ) -> bool {
4735 let position = self.selections.newest_anchor().head();
4736 let multibuffer = self.buffer.read(cx);
4737 let Some(buffer) = position
4738 .buffer_id
4739 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4740 else {
4741 return false;
4742 };
4743
4744 if let Some(completion_provider) = &self.completion_provider {
4745 completion_provider.is_completion_trigger(
4746 &buffer,
4747 position.text_anchor,
4748 text,
4749 trigger_in_words,
4750 menu_is_open,
4751 cx,
4752 )
4753 } else {
4754 false
4755 }
4756 }
4757
4758 /// If any empty selections is touching the start of its innermost containing autoclose
4759 /// region, expand it to select the brackets.
4760 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4761 let selections = self.selections.all::<usize>(cx);
4762 let buffer = self.buffer.read(cx).read(cx);
4763 let new_selections = self
4764 .selections_with_autoclose_regions(selections, &buffer)
4765 .map(|(mut selection, region)| {
4766 if !selection.is_empty() {
4767 return selection;
4768 }
4769
4770 if let Some(region) = region {
4771 let mut range = region.range.to_offset(&buffer);
4772 if selection.start == range.start && range.start >= region.pair.start.len() {
4773 range.start -= region.pair.start.len();
4774 if buffer.contains_str_at(range.start, ®ion.pair.start)
4775 && buffer.contains_str_at(range.end, ®ion.pair.end)
4776 {
4777 range.end += region.pair.end.len();
4778 selection.start = range.start;
4779 selection.end = range.end;
4780
4781 return selection;
4782 }
4783 }
4784 }
4785
4786 let always_treat_brackets_as_autoclosed = buffer
4787 .language_settings_at(selection.start, cx)
4788 .always_treat_brackets_as_autoclosed;
4789
4790 if !always_treat_brackets_as_autoclosed {
4791 return selection;
4792 }
4793
4794 if let Some(scope) = buffer.language_scope_at(selection.start) {
4795 for (pair, enabled) in scope.brackets() {
4796 if !enabled || !pair.close {
4797 continue;
4798 }
4799
4800 if buffer.contains_str_at(selection.start, &pair.end) {
4801 let pair_start_len = pair.start.len();
4802 if buffer.contains_str_at(
4803 selection.start.saturating_sub(pair_start_len),
4804 &pair.start,
4805 ) {
4806 selection.start -= pair_start_len;
4807 selection.end += pair.end.len();
4808
4809 return selection;
4810 }
4811 }
4812 }
4813 }
4814
4815 selection
4816 })
4817 .collect();
4818
4819 drop(buffer);
4820 self.change_selections(None, window, cx, |selections| {
4821 selections.select(new_selections)
4822 });
4823 }
4824
4825 /// Iterate the given selections, and for each one, find the smallest surrounding
4826 /// autoclose region. This uses the ordering of the selections and the autoclose
4827 /// regions to avoid repeated comparisons.
4828 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4829 &'a self,
4830 selections: impl IntoIterator<Item = Selection<D>>,
4831 buffer: &'a MultiBufferSnapshot,
4832 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4833 let mut i = 0;
4834 let mut regions = self.autoclose_regions.as_slice();
4835 selections.into_iter().map(move |selection| {
4836 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4837
4838 let mut enclosing = None;
4839 while let Some(pair_state) = regions.get(i) {
4840 if pair_state.range.end.to_offset(buffer) < range.start {
4841 regions = ®ions[i + 1..];
4842 i = 0;
4843 } else if pair_state.range.start.to_offset(buffer) > range.end {
4844 break;
4845 } else {
4846 if pair_state.selection_id == selection.id {
4847 enclosing = Some(pair_state);
4848 }
4849 i += 1;
4850 }
4851 }
4852
4853 (selection, enclosing)
4854 })
4855 }
4856
4857 /// Remove any autoclose regions that no longer contain their selection.
4858 fn invalidate_autoclose_regions(
4859 &mut self,
4860 mut selections: &[Selection<Anchor>],
4861 buffer: &MultiBufferSnapshot,
4862 ) {
4863 self.autoclose_regions.retain(|state| {
4864 let mut i = 0;
4865 while let Some(selection) = selections.get(i) {
4866 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4867 selections = &selections[1..];
4868 continue;
4869 }
4870 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4871 break;
4872 }
4873 if selection.id == state.selection_id {
4874 return true;
4875 } else {
4876 i += 1;
4877 }
4878 }
4879 false
4880 });
4881 }
4882
4883 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4884 let offset = position.to_offset(buffer);
4885 let (word_range, kind) = buffer.surrounding_word(offset, true);
4886 if offset > word_range.start && kind == Some(CharKind::Word) {
4887 Some(
4888 buffer
4889 .text_for_range(word_range.start..offset)
4890 .collect::<String>(),
4891 )
4892 } else {
4893 None
4894 }
4895 }
4896
4897 pub fn toggle_inline_values(
4898 &mut self,
4899 _: &ToggleInlineValues,
4900 _: &mut Window,
4901 cx: &mut Context<Self>,
4902 ) {
4903 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4904
4905 self.refresh_inline_values(cx);
4906 }
4907
4908 pub fn toggle_inlay_hints(
4909 &mut self,
4910 _: &ToggleInlayHints,
4911 _: &mut Window,
4912 cx: &mut Context<Self>,
4913 ) {
4914 self.refresh_inlay_hints(
4915 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4916 cx,
4917 );
4918 }
4919
4920 pub fn inlay_hints_enabled(&self) -> bool {
4921 self.inlay_hint_cache.enabled
4922 }
4923
4924 pub fn inline_values_enabled(&self) -> bool {
4925 self.inline_value_cache.enabled
4926 }
4927
4928 #[cfg(any(test, feature = "test-support"))]
4929 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4930 self.display_map
4931 .read(cx)
4932 .current_inlays()
4933 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4934 .cloned()
4935 .collect()
4936 }
4937
4938 #[cfg(any(test, feature = "test-support"))]
4939 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
4940 self.display_map
4941 .read(cx)
4942 .current_inlays()
4943 .cloned()
4944 .collect()
4945 }
4946
4947 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4948 if self.semantics_provider.is_none() || !self.mode.is_full() {
4949 return;
4950 }
4951
4952 let reason_description = reason.description();
4953 let ignore_debounce = matches!(
4954 reason,
4955 InlayHintRefreshReason::SettingsChange(_)
4956 | InlayHintRefreshReason::Toggle(_)
4957 | InlayHintRefreshReason::ExcerptsRemoved(_)
4958 | InlayHintRefreshReason::ModifiersChanged(_)
4959 );
4960 let (invalidate_cache, required_languages) = match reason {
4961 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4962 match self.inlay_hint_cache.modifiers_override(enabled) {
4963 Some(enabled) => {
4964 if enabled {
4965 (InvalidationStrategy::RefreshRequested, None)
4966 } else {
4967 self.splice_inlays(
4968 &self
4969 .visible_inlay_hints(cx)
4970 .iter()
4971 .map(|inlay| inlay.id)
4972 .collect::<Vec<InlayId>>(),
4973 Vec::new(),
4974 cx,
4975 );
4976 return;
4977 }
4978 }
4979 None => return,
4980 }
4981 }
4982 InlayHintRefreshReason::Toggle(enabled) => {
4983 if self.inlay_hint_cache.toggle(enabled) {
4984 if enabled {
4985 (InvalidationStrategy::RefreshRequested, None)
4986 } else {
4987 self.splice_inlays(
4988 &self
4989 .visible_inlay_hints(cx)
4990 .iter()
4991 .map(|inlay| inlay.id)
4992 .collect::<Vec<InlayId>>(),
4993 Vec::new(),
4994 cx,
4995 );
4996 return;
4997 }
4998 } else {
4999 return;
5000 }
5001 }
5002 InlayHintRefreshReason::SettingsChange(new_settings) => {
5003 match self.inlay_hint_cache.update_settings(
5004 &self.buffer,
5005 new_settings,
5006 self.visible_inlay_hints(cx),
5007 cx,
5008 ) {
5009 ControlFlow::Break(Some(InlaySplice {
5010 to_remove,
5011 to_insert,
5012 })) => {
5013 self.splice_inlays(&to_remove, to_insert, cx);
5014 return;
5015 }
5016 ControlFlow::Break(None) => return,
5017 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5018 }
5019 }
5020 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5021 if let Some(InlaySplice {
5022 to_remove,
5023 to_insert,
5024 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5025 {
5026 self.splice_inlays(&to_remove, to_insert, cx);
5027 }
5028 self.display_map.update(cx, |display_map, _| {
5029 display_map.remove_inlays_for_excerpts(&excerpts_removed)
5030 });
5031 return;
5032 }
5033 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5034 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5035 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5036 }
5037 InlayHintRefreshReason::RefreshRequested => {
5038 (InvalidationStrategy::RefreshRequested, None)
5039 }
5040 };
5041
5042 if let Some(InlaySplice {
5043 to_remove,
5044 to_insert,
5045 }) = self.inlay_hint_cache.spawn_hint_refresh(
5046 reason_description,
5047 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
5048 invalidate_cache,
5049 ignore_debounce,
5050 cx,
5051 ) {
5052 self.splice_inlays(&to_remove, to_insert, cx);
5053 }
5054 }
5055
5056 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5057 self.display_map
5058 .read(cx)
5059 .current_inlays()
5060 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5061 .cloned()
5062 .collect()
5063 }
5064
5065 pub fn excerpts_for_inlay_hints_query(
5066 &self,
5067 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5068 cx: &mut Context<Editor>,
5069 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5070 let Some(project) = self.project.as_ref() else {
5071 return HashMap::default();
5072 };
5073 let project = project.read(cx);
5074 let multi_buffer = self.buffer().read(cx);
5075 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5076 let multi_buffer_visible_start = self
5077 .scroll_manager
5078 .anchor()
5079 .anchor
5080 .to_point(&multi_buffer_snapshot);
5081 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5082 multi_buffer_visible_start
5083 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5084 Bias::Left,
5085 );
5086 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5087 multi_buffer_snapshot
5088 .range_to_buffer_ranges(multi_buffer_visible_range)
5089 .into_iter()
5090 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5091 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5092 let buffer_file = project::File::from_dyn(buffer.file())?;
5093 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5094 let worktree_entry = buffer_worktree
5095 .read(cx)
5096 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5097 if worktree_entry.is_ignored {
5098 return None;
5099 }
5100
5101 let language = buffer.language()?;
5102 if let Some(restrict_to_languages) = restrict_to_languages {
5103 if !restrict_to_languages.contains(language) {
5104 return None;
5105 }
5106 }
5107 Some((
5108 excerpt_id,
5109 (
5110 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5111 buffer.version().clone(),
5112 excerpt_visible_range,
5113 ),
5114 ))
5115 })
5116 .collect()
5117 }
5118
5119 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5120 TextLayoutDetails {
5121 text_system: window.text_system().clone(),
5122 editor_style: self.style.clone().unwrap(),
5123 rem_size: window.rem_size(),
5124 scroll_anchor: self.scroll_manager.anchor(),
5125 visible_rows: self.visible_line_count(),
5126 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5127 }
5128 }
5129
5130 pub fn splice_inlays(
5131 &self,
5132 to_remove: &[InlayId],
5133 to_insert: Vec<Inlay>,
5134 cx: &mut Context<Self>,
5135 ) {
5136 self.display_map.update(cx, |display_map, cx| {
5137 display_map.splice_inlays(to_remove, to_insert, cx)
5138 });
5139 cx.notify();
5140 }
5141
5142 fn trigger_on_type_formatting(
5143 &self,
5144 input: String,
5145 window: &mut Window,
5146 cx: &mut Context<Self>,
5147 ) -> Option<Task<Result<()>>> {
5148 if input.len() != 1 {
5149 return None;
5150 }
5151
5152 let project = self.project.as_ref()?;
5153 let position = self.selections.newest_anchor().head();
5154 let (buffer, buffer_position) = self
5155 .buffer
5156 .read(cx)
5157 .text_anchor_for_position(position, cx)?;
5158
5159 let settings = language_settings::language_settings(
5160 buffer
5161 .read(cx)
5162 .language_at(buffer_position)
5163 .map(|l| l.name()),
5164 buffer.read(cx).file(),
5165 cx,
5166 );
5167 if !settings.use_on_type_format {
5168 return None;
5169 }
5170
5171 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5172 // hence we do LSP request & edit on host side only — add formats to host's history.
5173 let push_to_lsp_host_history = true;
5174 // If this is not the host, append its history with new edits.
5175 let push_to_client_history = project.read(cx).is_via_collab();
5176
5177 let on_type_formatting = project.update(cx, |project, cx| {
5178 project.on_type_format(
5179 buffer.clone(),
5180 buffer_position,
5181 input,
5182 push_to_lsp_host_history,
5183 cx,
5184 )
5185 });
5186 Some(cx.spawn_in(window, async move |editor, cx| {
5187 if let Some(transaction) = on_type_formatting.await? {
5188 if push_to_client_history {
5189 buffer
5190 .update(cx, |buffer, _| {
5191 buffer.push_transaction(transaction, Instant::now());
5192 buffer.finalize_last_transaction();
5193 })
5194 .ok();
5195 }
5196 editor.update(cx, |editor, cx| {
5197 editor.refresh_document_highlights(cx);
5198 })?;
5199 }
5200 Ok(())
5201 }))
5202 }
5203
5204 pub fn show_word_completions(
5205 &mut self,
5206 _: &ShowWordCompletions,
5207 window: &mut Window,
5208 cx: &mut Context<Self>,
5209 ) {
5210 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5211 }
5212
5213 pub fn show_completions(
5214 &mut self,
5215 options: &ShowCompletions,
5216 window: &mut Window,
5217 cx: &mut Context<Self>,
5218 ) {
5219 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5220 }
5221
5222 fn open_or_update_completions_menu(
5223 &mut self,
5224 requested_source: Option<CompletionsMenuSource>,
5225 trigger: Option<&str>,
5226 window: &mut Window,
5227 cx: &mut Context<Self>,
5228 ) {
5229 if self.pending_rename.is_some() {
5230 return;
5231 }
5232
5233 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5234
5235 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5236 // inserted and selected. To handle that case, the start of the selection is used so that
5237 // the menu starts with all choices.
5238 let position = self
5239 .selections
5240 .newest_anchor()
5241 .start
5242 .bias_right(&multibuffer_snapshot);
5243 if position.diff_base_anchor.is_some() {
5244 return;
5245 }
5246 let (buffer, buffer_position) =
5247 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5248 output
5249 } else {
5250 return;
5251 };
5252 let buffer_snapshot = buffer.read(cx).snapshot();
5253
5254 let query: Option<Arc<String>> =
5255 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5256
5257 drop(multibuffer_snapshot);
5258
5259 let provider = match requested_source {
5260 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5261 Some(CompletionsMenuSource::Words) => None,
5262 Some(CompletionsMenuSource::SnippetChoices) => {
5263 log::error!("bug: SnippetChoices requested_source is not handled");
5264 None
5265 }
5266 };
5267
5268 let sort_completions = provider
5269 .as_ref()
5270 .map_or(false, |provider| provider.sort_completions());
5271
5272 let filter_completions = provider
5273 .as_ref()
5274 .map_or(true, |provider| provider.filter_completions());
5275
5276 let trigger_kind = match trigger {
5277 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5278 CompletionTriggerKind::TRIGGER_CHARACTER
5279 }
5280 _ => CompletionTriggerKind::INVOKED,
5281 };
5282 let completion_context = CompletionContext {
5283 trigger_character: trigger.and_then(|trigger| {
5284 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5285 Some(String::from(trigger))
5286 } else {
5287 None
5288 }
5289 }),
5290 trigger_kind,
5291 };
5292
5293 // Hide the current completions menu when a trigger char is typed. Without this, cached
5294 // completions from before the trigger char may be reused (#32774). Snippet choices could
5295 // involve trigger chars, so this is skipped in that case.
5296 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty()
5297 {
5298 let menu_is_open = matches!(
5299 self.context_menu.borrow().as_ref(),
5300 Some(CodeContextMenu::Completions(_))
5301 );
5302 if menu_is_open {
5303 self.hide_context_menu(window, cx);
5304 }
5305 }
5306
5307 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5308 if filter_completions {
5309 menu.filter(query.clone(), provider.clone(), window, cx);
5310 }
5311 // When `is_incomplete` is false, no need to re-query completions when the current query
5312 // is a suffix of the initial query.
5313 if !menu.is_incomplete {
5314 // If the new query is a suffix of the old query (typing more characters) and
5315 // the previous result was complete, the existing completions can be filtered.
5316 //
5317 // Note that this is always true for snippet completions.
5318 let query_matches = match (&menu.initial_query, &query) {
5319 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5320 (None, _) => true,
5321 _ => false,
5322 };
5323 if query_matches {
5324 let position_matches = if menu.initial_position == position {
5325 true
5326 } else {
5327 let snapshot = self.buffer.read(cx).read(cx);
5328 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5329 };
5330 if position_matches {
5331 return;
5332 }
5333 }
5334 }
5335 };
5336
5337 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5338 buffer_snapshot.surrounding_word(buffer_position)
5339 {
5340 let word_to_exclude = buffer_snapshot
5341 .text_for_range(word_range.clone())
5342 .collect::<String>();
5343 (
5344 buffer_snapshot.anchor_before(word_range.start)
5345 ..buffer_snapshot.anchor_after(buffer_position),
5346 Some(word_to_exclude),
5347 )
5348 } else {
5349 (buffer_position..buffer_position, None)
5350 };
5351
5352 let language = buffer_snapshot
5353 .language_at(buffer_position)
5354 .map(|language| language.name());
5355
5356 let completion_settings =
5357 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5358
5359 let show_completion_documentation = buffer_snapshot
5360 .settings_at(buffer_position, cx)
5361 .show_completion_documentation;
5362
5363 // The document can be large, so stay in reasonable bounds when searching for words,
5364 // otherwise completion pop-up might be slow to appear.
5365 const WORD_LOOKUP_ROWS: u32 = 5_000;
5366 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5367 let min_word_search = buffer_snapshot.clip_point(
5368 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5369 Bias::Left,
5370 );
5371 let max_word_search = buffer_snapshot.clip_point(
5372 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5373 Bias::Right,
5374 );
5375 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5376 ..buffer_snapshot.point_to_offset(max_word_search);
5377
5378 let skip_digits = query
5379 .as_ref()
5380 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5381
5382 let (mut words, provider_responses) = match &provider {
5383 Some(provider) => {
5384 let provider_responses = provider.completions(
5385 position.excerpt_id,
5386 &buffer,
5387 buffer_position,
5388 completion_context,
5389 window,
5390 cx,
5391 );
5392
5393 let words = match completion_settings.words {
5394 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5395 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5396 .background_spawn(async move {
5397 buffer_snapshot.words_in_range(WordsQuery {
5398 fuzzy_contents: None,
5399 range: word_search_range,
5400 skip_digits,
5401 })
5402 }),
5403 };
5404
5405 (words, provider_responses)
5406 }
5407 None => (
5408 cx.background_spawn(async move {
5409 buffer_snapshot.words_in_range(WordsQuery {
5410 fuzzy_contents: None,
5411 range: word_search_range,
5412 skip_digits,
5413 })
5414 }),
5415 Task::ready(Ok(Vec::new())),
5416 ),
5417 };
5418
5419 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5420
5421 let id = post_inc(&mut self.next_completion_id);
5422 let task = cx.spawn_in(window, async move |editor, cx| {
5423 let Ok(()) = editor.update(cx, |this, _| {
5424 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5425 }) else {
5426 return;
5427 };
5428
5429 // TODO: Ideally completions from different sources would be selectively re-queried, so
5430 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5431 let mut completions = Vec::new();
5432 let mut is_incomplete = false;
5433 if let Some(provider_responses) = provider_responses.await.log_err() {
5434 if !provider_responses.is_empty() {
5435 for response in provider_responses {
5436 completions.extend(response.completions);
5437 is_incomplete = is_incomplete || response.is_incomplete;
5438 }
5439 if completion_settings.words == WordsCompletionMode::Fallback {
5440 words = Task::ready(BTreeMap::default());
5441 }
5442 }
5443 }
5444
5445 let mut words = words.await;
5446 if let Some(word_to_exclude) = &word_to_exclude {
5447 words.remove(word_to_exclude);
5448 }
5449 for lsp_completion in &completions {
5450 words.remove(&lsp_completion.new_text);
5451 }
5452 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5453 replace_range: word_replace_range.clone(),
5454 new_text: word.clone(),
5455 label: CodeLabel::plain(word, None),
5456 icon_path: None,
5457 documentation: None,
5458 source: CompletionSource::BufferWord {
5459 word_range,
5460 resolved: false,
5461 },
5462 insert_text_mode: Some(InsertTextMode::AS_IS),
5463 confirm: None,
5464 }));
5465
5466 let menu = if completions.is_empty() {
5467 None
5468 } else {
5469 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5470 let languages = editor
5471 .workspace
5472 .as_ref()
5473 .and_then(|(workspace, _)| workspace.upgrade())
5474 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5475 let menu = CompletionsMenu::new(
5476 id,
5477 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5478 sort_completions,
5479 show_completion_documentation,
5480 position,
5481 query.clone(),
5482 is_incomplete,
5483 buffer.clone(),
5484 completions.into(),
5485 snippet_sort_order,
5486 languages,
5487 language,
5488 cx,
5489 );
5490
5491 let query = if filter_completions { query } else { None };
5492 let matches_task = if let Some(query) = query {
5493 menu.do_async_filtering(query, cx)
5494 } else {
5495 Task::ready(menu.unfiltered_matches())
5496 };
5497 (menu, matches_task)
5498 }) else {
5499 return;
5500 };
5501
5502 let matches = matches_task.await;
5503
5504 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5505 // Newer menu already set, so exit.
5506 match editor.context_menu.borrow().as_ref() {
5507 Some(CodeContextMenu::Completions(prev_menu)) => {
5508 if prev_menu.id > id {
5509 return;
5510 }
5511 }
5512 _ => {}
5513 };
5514
5515 // Only valid to take prev_menu because it the new menu is immediately set
5516 // below, or the menu is hidden.
5517 match editor.context_menu.borrow_mut().take() {
5518 Some(CodeContextMenu::Completions(prev_menu)) => {
5519 let position_matches =
5520 if prev_menu.initial_position == menu.initial_position {
5521 true
5522 } else {
5523 let snapshot = editor.buffer.read(cx).read(cx);
5524 prev_menu.initial_position.to_offset(&snapshot)
5525 == menu.initial_position.to_offset(&snapshot)
5526 };
5527 if position_matches {
5528 // Preserve markdown cache before `set_filter_results` because it will
5529 // try to populate the documentation cache.
5530 menu.preserve_markdown_cache(prev_menu);
5531 }
5532 }
5533 _ => {}
5534 };
5535
5536 menu.set_filter_results(matches, provider, window, cx);
5537 }) else {
5538 return;
5539 };
5540
5541 menu.visible().then_some(menu)
5542 };
5543
5544 editor
5545 .update_in(cx, |editor, window, cx| {
5546 if editor.focus_handle.is_focused(window) {
5547 if let Some(menu) = menu {
5548 *editor.context_menu.borrow_mut() =
5549 Some(CodeContextMenu::Completions(menu));
5550
5551 crate::hover_popover::hide_hover(editor, cx);
5552 if editor.show_edit_predictions_in_menu() {
5553 editor.update_visible_inline_completion(window, cx);
5554 } else {
5555 editor.discard_inline_completion(false, cx);
5556 }
5557
5558 cx.notify();
5559 return;
5560 }
5561 }
5562
5563 if editor.completion_tasks.len() <= 1 {
5564 // If there are no more completion tasks and the last menu was empty, we should hide it.
5565 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5566 // If it was already hidden and we don't show inline completions in the menu, we should
5567 // also show the inline-completion when available.
5568 if was_hidden && editor.show_edit_predictions_in_menu() {
5569 editor.update_visible_inline_completion(window, cx);
5570 }
5571 }
5572 })
5573 .ok();
5574 });
5575
5576 self.completion_tasks.push((id, task));
5577 }
5578
5579 #[cfg(feature = "test-support")]
5580 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5581 let menu = self.context_menu.borrow();
5582 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5583 let completions = menu.completions.borrow();
5584 Some(completions.to_vec())
5585 } else {
5586 None
5587 }
5588 }
5589
5590 pub fn with_completions_menu_matching_id<R>(
5591 &self,
5592 id: CompletionId,
5593 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5594 ) -> R {
5595 let mut context_menu = self.context_menu.borrow_mut();
5596 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5597 return f(None);
5598 };
5599 if completions_menu.id != id {
5600 return f(None);
5601 }
5602 f(Some(completions_menu))
5603 }
5604
5605 pub fn confirm_completion(
5606 &mut self,
5607 action: &ConfirmCompletion,
5608 window: &mut Window,
5609 cx: &mut Context<Self>,
5610 ) -> Option<Task<Result<()>>> {
5611 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5612 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5613 }
5614
5615 pub fn confirm_completion_insert(
5616 &mut self,
5617 _: &ConfirmCompletionInsert,
5618 window: &mut Window,
5619 cx: &mut Context<Self>,
5620 ) -> Option<Task<Result<()>>> {
5621 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5622 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5623 }
5624
5625 pub fn confirm_completion_replace(
5626 &mut self,
5627 _: &ConfirmCompletionReplace,
5628 window: &mut Window,
5629 cx: &mut Context<Self>,
5630 ) -> Option<Task<Result<()>>> {
5631 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5632 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5633 }
5634
5635 pub fn compose_completion(
5636 &mut self,
5637 action: &ComposeCompletion,
5638 window: &mut Window,
5639 cx: &mut Context<Self>,
5640 ) -> Option<Task<Result<()>>> {
5641 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5642 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5643 }
5644
5645 fn do_completion(
5646 &mut self,
5647 item_ix: Option<usize>,
5648 intent: CompletionIntent,
5649 window: &mut Window,
5650 cx: &mut Context<Editor>,
5651 ) -> Option<Task<Result<()>>> {
5652 use language::ToOffset as _;
5653
5654 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5655 else {
5656 return None;
5657 };
5658
5659 let candidate_id = {
5660 let entries = completions_menu.entries.borrow();
5661 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5662 if self.show_edit_predictions_in_menu() {
5663 self.discard_inline_completion(true, cx);
5664 }
5665 mat.candidate_id
5666 };
5667
5668 let completion = completions_menu
5669 .completions
5670 .borrow()
5671 .get(candidate_id)?
5672 .clone();
5673 cx.stop_propagation();
5674
5675 let buffer_handle = completions_menu.buffer.clone();
5676
5677 let CompletionEdit {
5678 new_text,
5679 snippet,
5680 replace_range,
5681 } = process_completion_for_edit(
5682 &completion,
5683 intent,
5684 &buffer_handle,
5685 &completions_menu.initial_position.text_anchor,
5686 cx,
5687 );
5688
5689 let buffer = buffer_handle.read(cx);
5690 let snapshot = self.buffer.read(cx).snapshot(cx);
5691 let newest_anchor = self.selections.newest_anchor();
5692 let replace_range_multibuffer = {
5693 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5694 let multibuffer_anchor = snapshot
5695 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5696 .unwrap()
5697 ..snapshot
5698 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5699 .unwrap();
5700 multibuffer_anchor.start.to_offset(&snapshot)
5701 ..multibuffer_anchor.end.to_offset(&snapshot)
5702 };
5703 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5704 return None;
5705 }
5706
5707 let old_text = buffer
5708 .text_for_range(replace_range.clone())
5709 .collect::<String>();
5710 let lookbehind = newest_anchor
5711 .start
5712 .text_anchor
5713 .to_offset(buffer)
5714 .saturating_sub(replace_range.start);
5715 let lookahead = replace_range
5716 .end
5717 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5718 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5719 let suffix = &old_text[lookbehind.min(old_text.len())..];
5720
5721 let selections = self.selections.all::<usize>(cx);
5722 let mut ranges = Vec::new();
5723 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5724
5725 for selection in &selections {
5726 let range = if selection.id == newest_anchor.id {
5727 replace_range_multibuffer.clone()
5728 } else {
5729 let mut range = selection.range();
5730
5731 // if prefix is present, don't duplicate it
5732 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5733 range.start = range.start.saturating_sub(lookbehind);
5734
5735 // if suffix is also present, mimic the newest cursor and replace it
5736 if selection.id != newest_anchor.id
5737 && snapshot.contains_str_at(range.end, suffix)
5738 {
5739 range.end += lookahead;
5740 }
5741 }
5742 range
5743 };
5744
5745 ranges.push(range.clone());
5746
5747 if !self.linked_edit_ranges.is_empty() {
5748 let start_anchor = snapshot.anchor_before(range.start);
5749 let end_anchor = snapshot.anchor_after(range.end);
5750 if let Some(ranges) = self
5751 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5752 {
5753 for (buffer, edits) in ranges {
5754 linked_edits
5755 .entry(buffer.clone())
5756 .or_default()
5757 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5758 }
5759 }
5760 }
5761 }
5762
5763 let common_prefix_len = old_text
5764 .chars()
5765 .zip(new_text.chars())
5766 .take_while(|(a, b)| a == b)
5767 .map(|(a, _)| a.len_utf8())
5768 .sum::<usize>();
5769
5770 cx.emit(EditorEvent::InputHandled {
5771 utf16_range_to_replace: None,
5772 text: new_text[common_prefix_len..].into(),
5773 });
5774
5775 self.transact(window, cx, |this, window, cx| {
5776 if let Some(mut snippet) = snippet {
5777 snippet.text = new_text.to_string();
5778 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5779 } else {
5780 this.buffer.update(cx, |buffer, cx| {
5781 let auto_indent = match completion.insert_text_mode {
5782 Some(InsertTextMode::AS_IS) => None,
5783 _ => this.autoindent_mode.clone(),
5784 };
5785 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5786 buffer.edit(edits, auto_indent, cx);
5787 });
5788 }
5789 for (buffer, edits) in linked_edits {
5790 buffer.update(cx, |buffer, cx| {
5791 let snapshot = buffer.snapshot();
5792 let edits = edits
5793 .into_iter()
5794 .map(|(range, text)| {
5795 use text::ToPoint as TP;
5796 let end_point = TP::to_point(&range.end, &snapshot);
5797 let start_point = TP::to_point(&range.start, &snapshot);
5798 (start_point..end_point, text)
5799 })
5800 .sorted_by_key(|(range, _)| range.start);
5801 buffer.edit(edits, None, cx);
5802 })
5803 }
5804
5805 this.refresh_inline_completion(true, false, window, cx);
5806 });
5807
5808 let show_new_completions_on_confirm = completion
5809 .confirm
5810 .as_ref()
5811 .map_or(false, |confirm| confirm(intent, window, cx));
5812 if show_new_completions_on_confirm {
5813 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5814 }
5815
5816 let provider = self.completion_provider.as_ref()?;
5817 drop(completion);
5818 let apply_edits = provider.apply_additional_edits_for_completion(
5819 buffer_handle,
5820 completions_menu.completions.clone(),
5821 candidate_id,
5822 true,
5823 cx,
5824 );
5825
5826 let editor_settings = EditorSettings::get_global(cx);
5827 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5828 // After the code completion is finished, users often want to know what signatures are needed.
5829 // so we should automatically call signature_help
5830 self.show_signature_help(&ShowSignatureHelp, window, cx);
5831 }
5832
5833 Some(cx.foreground_executor().spawn(async move {
5834 apply_edits.await?;
5835 Ok(())
5836 }))
5837 }
5838
5839 pub fn toggle_code_actions(
5840 &mut self,
5841 action: &ToggleCodeActions,
5842 window: &mut Window,
5843 cx: &mut Context<Self>,
5844 ) {
5845 let quick_launch = action.quick_launch;
5846 let mut context_menu = self.context_menu.borrow_mut();
5847 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5848 if code_actions.deployed_from == action.deployed_from {
5849 // Toggle if we're selecting the same one
5850 *context_menu = None;
5851 cx.notify();
5852 return;
5853 } else {
5854 // Otherwise, clear it and start a new one
5855 *context_menu = None;
5856 cx.notify();
5857 }
5858 }
5859 drop(context_menu);
5860 let snapshot = self.snapshot(window, cx);
5861 let deployed_from = action.deployed_from.clone();
5862 let action = action.clone();
5863 self.completion_tasks.clear();
5864 self.discard_inline_completion(false, cx);
5865
5866 let multibuffer_point = match &action.deployed_from {
5867 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5868 DisplayPoint::new(*row, 0).to_point(&snapshot)
5869 }
5870 _ => self.selections.newest::<Point>(cx).head(),
5871 };
5872 let Some((buffer, buffer_row)) = snapshot
5873 .buffer_snapshot
5874 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5875 .and_then(|(buffer_snapshot, range)| {
5876 self.buffer()
5877 .read(cx)
5878 .buffer(buffer_snapshot.remote_id())
5879 .map(|buffer| (buffer, range.start.row))
5880 })
5881 else {
5882 return;
5883 };
5884 let buffer_id = buffer.read(cx).remote_id();
5885 let tasks = self
5886 .tasks
5887 .get(&(buffer_id, buffer_row))
5888 .map(|t| Arc::new(t.to_owned()));
5889
5890 if !self.focus_handle.is_focused(window) {
5891 return;
5892 }
5893 let project = self.project.clone();
5894
5895 let code_actions_task = match deployed_from {
5896 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5897 _ => self.code_actions(buffer_row, window, cx),
5898 };
5899
5900 let runnable_task = match deployed_from {
5901 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5902 _ => {
5903 let mut task_context_task = Task::ready(None);
5904 if let Some(tasks) = &tasks {
5905 if let Some(project) = project {
5906 task_context_task =
5907 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5908 }
5909 }
5910
5911 cx.spawn_in(window, {
5912 let buffer = buffer.clone();
5913 async move |editor, cx| {
5914 let task_context = task_context_task.await;
5915
5916 let resolved_tasks =
5917 tasks
5918 .zip(task_context.clone())
5919 .map(|(tasks, task_context)| ResolvedTasks {
5920 templates: tasks.resolve(&task_context).collect(),
5921 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5922 multibuffer_point.row,
5923 tasks.column,
5924 )),
5925 });
5926 let debug_scenarios = editor
5927 .update(cx, |editor, cx| {
5928 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5929 })?
5930 .await;
5931 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5932 }
5933 })
5934 }
5935 };
5936
5937 cx.spawn_in(window, async move |editor, cx| {
5938 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5939 let code_actions = code_actions_task.await;
5940 let spawn_straight_away = quick_launch
5941 && resolved_tasks
5942 .as_ref()
5943 .map_or(false, |tasks| tasks.templates.len() == 1)
5944 && code_actions
5945 .as_ref()
5946 .map_or(true, |actions| actions.is_empty())
5947 && debug_scenarios.is_empty();
5948
5949 editor.update_in(cx, |editor, window, cx| {
5950 crate::hover_popover::hide_hover(editor, cx);
5951 *editor.context_menu.borrow_mut() =
5952 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5953 buffer,
5954 actions: CodeActionContents::new(
5955 resolved_tasks,
5956 code_actions,
5957 debug_scenarios,
5958 task_context.unwrap_or_default(),
5959 ),
5960 selected_item: Default::default(),
5961 scroll_handle: UniformListScrollHandle::default(),
5962 deployed_from,
5963 }));
5964 cx.notify();
5965 if spawn_straight_away {
5966 if let Some(task) = editor.confirm_code_action(
5967 &ConfirmCodeAction { item_ix: Some(0) },
5968 window,
5969 cx,
5970 ) {
5971 return task;
5972 }
5973 }
5974
5975 Task::ready(Ok(()))
5976 })
5977 })
5978 .detach_and_log_err(cx);
5979 }
5980
5981 fn debug_scenarios(
5982 &mut self,
5983 resolved_tasks: &Option<ResolvedTasks>,
5984 buffer: &Entity<Buffer>,
5985 cx: &mut App,
5986 ) -> Task<Vec<task::DebugScenario>> {
5987 maybe!({
5988 let project = self.project.as_ref()?;
5989 let dap_store = project.read(cx).dap_store();
5990 let mut scenarios = vec![];
5991 let resolved_tasks = resolved_tasks.as_ref()?;
5992 let buffer = buffer.read(cx);
5993 let language = buffer.language()?;
5994 let file = buffer.file();
5995 let debug_adapter = language_settings(language.name().into(), file, cx)
5996 .debuggers
5997 .first()
5998 .map(SharedString::from)
5999 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6000
6001 dap_store.update(cx, |dap_store, cx| {
6002 for (_, task) in &resolved_tasks.templates {
6003 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6004 task.original_task().clone(),
6005 debug_adapter.clone().into(),
6006 task.display_label().to_owned().into(),
6007 cx,
6008 );
6009 scenarios.push(maybe_scenario);
6010 }
6011 });
6012 Some(cx.background_spawn(async move {
6013 let scenarios = futures::future::join_all(scenarios)
6014 .await
6015 .into_iter()
6016 .flatten()
6017 .collect::<Vec<_>>();
6018 scenarios
6019 }))
6020 })
6021 .unwrap_or_else(|| Task::ready(vec![]))
6022 }
6023
6024 fn code_actions(
6025 &mut self,
6026 buffer_row: u32,
6027 window: &mut Window,
6028 cx: &mut Context<Self>,
6029 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6030 let mut task = self.code_actions_task.take();
6031 cx.spawn_in(window, async move |editor, cx| {
6032 while let Some(prev_task) = task {
6033 prev_task.await.log_err();
6034 task = editor
6035 .update(cx, |this, _| this.code_actions_task.take())
6036 .ok()?;
6037 }
6038
6039 editor
6040 .update(cx, |editor, cx| {
6041 editor
6042 .available_code_actions
6043 .clone()
6044 .and_then(|(location, code_actions)| {
6045 let snapshot = location.buffer.read(cx).snapshot();
6046 let point_range = location.range.to_point(&snapshot);
6047 let point_range = point_range.start.row..=point_range.end.row;
6048 if point_range.contains(&buffer_row) {
6049 Some(code_actions)
6050 } else {
6051 None
6052 }
6053 })
6054 })
6055 .ok()
6056 .flatten()
6057 })
6058 }
6059
6060 pub fn confirm_code_action(
6061 &mut self,
6062 action: &ConfirmCodeAction,
6063 window: &mut Window,
6064 cx: &mut Context<Self>,
6065 ) -> Option<Task<Result<()>>> {
6066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6067
6068 let actions_menu =
6069 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6070 menu
6071 } else {
6072 return None;
6073 };
6074
6075 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6076 let action = actions_menu.actions.get(action_ix)?;
6077 let title = action.label();
6078 let buffer = actions_menu.buffer;
6079 let workspace = self.workspace()?;
6080
6081 match action {
6082 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6083 workspace.update(cx, |workspace, cx| {
6084 workspace.schedule_resolved_task(
6085 task_source_kind,
6086 resolved_task,
6087 false,
6088 window,
6089 cx,
6090 );
6091
6092 Some(Task::ready(Ok(())))
6093 })
6094 }
6095 CodeActionsItem::CodeAction {
6096 excerpt_id,
6097 action,
6098 provider,
6099 } => {
6100 let apply_code_action =
6101 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6102 let workspace = workspace.downgrade();
6103 Some(cx.spawn_in(window, async move |editor, cx| {
6104 let project_transaction = apply_code_action.await?;
6105 Self::open_project_transaction(
6106 &editor,
6107 workspace,
6108 project_transaction,
6109 title,
6110 cx,
6111 )
6112 .await
6113 }))
6114 }
6115 CodeActionsItem::DebugScenario(scenario) => {
6116 let context = actions_menu.actions.context.clone();
6117
6118 workspace.update(cx, |workspace, cx| {
6119 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6120 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6121 });
6122 Some(Task::ready(Ok(())))
6123 }
6124 }
6125 }
6126
6127 pub async fn open_project_transaction(
6128 this: &WeakEntity<Editor>,
6129 workspace: WeakEntity<Workspace>,
6130 transaction: ProjectTransaction,
6131 title: String,
6132 cx: &mut AsyncWindowContext,
6133 ) -> Result<()> {
6134 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6135 cx.update(|_, cx| {
6136 entries.sort_unstable_by_key(|(buffer, _)| {
6137 buffer.read(cx).file().map(|f| f.path().clone())
6138 });
6139 })?;
6140
6141 // If the project transaction's edits are all contained within this editor, then
6142 // avoid opening a new editor to display them.
6143
6144 if let Some((buffer, transaction)) = entries.first() {
6145 if entries.len() == 1 {
6146 let excerpt = this.update(cx, |editor, cx| {
6147 editor
6148 .buffer()
6149 .read(cx)
6150 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6151 })?;
6152 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6153 if excerpted_buffer == *buffer {
6154 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6155 let excerpt_range = excerpt_range.to_offset(buffer);
6156 buffer
6157 .edited_ranges_for_transaction::<usize>(transaction)
6158 .all(|range| {
6159 excerpt_range.start <= range.start
6160 && excerpt_range.end >= range.end
6161 })
6162 })?;
6163
6164 if all_edits_within_excerpt {
6165 return Ok(());
6166 }
6167 }
6168 }
6169 }
6170 } else {
6171 return Ok(());
6172 }
6173
6174 let mut ranges_to_highlight = Vec::new();
6175 let excerpt_buffer = cx.new(|cx| {
6176 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6177 for (buffer_handle, transaction) in &entries {
6178 let edited_ranges = buffer_handle
6179 .read(cx)
6180 .edited_ranges_for_transaction::<Point>(transaction)
6181 .collect::<Vec<_>>();
6182 let (ranges, _) = multibuffer.set_excerpts_for_path(
6183 PathKey::for_buffer(buffer_handle, cx),
6184 buffer_handle.clone(),
6185 edited_ranges,
6186 DEFAULT_MULTIBUFFER_CONTEXT,
6187 cx,
6188 );
6189
6190 ranges_to_highlight.extend(ranges);
6191 }
6192 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6193 multibuffer
6194 })?;
6195
6196 workspace.update_in(cx, |workspace, window, cx| {
6197 let project = workspace.project().clone();
6198 let editor =
6199 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6200 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6201 editor.update(cx, |editor, cx| {
6202 editor.highlight_background::<Self>(
6203 &ranges_to_highlight,
6204 |theme| theme.colors().editor_highlighted_line_background,
6205 cx,
6206 );
6207 });
6208 })?;
6209
6210 Ok(())
6211 }
6212
6213 pub fn clear_code_action_providers(&mut self) {
6214 self.code_action_providers.clear();
6215 self.available_code_actions.take();
6216 }
6217
6218 pub fn add_code_action_provider(
6219 &mut self,
6220 provider: Rc<dyn CodeActionProvider>,
6221 window: &mut Window,
6222 cx: &mut Context<Self>,
6223 ) {
6224 if self
6225 .code_action_providers
6226 .iter()
6227 .any(|existing_provider| existing_provider.id() == provider.id())
6228 {
6229 return;
6230 }
6231
6232 self.code_action_providers.push(provider);
6233 self.refresh_code_actions(window, cx);
6234 }
6235
6236 pub fn remove_code_action_provider(
6237 &mut self,
6238 id: Arc<str>,
6239 window: &mut Window,
6240 cx: &mut Context<Self>,
6241 ) {
6242 self.code_action_providers
6243 .retain(|provider| provider.id() != id);
6244 self.refresh_code_actions(window, cx);
6245 }
6246
6247 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6248 !self.code_action_providers.is_empty()
6249 && EditorSettings::get_global(cx).toolbar.code_actions
6250 }
6251
6252 pub fn has_available_code_actions(&self) -> bool {
6253 self.available_code_actions
6254 .as_ref()
6255 .is_some_and(|(_, actions)| !actions.is_empty())
6256 }
6257
6258 fn render_inline_code_actions(
6259 &self,
6260 icon_size: ui::IconSize,
6261 display_row: DisplayRow,
6262 is_active: bool,
6263 cx: &mut Context<Self>,
6264 ) -> AnyElement {
6265 let show_tooltip = !self.context_menu_visible();
6266 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6267 .icon_size(icon_size)
6268 .shape(ui::IconButtonShape::Square)
6269 .style(ButtonStyle::Transparent)
6270 .icon_color(ui::Color::Hidden)
6271 .toggle_state(is_active)
6272 .when(show_tooltip, |this| {
6273 this.tooltip({
6274 let focus_handle = self.focus_handle.clone();
6275 move |window, cx| {
6276 Tooltip::for_action_in(
6277 "Toggle Code Actions",
6278 &ToggleCodeActions {
6279 deployed_from: None,
6280 quick_launch: false,
6281 },
6282 &focus_handle,
6283 window,
6284 cx,
6285 )
6286 }
6287 })
6288 })
6289 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6290 window.focus(&editor.focus_handle(cx));
6291 editor.toggle_code_actions(
6292 &crate::actions::ToggleCodeActions {
6293 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6294 display_row,
6295 )),
6296 quick_launch: false,
6297 },
6298 window,
6299 cx,
6300 );
6301 }))
6302 .into_any_element()
6303 }
6304
6305 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6306 &self.context_menu
6307 }
6308
6309 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6310 let newest_selection = self.selections.newest_anchor().clone();
6311 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6312 let buffer = self.buffer.read(cx);
6313 if newest_selection.head().diff_base_anchor.is_some() {
6314 return None;
6315 }
6316 let (start_buffer, start) =
6317 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6318 let (end_buffer, end) =
6319 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6320 if start_buffer != end_buffer {
6321 return None;
6322 }
6323
6324 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6325 cx.background_executor()
6326 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6327 .await;
6328
6329 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6330 let providers = this.code_action_providers.clone();
6331 let tasks = this
6332 .code_action_providers
6333 .iter()
6334 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6335 .collect::<Vec<_>>();
6336 (providers, tasks)
6337 })?;
6338
6339 let mut actions = Vec::new();
6340 for (provider, provider_actions) in
6341 providers.into_iter().zip(future::join_all(tasks).await)
6342 {
6343 if let Some(provider_actions) = provider_actions.log_err() {
6344 actions.extend(provider_actions.into_iter().map(|action| {
6345 AvailableCodeAction {
6346 excerpt_id: newest_selection.start.excerpt_id,
6347 action,
6348 provider: provider.clone(),
6349 }
6350 }));
6351 }
6352 }
6353
6354 this.update(cx, |this, cx| {
6355 this.available_code_actions = if actions.is_empty() {
6356 None
6357 } else {
6358 Some((
6359 Location {
6360 buffer: start_buffer,
6361 range: start..end,
6362 },
6363 actions.into(),
6364 ))
6365 };
6366 cx.notify();
6367 })
6368 }));
6369 None
6370 }
6371
6372 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6373 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6374 self.show_git_blame_inline = false;
6375
6376 self.show_git_blame_inline_delay_task =
6377 Some(cx.spawn_in(window, async move |this, cx| {
6378 cx.background_executor().timer(delay).await;
6379
6380 this.update(cx, |this, cx| {
6381 this.show_git_blame_inline = true;
6382 cx.notify();
6383 })
6384 .log_err();
6385 }));
6386 }
6387 }
6388
6389 fn show_blame_popover(
6390 &mut self,
6391 blame_entry: &BlameEntry,
6392 position: gpui::Point<Pixels>,
6393 cx: &mut Context<Self>,
6394 ) {
6395 if let Some(state) = &mut self.inline_blame_popover {
6396 state.hide_task.take();
6397 } else {
6398 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6399 let blame_entry = blame_entry.clone();
6400 let show_task = cx.spawn(async move |editor, cx| {
6401 cx.background_executor()
6402 .timer(std::time::Duration::from_millis(delay))
6403 .await;
6404 editor
6405 .update(cx, |editor, cx| {
6406 editor.inline_blame_popover_show_task.take();
6407 let Some(blame) = editor.blame.as_ref() else {
6408 return;
6409 };
6410 let blame = blame.read(cx);
6411 let details = blame.details_for_entry(&blame_entry);
6412 let markdown = cx.new(|cx| {
6413 Markdown::new(
6414 details
6415 .as_ref()
6416 .map(|message| message.message.clone())
6417 .unwrap_or_default(),
6418 None,
6419 None,
6420 cx,
6421 )
6422 });
6423 editor.inline_blame_popover = Some(InlineBlamePopover {
6424 position,
6425 hide_task: None,
6426 popover_bounds: None,
6427 popover_state: InlineBlamePopoverState {
6428 scroll_handle: ScrollHandle::new(),
6429 commit_message: details,
6430 markdown,
6431 },
6432 });
6433 cx.notify();
6434 })
6435 .ok();
6436 });
6437 self.inline_blame_popover_show_task = Some(show_task);
6438 }
6439 }
6440
6441 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6442 self.inline_blame_popover_show_task.take();
6443 if let Some(state) = &mut self.inline_blame_popover {
6444 let hide_task = cx.spawn(async move |editor, cx| {
6445 cx.background_executor()
6446 .timer(std::time::Duration::from_millis(100))
6447 .await;
6448 editor
6449 .update(cx, |editor, cx| {
6450 editor.inline_blame_popover.take();
6451 cx.notify();
6452 })
6453 .ok();
6454 });
6455 state.hide_task = Some(hide_task);
6456 }
6457 }
6458
6459 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6460 if self.pending_rename.is_some() {
6461 return None;
6462 }
6463
6464 let provider = self.semantics_provider.clone()?;
6465 let buffer = self.buffer.read(cx);
6466 let newest_selection = self.selections.newest_anchor().clone();
6467 let cursor_position = newest_selection.head();
6468 let (cursor_buffer, cursor_buffer_position) =
6469 buffer.text_anchor_for_position(cursor_position, cx)?;
6470 let (tail_buffer, tail_buffer_position) =
6471 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6472 if cursor_buffer != tail_buffer {
6473 return None;
6474 }
6475
6476 let snapshot = cursor_buffer.read(cx).snapshot();
6477 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6478 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6479 if start_word_range != end_word_range {
6480 self.document_highlights_task.take();
6481 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6482 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6483 return None;
6484 }
6485
6486 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6487 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6488 cx.background_executor()
6489 .timer(Duration::from_millis(debounce))
6490 .await;
6491
6492 let highlights = if let Some(highlights) = cx
6493 .update(|cx| {
6494 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6495 })
6496 .ok()
6497 .flatten()
6498 {
6499 highlights.await.log_err()
6500 } else {
6501 None
6502 };
6503
6504 if let Some(highlights) = highlights {
6505 this.update(cx, |this, cx| {
6506 if this.pending_rename.is_some() {
6507 return;
6508 }
6509
6510 let buffer_id = cursor_position.buffer_id;
6511 let buffer = this.buffer.read(cx);
6512 if !buffer
6513 .text_anchor_for_position(cursor_position, cx)
6514 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6515 {
6516 return;
6517 }
6518
6519 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6520 let mut write_ranges = Vec::new();
6521 let mut read_ranges = Vec::new();
6522 for highlight in highlights {
6523 for (excerpt_id, excerpt_range) in
6524 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6525 {
6526 let start = highlight
6527 .range
6528 .start
6529 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6530 let end = highlight
6531 .range
6532 .end
6533 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6534 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6535 continue;
6536 }
6537
6538 let range = Anchor {
6539 buffer_id,
6540 excerpt_id,
6541 text_anchor: start,
6542 diff_base_anchor: None,
6543 }..Anchor {
6544 buffer_id,
6545 excerpt_id,
6546 text_anchor: end,
6547 diff_base_anchor: None,
6548 };
6549 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6550 write_ranges.push(range);
6551 } else {
6552 read_ranges.push(range);
6553 }
6554 }
6555 }
6556
6557 this.highlight_background::<DocumentHighlightRead>(
6558 &read_ranges,
6559 |theme| theme.colors().editor_document_highlight_read_background,
6560 cx,
6561 );
6562 this.highlight_background::<DocumentHighlightWrite>(
6563 &write_ranges,
6564 |theme| theme.colors().editor_document_highlight_write_background,
6565 cx,
6566 );
6567 cx.notify();
6568 })
6569 .log_err();
6570 }
6571 }));
6572 None
6573 }
6574
6575 fn prepare_highlight_query_from_selection(
6576 &mut self,
6577 cx: &mut Context<Editor>,
6578 ) -> Option<(String, Range<Anchor>)> {
6579 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6580 return None;
6581 }
6582 if !EditorSettings::get_global(cx).selection_highlight {
6583 return None;
6584 }
6585 if self.selections.count() != 1 || self.selections.line_mode {
6586 return None;
6587 }
6588 let selection = self.selections.newest::<Point>(cx);
6589 if selection.is_empty() || selection.start.row != selection.end.row {
6590 return None;
6591 }
6592 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6593 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6594 let query = multi_buffer_snapshot
6595 .text_for_range(selection_anchor_range.clone())
6596 .collect::<String>();
6597 if query.trim().is_empty() {
6598 return None;
6599 }
6600 Some((query, selection_anchor_range))
6601 }
6602
6603 fn update_selection_occurrence_highlights(
6604 &mut self,
6605 query_text: String,
6606 query_range: Range<Anchor>,
6607 multi_buffer_range_to_query: Range<Point>,
6608 use_debounce: bool,
6609 window: &mut Window,
6610 cx: &mut Context<Editor>,
6611 ) -> Task<()> {
6612 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6613 cx.spawn_in(window, async move |editor, cx| {
6614 if use_debounce {
6615 cx.background_executor()
6616 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6617 .await;
6618 }
6619 let match_task = cx.background_spawn(async move {
6620 let buffer_ranges = multi_buffer_snapshot
6621 .range_to_buffer_ranges(multi_buffer_range_to_query)
6622 .into_iter()
6623 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6624 let mut match_ranges = Vec::new();
6625 let Ok(regex) = project::search::SearchQuery::text(
6626 query_text.clone(),
6627 false,
6628 false,
6629 false,
6630 Default::default(),
6631 Default::default(),
6632 false,
6633 None,
6634 ) else {
6635 return Vec::default();
6636 };
6637 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6638 match_ranges.extend(
6639 regex
6640 .search(&buffer_snapshot, Some(search_range.clone()))
6641 .await
6642 .into_iter()
6643 .filter_map(|match_range| {
6644 let match_start = buffer_snapshot
6645 .anchor_after(search_range.start + match_range.start);
6646 let match_end = buffer_snapshot
6647 .anchor_before(search_range.start + match_range.end);
6648 let match_anchor_range = Anchor::range_in_buffer(
6649 excerpt_id,
6650 buffer_snapshot.remote_id(),
6651 match_start..match_end,
6652 );
6653 (match_anchor_range != query_range).then_some(match_anchor_range)
6654 }),
6655 );
6656 }
6657 match_ranges
6658 });
6659 let match_ranges = match_task.await;
6660 editor
6661 .update_in(cx, |editor, _, cx| {
6662 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6663 if !match_ranges.is_empty() {
6664 editor.highlight_background::<SelectedTextHighlight>(
6665 &match_ranges,
6666 |theme| theme.colors().editor_document_highlight_bracket_background,
6667 cx,
6668 )
6669 }
6670 })
6671 .log_err();
6672 })
6673 }
6674
6675 fn refresh_selected_text_highlights(
6676 &mut self,
6677 on_buffer_edit: bool,
6678 window: &mut Window,
6679 cx: &mut Context<Editor>,
6680 ) {
6681 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6682 else {
6683 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6684 self.quick_selection_highlight_task.take();
6685 self.debounced_selection_highlight_task.take();
6686 return;
6687 };
6688 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6689 if on_buffer_edit
6690 || self
6691 .quick_selection_highlight_task
6692 .as_ref()
6693 .map_or(true, |(prev_anchor_range, _)| {
6694 prev_anchor_range != &query_range
6695 })
6696 {
6697 let multi_buffer_visible_start = self
6698 .scroll_manager
6699 .anchor()
6700 .anchor
6701 .to_point(&multi_buffer_snapshot);
6702 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6703 multi_buffer_visible_start
6704 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6705 Bias::Left,
6706 );
6707 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6708 self.quick_selection_highlight_task = Some((
6709 query_range.clone(),
6710 self.update_selection_occurrence_highlights(
6711 query_text.clone(),
6712 query_range.clone(),
6713 multi_buffer_visible_range,
6714 false,
6715 window,
6716 cx,
6717 ),
6718 ));
6719 }
6720 if on_buffer_edit
6721 || self
6722 .debounced_selection_highlight_task
6723 .as_ref()
6724 .map_or(true, |(prev_anchor_range, _)| {
6725 prev_anchor_range != &query_range
6726 })
6727 {
6728 let multi_buffer_start = multi_buffer_snapshot
6729 .anchor_before(0)
6730 .to_point(&multi_buffer_snapshot);
6731 let multi_buffer_end = multi_buffer_snapshot
6732 .anchor_after(multi_buffer_snapshot.len())
6733 .to_point(&multi_buffer_snapshot);
6734 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6735 self.debounced_selection_highlight_task = Some((
6736 query_range.clone(),
6737 self.update_selection_occurrence_highlights(
6738 query_text,
6739 query_range,
6740 multi_buffer_full_range,
6741 true,
6742 window,
6743 cx,
6744 ),
6745 ));
6746 }
6747 }
6748
6749 pub fn refresh_inline_completion(
6750 &mut self,
6751 debounce: bool,
6752 user_requested: bool,
6753 window: &mut Window,
6754 cx: &mut Context<Self>,
6755 ) -> Option<()> {
6756 let provider = self.edit_prediction_provider()?;
6757 let cursor = self.selections.newest_anchor().head();
6758 let (buffer, cursor_buffer_position) =
6759 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6760
6761 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6762 self.discard_inline_completion(false, cx);
6763 return None;
6764 }
6765
6766 if !user_requested
6767 && (!self.should_show_edit_predictions()
6768 || !self.is_focused(window)
6769 || buffer.read(cx).is_empty())
6770 {
6771 self.discard_inline_completion(false, cx);
6772 return None;
6773 }
6774
6775 self.update_visible_inline_completion(window, cx);
6776 provider.refresh(
6777 self.project.clone(),
6778 buffer,
6779 cursor_buffer_position,
6780 debounce,
6781 cx,
6782 );
6783 Some(())
6784 }
6785
6786 fn show_edit_predictions_in_menu(&self) -> bool {
6787 match self.edit_prediction_settings {
6788 EditPredictionSettings::Disabled => false,
6789 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6790 }
6791 }
6792
6793 pub fn edit_predictions_enabled(&self) -> bool {
6794 match self.edit_prediction_settings {
6795 EditPredictionSettings::Disabled => false,
6796 EditPredictionSettings::Enabled { .. } => true,
6797 }
6798 }
6799
6800 fn edit_prediction_requires_modifier(&self) -> bool {
6801 match self.edit_prediction_settings {
6802 EditPredictionSettings::Disabled => false,
6803 EditPredictionSettings::Enabled {
6804 preview_requires_modifier,
6805 ..
6806 } => preview_requires_modifier,
6807 }
6808 }
6809
6810 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6811 if self.edit_prediction_provider.is_none() {
6812 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6813 } else {
6814 let selection = self.selections.newest_anchor();
6815 let cursor = selection.head();
6816
6817 if let Some((buffer, cursor_buffer_position)) =
6818 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6819 {
6820 self.edit_prediction_settings =
6821 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6822 }
6823 }
6824 }
6825
6826 fn edit_prediction_settings_at_position(
6827 &self,
6828 buffer: &Entity<Buffer>,
6829 buffer_position: language::Anchor,
6830 cx: &App,
6831 ) -> EditPredictionSettings {
6832 if !self.mode.is_full()
6833 || !self.show_inline_completions_override.unwrap_or(true)
6834 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6835 {
6836 return EditPredictionSettings::Disabled;
6837 }
6838
6839 let buffer = buffer.read(cx);
6840
6841 let file = buffer.file();
6842
6843 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6844 return EditPredictionSettings::Disabled;
6845 };
6846
6847 let by_provider = matches!(
6848 self.menu_inline_completions_policy,
6849 MenuInlineCompletionsPolicy::ByProvider
6850 );
6851
6852 let show_in_menu = by_provider
6853 && self
6854 .edit_prediction_provider
6855 .as_ref()
6856 .map_or(false, |provider| {
6857 provider.provider.show_completions_in_menu()
6858 });
6859
6860 let preview_requires_modifier =
6861 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6862
6863 EditPredictionSettings::Enabled {
6864 show_in_menu,
6865 preview_requires_modifier,
6866 }
6867 }
6868
6869 fn should_show_edit_predictions(&self) -> bool {
6870 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6871 }
6872
6873 pub fn edit_prediction_preview_is_active(&self) -> bool {
6874 matches!(
6875 self.edit_prediction_preview,
6876 EditPredictionPreview::Active { .. }
6877 )
6878 }
6879
6880 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6881 let cursor = self.selections.newest_anchor().head();
6882 if let Some((buffer, cursor_position)) =
6883 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6884 {
6885 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6886 } else {
6887 false
6888 }
6889 }
6890
6891 pub fn supports_minimap(&self, cx: &App) -> bool {
6892 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6893 }
6894
6895 fn edit_predictions_enabled_in_buffer(
6896 &self,
6897 buffer: &Entity<Buffer>,
6898 buffer_position: language::Anchor,
6899 cx: &App,
6900 ) -> bool {
6901 maybe!({
6902 if self.read_only(cx) {
6903 return Some(false);
6904 }
6905 let provider = self.edit_prediction_provider()?;
6906 if !provider.is_enabled(&buffer, buffer_position, cx) {
6907 return Some(false);
6908 }
6909 let buffer = buffer.read(cx);
6910 let Some(file) = buffer.file() else {
6911 return Some(true);
6912 };
6913 let settings = all_language_settings(Some(file), cx);
6914 Some(settings.edit_predictions_enabled_for_file(file, cx))
6915 })
6916 .unwrap_or(false)
6917 }
6918
6919 fn cycle_inline_completion(
6920 &mut self,
6921 direction: Direction,
6922 window: &mut Window,
6923 cx: &mut Context<Self>,
6924 ) -> Option<()> {
6925 let provider = self.edit_prediction_provider()?;
6926 let cursor = self.selections.newest_anchor().head();
6927 let (buffer, cursor_buffer_position) =
6928 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6929 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6930 return None;
6931 }
6932
6933 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6934 self.update_visible_inline_completion(window, cx);
6935
6936 Some(())
6937 }
6938
6939 pub fn show_inline_completion(
6940 &mut self,
6941 _: &ShowEditPrediction,
6942 window: &mut Window,
6943 cx: &mut Context<Self>,
6944 ) {
6945 if !self.has_active_inline_completion() {
6946 self.refresh_inline_completion(false, true, window, cx);
6947 return;
6948 }
6949
6950 self.update_visible_inline_completion(window, cx);
6951 }
6952
6953 pub fn display_cursor_names(
6954 &mut self,
6955 _: &DisplayCursorNames,
6956 window: &mut Window,
6957 cx: &mut Context<Self>,
6958 ) {
6959 self.show_cursor_names(window, cx);
6960 }
6961
6962 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6963 self.show_cursor_names = true;
6964 cx.notify();
6965 cx.spawn_in(window, async move |this, cx| {
6966 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6967 this.update(cx, |this, cx| {
6968 this.show_cursor_names = false;
6969 cx.notify()
6970 })
6971 .ok()
6972 })
6973 .detach();
6974 }
6975
6976 pub fn next_edit_prediction(
6977 &mut self,
6978 _: &NextEditPrediction,
6979 window: &mut Window,
6980 cx: &mut Context<Self>,
6981 ) {
6982 if self.has_active_inline_completion() {
6983 self.cycle_inline_completion(Direction::Next, window, cx);
6984 } else {
6985 let is_copilot_disabled = self
6986 .refresh_inline_completion(false, true, window, cx)
6987 .is_none();
6988 if is_copilot_disabled {
6989 cx.propagate();
6990 }
6991 }
6992 }
6993
6994 pub fn previous_edit_prediction(
6995 &mut self,
6996 _: &PreviousEditPrediction,
6997 window: &mut Window,
6998 cx: &mut Context<Self>,
6999 ) {
7000 if self.has_active_inline_completion() {
7001 self.cycle_inline_completion(Direction::Prev, window, cx);
7002 } else {
7003 let is_copilot_disabled = self
7004 .refresh_inline_completion(false, true, window, cx)
7005 .is_none();
7006 if is_copilot_disabled {
7007 cx.propagate();
7008 }
7009 }
7010 }
7011
7012 pub fn accept_edit_prediction(
7013 &mut self,
7014 _: &AcceptEditPrediction,
7015 window: &mut Window,
7016 cx: &mut Context<Self>,
7017 ) {
7018 if self.show_edit_predictions_in_menu() {
7019 self.hide_context_menu(window, cx);
7020 }
7021
7022 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7023 return;
7024 };
7025
7026 self.report_inline_completion_event(
7027 active_inline_completion.completion_id.clone(),
7028 true,
7029 cx,
7030 );
7031
7032 match &active_inline_completion.completion {
7033 InlineCompletion::Move { target, .. } => {
7034 let target = *target;
7035
7036 if let Some(position_map) = &self.last_position_map {
7037 if position_map
7038 .visible_row_range
7039 .contains(&target.to_display_point(&position_map.snapshot).row())
7040 || !self.edit_prediction_requires_modifier()
7041 {
7042 self.unfold_ranges(&[target..target], true, false, cx);
7043 // Note that this is also done in vim's handler of the Tab action.
7044 self.change_selections(
7045 Some(Autoscroll::newest()),
7046 window,
7047 cx,
7048 |selections| {
7049 selections.select_anchor_ranges([target..target]);
7050 },
7051 );
7052 self.clear_row_highlights::<EditPredictionPreview>();
7053
7054 self.edit_prediction_preview
7055 .set_previous_scroll_position(None);
7056 } else {
7057 self.edit_prediction_preview
7058 .set_previous_scroll_position(Some(
7059 position_map.snapshot.scroll_anchor,
7060 ));
7061
7062 self.highlight_rows::<EditPredictionPreview>(
7063 target..target,
7064 cx.theme().colors().editor_highlighted_line_background,
7065 RowHighlightOptions {
7066 autoscroll: true,
7067 ..Default::default()
7068 },
7069 cx,
7070 );
7071 self.request_autoscroll(Autoscroll::fit(), cx);
7072 }
7073 }
7074 }
7075 InlineCompletion::Edit { edits, .. } => {
7076 if let Some(provider) = self.edit_prediction_provider() {
7077 provider.accept(cx);
7078 }
7079
7080 // Store the transaction ID and selections before applying the edit
7081 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7082
7083 let snapshot = self.buffer.read(cx).snapshot(cx);
7084 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7085
7086 self.buffer.update(cx, |buffer, cx| {
7087 buffer.edit(edits.iter().cloned(), None, cx)
7088 });
7089
7090 self.change_selections(None, window, cx, |s| {
7091 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7092 });
7093
7094 let selections = self.selections.disjoint_anchors();
7095 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7096 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7097 if has_new_transaction {
7098 self.selection_history
7099 .insert_transaction(transaction_id_now, selections);
7100 }
7101 }
7102
7103 self.update_visible_inline_completion(window, cx);
7104 if self.active_inline_completion.is_none() {
7105 self.refresh_inline_completion(true, true, window, cx);
7106 }
7107
7108 cx.notify();
7109 }
7110 }
7111
7112 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7113 }
7114
7115 pub fn accept_partial_inline_completion(
7116 &mut self,
7117 _: &AcceptPartialEditPrediction,
7118 window: &mut Window,
7119 cx: &mut Context<Self>,
7120 ) {
7121 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7122 return;
7123 };
7124 if self.selections.count() != 1 {
7125 return;
7126 }
7127
7128 self.report_inline_completion_event(
7129 active_inline_completion.completion_id.clone(),
7130 true,
7131 cx,
7132 );
7133
7134 match &active_inline_completion.completion {
7135 InlineCompletion::Move { target, .. } => {
7136 let target = *target;
7137 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7138 selections.select_anchor_ranges([target..target]);
7139 });
7140 }
7141 InlineCompletion::Edit { edits, .. } => {
7142 // Find an insertion that starts at the cursor position.
7143 let snapshot = self.buffer.read(cx).snapshot(cx);
7144 let cursor_offset = self.selections.newest::<usize>(cx).head();
7145 let insertion = edits.iter().find_map(|(range, text)| {
7146 let range = range.to_offset(&snapshot);
7147 if range.is_empty() && range.start == cursor_offset {
7148 Some(text)
7149 } else {
7150 None
7151 }
7152 });
7153
7154 if let Some(text) = insertion {
7155 let mut partial_completion = text
7156 .chars()
7157 .by_ref()
7158 .take_while(|c| c.is_alphabetic())
7159 .collect::<String>();
7160 if partial_completion.is_empty() {
7161 partial_completion = text
7162 .chars()
7163 .by_ref()
7164 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7165 .collect::<String>();
7166 }
7167
7168 cx.emit(EditorEvent::InputHandled {
7169 utf16_range_to_replace: None,
7170 text: partial_completion.clone().into(),
7171 });
7172
7173 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7174
7175 self.refresh_inline_completion(true, true, window, cx);
7176 cx.notify();
7177 } else {
7178 self.accept_edit_prediction(&Default::default(), window, cx);
7179 }
7180 }
7181 }
7182 }
7183
7184 fn discard_inline_completion(
7185 &mut self,
7186 should_report_inline_completion_event: bool,
7187 cx: &mut Context<Self>,
7188 ) -> bool {
7189 if should_report_inline_completion_event {
7190 let completion_id = self
7191 .active_inline_completion
7192 .as_ref()
7193 .and_then(|active_completion| active_completion.completion_id.clone());
7194
7195 self.report_inline_completion_event(completion_id, false, cx);
7196 }
7197
7198 if let Some(provider) = self.edit_prediction_provider() {
7199 provider.discard(cx);
7200 }
7201
7202 self.take_active_inline_completion(cx)
7203 }
7204
7205 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7206 let Some(provider) = self.edit_prediction_provider() else {
7207 return;
7208 };
7209
7210 let Some((_, buffer, _)) = self
7211 .buffer
7212 .read(cx)
7213 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7214 else {
7215 return;
7216 };
7217
7218 let extension = buffer
7219 .read(cx)
7220 .file()
7221 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7222
7223 let event_type = match accepted {
7224 true => "Edit Prediction Accepted",
7225 false => "Edit Prediction Discarded",
7226 };
7227 telemetry::event!(
7228 event_type,
7229 provider = provider.name(),
7230 prediction_id = id,
7231 suggestion_accepted = accepted,
7232 file_extension = extension,
7233 );
7234 }
7235
7236 pub fn has_active_inline_completion(&self) -> bool {
7237 self.active_inline_completion.is_some()
7238 }
7239
7240 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7241 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7242 return false;
7243 };
7244
7245 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7246 self.clear_highlights::<InlineCompletionHighlight>(cx);
7247 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7248 true
7249 }
7250
7251 /// Returns true when we're displaying the edit prediction popover below the cursor
7252 /// like we are not previewing and the LSP autocomplete menu is visible
7253 /// or we are in `when_holding_modifier` mode.
7254 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7255 if self.edit_prediction_preview_is_active()
7256 || !self.show_edit_predictions_in_menu()
7257 || !self.edit_predictions_enabled()
7258 {
7259 return false;
7260 }
7261
7262 if self.has_visible_completions_menu() {
7263 return true;
7264 }
7265
7266 has_completion && self.edit_prediction_requires_modifier()
7267 }
7268
7269 fn handle_modifiers_changed(
7270 &mut self,
7271 modifiers: Modifiers,
7272 position_map: &PositionMap,
7273 window: &mut Window,
7274 cx: &mut Context<Self>,
7275 ) {
7276 if self.show_edit_predictions_in_menu() {
7277 self.update_edit_prediction_preview(&modifiers, window, cx);
7278 }
7279
7280 self.update_selection_mode(&modifiers, position_map, window, cx);
7281
7282 let mouse_position = window.mouse_position();
7283 if !position_map.text_hitbox.is_hovered(window) {
7284 return;
7285 }
7286
7287 self.update_hovered_link(
7288 position_map.point_for_position(mouse_position),
7289 &position_map.snapshot,
7290 modifiers,
7291 window,
7292 cx,
7293 )
7294 }
7295
7296 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7297 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7298 if invert {
7299 match multi_cursor_setting {
7300 MultiCursorModifier::Alt => modifiers.alt,
7301 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7302 }
7303 } else {
7304 match multi_cursor_setting {
7305 MultiCursorModifier::Alt => modifiers.secondary(),
7306 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7307 }
7308 }
7309 }
7310
7311 fn columnar_selection_mode(
7312 modifiers: &Modifiers,
7313 cx: &mut Context<Self>,
7314 ) -> Option<ColumnarMode> {
7315 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7316 if Self::multi_cursor_modifier(false, modifiers, cx) {
7317 Some(ColumnarMode::FromMouse)
7318 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7319 Some(ColumnarMode::FromSelection)
7320 } else {
7321 None
7322 }
7323 } else {
7324 None
7325 }
7326 }
7327
7328 fn update_selection_mode(
7329 &mut self,
7330 modifiers: &Modifiers,
7331 position_map: &PositionMap,
7332 window: &mut Window,
7333 cx: &mut Context<Self>,
7334 ) {
7335 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7336 return;
7337 };
7338 if self.selections.pending.is_none() {
7339 return;
7340 }
7341
7342 let mouse_position = window.mouse_position();
7343 let point_for_position = position_map.point_for_position(mouse_position);
7344 let position = point_for_position.previous_valid;
7345
7346 self.select(
7347 SelectPhase::BeginColumnar {
7348 position,
7349 reset: false,
7350 mode,
7351 goal_column: point_for_position.exact_unclipped.column(),
7352 },
7353 window,
7354 cx,
7355 );
7356 }
7357
7358 fn update_edit_prediction_preview(
7359 &mut self,
7360 modifiers: &Modifiers,
7361 window: &mut Window,
7362 cx: &mut Context<Self>,
7363 ) {
7364 let mut modifiers_held = false;
7365 if let Some(accept_keystroke) = self
7366 .accept_edit_prediction_keybind(false, window, cx)
7367 .keystroke()
7368 {
7369 modifiers_held = modifiers_held
7370 || (&accept_keystroke.modifiers == modifiers
7371 && accept_keystroke.modifiers.modified());
7372 };
7373 if let Some(accept_partial_keystroke) = self
7374 .accept_edit_prediction_keybind(true, window, cx)
7375 .keystroke()
7376 {
7377 modifiers_held = modifiers_held
7378 || (&accept_partial_keystroke.modifiers == modifiers
7379 && accept_partial_keystroke.modifiers.modified());
7380 }
7381
7382 if modifiers_held {
7383 if matches!(
7384 self.edit_prediction_preview,
7385 EditPredictionPreview::Inactive { .. }
7386 ) {
7387 self.edit_prediction_preview = EditPredictionPreview::Active {
7388 previous_scroll_position: None,
7389 since: Instant::now(),
7390 };
7391
7392 self.update_visible_inline_completion(window, cx);
7393 cx.notify();
7394 }
7395 } else if let EditPredictionPreview::Active {
7396 previous_scroll_position,
7397 since,
7398 } = self.edit_prediction_preview
7399 {
7400 if let (Some(previous_scroll_position), Some(position_map)) =
7401 (previous_scroll_position, self.last_position_map.as_ref())
7402 {
7403 self.set_scroll_position(
7404 previous_scroll_position
7405 .scroll_position(&position_map.snapshot.display_snapshot),
7406 window,
7407 cx,
7408 );
7409 }
7410
7411 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7412 released_too_fast: since.elapsed() < Duration::from_millis(200),
7413 };
7414 self.clear_row_highlights::<EditPredictionPreview>();
7415 self.update_visible_inline_completion(window, cx);
7416 cx.notify();
7417 }
7418 }
7419
7420 fn update_visible_inline_completion(
7421 &mut self,
7422 _window: &mut Window,
7423 cx: &mut Context<Self>,
7424 ) -> Option<()> {
7425 let selection = self.selections.newest_anchor();
7426 let cursor = selection.head();
7427 let multibuffer = self.buffer.read(cx).snapshot(cx);
7428 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7429 let excerpt_id = cursor.excerpt_id;
7430
7431 let show_in_menu = self.show_edit_predictions_in_menu();
7432 let completions_menu_has_precedence = !show_in_menu
7433 && (self.context_menu.borrow().is_some()
7434 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7435
7436 if completions_menu_has_precedence
7437 || !offset_selection.is_empty()
7438 || self
7439 .active_inline_completion
7440 .as_ref()
7441 .map_or(false, |completion| {
7442 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7443 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7444 !invalidation_range.contains(&offset_selection.head())
7445 })
7446 {
7447 self.discard_inline_completion(false, cx);
7448 return None;
7449 }
7450
7451 self.take_active_inline_completion(cx);
7452 let Some(provider) = self.edit_prediction_provider() else {
7453 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7454 return None;
7455 };
7456
7457 let (buffer, cursor_buffer_position) =
7458 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7459
7460 self.edit_prediction_settings =
7461 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7462
7463 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7464
7465 if self.edit_prediction_indent_conflict {
7466 let cursor_point = cursor.to_point(&multibuffer);
7467
7468 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7469
7470 if let Some((_, indent)) = indents.iter().next() {
7471 if indent.len == cursor_point.column {
7472 self.edit_prediction_indent_conflict = false;
7473 }
7474 }
7475 }
7476
7477 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7478 let edits = inline_completion
7479 .edits
7480 .into_iter()
7481 .flat_map(|(range, new_text)| {
7482 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7483 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7484 Some((start..end, new_text))
7485 })
7486 .collect::<Vec<_>>();
7487 if edits.is_empty() {
7488 return None;
7489 }
7490
7491 let first_edit_start = edits.first().unwrap().0.start;
7492 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7493 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7494
7495 let last_edit_end = edits.last().unwrap().0.end;
7496 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7497 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7498
7499 let cursor_row = cursor.to_point(&multibuffer).row;
7500
7501 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7502
7503 let mut inlay_ids = Vec::new();
7504 let invalidation_row_range;
7505 let move_invalidation_row_range = if cursor_row < edit_start_row {
7506 Some(cursor_row..edit_end_row)
7507 } else if cursor_row > edit_end_row {
7508 Some(edit_start_row..cursor_row)
7509 } else {
7510 None
7511 };
7512 let is_move =
7513 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7514 let completion = if is_move {
7515 invalidation_row_range =
7516 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7517 let target = first_edit_start;
7518 InlineCompletion::Move { target, snapshot }
7519 } else {
7520 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7521 && !self.inline_completions_hidden_for_vim_mode;
7522
7523 if show_completions_in_buffer {
7524 if edits
7525 .iter()
7526 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7527 {
7528 let mut inlays = Vec::new();
7529 for (range, new_text) in &edits {
7530 let inlay = Inlay::inline_completion(
7531 post_inc(&mut self.next_inlay_id),
7532 range.start,
7533 new_text.as_str(),
7534 );
7535 inlay_ids.push(inlay.id);
7536 inlays.push(inlay);
7537 }
7538
7539 self.splice_inlays(&[], inlays, cx);
7540 } else {
7541 let background_color = cx.theme().status().deleted_background;
7542 self.highlight_text::<InlineCompletionHighlight>(
7543 edits.iter().map(|(range, _)| range.clone()).collect(),
7544 HighlightStyle {
7545 background_color: Some(background_color),
7546 ..Default::default()
7547 },
7548 cx,
7549 );
7550 }
7551 }
7552
7553 invalidation_row_range = edit_start_row..edit_end_row;
7554
7555 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7556 if provider.show_tab_accept_marker() {
7557 EditDisplayMode::TabAccept
7558 } else {
7559 EditDisplayMode::Inline
7560 }
7561 } else {
7562 EditDisplayMode::DiffPopover
7563 };
7564
7565 InlineCompletion::Edit {
7566 edits,
7567 edit_preview: inline_completion.edit_preview,
7568 display_mode,
7569 snapshot,
7570 }
7571 };
7572
7573 let invalidation_range = multibuffer
7574 .anchor_before(Point::new(invalidation_row_range.start, 0))
7575 ..multibuffer.anchor_after(Point::new(
7576 invalidation_row_range.end,
7577 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7578 ));
7579
7580 self.stale_inline_completion_in_menu = None;
7581 self.active_inline_completion = Some(InlineCompletionState {
7582 inlay_ids,
7583 completion,
7584 completion_id: inline_completion.id,
7585 invalidation_range,
7586 });
7587
7588 cx.notify();
7589
7590 Some(())
7591 }
7592
7593 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7594 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7595 }
7596
7597 fn clear_tasks(&mut self) {
7598 self.tasks.clear()
7599 }
7600
7601 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7602 if self.tasks.insert(key, value).is_some() {
7603 // This case should hopefully be rare, but just in case...
7604 log::error!(
7605 "multiple different run targets found on a single line, only the last target will be rendered"
7606 )
7607 }
7608 }
7609
7610 /// Get all display points of breakpoints that will be rendered within editor
7611 ///
7612 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7613 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7614 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7615 fn active_breakpoints(
7616 &self,
7617 range: Range<DisplayRow>,
7618 window: &mut Window,
7619 cx: &mut Context<Self>,
7620 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7621 let mut breakpoint_display_points = HashMap::default();
7622
7623 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7624 return breakpoint_display_points;
7625 };
7626
7627 let snapshot = self.snapshot(window, cx);
7628
7629 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7630 let Some(project) = self.project.as_ref() else {
7631 return breakpoint_display_points;
7632 };
7633
7634 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7635 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7636
7637 for (buffer_snapshot, range, excerpt_id) in
7638 multi_buffer_snapshot.range_to_buffer_ranges(range)
7639 {
7640 let Some(buffer) = project
7641 .read(cx)
7642 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7643 else {
7644 continue;
7645 };
7646 let breakpoints = breakpoint_store.read(cx).breakpoints(
7647 &buffer,
7648 Some(
7649 buffer_snapshot.anchor_before(range.start)
7650 ..buffer_snapshot.anchor_after(range.end),
7651 ),
7652 buffer_snapshot,
7653 cx,
7654 );
7655 for (breakpoint, state) in breakpoints {
7656 let multi_buffer_anchor =
7657 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7658 let position = multi_buffer_anchor
7659 .to_point(&multi_buffer_snapshot)
7660 .to_display_point(&snapshot);
7661
7662 breakpoint_display_points.insert(
7663 position.row(),
7664 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7665 );
7666 }
7667 }
7668
7669 breakpoint_display_points
7670 }
7671
7672 fn breakpoint_context_menu(
7673 &self,
7674 anchor: Anchor,
7675 window: &mut Window,
7676 cx: &mut Context<Self>,
7677 ) -> Entity<ui::ContextMenu> {
7678 let weak_editor = cx.weak_entity();
7679 let focus_handle = self.focus_handle(cx);
7680
7681 let row = self
7682 .buffer
7683 .read(cx)
7684 .snapshot(cx)
7685 .summary_for_anchor::<Point>(&anchor)
7686 .row;
7687
7688 let breakpoint = self
7689 .breakpoint_at_row(row, window, cx)
7690 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7691
7692 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7693 "Edit Log Breakpoint"
7694 } else {
7695 "Set Log Breakpoint"
7696 };
7697
7698 let condition_breakpoint_msg = if breakpoint
7699 .as_ref()
7700 .is_some_and(|bp| bp.1.condition.is_some())
7701 {
7702 "Edit Condition Breakpoint"
7703 } else {
7704 "Set Condition Breakpoint"
7705 };
7706
7707 let hit_condition_breakpoint_msg = if breakpoint
7708 .as_ref()
7709 .is_some_and(|bp| bp.1.hit_condition.is_some())
7710 {
7711 "Edit Hit Condition Breakpoint"
7712 } else {
7713 "Set Hit Condition Breakpoint"
7714 };
7715
7716 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7717 "Unset Breakpoint"
7718 } else {
7719 "Set Breakpoint"
7720 };
7721
7722 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7723
7724 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7725 BreakpointState::Enabled => Some("Disable"),
7726 BreakpointState::Disabled => Some("Enable"),
7727 });
7728
7729 let (anchor, breakpoint) =
7730 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7731
7732 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7733 menu.on_blur_subscription(Subscription::new(|| {}))
7734 .context(focus_handle)
7735 .when(run_to_cursor, |this| {
7736 let weak_editor = weak_editor.clone();
7737 this.entry("Run to cursor", None, move |window, cx| {
7738 weak_editor
7739 .update(cx, |editor, cx| {
7740 editor.change_selections(None, window, cx, |s| {
7741 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7742 });
7743 })
7744 .ok();
7745
7746 window.dispatch_action(Box::new(RunToCursor), cx);
7747 })
7748 .separator()
7749 })
7750 .when_some(toggle_state_msg, |this, msg| {
7751 this.entry(msg, None, {
7752 let weak_editor = weak_editor.clone();
7753 let breakpoint = breakpoint.clone();
7754 move |_window, cx| {
7755 weak_editor
7756 .update(cx, |this, cx| {
7757 this.edit_breakpoint_at_anchor(
7758 anchor,
7759 breakpoint.as_ref().clone(),
7760 BreakpointEditAction::InvertState,
7761 cx,
7762 );
7763 })
7764 .log_err();
7765 }
7766 })
7767 })
7768 .entry(set_breakpoint_msg, None, {
7769 let weak_editor = weak_editor.clone();
7770 let breakpoint = breakpoint.clone();
7771 move |_window, cx| {
7772 weak_editor
7773 .update(cx, |this, cx| {
7774 this.edit_breakpoint_at_anchor(
7775 anchor,
7776 breakpoint.as_ref().clone(),
7777 BreakpointEditAction::Toggle,
7778 cx,
7779 );
7780 })
7781 .log_err();
7782 }
7783 })
7784 .entry(log_breakpoint_msg, None, {
7785 let breakpoint = breakpoint.clone();
7786 let weak_editor = weak_editor.clone();
7787 move |window, cx| {
7788 weak_editor
7789 .update(cx, |this, cx| {
7790 this.add_edit_breakpoint_block(
7791 anchor,
7792 breakpoint.as_ref(),
7793 BreakpointPromptEditAction::Log,
7794 window,
7795 cx,
7796 );
7797 })
7798 .log_err();
7799 }
7800 })
7801 .entry(condition_breakpoint_msg, None, {
7802 let breakpoint = breakpoint.clone();
7803 let weak_editor = weak_editor.clone();
7804 move |window, cx| {
7805 weak_editor
7806 .update(cx, |this, cx| {
7807 this.add_edit_breakpoint_block(
7808 anchor,
7809 breakpoint.as_ref(),
7810 BreakpointPromptEditAction::Condition,
7811 window,
7812 cx,
7813 );
7814 })
7815 .log_err();
7816 }
7817 })
7818 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7819 weak_editor
7820 .update(cx, |this, cx| {
7821 this.add_edit_breakpoint_block(
7822 anchor,
7823 breakpoint.as_ref(),
7824 BreakpointPromptEditAction::HitCondition,
7825 window,
7826 cx,
7827 );
7828 })
7829 .log_err();
7830 })
7831 })
7832 }
7833
7834 fn render_breakpoint(
7835 &self,
7836 position: Anchor,
7837 row: DisplayRow,
7838 breakpoint: &Breakpoint,
7839 state: Option<BreakpointSessionState>,
7840 cx: &mut Context<Self>,
7841 ) -> IconButton {
7842 let is_rejected = state.is_some_and(|s| !s.verified);
7843 // Is it a breakpoint that shows up when hovering over gutter?
7844 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7845 (false, false),
7846 |PhantomBreakpointIndicator {
7847 is_active,
7848 display_row,
7849 collides_with_existing_breakpoint,
7850 }| {
7851 (
7852 is_active && display_row == row,
7853 collides_with_existing_breakpoint,
7854 )
7855 },
7856 );
7857
7858 let (color, icon) = {
7859 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7860 (false, false) => ui::IconName::DebugBreakpoint,
7861 (true, false) => ui::IconName::DebugLogBreakpoint,
7862 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7863 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7864 };
7865
7866 let color = if is_phantom {
7867 Color::Hint
7868 } else if is_rejected {
7869 Color::Disabled
7870 } else {
7871 Color::Debugger
7872 };
7873
7874 (color, icon)
7875 };
7876
7877 let breakpoint = Arc::from(breakpoint.clone());
7878
7879 let alt_as_text = gpui::Keystroke {
7880 modifiers: Modifiers::secondary_key(),
7881 ..Default::default()
7882 };
7883 let primary_action_text = if breakpoint.is_disabled() {
7884 "Enable breakpoint"
7885 } else if is_phantom && !collides_with_existing {
7886 "Set breakpoint"
7887 } else {
7888 "Unset breakpoint"
7889 };
7890 let focus_handle = self.focus_handle.clone();
7891
7892 let meta = if is_rejected {
7893 SharedString::from("No executable code is associated with this line.")
7894 } else if collides_with_existing && !breakpoint.is_disabled() {
7895 SharedString::from(format!(
7896 "{alt_as_text}-click to disable,\nright-click for more options."
7897 ))
7898 } else {
7899 SharedString::from("Right-click for more options.")
7900 };
7901 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7902 .icon_size(IconSize::XSmall)
7903 .size(ui::ButtonSize::None)
7904 .when(is_rejected, |this| {
7905 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7906 })
7907 .icon_color(color)
7908 .style(ButtonStyle::Transparent)
7909 .on_click(cx.listener({
7910 let breakpoint = breakpoint.clone();
7911
7912 move |editor, event: &ClickEvent, window, cx| {
7913 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7914 BreakpointEditAction::InvertState
7915 } else {
7916 BreakpointEditAction::Toggle
7917 };
7918
7919 window.focus(&editor.focus_handle(cx));
7920 editor.edit_breakpoint_at_anchor(
7921 position,
7922 breakpoint.as_ref().clone(),
7923 edit_action,
7924 cx,
7925 );
7926 }
7927 }))
7928 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7929 editor.set_breakpoint_context_menu(
7930 row,
7931 Some(position),
7932 event.down.position,
7933 window,
7934 cx,
7935 );
7936 }))
7937 .tooltip(move |window, cx| {
7938 Tooltip::with_meta_in(
7939 primary_action_text,
7940 Some(&ToggleBreakpoint),
7941 meta.clone(),
7942 &focus_handle,
7943 window,
7944 cx,
7945 )
7946 })
7947 }
7948
7949 fn build_tasks_context(
7950 project: &Entity<Project>,
7951 buffer: &Entity<Buffer>,
7952 buffer_row: u32,
7953 tasks: &Arc<RunnableTasks>,
7954 cx: &mut Context<Self>,
7955 ) -> Task<Option<task::TaskContext>> {
7956 let position = Point::new(buffer_row, tasks.column);
7957 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7958 let location = Location {
7959 buffer: buffer.clone(),
7960 range: range_start..range_start,
7961 };
7962 // Fill in the environmental variables from the tree-sitter captures
7963 let mut captured_task_variables = TaskVariables::default();
7964 for (capture_name, value) in tasks.extra_variables.clone() {
7965 captured_task_variables.insert(
7966 task::VariableName::Custom(capture_name.into()),
7967 value.clone(),
7968 );
7969 }
7970 project.update(cx, |project, cx| {
7971 project.task_store().update(cx, |task_store, cx| {
7972 task_store.task_context_for_location(captured_task_variables, location, cx)
7973 })
7974 })
7975 }
7976
7977 pub fn spawn_nearest_task(
7978 &mut self,
7979 action: &SpawnNearestTask,
7980 window: &mut Window,
7981 cx: &mut Context<Self>,
7982 ) {
7983 let Some((workspace, _)) = self.workspace.clone() else {
7984 return;
7985 };
7986 let Some(project) = self.project.clone() else {
7987 return;
7988 };
7989
7990 // Try to find a closest, enclosing node using tree-sitter that has a
7991 // task
7992 let Some((buffer, buffer_row, tasks)) = self
7993 .find_enclosing_node_task(cx)
7994 // Or find the task that's closest in row-distance.
7995 .or_else(|| self.find_closest_task(cx))
7996 else {
7997 return;
7998 };
7999
8000 let reveal_strategy = action.reveal;
8001 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8002 cx.spawn_in(window, async move |_, cx| {
8003 let context = task_context.await?;
8004 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8005
8006 let resolved = &mut resolved_task.resolved;
8007 resolved.reveal = reveal_strategy;
8008
8009 workspace
8010 .update_in(cx, |workspace, window, cx| {
8011 workspace.schedule_resolved_task(
8012 task_source_kind,
8013 resolved_task,
8014 false,
8015 window,
8016 cx,
8017 );
8018 })
8019 .ok()
8020 })
8021 .detach();
8022 }
8023
8024 fn find_closest_task(
8025 &mut self,
8026 cx: &mut Context<Self>,
8027 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8028 let cursor_row = self.selections.newest_adjusted(cx).head().row;
8029
8030 let ((buffer_id, row), tasks) = self
8031 .tasks
8032 .iter()
8033 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8034
8035 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8036 let tasks = Arc::new(tasks.to_owned());
8037 Some((buffer, *row, tasks))
8038 }
8039
8040 fn find_enclosing_node_task(
8041 &mut self,
8042 cx: &mut Context<Self>,
8043 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8044 let snapshot = self.buffer.read(cx).snapshot(cx);
8045 let offset = self.selections.newest::<usize>(cx).head();
8046 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8047 let buffer_id = excerpt.buffer().remote_id();
8048
8049 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8050 let mut cursor = layer.node().walk();
8051
8052 while cursor.goto_first_child_for_byte(offset).is_some() {
8053 if cursor.node().end_byte() == offset {
8054 cursor.goto_next_sibling();
8055 }
8056 }
8057
8058 // Ascend to the smallest ancestor that contains the range and has a task.
8059 loop {
8060 let node = cursor.node();
8061 let node_range = node.byte_range();
8062 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8063
8064 // Check if this node contains our offset
8065 if node_range.start <= offset && node_range.end >= offset {
8066 // If it contains offset, check for task
8067 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8068 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8069 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8070 }
8071 }
8072
8073 if !cursor.goto_parent() {
8074 break;
8075 }
8076 }
8077 None
8078 }
8079
8080 fn render_run_indicator(
8081 &self,
8082 _style: &EditorStyle,
8083 is_active: bool,
8084 row: DisplayRow,
8085 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8086 cx: &mut Context<Self>,
8087 ) -> IconButton {
8088 let color = Color::Muted;
8089 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8090
8091 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8092 .shape(ui::IconButtonShape::Square)
8093 .icon_size(IconSize::XSmall)
8094 .icon_color(color)
8095 .toggle_state(is_active)
8096 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8097 let quick_launch = e.down.button == MouseButton::Left;
8098 window.focus(&editor.focus_handle(cx));
8099 editor.toggle_code_actions(
8100 &ToggleCodeActions {
8101 deployed_from: Some(CodeActionSource::RunMenu(row)),
8102 quick_launch,
8103 },
8104 window,
8105 cx,
8106 );
8107 }))
8108 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8109 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8110 }))
8111 }
8112
8113 pub fn context_menu_visible(&self) -> bool {
8114 !self.edit_prediction_preview_is_active()
8115 && self
8116 .context_menu
8117 .borrow()
8118 .as_ref()
8119 .map_or(false, |menu| menu.visible())
8120 }
8121
8122 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8123 self.context_menu
8124 .borrow()
8125 .as_ref()
8126 .map(|menu| menu.origin())
8127 }
8128
8129 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8130 self.context_menu_options = Some(options);
8131 }
8132
8133 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8134 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8135
8136 fn render_edit_prediction_popover(
8137 &mut self,
8138 text_bounds: &Bounds<Pixels>,
8139 content_origin: gpui::Point<Pixels>,
8140 right_margin: Pixels,
8141 editor_snapshot: &EditorSnapshot,
8142 visible_row_range: Range<DisplayRow>,
8143 scroll_top: f32,
8144 scroll_bottom: f32,
8145 line_layouts: &[LineWithInvisibles],
8146 line_height: Pixels,
8147 scroll_pixel_position: gpui::Point<Pixels>,
8148 newest_selection_head: Option<DisplayPoint>,
8149 editor_width: Pixels,
8150 style: &EditorStyle,
8151 window: &mut Window,
8152 cx: &mut App,
8153 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8154 if self.mode().is_minimap() {
8155 return None;
8156 }
8157 let active_inline_completion = self.active_inline_completion.as_ref()?;
8158
8159 if self.edit_prediction_visible_in_cursor_popover(true) {
8160 return None;
8161 }
8162
8163 match &active_inline_completion.completion {
8164 InlineCompletion::Move { target, .. } => {
8165 let target_display_point = target.to_display_point(editor_snapshot);
8166
8167 if self.edit_prediction_requires_modifier() {
8168 if !self.edit_prediction_preview_is_active() {
8169 return None;
8170 }
8171
8172 self.render_edit_prediction_modifier_jump_popover(
8173 text_bounds,
8174 content_origin,
8175 visible_row_range,
8176 line_layouts,
8177 line_height,
8178 scroll_pixel_position,
8179 newest_selection_head,
8180 target_display_point,
8181 window,
8182 cx,
8183 )
8184 } else {
8185 self.render_edit_prediction_eager_jump_popover(
8186 text_bounds,
8187 content_origin,
8188 editor_snapshot,
8189 visible_row_range,
8190 scroll_top,
8191 scroll_bottom,
8192 line_height,
8193 scroll_pixel_position,
8194 target_display_point,
8195 editor_width,
8196 window,
8197 cx,
8198 )
8199 }
8200 }
8201 InlineCompletion::Edit {
8202 display_mode: EditDisplayMode::Inline,
8203 ..
8204 } => None,
8205 InlineCompletion::Edit {
8206 display_mode: EditDisplayMode::TabAccept,
8207 edits,
8208 ..
8209 } => {
8210 let range = &edits.first()?.0;
8211 let target_display_point = range.end.to_display_point(editor_snapshot);
8212
8213 self.render_edit_prediction_end_of_line_popover(
8214 "Accept",
8215 editor_snapshot,
8216 visible_row_range,
8217 target_display_point,
8218 line_height,
8219 scroll_pixel_position,
8220 content_origin,
8221 editor_width,
8222 window,
8223 cx,
8224 )
8225 }
8226 InlineCompletion::Edit {
8227 edits,
8228 edit_preview,
8229 display_mode: EditDisplayMode::DiffPopover,
8230 snapshot,
8231 } => self.render_edit_prediction_diff_popover(
8232 text_bounds,
8233 content_origin,
8234 right_margin,
8235 editor_snapshot,
8236 visible_row_range,
8237 line_layouts,
8238 line_height,
8239 scroll_pixel_position,
8240 newest_selection_head,
8241 editor_width,
8242 style,
8243 edits,
8244 edit_preview,
8245 snapshot,
8246 window,
8247 cx,
8248 ),
8249 }
8250 }
8251
8252 fn render_edit_prediction_modifier_jump_popover(
8253 &mut self,
8254 text_bounds: &Bounds<Pixels>,
8255 content_origin: gpui::Point<Pixels>,
8256 visible_row_range: Range<DisplayRow>,
8257 line_layouts: &[LineWithInvisibles],
8258 line_height: Pixels,
8259 scroll_pixel_position: gpui::Point<Pixels>,
8260 newest_selection_head: Option<DisplayPoint>,
8261 target_display_point: DisplayPoint,
8262 window: &mut Window,
8263 cx: &mut App,
8264 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8265 let scrolled_content_origin =
8266 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8267
8268 const SCROLL_PADDING_Y: Pixels = px(12.);
8269
8270 if target_display_point.row() < visible_row_range.start {
8271 return self.render_edit_prediction_scroll_popover(
8272 |_| SCROLL_PADDING_Y,
8273 IconName::ArrowUp,
8274 visible_row_range,
8275 line_layouts,
8276 newest_selection_head,
8277 scrolled_content_origin,
8278 window,
8279 cx,
8280 );
8281 } else if target_display_point.row() >= visible_row_range.end {
8282 return self.render_edit_prediction_scroll_popover(
8283 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8284 IconName::ArrowDown,
8285 visible_row_range,
8286 line_layouts,
8287 newest_selection_head,
8288 scrolled_content_origin,
8289 window,
8290 cx,
8291 );
8292 }
8293
8294 const POLE_WIDTH: Pixels = px(2.);
8295
8296 let line_layout =
8297 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8298 let target_column = target_display_point.column() as usize;
8299
8300 let target_x = line_layout.x_for_index(target_column);
8301 let target_y =
8302 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8303
8304 let flag_on_right = target_x < text_bounds.size.width / 2.;
8305
8306 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8307 border_color.l += 0.001;
8308
8309 let mut element = v_flex()
8310 .items_end()
8311 .when(flag_on_right, |el| el.items_start())
8312 .child(if flag_on_right {
8313 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8314 .rounded_bl(px(0.))
8315 .rounded_tl(px(0.))
8316 .border_l_2()
8317 .border_color(border_color)
8318 } else {
8319 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8320 .rounded_br(px(0.))
8321 .rounded_tr(px(0.))
8322 .border_r_2()
8323 .border_color(border_color)
8324 })
8325 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8326 .into_any();
8327
8328 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8329
8330 let mut origin = scrolled_content_origin + point(target_x, target_y)
8331 - point(
8332 if flag_on_right {
8333 POLE_WIDTH
8334 } else {
8335 size.width - POLE_WIDTH
8336 },
8337 size.height - line_height,
8338 );
8339
8340 origin.x = origin.x.max(content_origin.x);
8341
8342 element.prepaint_at(origin, window, cx);
8343
8344 Some((element, origin))
8345 }
8346
8347 fn render_edit_prediction_scroll_popover(
8348 &mut self,
8349 to_y: impl Fn(Size<Pixels>) -> Pixels,
8350 scroll_icon: IconName,
8351 visible_row_range: Range<DisplayRow>,
8352 line_layouts: &[LineWithInvisibles],
8353 newest_selection_head: Option<DisplayPoint>,
8354 scrolled_content_origin: gpui::Point<Pixels>,
8355 window: &mut Window,
8356 cx: &mut App,
8357 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8358 let mut element = self
8359 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8360 .into_any();
8361
8362 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8363
8364 let cursor = newest_selection_head?;
8365 let cursor_row_layout =
8366 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8367 let cursor_column = cursor.column() as usize;
8368
8369 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8370
8371 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8372
8373 element.prepaint_at(origin, window, cx);
8374 Some((element, origin))
8375 }
8376
8377 fn render_edit_prediction_eager_jump_popover(
8378 &mut self,
8379 text_bounds: &Bounds<Pixels>,
8380 content_origin: gpui::Point<Pixels>,
8381 editor_snapshot: &EditorSnapshot,
8382 visible_row_range: Range<DisplayRow>,
8383 scroll_top: f32,
8384 scroll_bottom: f32,
8385 line_height: Pixels,
8386 scroll_pixel_position: gpui::Point<Pixels>,
8387 target_display_point: DisplayPoint,
8388 editor_width: Pixels,
8389 window: &mut Window,
8390 cx: &mut App,
8391 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8392 if target_display_point.row().as_f32() < scroll_top {
8393 let mut element = self
8394 .render_edit_prediction_line_popover(
8395 "Jump to Edit",
8396 Some(IconName::ArrowUp),
8397 window,
8398 cx,
8399 )?
8400 .into_any();
8401
8402 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8403 let offset = point(
8404 (text_bounds.size.width - size.width) / 2.,
8405 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8406 );
8407
8408 let origin = text_bounds.origin + offset;
8409 element.prepaint_at(origin, window, cx);
8410 Some((element, origin))
8411 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8412 let mut element = self
8413 .render_edit_prediction_line_popover(
8414 "Jump to Edit",
8415 Some(IconName::ArrowDown),
8416 window,
8417 cx,
8418 )?
8419 .into_any();
8420
8421 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8422 let offset = point(
8423 (text_bounds.size.width - size.width) / 2.,
8424 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8425 );
8426
8427 let origin = text_bounds.origin + offset;
8428 element.prepaint_at(origin, window, cx);
8429 Some((element, origin))
8430 } else {
8431 self.render_edit_prediction_end_of_line_popover(
8432 "Jump to Edit",
8433 editor_snapshot,
8434 visible_row_range,
8435 target_display_point,
8436 line_height,
8437 scroll_pixel_position,
8438 content_origin,
8439 editor_width,
8440 window,
8441 cx,
8442 )
8443 }
8444 }
8445
8446 fn render_edit_prediction_end_of_line_popover(
8447 self: &mut Editor,
8448 label: &'static str,
8449 editor_snapshot: &EditorSnapshot,
8450 visible_row_range: Range<DisplayRow>,
8451 target_display_point: DisplayPoint,
8452 line_height: Pixels,
8453 scroll_pixel_position: gpui::Point<Pixels>,
8454 content_origin: gpui::Point<Pixels>,
8455 editor_width: Pixels,
8456 window: &mut Window,
8457 cx: &mut App,
8458 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8459 let target_line_end = DisplayPoint::new(
8460 target_display_point.row(),
8461 editor_snapshot.line_len(target_display_point.row()),
8462 );
8463
8464 let mut element = self
8465 .render_edit_prediction_line_popover(label, None, window, cx)?
8466 .into_any();
8467
8468 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8469
8470 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8471
8472 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8473 let mut origin = start_point
8474 + line_origin
8475 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8476 origin.x = origin.x.max(content_origin.x);
8477
8478 let max_x = content_origin.x + editor_width - size.width;
8479
8480 if origin.x > max_x {
8481 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8482
8483 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8484 origin.y += offset;
8485 IconName::ArrowUp
8486 } else {
8487 origin.y -= offset;
8488 IconName::ArrowDown
8489 };
8490
8491 element = self
8492 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8493 .into_any();
8494
8495 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8496
8497 origin.x = content_origin.x + editor_width - size.width - px(2.);
8498 }
8499
8500 element.prepaint_at(origin, window, cx);
8501 Some((element, origin))
8502 }
8503
8504 fn render_edit_prediction_diff_popover(
8505 self: &Editor,
8506 text_bounds: &Bounds<Pixels>,
8507 content_origin: gpui::Point<Pixels>,
8508 right_margin: Pixels,
8509 editor_snapshot: &EditorSnapshot,
8510 visible_row_range: Range<DisplayRow>,
8511 line_layouts: &[LineWithInvisibles],
8512 line_height: Pixels,
8513 scroll_pixel_position: gpui::Point<Pixels>,
8514 newest_selection_head: Option<DisplayPoint>,
8515 editor_width: Pixels,
8516 style: &EditorStyle,
8517 edits: &Vec<(Range<Anchor>, String)>,
8518 edit_preview: &Option<language::EditPreview>,
8519 snapshot: &language::BufferSnapshot,
8520 window: &mut Window,
8521 cx: &mut App,
8522 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8523 let edit_start = edits
8524 .first()
8525 .unwrap()
8526 .0
8527 .start
8528 .to_display_point(editor_snapshot);
8529 let edit_end = edits
8530 .last()
8531 .unwrap()
8532 .0
8533 .end
8534 .to_display_point(editor_snapshot);
8535
8536 let is_visible = visible_row_range.contains(&edit_start.row())
8537 || visible_row_range.contains(&edit_end.row());
8538 if !is_visible {
8539 return None;
8540 }
8541
8542 let highlighted_edits =
8543 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8544
8545 let styled_text = highlighted_edits.to_styled_text(&style.text);
8546 let line_count = highlighted_edits.text.lines().count();
8547
8548 const BORDER_WIDTH: Pixels = px(1.);
8549
8550 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8551 let has_keybind = keybind.is_some();
8552
8553 let mut element = h_flex()
8554 .items_start()
8555 .child(
8556 h_flex()
8557 .bg(cx.theme().colors().editor_background)
8558 .border(BORDER_WIDTH)
8559 .shadow_sm()
8560 .border_color(cx.theme().colors().border)
8561 .rounded_l_lg()
8562 .when(line_count > 1, |el| el.rounded_br_lg())
8563 .pr_1()
8564 .child(styled_text),
8565 )
8566 .child(
8567 h_flex()
8568 .h(line_height + BORDER_WIDTH * 2.)
8569 .px_1p5()
8570 .gap_1()
8571 // Workaround: For some reason, there's a gap if we don't do this
8572 .ml(-BORDER_WIDTH)
8573 .shadow(vec![gpui::BoxShadow {
8574 color: gpui::black().opacity(0.05),
8575 offset: point(px(1.), px(1.)),
8576 blur_radius: px(2.),
8577 spread_radius: px(0.),
8578 }])
8579 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8580 .border(BORDER_WIDTH)
8581 .border_color(cx.theme().colors().border)
8582 .rounded_r_lg()
8583 .id("edit_prediction_diff_popover_keybind")
8584 .when(!has_keybind, |el| {
8585 let status_colors = cx.theme().status();
8586
8587 el.bg(status_colors.error_background)
8588 .border_color(status_colors.error.opacity(0.6))
8589 .child(Icon::new(IconName::Info).color(Color::Error))
8590 .cursor_default()
8591 .hoverable_tooltip(move |_window, cx| {
8592 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8593 })
8594 })
8595 .children(keybind),
8596 )
8597 .into_any();
8598
8599 let longest_row =
8600 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8601 let longest_line_width = if visible_row_range.contains(&longest_row) {
8602 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8603 } else {
8604 layout_line(
8605 longest_row,
8606 editor_snapshot,
8607 style,
8608 editor_width,
8609 |_| false,
8610 window,
8611 cx,
8612 )
8613 .width
8614 };
8615
8616 let viewport_bounds =
8617 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8618 right: -right_margin,
8619 ..Default::default()
8620 });
8621
8622 let x_after_longest =
8623 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8624 - scroll_pixel_position.x;
8625
8626 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8627
8628 // Fully visible if it can be displayed within the window (allow overlapping other
8629 // panes). However, this is only allowed if the popover starts within text_bounds.
8630 let can_position_to_the_right = x_after_longest < text_bounds.right()
8631 && x_after_longest + element_bounds.width < viewport_bounds.right();
8632
8633 let mut origin = if can_position_to_the_right {
8634 point(
8635 x_after_longest,
8636 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8637 - scroll_pixel_position.y,
8638 )
8639 } else {
8640 let cursor_row = newest_selection_head.map(|head| head.row());
8641 let above_edit = edit_start
8642 .row()
8643 .0
8644 .checked_sub(line_count as u32)
8645 .map(DisplayRow);
8646 let below_edit = Some(edit_end.row() + 1);
8647 let above_cursor =
8648 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8649 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8650
8651 // Place the edit popover adjacent to the edit if there is a location
8652 // available that is onscreen and does not obscure the cursor. Otherwise,
8653 // place it adjacent to the cursor.
8654 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8655 .into_iter()
8656 .flatten()
8657 .find(|&start_row| {
8658 let end_row = start_row + line_count as u32;
8659 visible_row_range.contains(&start_row)
8660 && visible_row_range.contains(&end_row)
8661 && cursor_row.map_or(true, |cursor_row| {
8662 !((start_row..end_row).contains(&cursor_row))
8663 })
8664 })?;
8665
8666 content_origin
8667 + point(
8668 -scroll_pixel_position.x,
8669 row_target.as_f32() * line_height - scroll_pixel_position.y,
8670 )
8671 };
8672
8673 origin.x -= BORDER_WIDTH;
8674
8675 window.defer_draw(element, origin, 1);
8676
8677 // Do not return an element, since it will already be drawn due to defer_draw.
8678 None
8679 }
8680
8681 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8682 px(30.)
8683 }
8684
8685 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8686 if self.read_only(cx) {
8687 cx.theme().players().read_only()
8688 } else {
8689 self.style.as_ref().unwrap().local_player
8690 }
8691 }
8692
8693 fn render_edit_prediction_accept_keybind(
8694 &self,
8695 window: &mut Window,
8696 cx: &App,
8697 ) -> Option<AnyElement> {
8698 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8699 let accept_keystroke = accept_binding.keystroke()?;
8700
8701 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8702
8703 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8704 Color::Accent
8705 } else {
8706 Color::Muted
8707 };
8708
8709 h_flex()
8710 .px_0p5()
8711 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8712 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8713 .text_size(TextSize::XSmall.rems(cx))
8714 .child(h_flex().children(ui::render_modifiers(
8715 &accept_keystroke.modifiers,
8716 PlatformStyle::platform(),
8717 Some(modifiers_color),
8718 Some(IconSize::XSmall.rems().into()),
8719 true,
8720 )))
8721 .when(is_platform_style_mac, |parent| {
8722 parent.child(accept_keystroke.key.clone())
8723 })
8724 .when(!is_platform_style_mac, |parent| {
8725 parent.child(
8726 Key::new(
8727 util::capitalize(&accept_keystroke.key),
8728 Some(Color::Default),
8729 )
8730 .size(Some(IconSize::XSmall.rems().into())),
8731 )
8732 })
8733 .into_any()
8734 .into()
8735 }
8736
8737 fn render_edit_prediction_line_popover(
8738 &self,
8739 label: impl Into<SharedString>,
8740 icon: Option<IconName>,
8741 window: &mut Window,
8742 cx: &App,
8743 ) -> Option<Stateful<Div>> {
8744 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8745
8746 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8747 let has_keybind = keybind.is_some();
8748
8749 let result = h_flex()
8750 .id("ep-line-popover")
8751 .py_0p5()
8752 .pl_1()
8753 .pr(padding_right)
8754 .gap_1()
8755 .rounded_md()
8756 .border_1()
8757 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8758 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8759 .shadow_sm()
8760 .when(!has_keybind, |el| {
8761 let status_colors = cx.theme().status();
8762
8763 el.bg(status_colors.error_background)
8764 .border_color(status_colors.error.opacity(0.6))
8765 .pl_2()
8766 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8767 .cursor_default()
8768 .hoverable_tooltip(move |_window, cx| {
8769 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8770 })
8771 })
8772 .children(keybind)
8773 .child(
8774 Label::new(label)
8775 .size(LabelSize::Small)
8776 .when(!has_keybind, |el| {
8777 el.color(cx.theme().status().error.into()).strikethrough()
8778 }),
8779 )
8780 .when(!has_keybind, |el| {
8781 el.child(
8782 h_flex().ml_1().child(
8783 Icon::new(IconName::Info)
8784 .size(IconSize::Small)
8785 .color(cx.theme().status().error.into()),
8786 ),
8787 )
8788 })
8789 .when_some(icon, |element, icon| {
8790 element.child(
8791 div()
8792 .mt(px(1.5))
8793 .child(Icon::new(icon).size(IconSize::Small)),
8794 )
8795 });
8796
8797 Some(result)
8798 }
8799
8800 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8801 let accent_color = cx.theme().colors().text_accent;
8802 let editor_bg_color = cx.theme().colors().editor_background;
8803 editor_bg_color.blend(accent_color.opacity(0.1))
8804 }
8805
8806 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8807 let accent_color = cx.theme().colors().text_accent;
8808 let editor_bg_color = cx.theme().colors().editor_background;
8809 editor_bg_color.blend(accent_color.opacity(0.6))
8810 }
8811
8812 fn render_edit_prediction_cursor_popover(
8813 &self,
8814 min_width: Pixels,
8815 max_width: Pixels,
8816 cursor_point: Point,
8817 style: &EditorStyle,
8818 accept_keystroke: Option<&gpui::Keystroke>,
8819 _window: &Window,
8820 cx: &mut Context<Editor>,
8821 ) -> Option<AnyElement> {
8822 let provider = self.edit_prediction_provider.as_ref()?;
8823
8824 if provider.provider.needs_terms_acceptance(cx) {
8825 return Some(
8826 h_flex()
8827 .min_w(min_width)
8828 .flex_1()
8829 .px_2()
8830 .py_1()
8831 .gap_3()
8832 .elevation_2(cx)
8833 .hover(|style| style.bg(cx.theme().colors().element_hover))
8834 .id("accept-terms")
8835 .cursor_pointer()
8836 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8837 .on_click(cx.listener(|this, _event, window, cx| {
8838 cx.stop_propagation();
8839 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8840 window.dispatch_action(
8841 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8842 cx,
8843 );
8844 }))
8845 .child(
8846 h_flex()
8847 .flex_1()
8848 .gap_2()
8849 .child(Icon::new(IconName::ZedPredict))
8850 .child(Label::new("Accept Terms of Service"))
8851 .child(div().w_full())
8852 .child(
8853 Icon::new(IconName::ArrowUpRight)
8854 .color(Color::Muted)
8855 .size(IconSize::Small),
8856 )
8857 .into_any_element(),
8858 )
8859 .into_any(),
8860 );
8861 }
8862
8863 let is_refreshing = provider.provider.is_refreshing(cx);
8864
8865 fn pending_completion_container() -> Div {
8866 h_flex()
8867 .h_full()
8868 .flex_1()
8869 .gap_2()
8870 .child(Icon::new(IconName::ZedPredict))
8871 }
8872
8873 let completion = match &self.active_inline_completion {
8874 Some(prediction) => {
8875 if !self.has_visible_completions_menu() {
8876 const RADIUS: Pixels = px(6.);
8877 const BORDER_WIDTH: Pixels = px(1.);
8878
8879 return Some(
8880 h_flex()
8881 .elevation_2(cx)
8882 .border(BORDER_WIDTH)
8883 .border_color(cx.theme().colors().border)
8884 .when(accept_keystroke.is_none(), |el| {
8885 el.border_color(cx.theme().status().error)
8886 })
8887 .rounded(RADIUS)
8888 .rounded_tl(px(0.))
8889 .overflow_hidden()
8890 .child(div().px_1p5().child(match &prediction.completion {
8891 InlineCompletion::Move { target, snapshot } => {
8892 use text::ToPoint as _;
8893 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8894 {
8895 Icon::new(IconName::ZedPredictDown)
8896 } else {
8897 Icon::new(IconName::ZedPredictUp)
8898 }
8899 }
8900 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8901 }))
8902 .child(
8903 h_flex()
8904 .gap_1()
8905 .py_1()
8906 .px_2()
8907 .rounded_r(RADIUS - BORDER_WIDTH)
8908 .border_l_1()
8909 .border_color(cx.theme().colors().border)
8910 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8911 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8912 el.child(
8913 Label::new("Hold")
8914 .size(LabelSize::Small)
8915 .when(accept_keystroke.is_none(), |el| {
8916 el.strikethrough()
8917 })
8918 .line_height_style(LineHeightStyle::UiLabel),
8919 )
8920 })
8921 .id("edit_prediction_cursor_popover_keybind")
8922 .when(accept_keystroke.is_none(), |el| {
8923 let status_colors = cx.theme().status();
8924
8925 el.bg(status_colors.error_background)
8926 .border_color(status_colors.error.opacity(0.6))
8927 .child(Icon::new(IconName::Info).color(Color::Error))
8928 .cursor_default()
8929 .hoverable_tooltip(move |_window, cx| {
8930 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8931 .into()
8932 })
8933 })
8934 .when_some(
8935 accept_keystroke.as_ref(),
8936 |el, accept_keystroke| {
8937 el.child(h_flex().children(ui::render_modifiers(
8938 &accept_keystroke.modifiers,
8939 PlatformStyle::platform(),
8940 Some(Color::Default),
8941 Some(IconSize::XSmall.rems().into()),
8942 false,
8943 )))
8944 },
8945 ),
8946 )
8947 .into_any(),
8948 );
8949 }
8950
8951 self.render_edit_prediction_cursor_popover_preview(
8952 prediction,
8953 cursor_point,
8954 style,
8955 cx,
8956 )?
8957 }
8958
8959 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8960 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8961 stale_completion,
8962 cursor_point,
8963 style,
8964 cx,
8965 )?,
8966
8967 None => {
8968 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8969 }
8970 },
8971
8972 None => pending_completion_container().child(Label::new("No Prediction")),
8973 };
8974
8975 let completion = if is_refreshing {
8976 completion
8977 .with_animation(
8978 "loading-completion",
8979 Animation::new(Duration::from_secs(2))
8980 .repeat()
8981 .with_easing(pulsating_between(0.4, 0.8)),
8982 |label, delta| label.opacity(delta),
8983 )
8984 .into_any_element()
8985 } else {
8986 completion.into_any_element()
8987 };
8988
8989 let has_completion = self.active_inline_completion.is_some();
8990
8991 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8992 Some(
8993 h_flex()
8994 .min_w(min_width)
8995 .max_w(max_width)
8996 .flex_1()
8997 .elevation_2(cx)
8998 .border_color(cx.theme().colors().border)
8999 .child(
9000 div()
9001 .flex_1()
9002 .py_1()
9003 .px_2()
9004 .overflow_hidden()
9005 .child(completion),
9006 )
9007 .when_some(accept_keystroke, |el, accept_keystroke| {
9008 if !accept_keystroke.modifiers.modified() {
9009 return el;
9010 }
9011
9012 el.child(
9013 h_flex()
9014 .h_full()
9015 .border_l_1()
9016 .rounded_r_lg()
9017 .border_color(cx.theme().colors().border)
9018 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9019 .gap_1()
9020 .py_1()
9021 .px_2()
9022 .child(
9023 h_flex()
9024 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9025 .when(is_platform_style_mac, |parent| parent.gap_1())
9026 .child(h_flex().children(ui::render_modifiers(
9027 &accept_keystroke.modifiers,
9028 PlatformStyle::platform(),
9029 Some(if !has_completion {
9030 Color::Muted
9031 } else {
9032 Color::Default
9033 }),
9034 None,
9035 false,
9036 ))),
9037 )
9038 .child(Label::new("Preview").into_any_element())
9039 .opacity(if has_completion { 1.0 } else { 0.4 }),
9040 )
9041 })
9042 .into_any(),
9043 )
9044 }
9045
9046 fn render_edit_prediction_cursor_popover_preview(
9047 &self,
9048 completion: &InlineCompletionState,
9049 cursor_point: Point,
9050 style: &EditorStyle,
9051 cx: &mut Context<Editor>,
9052 ) -> Option<Div> {
9053 use text::ToPoint as _;
9054
9055 fn render_relative_row_jump(
9056 prefix: impl Into<String>,
9057 current_row: u32,
9058 target_row: u32,
9059 ) -> Div {
9060 let (row_diff, arrow) = if target_row < current_row {
9061 (current_row - target_row, IconName::ArrowUp)
9062 } else {
9063 (target_row - current_row, IconName::ArrowDown)
9064 };
9065
9066 h_flex()
9067 .child(
9068 Label::new(format!("{}{}", prefix.into(), row_diff))
9069 .color(Color::Muted)
9070 .size(LabelSize::Small),
9071 )
9072 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9073 }
9074
9075 match &completion.completion {
9076 InlineCompletion::Move {
9077 target, snapshot, ..
9078 } => Some(
9079 h_flex()
9080 .px_2()
9081 .gap_2()
9082 .flex_1()
9083 .child(
9084 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9085 Icon::new(IconName::ZedPredictDown)
9086 } else {
9087 Icon::new(IconName::ZedPredictUp)
9088 },
9089 )
9090 .child(Label::new("Jump to Edit")),
9091 ),
9092
9093 InlineCompletion::Edit {
9094 edits,
9095 edit_preview,
9096 snapshot,
9097 display_mode: _,
9098 } => {
9099 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9100
9101 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9102 &snapshot,
9103 &edits,
9104 edit_preview.as_ref()?,
9105 true,
9106 cx,
9107 )
9108 .first_line_preview();
9109
9110 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9111 .with_default_highlights(&style.text, highlighted_edits.highlights);
9112
9113 let preview = h_flex()
9114 .gap_1()
9115 .min_w_16()
9116 .child(styled_text)
9117 .when(has_more_lines, |parent| parent.child("…"));
9118
9119 let left = if first_edit_row != cursor_point.row {
9120 render_relative_row_jump("", cursor_point.row, first_edit_row)
9121 .into_any_element()
9122 } else {
9123 Icon::new(IconName::ZedPredict).into_any_element()
9124 };
9125
9126 Some(
9127 h_flex()
9128 .h_full()
9129 .flex_1()
9130 .gap_2()
9131 .pr_1()
9132 .overflow_x_hidden()
9133 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9134 .child(left)
9135 .child(preview),
9136 )
9137 }
9138 }
9139 }
9140
9141 pub fn render_context_menu(
9142 &self,
9143 style: &EditorStyle,
9144 max_height_in_lines: u32,
9145 window: &mut Window,
9146 cx: &mut Context<Editor>,
9147 ) -> Option<AnyElement> {
9148 let menu = self.context_menu.borrow();
9149 let menu = menu.as_ref()?;
9150 if !menu.visible() {
9151 return None;
9152 };
9153 Some(menu.render(style, max_height_in_lines, window, cx))
9154 }
9155
9156 fn render_context_menu_aside(
9157 &mut self,
9158 max_size: Size<Pixels>,
9159 window: &mut Window,
9160 cx: &mut Context<Editor>,
9161 ) -> Option<AnyElement> {
9162 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9163 if menu.visible() {
9164 menu.render_aside(max_size, window, cx)
9165 } else {
9166 None
9167 }
9168 })
9169 }
9170
9171 fn hide_context_menu(
9172 &mut self,
9173 window: &mut Window,
9174 cx: &mut Context<Self>,
9175 ) -> Option<CodeContextMenu> {
9176 cx.notify();
9177 self.completion_tasks.clear();
9178 let context_menu = self.context_menu.borrow_mut().take();
9179 self.stale_inline_completion_in_menu.take();
9180 self.update_visible_inline_completion(window, cx);
9181 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9182 if let Some(completion_provider) = &self.completion_provider {
9183 completion_provider.selection_changed(None, window, cx);
9184 }
9185 }
9186 context_menu
9187 }
9188
9189 fn show_snippet_choices(
9190 &mut self,
9191 choices: &Vec<String>,
9192 selection: Range<Anchor>,
9193 cx: &mut Context<Self>,
9194 ) {
9195 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9196 (Some(a), Some(b)) if a == b => a,
9197 _ => {
9198 log::error!("expected anchor range to have matching buffer IDs");
9199 return;
9200 }
9201 };
9202 let multi_buffer = self.buffer().read(cx);
9203 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9204 return;
9205 };
9206
9207 let id = post_inc(&mut self.next_completion_id);
9208 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9209 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9210 CompletionsMenu::new_snippet_choices(
9211 id,
9212 true,
9213 choices,
9214 selection,
9215 buffer,
9216 snippet_sort_order,
9217 ),
9218 ));
9219 }
9220
9221 pub fn insert_snippet(
9222 &mut self,
9223 insertion_ranges: &[Range<usize>],
9224 snippet: Snippet,
9225 window: &mut Window,
9226 cx: &mut Context<Self>,
9227 ) -> Result<()> {
9228 struct Tabstop<T> {
9229 is_end_tabstop: bool,
9230 ranges: Vec<Range<T>>,
9231 choices: Option<Vec<String>>,
9232 }
9233
9234 let tabstops = self.buffer.update(cx, |buffer, cx| {
9235 let snippet_text: Arc<str> = snippet.text.clone().into();
9236 let edits = insertion_ranges
9237 .iter()
9238 .cloned()
9239 .map(|range| (range, snippet_text.clone()));
9240 let autoindent_mode = AutoindentMode::Block {
9241 original_indent_columns: Vec::new(),
9242 };
9243 buffer.edit(edits, Some(autoindent_mode), cx);
9244
9245 let snapshot = &*buffer.read(cx);
9246 let snippet = &snippet;
9247 snippet
9248 .tabstops
9249 .iter()
9250 .map(|tabstop| {
9251 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9252 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9253 });
9254 let mut tabstop_ranges = tabstop
9255 .ranges
9256 .iter()
9257 .flat_map(|tabstop_range| {
9258 let mut delta = 0_isize;
9259 insertion_ranges.iter().map(move |insertion_range| {
9260 let insertion_start = insertion_range.start as isize + delta;
9261 delta +=
9262 snippet.text.len() as isize - insertion_range.len() as isize;
9263
9264 let start = ((insertion_start + tabstop_range.start) as usize)
9265 .min(snapshot.len());
9266 let end = ((insertion_start + tabstop_range.end) as usize)
9267 .min(snapshot.len());
9268 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9269 })
9270 })
9271 .collect::<Vec<_>>();
9272 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9273
9274 Tabstop {
9275 is_end_tabstop,
9276 ranges: tabstop_ranges,
9277 choices: tabstop.choices.clone(),
9278 }
9279 })
9280 .collect::<Vec<_>>()
9281 });
9282 if let Some(tabstop) = tabstops.first() {
9283 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9284 // Reverse order so that the first range is the newest created selection.
9285 // Completions will use it and autoscroll will prioritize it.
9286 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9287 });
9288
9289 if let Some(choices) = &tabstop.choices {
9290 if let Some(selection) = tabstop.ranges.first() {
9291 self.show_snippet_choices(choices, selection.clone(), cx)
9292 }
9293 }
9294
9295 // If we're already at the last tabstop and it's at the end of the snippet,
9296 // we're done, we don't need to keep the state around.
9297 if !tabstop.is_end_tabstop {
9298 let choices = tabstops
9299 .iter()
9300 .map(|tabstop| tabstop.choices.clone())
9301 .collect();
9302
9303 let ranges = tabstops
9304 .into_iter()
9305 .map(|tabstop| tabstop.ranges)
9306 .collect::<Vec<_>>();
9307
9308 self.snippet_stack.push(SnippetState {
9309 active_index: 0,
9310 ranges,
9311 choices,
9312 });
9313 }
9314
9315 // Check whether the just-entered snippet ends with an auto-closable bracket.
9316 if self.autoclose_regions.is_empty() {
9317 let snapshot = self.buffer.read(cx).snapshot(cx);
9318 for selection in &mut self.selections.all::<Point>(cx) {
9319 let selection_head = selection.head();
9320 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9321 continue;
9322 };
9323
9324 let mut bracket_pair = None;
9325 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9326 let prev_chars = snapshot
9327 .reversed_chars_at(selection_head)
9328 .collect::<String>();
9329 for (pair, enabled) in scope.brackets() {
9330 if enabled
9331 && pair.close
9332 && prev_chars.starts_with(pair.start.as_str())
9333 && next_chars.starts_with(pair.end.as_str())
9334 {
9335 bracket_pair = Some(pair.clone());
9336 break;
9337 }
9338 }
9339 if let Some(pair) = bracket_pair {
9340 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9341 let autoclose_enabled =
9342 self.use_autoclose && snapshot_settings.use_autoclose;
9343 if autoclose_enabled {
9344 let start = snapshot.anchor_after(selection_head);
9345 let end = snapshot.anchor_after(selection_head);
9346 self.autoclose_regions.push(AutocloseRegion {
9347 selection_id: selection.id,
9348 range: start..end,
9349 pair,
9350 });
9351 }
9352 }
9353 }
9354 }
9355 }
9356 Ok(())
9357 }
9358
9359 pub fn move_to_next_snippet_tabstop(
9360 &mut self,
9361 window: &mut Window,
9362 cx: &mut Context<Self>,
9363 ) -> bool {
9364 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9365 }
9366
9367 pub fn move_to_prev_snippet_tabstop(
9368 &mut self,
9369 window: &mut Window,
9370 cx: &mut Context<Self>,
9371 ) -> bool {
9372 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9373 }
9374
9375 pub fn move_to_snippet_tabstop(
9376 &mut self,
9377 bias: Bias,
9378 window: &mut Window,
9379 cx: &mut Context<Self>,
9380 ) -> bool {
9381 if let Some(mut snippet) = self.snippet_stack.pop() {
9382 match bias {
9383 Bias::Left => {
9384 if snippet.active_index > 0 {
9385 snippet.active_index -= 1;
9386 } else {
9387 self.snippet_stack.push(snippet);
9388 return false;
9389 }
9390 }
9391 Bias::Right => {
9392 if snippet.active_index + 1 < snippet.ranges.len() {
9393 snippet.active_index += 1;
9394 } else {
9395 self.snippet_stack.push(snippet);
9396 return false;
9397 }
9398 }
9399 }
9400 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9401 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9402 // Reverse order so that the first range is the newest created selection.
9403 // Completions will use it and autoscroll will prioritize it.
9404 s.select_ranges(current_ranges.iter().rev().cloned())
9405 });
9406
9407 if let Some(choices) = &snippet.choices[snippet.active_index] {
9408 if let Some(selection) = current_ranges.first() {
9409 self.show_snippet_choices(&choices, selection.clone(), cx);
9410 }
9411 }
9412
9413 // If snippet state is not at the last tabstop, push it back on the stack
9414 if snippet.active_index + 1 < snippet.ranges.len() {
9415 self.snippet_stack.push(snippet);
9416 }
9417 return true;
9418 }
9419 }
9420
9421 false
9422 }
9423
9424 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9425 self.transact(window, cx, |this, window, cx| {
9426 this.select_all(&SelectAll, window, cx);
9427 this.insert("", window, cx);
9428 });
9429 }
9430
9431 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9432 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9433 self.transact(window, cx, |this, window, cx| {
9434 this.select_autoclose_pair(window, cx);
9435 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9436 if !this.linked_edit_ranges.is_empty() {
9437 let selections = this.selections.all::<MultiBufferPoint>(cx);
9438 let snapshot = this.buffer.read(cx).snapshot(cx);
9439
9440 for selection in selections.iter() {
9441 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9442 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9443 if selection_start.buffer_id != selection_end.buffer_id {
9444 continue;
9445 }
9446 if let Some(ranges) =
9447 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9448 {
9449 for (buffer, entries) in ranges {
9450 linked_ranges.entry(buffer).or_default().extend(entries);
9451 }
9452 }
9453 }
9454 }
9455
9456 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9457 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9458 for selection in &mut selections {
9459 if selection.is_empty() {
9460 let old_head = selection.head();
9461 let mut new_head =
9462 movement::left(&display_map, old_head.to_display_point(&display_map))
9463 .to_point(&display_map);
9464 if let Some((buffer, line_buffer_range)) = display_map
9465 .buffer_snapshot
9466 .buffer_line_for_row(MultiBufferRow(old_head.row))
9467 {
9468 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9469 let indent_len = match indent_size.kind {
9470 IndentKind::Space => {
9471 buffer.settings_at(line_buffer_range.start, cx).tab_size
9472 }
9473 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9474 };
9475 if old_head.column <= indent_size.len && old_head.column > 0 {
9476 let indent_len = indent_len.get();
9477 new_head = cmp::min(
9478 new_head,
9479 MultiBufferPoint::new(
9480 old_head.row,
9481 ((old_head.column - 1) / indent_len) * indent_len,
9482 ),
9483 );
9484 }
9485 }
9486
9487 selection.set_head(new_head, SelectionGoal::None);
9488 }
9489 }
9490
9491 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9492 s.select(selections)
9493 });
9494 this.insert("", window, cx);
9495 let empty_str: Arc<str> = Arc::from("");
9496 for (buffer, edits) in linked_ranges {
9497 let snapshot = buffer.read(cx).snapshot();
9498 use text::ToPoint as TP;
9499
9500 let edits = edits
9501 .into_iter()
9502 .map(|range| {
9503 let end_point = TP::to_point(&range.end, &snapshot);
9504 let mut start_point = TP::to_point(&range.start, &snapshot);
9505
9506 if end_point == start_point {
9507 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9508 .saturating_sub(1);
9509 start_point =
9510 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9511 };
9512
9513 (start_point..end_point, empty_str.clone())
9514 })
9515 .sorted_by_key(|(range, _)| range.start)
9516 .collect::<Vec<_>>();
9517 buffer.update(cx, |this, cx| {
9518 this.edit(edits, None, cx);
9519 })
9520 }
9521 this.refresh_inline_completion(true, false, window, cx);
9522 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9523 });
9524 }
9525
9526 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9527 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9528 self.transact(window, cx, |this, window, cx| {
9529 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9530 s.move_with(|map, selection| {
9531 if selection.is_empty() {
9532 let cursor = movement::right(map, selection.head());
9533 selection.end = cursor;
9534 selection.reversed = true;
9535 selection.goal = SelectionGoal::None;
9536 }
9537 })
9538 });
9539 this.insert("", window, cx);
9540 this.refresh_inline_completion(true, false, window, cx);
9541 });
9542 }
9543
9544 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9545 if self.mode.is_single_line() {
9546 cx.propagate();
9547 return;
9548 }
9549
9550 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9551 if self.move_to_prev_snippet_tabstop(window, cx) {
9552 return;
9553 }
9554 self.outdent(&Outdent, window, cx);
9555 }
9556
9557 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9558 if self.mode.is_single_line() {
9559 cx.propagate();
9560 return;
9561 }
9562
9563 if self.move_to_next_snippet_tabstop(window, cx) {
9564 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9565 return;
9566 }
9567 if self.read_only(cx) {
9568 return;
9569 }
9570 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9571 let mut selections = self.selections.all_adjusted(cx);
9572 let buffer = self.buffer.read(cx);
9573 let snapshot = buffer.snapshot(cx);
9574 let rows_iter = selections.iter().map(|s| s.head().row);
9575 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9576
9577 let has_some_cursor_in_whitespace = selections
9578 .iter()
9579 .filter(|selection| selection.is_empty())
9580 .any(|selection| {
9581 let cursor = selection.head();
9582 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9583 cursor.column < current_indent.len
9584 });
9585
9586 let mut edits = Vec::new();
9587 let mut prev_edited_row = 0;
9588 let mut row_delta = 0;
9589 for selection in &mut selections {
9590 if selection.start.row != prev_edited_row {
9591 row_delta = 0;
9592 }
9593 prev_edited_row = selection.end.row;
9594
9595 // If the selection is non-empty, then increase the indentation of the selected lines.
9596 if !selection.is_empty() {
9597 row_delta =
9598 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9599 continue;
9600 }
9601
9602 let cursor = selection.head();
9603 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9604 if let Some(suggested_indent) =
9605 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9606 {
9607 // Don't do anything if already at suggested indent
9608 // and there is any other cursor which is not
9609 if has_some_cursor_in_whitespace
9610 && cursor.column == current_indent.len
9611 && current_indent.len == suggested_indent.len
9612 {
9613 continue;
9614 }
9615
9616 // Adjust line and move cursor to suggested indent
9617 // if cursor is not at suggested indent
9618 if cursor.column < suggested_indent.len
9619 && cursor.column <= current_indent.len
9620 && current_indent.len <= suggested_indent.len
9621 {
9622 selection.start = Point::new(cursor.row, suggested_indent.len);
9623 selection.end = selection.start;
9624 if row_delta == 0 {
9625 edits.extend(Buffer::edit_for_indent_size_adjustment(
9626 cursor.row,
9627 current_indent,
9628 suggested_indent,
9629 ));
9630 row_delta = suggested_indent.len - current_indent.len;
9631 }
9632 continue;
9633 }
9634
9635 // If current indent is more than suggested indent
9636 // only move cursor to current indent and skip indent
9637 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9638 selection.start = Point::new(cursor.row, current_indent.len);
9639 selection.end = selection.start;
9640 continue;
9641 }
9642 }
9643
9644 // Otherwise, insert a hard or soft tab.
9645 let settings = buffer.language_settings_at(cursor, cx);
9646 let tab_size = if settings.hard_tabs {
9647 IndentSize::tab()
9648 } else {
9649 let tab_size = settings.tab_size.get();
9650 let indent_remainder = snapshot
9651 .text_for_range(Point::new(cursor.row, 0)..cursor)
9652 .flat_map(str::chars)
9653 .fold(row_delta % tab_size, |counter: u32, c| {
9654 if c == '\t' {
9655 0
9656 } else {
9657 (counter + 1) % tab_size
9658 }
9659 });
9660
9661 let chars_to_next_tab_stop = tab_size - indent_remainder;
9662 IndentSize::spaces(chars_to_next_tab_stop)
9663 };
9664 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9665 selection.end = selection.start;
9666 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9667 row_delta += tab_size.len;
9668 }
9669
9670 self.transact(window, cx, |this, window, cx| {
9671 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9672 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9673 s.select(selections)
9674 });
9675 this.refresh_inline_completion(true, false, window, cx);
9676 });
9677 }
9678
9679 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9680 if self.read_only(cx) {
9681 return;
9682 }
9683 if self.mode.is_single_line() {
9684 cx.propagate();
9685 return;
9686 }
9687
9688 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9689 let mut selections = self.selections.all::<Point>(cx);
9690 let mut prev_edited_row = 0;
9691 let mut row_delta = 0;
9692 let mut edits = Vec::new();
9693 let buffer = self.buffer.read(cx);
9694 let snapshot = buffer.snapshot(cx);
9695 for selection in &mut selections {
9696 if selection.start.row != prev_edited_row {
9697 row_delta = 0;
9698 }
9699 prev_edited_row = selection.end.row;
9700
9701 row_delta =
9702 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9703 }
9704
9705 self.transact(window, cx, |this, window, cx| {
9706 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9707 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9708 s.select(selections)
9709 });
9710 });
9711 }
9712
9713 fn indent_selection(
9714 buffer: &MultiBuffer,
9715 snapshot: &MultiBufferSnapshot,
9716 selection: &mut Selection<Point>,
9717 edits: &mut Vec<(Range<Point>, String)>,
9718 delta_for_start_row: u32,
9719 cx: &App,
9720 ) -> u32 {
9721 let settings = buffer.language_settings_at(selection.start, cx);
9722 let tab_size = settings.tab_size.get();
9723 let indent_kind = if settings.hard_tabs {
9724 IndentKind::Tab
9725 } else {
9726 IndentKind::Space
9727 };
9728 let mut start_row = selection.start.row;
9729 let mut end_row = selection.end.row + 1;
9730
9731 // If a selection ends at the beginning of a line, don't indent
9732 // that last line.
9733 if selection.end.column == 0 && selection.end.row > selection.start.row {
9734 end_row -= 1;
9735 }
9736
9737 // Avoid re-indenting a row that has already been indented by a
9738 // previous selection, but still update this selection's column
9739 // to reflect that indentation.
9740 if delta_for_start_row > 0 {
9741 start_row += 1;
9742 selection.start.column += delta_for_start_row;
9743 if selection.end.row == selection.start.row {
9744 selection.end.column += delta_for_start_row;
9745 }
9746 }
9747
9748 let mut delta_for_end_row = 0;
9749 let has_multiple_rows = start_row + 1 != end_row;
9750 for row in start_row..end_row {
9751 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9752 let indent_delta = match (current_indent.kind, indent_kind) {
9753 (IndentKind::Space, IndentKind::Space) => {
9754 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9755 IndentSize::spaces(columns_to_next_tab_stop)
9756 }
9757 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9758 (_, IndentKind::Tab) => IndentSize::tab(),
9759 };
9760
9761 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9762 0
9763 } else {
9764 selection.start.column
9765 };
9766 let row_start = Point::new(row, start);
9767 edits.push((
9768 row_start..row_start,
9769 indent_delta.chars().collect::<String>(),
9770 ));
9771
9772 // Update this selection's endpoints to reflect the indentation.
9773 if row == selection.start.row {
9774 selection.start.column += indent_delta.len;
9775 }
9776 if row == selection.end.row {
9777 selection.end.column += indent_delta.len;
9778 delta_for_end_row = indent_delta.len;
9779 }
9780 }
9781
9782 if selection.start.row == selection.end.row {
9783 delta_for_start_row + delta_for_end_row
9784 } else {
9785 delta_for_end_row
9786 }
9787 }
9788
9789 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9790 if self.read_only(cx) {
9791 return;
9792 }
9793 if self.mode.is_single_line() {
9794 cx.propagate();
9795 return;
9796 }
9797
9798 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9799 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9800 let selections = self.selections.all::<Point>(cx);
9801 let mut deletion_ranges = Vec::new();
9802 let mut last_outdent = None;
9803 {
9804 let buffer = self.buffer.read(cx);
9805 let snapshot = buffer.snapshot(cx);
9806 for selection in &selections {
9807 let settings = buffer.language_settings_at(selection.start, cx);
9808 let tab_size = settings.tab_size.get();
9809 let mut rows = selection.spanned_rows(false, &display_map);
9810
9811 // Avoid re-outdenting a row that has already been outdented by a
9812 // previous selection.
9813 if let Some(last_row) = last_outdent {
9814 if last_row == rows.start {
9815 rows.start = rows.start.next_row();
9816 }
9817 }
9818 let has_multiple_rows = rows.len() > 1;
9819 for row in rows.iter_rows() {
9820 let indent_size = snapshot.indent_size_for_line(row);
9821 if indent_size.len > 0 {
9822 let deletion_len = match indent_size.kind {
9823 IndentKind::Space => {
9824 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9825 if columns_to_prev_tab_stop == 0 {
9826 tab_size
9827 } else {
9828 columns_to_prev_tab_stop
9829 }
9830 }
9831 IndentKind::Tab => 1,
9832 };
9833 let start = if has_multiple_rows
9834 || deletion_len > selection.start.column
9835 || indent_size.len < selection.start.column
9836 {
9837 0
9838 } else {
9839 selection.start.column - deletion_len
9840 };
9841 deletion_ranges.push(
9842 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9843 );
9844 last_outdent = Some(row);
9845 }
9846 }
9847 }
9848 }
9849
9850 self.transact(window, cx, |this, window, cx| {
9851 this.buffer.update(cx, |buffer, cx| {
9852 let empty_str: Arc<str> = Arc::default();
9853 buffer.edit(
9854 deletion_ranges
9855 .into_iter()
9856 .map(|range| (range, empty_str.clone())),
9857 None,
9858 cx,
9859 );
9860 });
9861 let selections = this.selections.all::<usize>(cx);
9862 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9863 s.select(selections)
9864 });
9865 });
9866 }
9867
9868 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9869 if self.read_only(cx) {
9870 return;
9871 }
9872 if self.mode.is_single_line() {
9873 cx.propagate();
9874 return;
9875 }
9876
9877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9878 let selections = self
9879 .selections
9880 .all::<usize>(cx)
9881 .into_iter()
9882 .map(|s| s.range());
9883
9884 self.transact(window, cx, |this, window, cx| {
9885 this.buffer.update(cx, |buffer, cx| {
9886 buffer.autoindent_ranges(selections, cx);
9887 });
9888 let selections = this.selections.all::<usize>(cx);
9889 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9890 s.select(selections)
9891 });
9892 });
9893 }
9894
9895 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9896 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9897 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9898 let selections = self.selections.all::<Point>(cx);
9899
9900 let mut new_cursors = Vec::new();
9901 let mut edit_ranges = Vec::new();
9902 let mut selections = selections.iter().peekable();
9903 while let Some(selection) = selections.next() {
9904 let mut rows = selection.spanned_rows(false, &display_map);
9905 let goal_display_column = selection.head().to_display_point(&display_map).column();
9906
9907 // Accumulate contiguous regions of rows that we want to delete.
9908 while let Some(next_selection) = selections.peek() {
9909 let next_rows = next_selection.spanned_rows(false, &display_map);
9910 if next_rows.start <= rows.end {
9911 rows.end = next_rows.end;
9912 selections.next().unwrap();
9913 } else {
9914 break;
9915 }
9916 }
9917
9918 let buffer = &display_map.buffer_snapshot;
9919 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9920 let edit_end;
9921 let cursor_buffer_row;
9922 if buffer.max_point().row >= rows.end.0 {
9923 // If there's a line after the range, delete the \n from the end of the row range
9924 // and position the cursor on the next line.
9925 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9926 cursor_buffer_row = rows.end;
9927 } else {
9928 // If there isn't a line after the range, delete the \n from the line before the
9929 // start of the row range and position the cursor there.
9930 edit_start = edit_start.saturating_sub(1);
9931 edit_end = buffer.len();
9932 cursor_buffer_row = rows.start.previous_row();
9933 }
9934
9935 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9936 *cursor.column_mut() =
9937 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9938
9939 new_cursors.push((
9940 selection.id,
9941 buffer.anchor_after(cursor.to_point(&display_map)),
9942 ));
9943 edit_ranges.push(edit_start..edit_end);
9944 }
9945
9946 self.transact(window, cx, |this, window, cx| {
9947 let buffer = this.buffer.update(cx, |buffer, cx| {
9948 let empty_str: Arc<str> = Arc::default();
9949 buffer.edit(
9950 edit_ranges
9951 .into_iter()
9952 .map(|range| (range, empty_str.clone())),
9953 None,
9954 cx,
9955 );
9956 buffer.snapshot(cx)
9957 });
9958 let new_selections = new_cursors
9959 .into_iter()
9960 .map(|(id, cursor)| {
9961 let cursor = cursor.to_point(&buffer);
9962 Selection {
9963 id,
9964 start: cursor,
9965 end: cursor,
9966 reversed: false,
9967 goal: SelectionGoal::None,
9968 }
9969 })
9970 .collect();
9971
9972 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9973 s.select(new_selections);
9974 });
9975 });
9976 }
9977
9978 pub fn join_lines_impl(
9979 &mut self,
9980 insert_whitespace: bool,
9981 window: &mut Window,
9982 cx: &mut Context<Self>,
9983 ) {
9984 if self.read_only(cx) {
9985 return;
9986 }
9987 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9988 for selection in self.selections.all::<Point>(cx) {
9989 let start = MultiBufferRow(selection.start.row);
9990 // Treat single line selections as if they include the next line. Otherwise this action
9991 // would do nothing for single line selections individual cursors.
9992 let end = if selection.start.row == selection.end.row {
9993 MultiBufferRow(selection.start.row + 1)
9994 } else {
9995 MultiBufferRow(selection.end.row)
9996 };
9997
9998 if let Some(last_row_range) = row_ranges.last_mut() {
9999 if start <= last_row_range.end {
10000 last_row_range.end = end;
10001 continue;
10002 }
10003 }
10004 row_ranges.push(start..end);
10005 }
10006
10007 let snapshot = self.buffer.read(cx).snapshot(cx);
10008 let mut cursor_positions = Vec::new();
10009 for row_range in &row_ranges {
10010 let anchor = snapshot.anchor_before(Point::new(
10011 row_range.end.previous_row().0,
10012 snapshot.line_len(row_range.end.previous_row()),
10013 ));
10014 cursor_positions.push(anchor..anchor);
10015 }
10016
10017 self.transact(window, cx, |this, window, cx| {
10018 for row_range in row_ranges.into_iter().rev() {
10019 for row in row_range.iter_rows().rev() {
10020 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10021 let next_line_row = row.next_row();
10022 let indent = snapshot.indent_size_for_line(next_line_row);
10023 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10024
10025 let replace =
10026 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10027 " "
10028 } else {
10029 ""
10030 };
10031
10032 this.buffer.update(cx, |buffer, cx| {
10033 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10034 });
10035 }
10036 }
10037
10038 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10039 s.select_anchor_ranges(cursor_positions)
10040 });
10041 });
10042 }
10043
10044 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10045 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10046 self.join_lines_impl(true, window, cx);
10047 }
10048
10049 pub fn sort_lines_case_sensitive(
10050 &mut self,
10051 _: &SortLinesCaseSensitive,
10052 window: &mut Window,
10053 cx: &mut Context<Self>,
10054 ) {
10055 self.manipulate_lines(window, cx, |lines| lines.sort())
10056 }
10057
10058 pub fn sort_lines_case_insensitive(
10059 &mut self,
10060 _: &SortLinesCaseInsensitive,
10061 window: &mut Window,
10062 cx: &mut Context<Self>,
10063 ) {
10064 self.manipulate_lines(window, cx, |lines| {
10065 lines.sort_by_key(|line| line.to_lowercase())
10066 })
10067 }
10068
10069 pub fn unique_lines_case_insensitive(
10070 &mut self,
10071 _: &UniqueLinesCaseInsensitive,
10072 window: &mut Window,
10073 cx: &mut Context<Self>,
10074 ) {
10075 self.manipulate_lines(window, cx, |lines| {
10076 let mut seen = HashSet::default();
10077 lines.retain(|line| seen.insert(line.to_lowercase()));
10078 })
10079 }
10080
10081 pub fn unique_lines_case_sensitive(
10082 &mut self,
10083 _: &UniqueLinesCaseSensitive,
10084 window: &mut Window,
10085 cx: &mut Context<Self>,
10086 ) {
10087 self.manipulate_lines(window, cx, |lines| {
10088 let mut seen = HashSet::default();
10089 lines.retain(|line| seen.insert(*line));
10090 })
10091 }
10092
10093 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10094 let Some(project) = self.project.clone() else {
10095 return;
10096 };
10097 self.reload(project, window, cx)
10098 .detach_and_notify_err(window, cx);
10099 }
10100
10101 pub fn restore_file(
10102 &mut self,
10103 _: &::git::RestoreFile,
10104 window: &mut Window,
10105 cx: &mut Context<Self>,
10106 ) {
10107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10108 let mut buffer_ids = HashSet::default();
10109 let snapshot = self.buffer().read(cx).snapshot(cx);
10110 for selection in self.selections.all::<usize>(cx) {
10111 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10112 }
10113
10114 let buffer = self.buffer().read(cx);
10115 let ranges = buffer_ids
10116 .into_iter()
10117 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10118 .collect::<Vec<_>>();
10119
10120 self.restore_hunks_in_ranges(ranges, window, cx);
10121 }
10122
10123 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10124 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10125 let selections = self
10126 .selections
10127 .all(cx)
10128 .into_iter()
10129 .map(|s| s.range())
10130 .collect();
10131 self.restore_hunks_in_ranges(selections, window, cx);
10132 }
10133
10134 pub fn restore_hunks_in_ranges(
10135 &mut self,
10136 ranges: Vec<Range<Point>>,
10137 window: &mut Window,
10138 cx: &mut Context<Editor>,
10139 ) {
10140 let mut revert_changes = HashMap::default();
10141 let chunk_by = self
10142 .snapshot(window, cx)
10143 .hunks_for_ranges(ranges)
10144 .into_iter()
10145 .chunk_by(|hunk| hunk.buffer_id);
10146 for (buffer_id, hunks) in &chunk_by {
10147 let hunks = hunks.collect::<Vec<_>>();
10148 for hunk in &hunks {
10149 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10150 }
10151 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10152 }
10153 drop(chunk_by);
10154 if !revert_changes.is_empty() {
10155 self.transact(window, cx, |editor, window, cx| {
10156 editor.restore(revert_changes, window, cx);
10157 });
10158 }
10159 }
10160
10161 pub fn open_active_item_in_terminal(
10162 &mut self,
10163 _: &OpenInTerminal,
10164 window: &mut Window,
10165 cx: &mut Context<Self>,
10166 ) {
10167 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10168 let project_path = buffer.read(cx).project_path(cx)?;
10169 let project = self.project.as_ref()?.read(cx);
10170 let entry = project.entry_for_path(&project_path, cx)?;
10171 let parent = match &entry.canonical_path {
10172 Some(canonical_path) => canonical_path.to_path_buf(),
10173 None => project.absolute_path(&project_path, cx)?,
10174 }
10175 .parent()?
10176 .to_path_buf();
10177 Some(parent)
10178 }) {
10179 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10180 }
10181 }
10182
10183 fn set_breakpoint_context_menu(
10184 &mut self,
10185 display_row: DisplayRow,
10186 position: Option<Anchor>,
10187 clicked_point: gpui::Point<Pixels>,
10188 window: &mut Window,
10189 cx: &mut Context<Self>,
10190 ) {
10191 let source = self
10192 .buffer
10193 .read(cx)
10194 .snapshot(cx)
10195 .anchor_before(Point::new(display_row.0, 0u32));
10196
10197 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10198
10199 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10200 self,
10201 source,
10202 clicked_point,
10203 context_menu,
10204 window,
10205 cx,
10206 );
10207 }
10208
10209 fn add_edit_breakpoint_block(
10210 &mut self,
10211 anchor: Anchor,
10212 breakpoint: &Breakpoint,
10213 edit_action: BreakpointPromptEditAction,
10214 window: &mut Window,
10215 cx: &mut Context<Self>,
10216 ) {
10217 let weak_editor = cx.weak_entity();
10218 let bp_prompt = cx.new(|cx| {
10219 BreakpointPromptEditor::new(
10220 weak_editor,
10221 anchor,
10222 breakpoint.clone(),
10223 edit_action,
10224 window,
10225 cx,
10226 )
10227 });
10228
10229 let height = bp_prompt.update(cx, |this, cx| {
10230 this.prompt
10231 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10232 });
10233 let cloned_prompt = bp_prompt.clone();
10234 let blocks = vec![BlockProperties {
10235 style: BlockStyle::Sticky,
10236 placement: BlockPlacement::Above(anchor),
10237 height: Some(height),
10238 render: Arc::new(move |cx| {
10239 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10240 cloned_prompt.clone().into_any_element()
10241 }),
10242 priority: 0,
10243 render_in_minimap: true,
10244 }];
10245
10246 let focus_handle = bp_prompt.focus_handle(cx);
10247 window.focus(&focus_handle);
10248
10249 let block_ids = self.insert_blocks(blocks, None, cx);
10250 bp_prompt.update(cx, |prompt, _| {
10251 prompt.add_block_ids(block_ids);
10252 });
10253 }
10254
10255 pub(crate) fn breakpoint_at_row(
10256 &self,
10257 row: u32,
10258 window: &mut Window,
10259 cx: &mut Context<Self>,
10260 ) -> Option<(Anchor, Breakpoint)> {
10261 let snapshot = self.snapshot(window, cx);
10262 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10263
10264 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10265 }
10266
10267 pub(crate) fn breakpoint_at_anchor(
10268 &self,
10269 breakpoint_position: Anchor,
10270 snapshot: &EditorSnapshot,
10271 cx: &mut Context<Self>,
10272 ) -> Option<(Anchor, Breakpoint)> {
10273 let project = self.project.clone()?;
10274
10275 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10276 snapshot
10277 .buffer_snapshot
10278 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10279 })?;
10280
10281 let enclosing_excerpt = breakpoint_position.excerpt_id;
10282 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10283 let buffer_snapshot = buffer.read(cx).snapshot();
10284
10285 let row = buffer_snapshot
10286 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10287 .row;
10288
10289 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10290 let anchor_end = snapshot
10291 .buffer_snapshot
10292 .anchor_after(Point::new(row, line_len));
10293
10294 let bp = self
10295 .breakpoint_store
10296 .as_ref()?
10297 .read_with(cx, |breakpoint_store, cx| {
10298 breakpoint_store
10299 .breakpoints(
10300 &buffer,
10301 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10302 &buffer_snapshot,
10303 cx,
10304 )
10305 .next()
10306 .and_then(|(bp, _)| {
10307 let breakpoint_row = buffer_snapshot
10308 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10309 .row;
10310
10311 if breakpoint_row == row {
10312 snapshot
10313 .buffer_snapshot
10314 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10315 .map(|position| (position, bp.bp.clone()))
10316 } else {
10317 None
10318 }
10319 })
10320 });
10321 bp
10322 }
10323
10324 pub fn edit_log_breakpoint(
10325 &mut self,
10326 _: &EditLogBreakpoint,
10327 window: &mut Window,
10328 cx: &mut Context<Self>,
10329 ) {
10330 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10331 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10332 message: None,
10333 state: BreakpointState::Enabled,
10334 condition: None,
10335 hit_condition: None,
10336 });
10337
10338 self.add_edit_breakpoint_block(
10339 anchor,
10340 &breakpoint,
10341 BreakpointPromptEditAction::Log,
10342 window,
10343 cx,
10344 );
10345 }
10346 }
10347
10348 fn breakpoints_at_cursors(
10349 &self,
10350 window: &mut Window,
10351 cx: &mut Context<Self>,
10352 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10353 let snapshot = self.snapshot(window, cx);
10354 let cursors = self
10355 .selections
10356 .disjoint_anchors()
10357 .into_iter()
10358 .map(|selection| {
10359 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10360
10361 let breakpoint_position = self
10362 .breakpoint_at_row(cursor_position.row, window, cx)
10363 .map(|bp| bp.0)
10364 .unwrap_or_else(|| {
10365 snapshot
10366 .display_snapshot
10367 .buffer_snapshot
10368 .anchor_after(Point::new(cursor_position.row, 0))
10369 });
10370
10371 let breakpoint = self
10372 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10373 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10374
10375 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10376 })
10377 // 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.
10378 .collect::<HashMap<Anchor, _>>();
10379
10380 cursors.into_iter().collect()
10381 }
10382
10383 pub fn enable_breakpoint(
10384 &mut self,
10385 _: &crate::actions::EnableBreakpoint,
10386 window: &mut Window,
10387 cx: &mut Context<Self>,
10388 ) {
10389 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10390 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10391 continue;
10392 };
10393 self.edit_breakpoint_at_anchor(
10394 anchor,
10395 breakpoint,
10396 BreakpointEditAction::InvertState,
10397 cx,
10398 );
10399 }
10400 }
10401
10402 pub fn disable_breakpoint(
10403 &mut self,
10404 _: &crate::actions::DisableBreakpoint,
10405 window: &mut Window,
10406 cx: &mut Context<Self>,
10407 ) {
10408 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10409 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10410 continue;
10411 };
10412 self.edit_breakpoint_at_anchor(
10413 anchor,
10414 breakpoint,
10415 BreakpointEditAction::InvertState,
10416 cx,
10417 );
10418 }
10419 }
10420
10421 pub fn toggle_breakpoint(
10422 &mut self,
10423 _: &crate::actions::ToggleBreakpoint,
10424 window: &mut Window,
10425 cx: &mut Context<Self>,
10426 ) {
10427 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10428 if let Some(breakpoint) = breakpoint {
10429 self.edit_breakpoint_at_anchor(
10430 anchor,
10431 breakpoint,
10432 BreakpointEditAction::Toggle,
10433 cx,
10434 );
10435 } else {
10436 self.edit_breakpoint_at_anchor(
10437 anchor,
10438 Breakpoint::new_standard(),
10439 BreakpointEditAction::Toggle,
10440 cx,
10441 );
10442 }
10443 }
10444 }
10445
10446 pub fn edit_breakpoint_at_anchor(
10447 &mut self,
10448 breakpoint_position: Anchor,
10449 breakpoint: Breakpoint,
10450 edit_action: BreakpointEditAction,
10451 cx: &mut Context<Self>,
10452 ) {
10453 let Some(breakpoint_store) = &self.breakpoint_store else {
10454 return;
10455 };
10456
10457 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10458 if breakpoint_position == Anchor::min() {
10459 self.buffer()
10460 .read(cx)
10461 .excerpt_buffer_ids()
10462 .into_iter()
10463 .next()
10464 } else {
10465 None
10466 }
10467 }) else {
10468 return;
10469 };
10470
10471 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10472 return;
10473 };
10474
10475 breakpoint_store.update(cx, |breakpoint_store, cx| {
10476 breakpoint_store.toggle_breakpoint(
10477 buffer,
10478 BreakpointWithPosition {
10479 position: breakpoint_position.text_anchor,
10480 bp: breakpoint,
10481 },
10482 edit_action,
10483 cx,
10484 );
10485 });
10486
10487 cx.notify();
10488 }
10489
10490 #[cfg(any(test, feature = "test-support"))]
10491 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10492 self.breakpoint_store.clone()
10493 }
10494
10495 pub fn prepare_restore_change(
10496 &self,
10497 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10498 hunk: &MultiBufferDiffHunk,
10499 cx: &mut App,
10500 ) -> Option<()> {
10501 if hunk.is_created_file() {
10502 return None;
10503 }
10504 let buffer = self.buffer.read(cx);
10505 let diff = buffer.diff_for(hunk.buffer_id)?;
10506 let buffer = buffer.buffer(hunk.buffer_id)?;
10507 let buffer = buffer.read(cx);
10508 let original_text = diff
10509 .read(cx)
10510 .base_text()
10511 .as_rope()
10512 .slice(hunk.diff_base_byte_range.clone());
10513 let buffer_snapshot = buffer.snapshot();
10514 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10515 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10516 probe
10517 .0
10518 .start
10519 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10520 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10521 }) {
10522 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10523 Some(())
10524 } else {
10525 None
10526 }
10527 }
10528
10529 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10530 self.manipulate_lines(window, cx, |lines| lines.reverse())
10531 }
10532
10533 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10534 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10535 }
10536
10537 fn manipulate_lines<Fn>(
10538 &mut self,
10539 window: &mut Window,
10540 cx: &mut Context<Self>,
10541 mut callback: Fn,
10542 ) where
10543 Fn: FnMut(&mut Vec<&str>),
10544 {
10545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10546
10547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10548 let buffer = self.buffer.read(cx).snapshot(cx);
10549
10550 let mut edits = Vec::new();
10551
10552 let selections = self.selections.all::<Point>(cx);
10553 let mut selections = selections.iter().peekable();
10554 let mut contiguous_row_selections = Vec::new();
10555 let mut new_selections = Vec::new();
10556 let mut added_lines = 0;
10557 let mut removed_lines = 0;
10558
10559 while let Some(selection) = selections.next() {
10560 let (start_row, end_row) = consume_contiguous_rows(
10561 &mut contiguous_row_selections,
10562 selection,
10563 &display_map,
10564 &mut selections,
10565 );
10566
10567 let start_point = Point::new(start_row.0, 0);
10568 let end_point = Point::new(
10569 end_row.previous_row().0,
10570 buffer.line_len(end_row.previous_row()),
10571 );
10572 let text = buffer
10573 .text_for_range(start_point..end_point)
10574 .collect::<String>();
10575
10576 let mut lines = text.split('\n').collect_vec();
10577
10578 let lines_before = lines.len();
10579 callback(&mut lines);
10580 let lines_after = lines.len();
10581
10582 edits.push((start_point..end_point, lines.join("\n")));
10583
10584 // Selections must change based on added and removed line count
10585 let start_row =
10586 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10587 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10588 new_selections.push(Selection {
10589 id: selection.id,
10590 start: start_row,
10591 end: end_row,
10592 goal: SelectionGoal::None,
10593 reversed: selection.reversed,
10594 });
10595
10596 if lines_after > lines_before {
10597 added_lines += lines_after - lines_before;
10598 } else if lines_before > lines_after {
10599 removed_lines += lines_before - lines_after;
10600 }
10601 }
10602
10603 self.transact(window, cx, |this, window, cx| {
10604 let buffer = this.buffer.update(cx, |buffer, cx| {
10605 buffer.edit(edits, None, cx);
10606 buffer.snapshot(cx)
10607 });
10608
10609 // Recalculate offsets on newly edited buffer
10610 let new_selections = new_selections
10611 .iter()
10612 .map(|s| {
10613 let start_point = Point::new(s.start.0, 0);
10614 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10615 Selection {
10616 id: s.id,
10617 start: buffer.point_to_offset(start_point),
10618 end: buffer.point_to_offset(end_point),
10619 goal: s.goal,
10620 reversed: s.reversed,
10621 }
10622 })
10623 .collect();
10624
10625 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10626 s.select(new_selections);
10627 });
10628
10629 this.request_autoscroll(Autoscroll::fit(), cx);
10630 });
10631 }
10632
10633 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10634 self.manipulate_text(window, cx, |text| {
10635 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10636 if has_upper_case_characters {
10637 text.to_lowercase()
10638 } else {
10639 text.to_uppercase()
10640 }
10641 })
10642 }
10643
10644 pub fn convert_to_upper_case(
10645 &mut self,
10646 _: &ConvertToUpperCase,
10647 window: &mut Window,
10648 cx: &mut Context<Self>,
10649 ) {
10650 self.manipulate_text(window, cx, |text| text.to_uppercase())
10651 }
10652
10653 pub fn convert_to_lower_case(
10654 &mut self,
10655 _: &ConvertToLowerCase,
10656 window: &mut Window,
10657 cx: &mut Context<Self>,
10658 ) {
10659 self.manipulate_text(window, cx, |text| text.to_lowercase())
10660 }
10661
10662 pub fn convert_to_title_case(
10663 &mut self,
10664 _: &ConvertToTitleCase,
10665 window: &mut Window,
10666 cx: &mut Context<Self>,
10667 ) {
10668 self.manipulate_text(window, cx, |text| {
10669 text.split('\n')
10670 .map(|line| line.to_case(Case::Title))
10671 .join("\n")
10672 })
10673 }
10674
10675 pub fn convert_to_snake_case(
10676 &mut self,
10677 _: &ConvertToSnakeCase,
10678 window: &mut Window,
10679 cx: &mut Context<Self>,
10680 ) {
10681 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10682 }
10683
10684 pub fn convert_to_kebab_case(
10685 &mut self,
10686 _: &ConvertToKebabCase,
10687 window: &mut Window,
10688 cx: &mut Context<Self>,
10689 ) {
10690 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10691 }
10692
10693 pub fn convert_to_upper_camel_case(
10694 &mut self,
10695 _: &ConvertToUpperCamelCase,
10696 window: &mut Window,
10697 cx: &mut Context<Self>,
10698 ) {
10699 self.manipulate_text(window, cx, |text| {
10700 text.split('\n')
10701 .map(|line| line.to_case(Case::UpperCamel))
10702 .join("\n")
10703 })
10704 }
10705
10706 pub fn convert_to_lower_camel_case(
10707 &mut self,
10708 _: &ConvertToLowerCamelCase,
10709 window: &mut Window,
10710 cx: &mut Context<Self>,
10711 ) {
10712 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10713 }
10714
10715 pub fn convert_to_opposite_case(
10716 &mut self,
10717 _: &ConvertToOppositeCase,
10718 window: &mut Window,
10719 cx: &mut Context<Self>,
10720 ) {
10721 self.manipulate_text(window, cx, |text| {
10722 text.chars()
10723 .fold(String::with_capacity(text.len()), |mut t, c| {
10724 if c.is_uppercase() {
10725 t.extend(c.to_lowercase());
10726 } else {
10727 t.extend(c.to_uppercase());
10728 }
10729 t
10730 })
10731 })
10732 }
10733
10734 pub fn convert_to_rot13(
10735 &mut self,
10736 _: &ConvertToRot13,
10737 window: &mut Window,
10738 cx: &mut Context<Self>,
10739 ) {
10740 self.manipulate_text(window, cx, |text| {
10741 text.chars()
10742 .map(|c| match c {
10743 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10744 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10745 _ => c,
10746 })
10747 .collect()
10748 })
10749 }
10750
10751 pub fn convert_to_rot47(
10752 &mut self,
10753 _: &ConvertToRot47,
10754 window: &mut Window,
10755 cx: &mut Context<Self>,
10756 ) {
10757 self.manipulate_text(window, cx, |text| {
10758 text.chars()
10759 .map(|c| {
10760 let code_point = c as u32;
10761 if code_point >= 33 && code_point <= 126 {
10762 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10763 }
10764 c
10765 })
10766 .collect()
10767 })
10768 }
10769
10770 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10771 where
10772 Fn: FnMut(&str) -> String,
10773 {
10774 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10775 let buffer = self.buffer.read(cx).snapshot(cx);
10776
10777 let mut new_selections = Vec::new();
10778 let mut edits = Vec::new();
10779 let mut selection_adjustment = 0i32;
10780
10781 for selection in self.selections.all::<usize>(cx) {
10782 let selection_is_empty = selection.is_empty();
10783
10784 let (start, end) = if selection_is_empty {
10785 let word_range = movement::surrounding_word(
10786 &display_map,
10787 selection.start.to_display_point(&display_map),
10788 );
10789 let start = word_range.start.to_offset(&display_map, Bias::Left);
10790 let end = word_range.end.to_offset(&display_map, Bias::Left);
10791 (start, end)
10792 } else {
10793 (selection.start, selection.end)
10794 };
10795
10796 let text = buffer.text_for_range(start..end).collect::<String>();
10797 let old_length = text.len() as i32;
10798 let text = callback(&text);
10799
10800 new_selections.push(Selection {
10801 start: (start as i32 - selection_adjustment) as usize,
10802 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10803 goal: SelectionGoal::None,
10804 ..selection
10805 });
10806
10807 selection_adjustment += old_length - text.len() as i32;
10808
10809 edits.push((start..end, text));
10810 }
10811
10812 self.transact(window, cx, |this, window, cx| {
10813 this.buffer.update(cx, |buffer, cx| {
10814 buffer.edit(edits, None, cx);
10815 });
10816
10817 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10818 s.select(new_selections);
10819 });
10820
10821 this.request_autoscroll(Autoscroll::fit(), cx);
10822 });
10823 }
10824
10825 pub fn move_selection_on_drop(
10826 &mut self,
10827 selection: &Selection<Anchor>,
10828 target: DisplayPoint,
10829 is_cut: bool,
10830 window: &mut Window,
10831 cx: &mut Context<Self>,
10832 ) {
10833 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10834 let buffer = &display_map.buffer_snapshot;
10835 let mut edits = Vec::new();
10836 let insert_point = display_map
10837 .clip_point(target, Bias::Left)
10838 .to_point(&display_map);
10839 let text = buffer
10840 .text_for_range(selection.start..selection.end)
10841 .collect::<String>();
10842 if is_cut {
10843 edits.push(((selection.start..selection.end), String::new()));
10844 }
10845 let insert_anchor = buffer.anchor_before(insert_point);
10846 edits.push(((insert_anchor..insert_anchor), text));
10847 let last_edit_start = insert_anchor.bias_left(buffer);
10848 let last_edit_end = insert_anchor.bias_right(buffer);
10849 self.transact(window, cx, |this, window, cx| {
10850 this.buffer.update(cx, |buffer, cx| {
10851 buffer.edit(edits, None, cx);
10852 });
10853 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10854 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10855 });
10856 });
10857 }
10858
10859 pub fn clear_selection_drag_state(&mut self) {
10860 self.selection_drag_state = SelectionDragState::None;
10861 }
10862
10863 pub fn duplicate(
10864 &mut self,
10865 upwards: bool,
10866 whole_lines: bool,
10867 window: &mut Window,
10868 cx: &mut Context<Self>,
10869 ) {
10870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10871
10872 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10873 let buffer = &display_map.buffer_snapshot;
10874 let selections = self.selections.all::<Point>(cx);
10875
10876 let mut edits = Vec::new();
10877 let mut selections_iter = selections.iter().peekable();
10878 while let Some(selection) = selections_iter.next() {
10879 let mut rows = selection.spanned_rows(false, &display_map);
10880 // duplicate line-wise
10881 if whole_lines || selection.start == selection.end {
10882 // Avoid duplicating the same lines twice.
10883 while let Some(next_selection) = selections_iter.peek() {
10884 let next_rows = next_selection.spanned_rows(false, &display_map);
10885 if next_rows.start < rows.end {
10886 rows.end = next_rows.end;
10887 selections_iter.next().unwrap();
10888 } else {
10889 break;
10890 }
10891 }
10892
10893 // Copy the text from the selected row region and splice it either at the start
10894 // or end of the region.
10895 let start = Point::new(rows.start.0, 0);
10896 let end = Point::new(
10897 rows.end.previous_row().0,
10898 buffer.line_len(rows.end.previous_row()),
10899 );
10900 let text = buffer
10901 .text_for_range(start..end)
10902 .chain(Some("\n"))
10903 .collect::<String>();
10904 let insert_location = if upwards {
10905 Point::new(rows.end.0, 0)
10906 } else {
10907 start
10908 };
10909 edits.push((insert_location..insert_location, text));
10910 } else {
10911 // duplicate character-wise
10912 let start = selection.start;
10913 let end = selection.end;
10914 let text = buffer.text_for_range(start..end).collect::<String>();
10915 edits.push((selection.end..selection.end, text));
10916 }
10917 }
10918
10919 self.transact(window, cx, |this, _, cx| {
10920 this.buffer.update(cx, |buffer, cx| {
10921 buffer.edit(edits, None, cx);
10922 });
10923
10924 this.request_autoscroll(Autoscroll::fit(), cx);
10925 });
10926 }
10927
10928 pub fn duplicate_line_up(
10929 &mut self,
10930 _: &DuplicateLineUp,
10931 window: &mut Window,
10932 cx: &mut Context<Self>,
10933 ) {
10934 self.duplicate(true, true, window, cx);
10935 }
10936
10937 pub fn duplicate_line_down(
10938 &mut self,
10939 _: &DuplicateLineDown,
10940 window: &mut Window,
10941 cx: &mut Context<Self>,
10942 ) {
10943 self.duplicate(false, true, window, cx);
10944 }
10945
10946 pub fn duplicate_selection(
10947 &mut self,
10948 _: &DuplicateSelection,
10949 window: &mut Window,
10950 cx: &mut Context<Self>,
10951 ) {
10952 self.duplicate(false, false, window, cx);
10953 }
10954
10955 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10956 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10957 if self.mode.is_single_line() {
10958 cx.propagate();
10959 return;
10960 }
10961
10962 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10963 let buffer = self.buffer.read(cx).snapshot(cx);
10964
10965 let mut edits = Vec::new();
10966 let mut unfold_ranges = Vec::new();
10967 let mut refold_creases = Vec::new();
10968
10969 let selections = self.selections.all::<Point>(cx);
10970 let mut selections = selections.iter().peekable();
10971 let mut contiguous_row_selections = Vec::new();
10972 let mut new_selections = Vec::new();
10973
10974 while let Some(selection) = selections.next() {
10975 // Find all the selections that span a contiguous row range
10976 let (start_row, end_row) = consume_contiguous_rows(
10977 &mut contiguous_row_selections,
10978 selection,
10979 &display_map,
10980 &mut selections,
10981 );
10982
10983 // Move the text spanned by the row range to be before the line preceding the row range
10984 if start_row.0 > 0 {
10985 let range_to_move = Point::new(
10986 start_row.previous_row().0,
10987 buffer.line_len(start_row.previous_row()),
10988 )
10989 ..Point::new(
10990 end_row.previous_row().0,
10991 buffer.line_len(end_row.previous_row()),
10992 );
10993 let insertion_point = display_map
10994 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10995 .0;
10996
10997 // Don't move lines across excerpts
10998 if buffer
10999 .excerpt_containing(insertion_point..range_to_move.end)
11000 .is_some()
11001 {
11002 let text = buffer
11003 .text_for_range(range_to_move.clone())
11004 .flat_map(|s| s.chars())
11005 .skip(1)
11006 .chain(['\n'])
11007 .collect::<String>();
11008
11009 edits.push((
11010 buffer.anchor_after(range_to_move.start)
11011 ..buffer.anchor_before(range_to_move.end),
11012 String::new(),
11013 ));
11014 let insertion_anchor = buffer.anchor_after(insertion_point);
11015 edits.push((insertion_anchor..insertion_anchor, text));
11016
11017 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11018
11019 // Move selections up
11020 new_selections.extend(contiguous_row_selections.drain(..).map(
11021 |mut selection| {
11022 selection.start.row -= row_delta;
11023 selection.end.row -= row_delta;
11024 selection
11025 },
11026 ));
11027
11028 // Move folds up
11029 unfold_ranges.push(range_to_move.clone());
11030 for fold in display_map.folds_in_range(
11031 buffer.anchor_before(range_to_move.start)
11032 ..buffer.anchor_after(range_to_move.end),
11033 ) {
11034 let mut start = fold.range.start.to_point(&buffer);
11035 let mut end = fold.range.end.to_point(&buffer);
11036 start.row -= row_delta;
11037 end.row -= row_delta;
11038 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11039 }
11040 }
11041 }
11042
11043 // If we didn't move line(s), preserve the existing selections
11044 new_selections.append(&mut contiguous_row_selections);
11045 }
11046
11047 self.transact(window, cx, |this, window, cx| {
11048 this.unfold_ranges(&unfold_ranges, true, true, cx);
11049 this.buffer.update(cx, |buffer, cx| {
11050 for (range, text) in edits {
11051 buffer.edit([(range, text)], None, cx);
11052 }
11053 });
11054 this.fold_creases(refold_creases, true, window, cx);
11055 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11056 s.select(new_selections);
11057 })
11058 });
11059 }
11060
11061 pub fn move_line_down(
11062 &mut self,
11063 _: &MoveLineDown,
11064 window: &mut Window,
11065 cx: &mut Context<Self>,
11066 ) {
11067 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11068 if self.mode.is_single_line() {
11069 cx.propagate();
11070 return;
11071 }
11072
11073 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11074 let buffer = self.buffer.read(cx).snapshot(cx);
11075
11076 let mut edits = Vec::new();
11077 let mut unfold_ranges = Vec::new();
11078 let mut refold_creases = Vec::new();
11079
11080 let selections = self.selections.all::<Point>(cx);
11081 let mut selections = selections.iter().peekable();
11082 let mut contiguous_row_selections = Vec::new();
11083 let mut new_selections = Vec::new();
11084
11085 while let Some(selection) = selections.next() {
11086 // Find all the selections that span a contiguous row range
11087 let (start_row, end_row) = consume_contiguous_rows(
11088 &mut contiguous_row_selections,
11089 selection,
11090 &display_map,
11091 &mut selections,
11092 );
11093
11094 // Move the text spanned by the row range to be after the last line of the row range
11095 if end_row.0 <= buffer.max_point().row {
11096 let range_to_move =
11097 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11098 let insertion_point = display_map
11099 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11100 .0;
11101
11102 // Don't move lines across excerpt boundaries
11103 if buffer
11104 .excerpt_containing(range_to_move.start..insertion_point)
11105 .is_some()
11106 {
11107 let mut text = String::from("\n");
11108 text.extend(buffer.text_for_range(range_to_move.clone()));
11109 text.pop(); // Drop trailing newline
11110 edits.push((
11111 buffer.anchor_after(range_to_move.start)
11112 ..buffer.anchor_before(range_to_move.end),
11113 String::new(),
11114 ));
11115 let insertion_anchor = buffer.anchor_after(insertion_point);
11116 edits.push((insertion_anchor..insertion_anchor, text));
11117
11118 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11119
11120 // Move selections down
11121 new_selections.extend(contiguous_row_selections.drain(..).map(
11122 |mut selection| {
11123 selection.start.row += row_delta;
11124 selection.end.row += row_delta;
11125 selection
11126 },
11127 ));
11128
11129 // Move folds down
11130 unfold_ranges.push(range_to_move.clone());
11131 for fold in display_map.folds_in_range(
11132 buffer.anchor_before(range_to_move.start)
11133 ..buffer.anchor_after(range_to_move.end),
11134 ) {
11135 let mut start = fold.range.start.to_point(&buffer);
11136 let mut end = fold.range.end.to_point(&buffer);
11137 start.row += row_delta;
11138 end.row += row_delta;
11139 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11140 }
11141 }
11142 }
11143
11144 // If we didn't move line(s), preserve the existing selections
11145 new_selections.append(&mut contiguous_row_selections);
11146 }
11147
11148 self.transact(window, cx, |this, window, cx| {
11149 this.unfold_ranges(&unfold_ranges, true, true, cx);
11150 this.buffer.update(cx, |buffer, cx| {
11151 for (range, text) in edits {
11152 buffer.edit([(range, text)], None, cx);
11153 }
11154 });
11155 this.fold_creases(refold_creases, true, window, cx);
11156 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11157 s.select(new_selections)
11158 });
11159 });
11160 }
11161
11162 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11163 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11164 let text_layout_details = &self.text_layout_details(window);
11165 self.transact(window, cx, |this, window, cx| {
11166 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11167 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11168 s.move_with(|display_map, selection| {
11169 if !selection.is_empty() {
11170 return;
11171 }
11172
11173 let mut head = selection.head();
11174 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11175 if head.column() == display_map.line_len(head.row()) {
11176 transpose_offset = display_map
11177 .buffer_snapshot
11178 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11179 }
11180
11181 if transpose_offset == 0 {
11182 return;
11183 }
11184
11185 *head.column_mut() += 1;
11186 head = display_map.clip_point(head, Bias::Right);
11187 let goal = SelectionGoal::HorizontalPosition(
11188 display_map
11189 .x_for_display_point(head, text_layout_details)
11190 .into(),
11191 );
11192 selection.collapse_to(head, goal);
11193
11194 let transpose_start = display_map
11195 .buffer_snapshot
11196 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11197 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11198 let transpose_end = display_map
11199 .buffer_snapshot
11200 .clip_offset(transpose_offset + 1, Bias::Right);
11201 if let Some(ch) =
11202 display_map.buffer_snapshot.chars_at(transpose_start).next()
11203 {
11204 edits.push((transpose_start..transpose_offset, String::new()));
11205 edits.push((transpose_end..transpose_end, ch.to_string()));
11206 }
11207 }
11208 });
11209 edits
11210 });
11211 this.buffer
11212 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11213 let selections = this.selections.all::<usize>(cx);
11214 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11215 s.select(selections);
11216 });
11217 });
11218 }
11219
11220 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11221 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11222 if self.mode.is_single_line() {
11223 cx.propagate();
11224 return;
11225 }
11226
11227 self.rewrap_impl(RewrapOptions::default(), cx)
11228 }
11229
11230 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11231 let buffer = self.buffer.read(cx).snapshot(cx);
11232 let selections = self.selections.all::<Point>(cx);
11233
11234 // Shrink and split selections to respect paragraph boundaries.
11235 let ranges = selections.into_iter().flat_map(|selection| {
11236 let language_settings = buffer.language_settings_at(selection.head(), cx);
11237 let language_scope = buffer.language_scope_at(selection.head());
11238
11239 let Some(start_row) = (selection.start.row..=selection.end.row)
11240 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11241 else {
11242 return vec![];
11243 };
11244 let Some(end_row) = (selection.start.row..=selection.end.row)
11245 .rev()
11246 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11247 else {
11248 return vec![];
11249 };
11250
11251 let mut row = start_row;
11252 let mut ranges = Vec::new();
11253 while let Some(blank_row) =
11254 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11255 {
11256 let next_paragraph_start = (blank_row + 1..=end_row)
11257 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11258 .unwrap();
11259 ranges.push((
11260 language_settings.clone(),
11261 language_scope.clone(),
11262 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11263 ));
11264 row = next_paragraph_start;
11265 }
11266 ranges.push((
11267 language_settings.clone(),
11268 language_scope.clone(),
11269 Point::new(row, 0)..Point::new(end_row, 0),
11270 ));
11271
11272 ranges
11273 });
11274
11275 let mut edits = Vec::new();
11276 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11277
11278 for (language_settings, language_scope, range) in ranges {
11279 let mut start_row = range.start.row;
11280 let mut end_row = range.end.row;
11281
11282 // Skip selections that overlap with a range that has already been rewrapped.
11283 let selection_range = start_row..end_row;
11284 if rewrapped_row_ranges
11285 .iter()
11286 .any(|range| range.overlaps(&selection_range))
11287 {
11288 continue;
11289 }
11290
11291 let tab_size = language_settings.tab_size;
11292
11293 // Since not all lines in the selection may be at the same indent
11294 // level, choose the indent size that is the most common between all
11295 // of the lines.
11296 //
11297 // If there is a tie, we use the deepest indent.
11298 let (indent_size, indent_end) = {
11299 let mut indent_size_occurrences = HashMap::default();
11300 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11301
11302 for row in start_row..=end_row {
11303 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11304 rows_by_indent_size.entry(indent).or_default().push(row);
11305 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11306 }
11307
11308 let indent_size = indent_size_occurrences
11309 .into_iter()
11310 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11311 .map(|(indent, _)| indent)
11312 .unwrap_or_default();
11313 let row = rows_by_indent_size[&indent_size][0];
11314 let indent_end = Point::new(row, indent_size.len);
11315
11316 (indent_size, indent_end)
11317 };
11318
11319 let mut line_prefix = indent_size.chars().collect::<String>();
11320
11321 let mut inside_comment = false;
11322 if let Some(comment_prefix) = language_scope.and_then(|language| {
11323 language
11324 .line_comment_prefixes()
11325 .iter()
11326 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11327 .cloned()
11328 }) {
11329 line_prefix.push_str(&comment_prefix);
11330 inside_comment = true;
11331 }
11332
11333 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11334 RewrapBehavior::InComments => inside_comment,
11335 RewrapBehavior::InSelections => !range.is_empty(),
11336 RewrapBehavior::Anywhere => true,
11337 };
11338
11339 let should_rewrap = options.override_language_settings
11340 || allow_rewrap_based_on_language
11341 || self.hard_wrap.is_some();
11342 if !should_rewrap {
11343 continue;
11344 }
11345
11346 if range.is_empty() {
11347 'expand_upwards: while start_row > 0 {
11348 let prev_row = start_row - 1;
11349 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11350 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11351 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11352 {
11353 start_row = prev_row;
11354 } else {
11355 break 'expand_upwards;
11356 }
11357 }
11358
11359 'expand_downwards: while end_row < buffer.max_point().row {
11360 let next_row = end_row + 1;
11361 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11362 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11363 && !buffer.is_line_blank(MultiBufferRow(next_row))
11364 {
11365 end_row = next_row;
11366 } else {
11367 break 'expand_downwards;
11368 }
11369 }
11370 }
11371
11372 let start = Point::new(start_row, 0);
11373 let start_offset = start.to_offset(&buffer);
11374 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11375 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11376 let Some(lines_without_prefixes) = selection_text
11377 .lines()
11378 .map(|line| {
11379 line.strip_prefix(&line_prefix)
11380 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11381 .with_context(|| {
11382 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11383 })
11384 })
11385 .collect::<Result<Vec<_>, _>>()
11386 .log_err()
11387 else {
11388 continue;
11389 };
11390
11391 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11392 buffer
11393 .language_settings_at(Point::new(start_row, 0), cx)
11394 .preferred_line_length as usize
11395 });
11396 let wrapped_text = wrap_with_prefix(
11397 line_prefix,
11398 lines_without_prefixes.join("\n"),
11399 wrap_column,
11400 tab_size,
11401 options.preserve_existing_whitespace,
11402 );
11403
11404 // TODO: should always use char-based diff while still supporting cursor behavior that
11405 // matches vim.
11406 let mut diff_options = DiffOptions::default();
11407 if options.override_language_settings {
11408 diff_options.max_word_diff_len = 0;
11409 diff_options.max_word_diff_line_count = 0;
11410 } else {
11411 diff_options.max_word_diff_len = usize::MAX;
11412 diff_options.max_word_diff_line_count = usize::MAX;
11413 }
11414
11415 for (old_range, new_text) in
11416 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11417 {
11418 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11419 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11420 edits.push((edit_start..edit_end, new_text));
11421 }
11422
11423 rewrapped_row_ranges.push(start_row..=end_row);
11424 }
11425
11426 self.buffer
11427 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11428 }
11429
11430 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11431 let mut text = String::new();
11432 let buffer = self.buffer.read(cx).snapshot(cx);
11433 let mut selections = self.selections.all::<Point>(cx);
11434 let mut clipboard_selections = Vec::with_capacity(selections.len());
11435 {
11436 let max_point = buffer.max_point();
11437 let mut is_first = true;
11438 for selection in &mut selections {
11439 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11440 if is_entire_line {
11441 selection.start = Point::new(selection.start.row, 0);
11442 if !selection.is_empty() && selection.end.column == 0 {
11443 selection.end = cmp::min(max_point, selection.end);
11444 } else {
11445 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11446 }
11447 selection.goal = SelectionGoal::None;
11448 }
11449 if is_first {
11450 is_first = false;
11451 } else {
11452 text += "\n";
11453 }
11454 let mut len = 0;
11455 for chunk in buffer.text_for_range(selection.start..selection.end) {
11456 text.push_str(chunk);
11457 len += chunk.len();
11458 }
11459 clipboard_selections.push(ClipboardSelection {
11460 len,
11461 is_entire_line,
11462 first_line_indent: buffer
11463 .indent_size_for_line(MultiBufferRow(selection.start.row))
11464 .len,
11465 });
11466 }
11467 }
11468
11469 self.transact(window, cx, |this, window, cx| {
11470 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11471 s.select(selections);
11472 });
11473 this.insert("", window, cx);
11474 });
11475 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11476 }
11477
11478 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11480 let item = self.cut_common(window, cx);
11481 cx.write_to_clipboard(item);
11482 }
11483
11484 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11485 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11486 self.change_selections(None, window, cx, |s| {
11487 s.move_with(|snapshot, sel| {
11488 if sel.is_empty() {
11489 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11490 }
11491 });
11492 });
11493 let item = self.cut_common(window, cx);
11494 cx.set_global(KillRing(item))
11495 }
11496
11497 pub fn kill_ring_yank(
11498 &mut self,
11499 _: &KillRingYank,
11500 window: &mut Window,
11501 cx: &mut Context<Self>,
11502 ) {
11503 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11504 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11505 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11506 (kill_ring.text().to_string(), kill_ring.metadata_json())
11507 } else {
11508 return;
11509 }
11510 } else {
11511 return;
11512 };
11513 self.do_paste(&text, metadata, false, window, cx);
11514 }
11515
11516 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11517 self.do_copy(true, cx);
11518 }
11519
11520 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11521 self.do_copy(false, cx);
11522 }
11523
11524 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11525 let selections = self.selections.all::<Point>(cx);
11526 let buffer = self.buffer.read(cx).read(cx);
11527 let mut text = String::new();
11528
11529 let mut clipboard_selections = Vec::with_capacity(selections.len());
11530 {
11531 let max_point = buffer.max_point();
11532 let mut is_first = true;
11533 for selection in &selections {
11534 let mut start = selection.start;
11535 let mut end = selection.end;
11536 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11537 if is_entire_line {
11538 start = Point::new(start.row, 0);
11539 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11540 }
11541
11542 let mut trimmed_selections = Vec::new();
11543 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11544 let row = MultiBufferRow(start.row);
11545 let first_indent = buffer.indent_size_for_line(row);
11546 if first_indent.len == 0 || start.column > first_indent.len {
11547 trimmed_selections.push(start..end);
11548 } else {
11549 trimmed_selections.push(
11550 Point::new(row.0, first_indent.len)
11551 ..Point::new(row.0, buffer.line_len(row)),
11552 );
11553 for row in start.row + 1..=end.row {
11554 let mut line_len = buffer.line_len(MultiBufferRow(row));
11555 if row == end.row {
11556 line_len = end.column;
11557 }
11558 if line_len == 0 {
11559 trimmed_selections
11560 .push(Point::new(row, 0)..Point::new(row, line_len));
11561 continue;
11562 }
11563 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11564 if row_indent_size.len >= first_indent.len {
11565 trimmed_selections.push(
11566 Point::new(row, first_indent.len)..Point::new(row, line_len),
11567 );
11568 } else {
11569 trimmed_selections.clear();
11570 trimmed_selections.push(start..end);
11571 break;
11572 }
11573 }
11574 }
11575 } else {
11576 trimmed_selections.push(start..end);
11577 }
11578
11579 for trimmed_range in trimmed_selections {
11580 if is_first {
11581 is_first = false;
11582 } else {
11583 text += "\n";
11584 }
11585 let mut len = 0;
11586 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11587 text.push_str(chunk);
11588 len += chunk.len();
11589 }
11590 clipboard_selections.push(ClipboardSelection {
11591 len,
11592 is_entire_line,
11593 first_line_indent: buffer
11594 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11595 .len,
11596 });
11597 }
11598 }
11599 }
11600
11601 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11602 text,
11603 clipboard_selections,
11604 ));
11605 }
11606
11607 pub fn do_paste(
11608 &mut self,
11609 text: &String,
11610 clipboard_selections: Option<Vec<ClipboardSelection>>,
11611 handle_entire_lines: bool,
11612 window: &mut Window,
11613 cx: &mut Context<Self>,
11614 ) {
11615 if self.read_only(cx) {
11616 return;
11617 }
11618
11619 let clipboard_text = Cow::Borrowed(text);
11620
11621 self.transact(window, cx, |this, window, cx| {
11622 if let Some(mut clipboard_selections) = clipboard_selections {
11623 let old_selections = this.selections.all::<usize>(cx);
11624 let all_selections_were_entire_line =
11625 clipboard_selections.iter().all(|s| s.is_entire_line);
11626 let first_selection_indent_column =
11627 clipboard_selections.first().map(|s| s.first_line_indent);
11628 if clipboard_selections.len() != old_selections.len() {
11629 clipboard_selections.drain(..);
11630 }
11631 let cursor_offset = this.selections.last::<usize>(cx).head();
11632 let mut auto_indent_on_paste = true;
11633
11634 this.buffer.update(cx, |buffer, cx| {
11635 let snapshot = buffer.read(cx);
11636 auto_indent_on_paste = snapshot
11637 .language_settings_at(cursor_offset, cx)
11638 .auto_indent_on_paste;
11639
11640 let mut start_offset = 0;
11641 let mut edits = Vec::new();
11642 let mut original_indent_columns = Vec::new();
11643 for (ix, selection) in old_selections.iter().enumerate() {
11644 let to_insert;
11645 let entire_line;
11646 let original_indent_column;
11647 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11648 let end_offset = start_offset + clipboard_selection.len;
11649 to_insert = &clipboard_text[start_offset..end_offset];
11650 entire_line = clipboard_selection.is_entire_line;
11651 start_offset = end_offset + 1;
11652 original_indent_column = Some(clipboard_selection.first_line_indent);
11653 } else {
11654 to_insert = clipboard_text.as_str();
11655 entire_line = all_selections_were_entire_line;
11656 original_indent_column = first_selection_indent_column
11657 }
11658
11659 // If the corresponding selection was empty when this slice of the
11660 // clipboard text was written, then the entire line containing the
11661 // selection was copied. If this selection is also currently empty,
11662 // then paste the line before the current line of the buffer.
11663 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11664 let column = selection.start.to_point(&snapshot).column as usize;
11665 let line_start = selection.start - column;
11666 line_start..line_start
11667 } else {
11668 selection.range()
11669 };
11670
11671 edits.push((range, to_insert));
11672 original_indent_columns.push(original_indent_column);
11673 }
11674 drop(snapshot);
11675
11676 buffer.edit(
11677 edits,
11678 if auto_indent_on_paste {
11679 Some(AutoindentMode::Block {
11680 original_indent_columns,
11681 })
11682 } else {
11683 None
11684 },
11685 cx,
11686 );
11687 });
11688
11689 let selections = this.selections.all::<usize>(cx);
11690 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11691 s.select(selections)
11692 });
11693 } else {
11694 this.insert(&clipboard_text, window, cx);
11695 }
11696 });
11697 }
11698
11699 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11700 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11701 if let Some(item) = cx.read_from_clipboard() {
11702 let entries = item.entries();
11703
11704 match entries.first() {
11705 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11706 // of all the pasted entries.
11707 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11708 .do_paste(
11709 clipboard_string.text(),
11710 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11711 true,
11712 window,
11713 cx,
11714 ),
11715 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11716 }
11717 }
11718 }
11719
11720 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11721 if self.read_only(cx) {
11722 return;
11723 }
11724
11725 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11726
11727 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11728 if let Some((selections, _)) =
11729 self.selection_history.transaction(transaction_id).cloned()
11730 {
11731 self.change_selections(None, window, cx, |s| {
11732 s.select_anchors(selections.to_vec());
11733 });
11734 } else {
11735 log::error!(
11736 "No entry in selection_history found for undo. \
11737 This may correspond to a bug where undo does not update the selection. \
11738 If this is occurring, please add details to \
11739 https://github.com/zed-industries/zed/issues/22692"
11740 );
11741 }
11742 self.request_autoscroll(Autoscroll::fit(), cx);
11743 self.unmark_text(window, cx);
11744 self.refresh_inline_completion(true, false, window, cx);
11745 cx.emit(EditorEvent::Edited { transaction_id });
11746 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11747 }
11748 }
11749
11750 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11751 if self.read_only(cx) {
11752 return;
11753 }
11754
11755 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11756
11757 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11758 if let Some((_, Some(selections))) =
11759 self.selection_history.transaction(transaction_id).cloned()
11760 {
11761 self.change_selections(None, window, cx, |s| {
11762 s.select_anchors(selections.to_vec());
11763 });
11764 } else {
11765 log::error!(
11766 "No entry in selection_history found for redo. \
11767 This may correspond to a bug where undo does not update the selection. \
11768 If this is occurring, please add details to \
11769 https://github.com/zed-industries/zed/issues/22692"
11770 );
11771 }
11772 self.request_autoscroll(Autoscroll::fit(), cx);
11773 self.unmark_text(window, cx);
11774 self.refresh_inline_completion(true, false, window, cx);
11775 cx.emit(EditorEvent::Edited { transaction_id });
11776 }
11777 }
11778
11779 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11780 self.buffer
11781 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11782 }
11783
11784 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11785 self.buffer
11786 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11787 }
11788
11789 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11790 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11791 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11792 s.move_with(|map, selection| {
11793 let cursor = if selection.is_empty() {
11794 movement::left(map, selection.start)
11795 } else {
11796 selection.start
11797 };
11798 selection.collapse_to(cursor, SelectionGoal::None);
11799 });
11800 })
11801 }
11802
11803 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11804 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11805 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11806 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11807 })
11808 }
11809
11810 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11812 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11813 s.move_with(|map, selection| {
11814 let cursor = if selection.is_empty() {
11815 movement::right(map, selection.end)
11816 } else {
11817 selection.end
11818 };
11819 selection.collapse_to(cursor, SelectionGoal::None)
11820 });
11821 })
11822 }
11823
11824 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11826 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11827 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11828 })
11829 }
11830
11831 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11832 if self.take_rename(true, window, cx).is_some() {
11833 return;
11834 }
11835
11836 if self.mode.is_single_line() {
11837 cx.propagate();
11838 return;
11839 }
11840
11841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11842
11843 let text_layout_details = &self.text_layout_details(window);
11844 let selection_count = self.selections.count();
11845 let first_selection = self.selections.first_anchor();
11846
11847 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11848 s.move_with(|map, selection| {
11849 if !selection.is_empty() {
11850 selection.goal = SelectionGoal::None;
11851 }
11852 let (cursor, goal) = movement::up(
11853 map,
11854 selection.start,
11855 selection.goal,
11856 false,
11857 text_layout_details,
11858 );
11859 selection.collapse_to(cursor, goal);
11860 });
11861 });
11862
11863 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11864 {
11865 cx.propagate();
11866 }
11867 }
11868
11869 pub fn move_up_by_lines(
11870 &mut self,
11871 action: &MoveUpByLines,
11872 window: &mut Window,
11873 cx: &mut Context<Self>,
11874 ) {
11875 if self.take_rename(true, window, cx).is_some() {
11876 return;
11877 }
11878
11879 if self.mode.is_single_line() {
11880 cx.propagate();
11881 return;
11882 }
11883
11884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11885
11886 let text_layout_details = &self.text_layout_details(window);
11887
11888 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11889 s.move_with(|map, selection| {
11890 if !selection.is_empty() {
11891 selection.goal = SelectionGoal::None;
11892 }
11893 let (cursor, goal) = movement::up_by_rows(
11894 map,
11895 selection.start,
11896 action.lines,
11897 selection.goal,
11898 false,
11899 text_layout_details,
11900 );
11901 selection.collapse_to(cursor, goal);
11902 });
11903 })
11904 }
11905
11906 pub fn move_down_by_lines(
11907 &mut self,
11908 action: &MoveDownByLines,
11909 window: &mut Window,
11910 cx: &mut Context<Self>,
11911 ) {
11912 if self.take_rename(true, window, cx).is_some() {
11913 return;
11914 }
11915
11916 if self.mode.is_single_line() {
11917 cx.propagate();
11918 return;
11919 }
11920
11921 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11922
11923 let text_layout_details = &self.text_layout_details(window);
11924
11925 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11926 s.move_with(|map, selection| {
11927 if !selection.is_empty() {
11928 selection.goal = SelectionGoal::None;
11929 }
11930 let (cursor, goal) = movement::down_by_rows(
11931 map,
11932 selection.start,
11933 action.lines,
11934 selection.goal,
11935 false,
11936 text_layout_details,
11937 );
11938 selection.collapse_to(cursor, goal);
11939 });
11940 })
11941 }
11942
11943 pub fn select_down_by_lines(
11944 &mut self,
11945 action: &SelectDownByLines,
11946 window: &mut Window,
11947 cx: &mut Context<Self>,
11948 ) {
11949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11950 let text_layout_details = &self.text_layout_details(window);
11951 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11952 s.move_heads_with(|map, head, goal| {
11953 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11954 })
11955 })
11956 }
11957
11958 pub fn select_up_by_lines(
11959 &mut self,
11960 action: &SelectUpByLines,
11961 window: &mut Window,
11962 cx: &mut Context<Self>,
11963 ) {
11964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11965 let text_layout_details = &self.text_layout_details(window);
11966 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11967 s.move_heads_with(|map, head, goal| {
11968 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11969 })
11970 })
11971 }
11972
11973 pub fn select_page_up(
11974 &mut self,
11975 _: &SelectPageUp,
11976 window: &mut Window,
11977 cx: &mut Context<Self>,
11978 ) {
11979 let Some(row_count) = self.visible_row_count() else {
11980 return;
11981 };
11982
11983 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11984
11985 let text_layout_details = &self.text_layout_details(window);
11986
11987 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11988 s.move_heads_with(|map, head, goal| {
11989 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11990 })
11991 })
11992 }
11993
11994 pub fn move_page_up(
11995 &mut self,
11996 action: &MovePageUp,
11997 window: &mut Window,
11998 cx: &mut Context<Self>,
11999 ) {
12000 if self.take_rename(true, window, cx).is_some() {
12001 return;
12002 }
12003
12004 if self
12005 .context_menu
12006 .borrow_mut()
12007 .as_mut()
12008 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
12009 .unwrap_or(false)
12010 {
12011 return;
12012 }
12013
12014 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12015 cx.propagate();
12016 return;
12017 }
12018
12019 let Some(row_count) = self.visible_row_count() else {
12020 return;
12021 };
12022
12023 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12024
12025 let autoscroll = if action.center_cursor {
12026 Autoscroll::center()
12027 } else {
12028 Autoscroll::fit()
12029 };
12030
12031 let text_layout_details = &self.text_layout_details(window);
12032
12033 self.change_selections(Some(autoscroll), window, cx, |s| {
12034 s.move_with(|map, selection| {
12035 if !selection.is_empty() {
12036 selection.goal = SelectionGoal::None;
12037 }
12038 let (cursor, goal) = movement::up_by_rows(
12039 map,
12040 selection.end,
12041 row_count,
12042 selection.goal,
12043 false,
12044 text_layout_details,
12045 );
12046 selection.collapse_to(cursor, goal);
12047 });
12048 });
12049 }
12050
12051 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
12052 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12053 let text_layout_details = &self.text_layout_details(window);
12054 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12055 s.move_heads_with(|map, head, goal| {
12056 movement::up(map, head, goal, false, text_layout_details)
12057 })
12058 })
12059 }
12060
12061 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
12062 self.take_rename(true, window, cx);
12063
12064 if self.mode.is_single_line() {
12065 cx.propagate();
12066 return;
12067 }
12068
12069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12070
12071 let text_layout_details = &self.text_layout_details(window);
12072 let selection_count = self.selections.count();
12073 let first_selection = self.selections.first_anchor();
12074
12075 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12076 s.move_with(|map, selection| {
12077 if !selection.is_empty() {
12078 selection.goal = SelectionGoal::None;
12079 }
12080 let (cursor, goal) = movement::down(
12081 map,
12082 selection.end,
12083 selection.goal,
12084 false,
12085 text_layout_details,
12086 );
12087 selection.collapse_to(cursor, goal);
12088 });
12089 });
12090
12091 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12092 {
12093 cx.propagate();
12094 }
12095 }
12096
12097 pub fn select_page_down(
12098 &mut self,
12099 _: &SelectPageDown,
12100 window: &mut Window,
12101 cx: &mut Context<Self>,
12102 ) {
12103 let Some(row_count) = self.visible_row_count() else {
12104 return;
12105 };
12106
12107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12108
12109 let text_layout_details = &self.text_layout_details(window);
12110
12111 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12112 s.move_heads_with(|map, head, goal| {
12113 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12114 })
12115 })
12116 }
12117
12118 pub fn move_page_down(
12119 &mut self,
12120 action: &MovePageDown,
12121 window: &mut Window,
12122 cx: &mut Context<Self>,
12123 ) {
12124 if self.take_rename(true, window, cx).is_some() {
12125 return;
12126 }
12127
12128 if self
12129 .context_menu
12130 .borrow_mut()
12131 .as_mut()
12132 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12133 .unwrap_or(false)
12134 {
12135 return;
12136 }
12137
12138 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12139 cx.propagate();
12140 return;
12141 }
12142
12143 let Some(row_count) = self.visible_row_count() else {
12144 return;
12145 };
12146
12147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12148
12149 let autoscroll = if action.center_cursor {
12150 Autoscroll::center()
12151 } else {
12152 Autoscroll::fit()
12153 };
12154
12155 let text_layout_details = &self.text_layout_details(window);
12156 self.change_selections(Some(autoscroll), window, cx, |s| {
12157 s.move_with(|map, selection| {
12158 if !selection.is_empty() {
12159 selection.goal = SelectionGoal::None;
12160 }
12161 let (cursor, goal) = movement::down_by_rows(
12162 map,
12163 selection.end,
12164 row_count,
12165 selection.goal,
12166 false,
12167 text_layout_details,
12168 );
12169 selection.collapse_to(cursor, goal);
12170 });
12171 });
12172 }
12173
12174 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12175 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12176 let text_layout_details = &self.text_layout_details(window);
12177 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12178 s.move_heads_with(|map, head, goal| {
12179 movement::down(map, head, goal, false, text_layout_details)
12180 })
12181 });
12182 }
12183
12184 pub fn context_menu_first(
12185 &mut self,
12186 _: &ContextMenuFirst,
12187 window: &mut Window,
12188 cx: &mut Context<Self>,
12189 ) {
12190 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12191 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12192 }
12193 }
12194
12195 pub fn context_menu_prev(
12196 &mut self,
12197 _: &ContextMenuPrevious,
12198 window: &mut Window,
12199 cx: &mut Context<Self>,
12200 ) {
12201 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12202 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12203 }
12204 }
12205
12206 pub fn context_menu_next(
12207 &mut self,
12208 _: &ContextMenuNext,
12209 window: &mut Window,
12210 cx: &mut Context<Self>,
12211 ) {
12212 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12213 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12214 }
12215 }
12216
12217 pub fn context_menu_last(
12218 &mut self,
12219 _: &ContextMenuLast,
12220 window: &mut Window,
12221 cx: &mut Context<Self>,
12222 ) {
12223 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12224 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12225 }
12226 }
12227
12228 pub fn move_to_previous_word_start(
12229 &mut self,
12230 _: &MoveToPreviousWordStart,
12231 window: &mut Window,
12232 cx: &mut Context<Self>,
12233 ) {
12234 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12235 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12236 s.move_cursors_with(|map, head, _| {
12237 (
12238 movement::previous_word_start(map, head),
12239 SelectionGoal::None,
12240 )
12241 });
12242 })
12243 }
12244
12245 pub fn move_to_previous_subword_start(
12246 &mut self,
12247 _: &MoveToPreviousSubwordStart,
12248 window: &mut Window,
12249 cx: &mut Context<Self>,
12250 ) {
12251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12252 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12253 s.move_cursors_with(|map, head, _| {
12254 (
12255 movement::previous_subword_start(map, head),
12256 SelectionGoal::None,
12257 )
12258 });
12259 })
12260 }
12261
12262 pub fn select_to_previous_word_start(
12263 &mut self,
12264 _: &SelectToPreviousWordStart,
12265 window: &mut Window,
12266 cx: &mut Context<Self>,
12267 ) {
12268 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12269 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12270 s.move_heads_with(|map, head, _| {
12271 (
12272 movement::previous_word_start(map, head),
12273 SelectionGoal::None,
12274 )
12275 });
12276 })
12277 }
12278
12279 pub fn select_to_previous_subword_start(
12280 &mut self,
12281 _: &SelectToPreviousSubwordStart,
12282 window: &mut Window,
12283 cx: &mut Context<Self>,
12284 ) {
12285 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12286 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12287 s.move_heads_with(|map, head, _| {
12288 (
12289 movement::previous_subword_start(map, head),
12290 SelectionGoal::None,
12291 )
12292 });
12293 })
12294 }
12295
12296 pub fn delete_to_previous_word_start(
12297 &mut self,
12298 action: &DeleteToPreviousWordStart,
12299 window: &mut Window,
12300 cx: &mut Context<Self>,
12301 ) {
12302 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12303 self.transact(window, cx, |this, window, cx| {
12304 this.select_autoclose_pair(window, cx);
12305 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12306 s.move_with(|map, selection| {
12307 if selection.is_empty() {
12308 let cursor = if action.ignore_newlines {
12309 movement::previous_word_start(map, selection.head())
12310 } else {
12311 movement::previous_word_start_or_newline(map, selection.head())
12312 };
12313 selection.set_head(cursor, SelectionGoal::None);
12314 }
12315 });
12316 });
12317 this.insert("", window, cx);
12318 });
12319 }
12320
12321 pub fn delete_to_previous_subword_start(
12322 &mut self,
12323 _: &DeleteToPreviousSubwordStart,
12324 window: &mut Window,
12325 cx: &mut Context<Self>,
12326 ) {
12327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12328 self.transact(window, cx, |this, window, cx| {
12329 this.select_autoclose_pair(window, cx);
12330 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12331 s.move_with(|map, selection| {
12332 if selection.is_empty() {
12333 let cursor = movement::previous_subword_start(map, selection.head());
12334 selection.set_head(cursor, SelectionGoal::None);
12335 }
12336 });
12337 });
12338 this.insert("", window, cx);
12339 });
12340 }
12341
12342 pub fn move_to_next_word_end(
12343 &mut self,
12344 _: &MoveToNextWordEnd,
12345 window: &mut Window,
12346 cx: &mut Context<Self>,
12347 ) {
12348 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12349 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12350 s.move_cursors_with(|map, head, _| {
12351 (movement::next_word_end(map, head), SelectionGoal::None)
12352 });
12353 })
12354 }
12355
12356 pub fn move_to_next_subword_end(
12357 &mut self,
12358 _: &MoveToNextSubwordEnd,
12359 window: &mut Window,
12360 cx: &mut Context<Self>,
12361 ) {
12362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12363 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12364 s.move_cursors_with(|map, head, _| {
12365 (movement::next_subword_end(map, head), SelectionGoal::None)
12366 });
12367 })
12368 }
12369
12370 pub fn select_to_next_word_end(
12371 &mut self,
12372 _: &SelectToNextWordEnd,
12373 window: &mut Window,
12374 cx: &mut Context<Self>,
12375 ) {
12376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12377 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12378 s.move_heads_with(|map, head, _| {
12379 (movement::next_word_end(map, head), SelectionGoal::None)
12380 });
12381 })
12382 }
12383
12384 pub fn select_to_next_subword_end(
12385 &mut self,
12386 _: &SelectToNextSubwordEnd,
12387 window: &mut Window,
12388 cx: &mut Context<Self>,
12389 ) {
12390 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12391 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12392 s.move_heads_with(|map, head, _| {
12393 (movement::next_subword_end(map, head), SelectionGoal::None)
12394 });
12395 })
12396 }
12397
12398 pub fn delete_to_next_word_end(
12399 &mut self,
12400 action: &DeleteToNextWordEnd,
12401 window: &mut Window,
12402 cx: &mut Context<Self>,
12403 ) {
12404 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12405 self.transact(window, cx, |this, window, cx| {
12406 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12407 s.move_with(|map, selection| {
12408 if selection.is_empty() {
12409 let cursor = if action.ignore_newlines {
12410 movement::next_word_end(map, selection.head())
12411 } else {
12412 movement::next_word_end_or_newline(map, selection.head())
12413 };
12414 selection.set_head(cursor, SelectionGoal::None);
12415 }
12416 });
12417 });
12418 this.insert("", window, cx);
12419 });
12420 }
12421
12422 pub fn delete_to_next_subword_end(
12423 &mut self,
12424 _: &DeleteToNextSubwordEnd,
12425 window: &mut Window,
12426 cx: &mut Context<Self>,
12427 ) {
12428 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12429 self.transact(window, cx, |this, window, cx| {
12430 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12431 s.move_with(|map, selection| {
12432 if selection.is_empty() {
12433 let cursor = movement::next_subword_end(map, selection.head());
12434 selection.set_head(cursor, SelectionGoal::None);
12435 }
12436 });
12437 });
12438 this.insert("", window, cx);
12439 });
12440 }
12441
12442 pub fn move_to_beginning_of_line(
12443 &mut self,
12444 action: &MoveToBeginningOfLine,
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_cursors_with(|map, head, _| {
12451 (
12452 movement::indented_line_beginning(
12453 map,
12454 head,
12455 action.stop_at_soft_wraps,
12456 action.stop_at_indent,
12457 ),
12458 SelectionGoal::None,
12459 )
12460 });
12461 })
12462 }
12463
12464 pub fn select_to_beginning_of_line(
12465 &mut self,
12466 action: &SelectToBeginningOfLine,
12467 window: &mut Window,
12468 cx: &mut Context<Self>,
12469 ) {
12470 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12471 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12472 s.move_heads_with(|map, head, _| {
12473 (
12474 movement::indented_line_beginning(
12475 map,
12476 head,
12477 action.stop_at_soft_wraps,
12478 action.stop_at_indent,
12479 ),
12480 SelectionGoal::None,
12481 )
12482 });
12483 });
12484 }
12485
12486 pub fn delete_to_beginning_of_line(
12487 &mut self,
12488 action: &DeleteToBeginningOfLine,
12489 window: &mut Window,
12490 cx: &mut Context<Self>,
12491 ) {
12492 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12493 self.transact(window, cx, |this, window, cx| {
12494 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12495 s.move_with(|_, selection| {
12496 selection.reversed = true;
12497 });
12498 });
12499
12500 this.select_to_beginning_of_line(
12501 &SelectToBeginningOfLine {
12502 stop_at_soft_wraps: false,
12503 stop_at_indent: action.stop_at_indent,
12504 },
12505 window,
12506 cx,
12507 );
12508 this.backspace(&Backspace, window, cx);
12509 });
12510 }
12511
12512 pub fn move_to_end_of_line(
12513 &mut self,
12514 action: &MoveToEndOfLine,
12515 window: &mut Window,
12516 cx: &mut Context<Self>,
12517 ) {
12518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12519 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12520 s.move_cursors_with(|map, head, _| {
12521 (
12522 movement::line_end(map, head, action.stop_at_soft_wraps),
12523 SelectionGoal::None,
12524 )
12525 });
12526 })
12527 }
12528
12529 pub fn select_to_end_of_line(
12530 &mut self,
12531 action: &SelectToEndOfLine,
12532 window: &mut Window,
12533 cx: &mut Context<Self>,
12534 ) {
12535 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12536 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12537 s.move_heads_with(|map, head, _| {
12538 (
12539 movement::line_end(map, head, action.stop_at_soft_wraps),
12540 SelectionGoal::None,
12541 )
12542 });
12543 })
12544 }
12545
12546 pub fn delete_to_end_of_line(
12547 &mut self,
12548 _: &DeleteToEndOfLine,
12549 window: &mut Window,
12550 cx: &mut Context<Self>,
12551 ) {
12552 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12553 self.transact(window, cx, |this, window, cx| {
12554 this.select_to_end_of_line(
12555 &SelectToEndOfLine {
12556 stop_at_soft_wraps: false,
12557 },
12558 window,
12559 cx,
12560 );
12561 this.delete(&Delete, window, cx);
12562 });
12563 }
12564
12565 pub fn cut_to_end_of_line(
12566 &mut self,
12567 _: &CutToEndOfLine,
12568 window: &mut Window,
12569 cx: &mut Context<Self>,
12570 ) {
12571 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12572 self.transact(window, cx, |this, window, cx| {
12573 this.select_to_end_of_line(
12574 &SelectToEndOfLine {
12575 stop_at_soft_wraps: false,
12576 },
12577 window,
12578 cx,
12579 );
12580 this.cut(&Cut, window, cx);
12581 });
12582 }
12583
12584 pub fn move_to_start_of_paragraph(
12585 &mut self,
12586 _: &MoveToStartOfParagraph,
12587 window: &mut Window,
12588 cx: &mut Context<Self>,
12589 ) {
12590 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12591 cx.propagate();
12592 return;
12593 }
12594 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12595 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12596 s.move_with(|map, selection| {
12597 selection.collapse_to(
12598 movement::start_of_paragraph(map, selection.head(), 1),
12599 SelectionGoal::None,
12600 )
12601 });
12602 })
12603 }
12604
12605 pub fn move_to_end_of_paragraph(
12606 &mut self,
12607 _: &MoveToEndOfParagraph,
12608 window: &mut Window,
12609 cx: &mut Context<Self>,
12610 ) {
12611 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12612 cx.propagate();
12613 return;
12614 }
12615 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12616 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12617 s.move_with(|map, selection| {
12618 selection.collapse_to(
12619 movement::end_of_paragraph(map, selection.head(), 1),
12620 SelectionGoal::None,
12621 )
12622 });
12623 })
12624 }
12625
12626 pub fn select_to_start_of_paragraph(
12627 &mut self,
12628 _: &SelectToStartOfParagraph,
12629 window: &mut Window,
12630 cx: &mut Context<Self>,
12631 ) {
12632 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12633 cx.propagate();
12634 return;
12635 }
12636 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12637 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12638 s.move_heads_with(|map, head, _| {
12639 (
12640 movement::start_of_paragraph(map, head, 1),
12641 SelectionGoal::None,
12642 )
12643 });
12644 })
12645 }
12646
12647 pub fn select_to_end_of_paragraph(
12648 &mut self,
12649 _: &SelectToEndOfParagraph,
12650 window: &mut Window,
12651 cx: &mut Context<Self>,
12652 ) {
12653 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12654 cx.propagate();
12655 return;
12656 }
12657 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12658 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12659 s.move_heads_with(|map, head, _| {
12660 (
12661 movement::end_of_paragraph(map, head, 1),
12662 SelectionGoal::None,
12663 )
12664 });
12665 })
12666 }
12667
12668 pub fn move_to_start_of_excerpt(
12669 &mut self,
12670 _: &MoveToStartOfExcerpt,
12671 window: &mut Window,
12672 cx: &mut Context<Self>,
12673 ) {
12674 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12675 cx.propagate();
12676 return;
12677 }
12678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12679 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12680 s.move_with(|map, selection| {
12681 selection.collapse_to(
12682 movement::start_of_excerpt(
12683 map,
12684 selection.head(),
12685 workspace::searchable::Direction::Prev,
12686 ),
12687 SelectionGoal::None,
12688 )
12689 });
12690 })
12691 }
12692
12693 pub fn move_to_start_of_next_excerpt(
12694 &mut self,
12695 _: &MoveToStartOfNextExcerpt,
12696 window: &mut Window,
12697 cx: &mut Context<Self>,
12698 ) {
12699 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12700 cx.propagate();
12701 return;
12702 }
12703
12704 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12705 s.move_with(|map, selection| {
12706 selection.collapse_to(
12707 movement::start_of_excerpt(
12708 map,
12709 selection.head(),
12710 workspace::searchable::Direction::Next,
12711 ),
12712 SelectionGoal::None,
12713 )
12714 });
12715 })
12716 }
12717
12718 pub fn move_to_end_of_excerpt(
12719 &mut self,
12720 _: &MoveToEndOfExcerpt,
12721 window: &mut Window,
12722 cx: &mut Context<Self>,
12723 ) {
12724 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12725 cx.propagate();
12726 return;
12727 }
12728 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12729 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12730 s.move_with(|map, selection| {
12731 selection.collapse_to(
12732 movement::end_of_excerpt(
12733 map,
12734 selection.head(),
12735 workspace::searchable::Direction::Next,
12736 ),
12737 SelectionGoal::None,
12738 )
12739 });
12740 })
12741 }
12742
12743 pub fn move_to_end_of_previous_excerpt(
12744 &mut self,
12745 _: &MoveToEndOfPreviousExcerpt,
12746 window: &mut Window,
12747 cx: &mut Context<Self>,
12748 ) {
12749 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12750 cx.propagate();
12751 return;
12752 }
12753 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12754 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12755 s.move_with(|map, selection| {
12756 selection.collapse_to(
12757 movement::end_of_excerpt(
12758 map,
12759 selection.head(),
12760 workspace::searchable::Direction::Prev,
12761 ),
12762 SelectionGoal::None,
12763 )
12764 });
12765 })
12766 }
12767
12768 pub fn select_to_start_of_excerpt(
12769 &mut self,
12770 _: &SelectToStartOfExcerpt,
12771 window: &mut Window,
12772 cx: &mut Context<Self>,
12773 ) {
12774 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12775 cx.propagate();
12776 return;
12777 }
12778 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12779 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12780 s.move_heads_with(|map, head, _| {
12781 (
12782 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12783 SelectionGoal::None,
12784 )
12785 });
12786 })
12787 }
12788
12789 pub fn select_to_start_of_next_excerpt(
12790 &mut self,
12791 _: &SelectToStartOfNextExcerpt,
12792 window: &mut Window,
12793 cx: &mut Context<Self>,
12794 ) {
12795 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12796 cx.propagate();
12797 return;
12798 }
12799 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12800 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12801 s.move_heads_with(|map, head, _| {
12802 (
12803 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12804 SelectionGoal::None,
12805 )
12806 });
12807 })
12808 }
12809
12810 pub fn select_to_end_of_excerpt(
12811 &mut self,
12812 _: &SelectToEndOfExcerpt,
12813 window: &mut Window,
12814 cx: &mut Context<Self>,
12815 ) {
12816 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12817 cx.propagate();
12818 return;
12819 }
12820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12821 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12822 s.move_heads_with(|map, head, _| {
12823 (
12824 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12825 SelectionGoal::None,
12826 )
12827 });
12828 })
12829 }
12830
12831 pub fn select_to_end_of_previous_excerpt(
12832 &mut self,
12833 _: &SelectToEndOfPreviousExcerpt,
12834 window: &mut Window,
12835 cx: &mut Context<Self>,
12836 ) {
12837 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12838 cx.propagate();
12839 return;
12840 }
12841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12842 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12843 s.move_heads_with(|map, head, _| {
12844 (
12845 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12846 SelectionGoal::None,
12847 )
12848 });
12849 })
12850 }
12851
12852 pub fn move_to_beginning(
12853 &mut self,
12854 _: &MoveToBeginning,
12855 window: &mut Window,
12856 cx: &mut Context<Self>,
12857 ) {
12858 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12859 cx.propagate();
12860 return;
12861 }
12862 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12863 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12864 s.select_ranges(vec![0..0]);
12865 });
12866 }
12867
12868 pub fn select_to_beginning(
12869 &mut self,
12870 _: &SelectToBeginning,
12871 window: &mut Window,
12872 cx: &mut Context<Self>,
12873 ) {
12874 let mut selection = self.selections.last::<Point>(cx);
12875 selection.set_head(Point::zero(), SelectionGoal::None);
12876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12877 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12878 s.select(vec![selection]);
12879 });
12880 }
12881
12882 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12883 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12884 cx.propagate();
12885 return;
12886 }
12887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12888 let cursor = self.buffer.read(cx).read(cx).len();
12889 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12890 s.select_ranges(vec![cursor..cursor])
12891 });
12892 }
12893
12894 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12895 self.nav_history = nav_history;
12896 }
12897
12898 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12899 self.nav_history.as_ref()
12900 }
12901
12902 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12903 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12904 }
12905
12906 fn push_to_nav_history(
12907 &mut self,
12908 cursor_anchor: Anchor,
12909 new_position: Option<Point>,
12910 is_deactivate: bool,
12911 cx: &mut Context<Self>,
12912 ) {
12913 if let Some(nav_history) = self.nav_history.as_mut() {
12914 let buffer = self.buffer.read(cx).read(cx);
12915 let cursor_position = cursor_anchor.to_point(&buffer);
12916 let scroll_state = self.scroll_manager.anchor();
12917 let scroll_top_row = scroll_state.top_row(&buffer);
12918 drop(buffer);
12919
12920 if let Some(new_position) = new_position {
12921 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12922 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12923 return;
12924 }
12925 }
12926
12927 nav_history.push(
12928 Some(NavigationData {
12929 cursor_anchor,
12930 cursor_position,
12931 scroll_anchor: scroll_state,
12932 scroll_top_row,
12933 }),
12934 cx,
12935 );
12936 cx.emit(EditorEvent::PushedToNavHistory {
12937 anchor: cursor_anchor,
12938 is_deactivate,
12939 })
12940 }
12941 }
12942
12943 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12944 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12945 let buffer = self.buffer.read(cx).snapshot(cx);
12946 let mut selection = self.selections.first::<usize>(cx);
12947 selection.set_head(buffer.len(), SelectionGoal::None);
12948 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12949 s.select(vec![selection]);
12950 });
12951 }
12952
12953 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12955 let end = self.buffer.read(cx).read(cx).len();
12956 self.change_selections(None, window, cx, |s| {
12957 s.select_ranges(vec![0..end]);
12958 });
12959 }
12960
12961 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12962 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12963 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12964 let mut selections = self.selections.all::<Point>(cx);
12965 let max_point = display_map.buffer_snapshot.max_point();
12966 for selection in &mut selections {
12967 let rows = selection.spanned_rows(true, &display_map);
12968 selection.start = Point::new(rows.start.0, 0);
12969 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12970 selection.reversed = false;
12971 }
12972 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12973 s.select(selections);
12974 });
12975 }
12976
12977 pub fn split_selection_into_lines(
12978 &mut self,
12979 _: &SplitSelectionIntoLines,
12980 window: &mut Window,
12981 cx: &mut Context<Self>,
12982 ) {
12983 let selections = self
12984 .selections
12985 .all::<Point>(cx)
12986 .into_iter()
12987 .map(|selection| selection.start..selection.end)
12988 .collect::<Vec<_>>();
12989 self.unfold_ranges(&selections, true, true, cx);
12990
12991 let mut new_selection_ranges = Vec::new();
12992 {
12993 let buffer = self.buffer.read(cx).read(cx);
12994 for selection in selections {
12995 for row in selection.start.row..selection.end.row {
12996 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12997 new_selection_ranges.push(cursor..cursor);
12998 }
12999
13000 let is_multiline_selection = selection.start.row != selection.end.row;
13001 // Don't insert last one if it's a multi-line selection ending at the start of a line,
13002 // so this action feels more ergonomic when paired with other selection operations
13003 let should_skip_last = is_multiline_selection && selection.end.column == 0;
13004 if !should_skip_last {
13005 new_selection_ranges.push(selection.end..selection.end);
13006 }
13007 }
13008 }
13009 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13010 s.select_ranges(new_selection_ranges);
13011 });
13012 }
13013
13014 pub fn add_selection_above(
13015 &mut self,
13016 _: &AddSelectionAbove,
13017 window: &mut Window,
13018 cx: &mut Context<Self>,
13019 ) {
13020 self.add_selection(true, window, cx);
13021 }
13022
13023 pub fn add_selection_below(
13024 &mut self,
13025 _: &AddSelectionBelow,
13026 window: &mut Window,
13027 cx: &mut Context<Self>,
13028 ) {
13029 self.add_selection(false, window, cx);
13030 }
13031
13032 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
13033 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13034
13035 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13036 let all_selections = self.selections.all::<Point>(cx);
13037 let text_layout_details = self.text_layout_details(window);
13038
13039 let (mut columnar_selections, new_selections_to_columnarize) = {
13040 if let Some(state) = self.add_selections_state.as_ref() {
13041 let columnar_selection_ids: HashSet<_> = state
13042 .groups
13043 .iter()
13044 .flat_map(|group| group.stack.iter())
13045 .copied()
13046 .collect();
13047
13048 all_selections
13049 .into_iter()
13050 .partition(|s| columnar_selection_ids.contains(&s.id))
13051 } else {
13052 (Vec::new(), all_selections)
13053 }
13054 };
13055
13056 let mut state = self
13057 .add_selections_state
13058 .take()
13059 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
13060
13061 for selection in new_selections_to_columnarize {
13062 let range = selection.display_range(&display_map).sorted();
13063 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
13064 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
13065 let positions = start_x.min(end_x)..start_x.max(end_x);
13066 let mut stack = Vec::new();
13067 for row in range.start.row().0..=range.end.row().0 {
13068 if let Some(selection) = self.selections.build_columnar_selection(
13069 &display_map,
13070 DisplayRow(row),
13071 &positions,
13072 selection.reversed,
13073 &text_layout_details,
13074 ) {
13075 stack.push(selection.id);
13076 columnar_selections.push(selection);
13077 }
13078 }
13079 if !stack.is_empty() {
13080 if above {
13081 stack.reverse();
13082 }
13083 state.groups.push(AddSelectionsGroup { above, stack });
13084 }
13085 }
13086
13087 let mut final_selections = Vec::new();
13088 let end_row = if above {
13089 DisplayRow(0)
13090 } else {
13091 display_map.max_point().row()
13092 };
13093
13094 let mut last_added_item_per_group = HashMap::default();
13095 for group in state.groups.iter_mut() {
13096 if let Some(last_id) = group.stack.last() {
13097 last_added_item_per_group.insert(*last_id, group);
13098 }
13099 }
13100
13101 for selection in columnar_selections {
13102 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13103 if above == group.above {
13104 let range = selection.display_range(&display_map).sorted();
13105 debug_assert_eq!(range.start.row(), range.end.row());
13106 let mut row = range.start.row();
13107 let positions =
13108 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13109 px(start)..px(end)
13110 } else {
13111 let start_x =
13112 display_map.x_for_display_point(range.start, &text_layout_details);
13113 let end_x =
13114 display_map.x_for_display_point(range.end, &text_layout_details);
13115 start_x.min(end_x)..start_x.max(end_x)
13116 };
13117
13118 let mut maybe_new_selection = None;
13119 while row != end_row {
13120 if above {
13121 row.0 -= 1;
13122 } else {
13123 row.0 += 1;
13124 }
13125 if let Some(new_selection) = self.selections.build_columnar_selection(
13126 &display_map,
13127 row,
13128 &positions,
13129 selection.reversed,
13130 &text_layout_details,
13131 ) {
13132 maybe_new_selection = Some(new_selection);
13133 break;
13134 }
13135 }
13136
13137 if let Some(new_selection) = maybe_new_selection {
13138 group.stack.push(new_selection.id);
13139 if above {
13140 final_selections.push(new_selection);
13141 final_selections.push(selection);
13142 } else {
13143 final_selections.push(selection);
13144 final_selections.push(new_selection);
13145 }
13146 } else {
13147 final_selections.push(selection);
13148 }
13149 } else {
13150 group.stack.pop();
13151 }
13152 } else {
13153 final_selections.push(selection);
13154 }
13155 }
13156
13157 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13158 s.select(final_selections);
13159 });
13160
13161 let final_selection_ids: HashSet<_> = self
13162 .selections
13163 .all::<Point>(cx)
13164 .iter()
13165 .map(|s| s.id)
13166 .collect();
13167 state.groups.retain_mut(|group| {
13168 // selections might get merged above so we remove invalid items from stacks
13169 group.stack.retain(|id| final_selection_ids.contains(id));
13170
13171 // single selection in stack can be treated as initial state
13172 group.stack.len() > 1
13173 });
13174
13175 if !state.groups.is_empty() {
13176 self.add_selections_state = Some(state);
13177 }
13178 }
13179
13180 fn select_match_ranges(
13181 &mut self,
13182 range: Range<usize>,
13183 reversed: bool,
13184 replace_newest: bool,
13185 auto_scroll: Option<Autoscroll>,
13186 window: &mut Window,
13187 cx: &mut Context<Editor>,
13188 ) {
13189 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13190 self.change_selections(auto_scroll, window, cx, |s| {
13191 if replace_newest {
13192 s.delete(s.newest_anchor().id);
13193 }
13194 if reversed {
13195 s.insert_range(range.end..range.start);
13196 } else {
13197 s.insert_range(range);
13198 }
13199 });
13200 }
13201
13202 pub fn select_next_match_internal(
13203 &mut self,
13204 display_map: &DisplaySnapshot,
13205 replace_newest: bool,
13206 autoscroll: Option<Autoscroll>,
13207 window: &mut Window,
13208 cx: &mut Context<Self>,
13209 ) -> Result<()> {
13210 let buffer = &display_map.buffer_snapshot;
13211 let mut selections = self.selections.all::<usize>(cx);
13212 if let Some(mut select_next_state) = self.select_next_state.take() {
13213 let query = &select_next_state.query;
13214 if !select_next_state.done {
13215 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13216 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13217 let mut next_selected_range = None;
13218
13219 let bytes_after_last_selection =
13220 buffer.bytes_in_range(last_selection.end..buffer.len());
13221 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13222 let query_matches = query
13223 .stream_find_iter(bytes_after_last_selection)
13224 .map(|result| (last_selection.end, result))
13225 .chain(
13226 query
13227 .stream_find_iter(bytes_before_first_selection)
13228 .map(|result| (0, result)),
13229 );
13230
13231 for (start_offset, query_match) in query_matches {
13232 let query_match = query_match.unwrap(); // can only fail due to I/O
13233 let offset_range =
13234 start_offset + query_match.start()..start_offset + query_match.end();
13235 let display_range = offset_range.start.to_display_point(display_map)
13236 ..offset_range.end.to_display_point(display_map);
13237
13238 if !select_next_state.wordwise
13239 || (!movement::is_inside_word(display_map, display_range.start)
13240 && !movement::is_inside_word(display_map, display_range.end))
13241 {
13242 // TODO: This is n^2, because we might check all the selections
13243 if !selections
13244 .iter()
13245 .any(|selection| selection.range().overlaps(&offset_range))
13246 {
13247 next_selected_range = Some(offset_range);
13248 break;
13249 }
13250 }
13251 }
13252
13253 if let Some(next_selected_range) = next_selected_range {
13254 self.select_match_ranges(
13255 next_selected_range,
13256 last_selection.reversed,
13257 replace_newest,
13258 autoscroll,
13259 window,
13260 cx,
13261 );
13262 } else {
13263 select_next_state.done = true;
13264 }
13265 }
13266
13267 self.select_next_state = Some(select_next_state);
13268 } else {
13269 let mut only_carets = true;
13270 let mut same_text_selected = true;
13271 let mut selected_text = None;
13272
13273 let mut selections_iter = selections.iter().peekable();
13274 while let Some(selection) = selections_iter.next() {
13275 if selection.start != selection.end {
13276 only_carets = false;
13277 }
13278
13279 if same_text_selected {
13280 if selected_text.is_none() {
13281 selected_text =
13282 Some(buffer.text_for_range(selection.range()).collect::<String>());
13283 }
13284
13285 if let Some(next_selection) = selections_iter.peek() {
13286 if next_selection.range().len() == selection.range().len() {
13287 let next_selected_text = buffer
13288 .text_for_range(next_selection.range())
13289 .collect::<String>();
13290 if Some(next_selected_text) != selected_text {
13291 same_text_selected = false;
13292 selected_text = None;
13293 }
13294 } else {
13295 same_text_selected = false;
13296 selected_text = None;
13297 }
13298 }
13299 }
13300 }
13301
13302 if only_carets {
13303 for selection in &mut selections {
13304 let word_range = movement::surrounding_word(
13305 display_map,
13306 selection.start.to_display_point(display_map),
13307 );
13308 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13309 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13310 selection.goal = SelectionGoal::None;
13311 selection.reversed = false;
13312 self.select_match_ranges(
13313 selection.start..selection.end,
13314 selection.reversed,
13315 replace_newest,
13316 autoscroll,
13317 window,
13318 cx,
13319 );
13320 }
13321
13322 if selections.len() == 1 {
13323 let selection = selections
13324 .last()
13325 .expect("ensured that there's only one selection");
13326 let query = buffer
13327 .text_for_range(selection.start..selection.end)
13328 .collect::<String>();
13329 let is_empty = query.is_empty();
13330 let select_state = SelectNextState {
13331 query: AhoCorasick::new(&[query])?,
13332 wordwise: true,
13333 done: is_empty,
13334 };
13335 self.select_next_state = Some(select_state);
13336 } else {
13337 self.select_next_state = None;
13338 }
13339 } else if let Some(selected_text) = selected_text {
13340 self.select_next_state = Some(SelectNextState {
13341 query: AhoCorasick::new(&[selected_text])?,
13342 wordwise: false,
13343 done: false,
13344 });
13345 self.select_next_match_internal(
13346 display_map,
13347 replace_newest,
13348 autoscroll,
13349 window,
13350 cx,
13351 )?;
13352 }
13353 }
13354 Ok(())
13355 }
13356
13357 pub fn select_all_matches(
13358 &mut self,
13359 _action: &SelectAllMatches,
13360 window: &mut Window,
13361 cx: &mut Context<Self>,
13362 ) -> Result<()> {
13363 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13364
13365 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13366
13367 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13368 let Some(select_next_state) = self.select_next_state.as_mut() else {
13369 return Ok(());
13370 };
13371 if select_next_state.done {
13372 return Ok(());
13373 }
13374
13375 let mut new_selections = Vec::new();
13376
13377 let reversed = self.selections.oldest::<usize>(cx).reversed;
13378 let buffer = &display_map.buffer_snapshot;
13379 let query_matches = select_next_state
13380 .query
13381 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13382
13383 for query_match in query_matches.into_iter() {
13384 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13385 let offset_range = if reversed {
13386 query_match.end()..query_match.start()
13387 } else {
13388 query_match.start()..query_match.end()
13389 };
13390 let display_range = offset_range.start.to_display_point(&display_map)
13391 ..offset_range.end.to_display_point(&display_map);
13392
13393 if !select_next_state.wordwise
13394 || (!movement::is_inside_word(&display_map, display_range.start)
13395 && !movement::is_inside_word(&display_map, display_range.end))
13396 {
13397 new_selections.push(offset_range.start..offset_range.end);
13398 }
13399 }
13400
13401 select_next_state.done = true;
13402 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13403 self.change_selections(None, window, cx, |selections| {
13404 selections.select_ranges(new_selections)
13405 });
13406
13407 Ok(())
13408 }
13409
13410 pub fn select_next(
13411 &mut self,
13412 action: &SelectNext,
13413 window: &mut Window,
13414 cx: &mut Context<Self>,
13415 ) -> Result<()> {
13416 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13417 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13418 self.select_next_match_internal(
13419 &display_map,
13420 action.replace_newest,
13421 Some(Autoscroll::newest()),
13422 window,
13423 cx,
13424 )?;
13425 Ok(())
13426 }
13427
13428 pub fn select_previous(
13429 &mut self,
13430 action: &SelectPrevious,
13431 window: &mut Window,
13432 cx: &mut Context<Self>,
13433 ) -> Result<()> {
13434 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13435 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13436 let buffer = &display_map.buffer_snapshot;
13437 let mut selections = self.selections.all::<usize>(cx);
13438 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13439 let query = &select_prev_state.query;
13440 if !select_prev_state.done {
13441 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13442 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13443 let mut next_selected_range = None;
13444 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13445 let bytes_before_last_selection =
13446 buffer.reversed_bytes_in_range(0..last_selection.start);
13447 let bytes_after_first_selection =
13448 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13449 let query_matches = query
13450 .stream_find_iter(bytes_before_last_selection)
13451 .map(|result| (last_selection.start, result))
13452 .chain(
13453 query
13454 .stream_find_iter(bytes_after_first_selection)
13455 .map(|result| (buffer.len(), result)),
13456 );
13457 for (end_offset, query_match) in query_matches {
13458 let query_match = query_match.unwrap(); // can only fail due to I/O
13459 let offset_range =
13460 end_offset - query_match.end()..end_offset - query_match.start();
13461 let display_range = offset_range.start.to_display_point(&display_map)
13462 ..offset_range.end.to_display_point(&display_map);
13463
13464 if !select_prev_state.wordwise
13465 || (!movement::is_inside_word(&display_map, display_range.start)
13466 && !movement::is_inside_word(&display_map, display_range.end))
13467 {
13468 next_selected_range = Some(offset_range);
13469 break;
13470 }
13471 }
13472
13473 if let Some(next_selected_range) = next_selected_range {
13474 self.select_match_ranges(
13475 next_selected_range,
13476 last_selection.reversed,
13477 action.replace_newest,
13478 Some(Autoscroll::newest()),
13479 window,
13480 cx,
13481 );
13482 } else {
13483 select_prev_state.done = true;
13484 }
13485 }
13486
13487 self.select_prev_state = Some(select_prev_state);
13488 } else {
13489 let mut only_carets = true;
13490 let mut same_text_selected = true;
13491 let mut selected_text = None;
13492
13493 let mut selections_iter = selections.iter().peekable();
13494 while let Some(selection) = selections_iter.next() {
13495 if selection.start != selection.end {
13496 only_carets = false;
13497 }
13498
13499 if same_text_selected {
13500 if selected_text.is_none() {
13501 selected_text =
13502 Some(buffer.text_for_range(selection.range()).collect::<String>());
13503 }
13504
13505 if let Some(next_selection) = selections_iter.peek() {
13506 if next_selection.range().len() == selection.range().len() {
13507 let next_selected_text = buffer
13508 .text_for_range(next_selection.range())
13509 .collect::<String>();
13510 if Some(next_selected_text) != selected_text {
13511 same_text_selected = false;
13512 selected_text = None;
13513 }
13514 } else {
13515 same_text_selected = false;
13516 selected_text = None;
13517 }
13518 }
13519 }
13520 }
13521
13522 if only_carets {
13523 for selection in &mut selections {
13524 let word_range = movement::surrounding_word(
13525 &display_map,
13526 selection.start.to_display_point(&display_map),
13527 );
13528 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13529 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13530 selection.goal = SelectionGoal::None;
13531 selection.reversed = false;
13532 self.select_match_ranges(
13533 selection.start..selection.end,
13534 selection.reversed,
13535 action.replace_newest,
13536 Some(Autoscroll::newest()),
13537 window,
13538 cx,
13539 );
13540 }
13541 if selections.len() == 1 {
13542 let selection = selections
13543 .last()
13544 .expect("ensured that there's only one selection");
13545 let query = buffer
13546 .text_for_range(selection.start..selection.end)
13547 .collect::<String>();
13548 let is_empty = query.is_empty();
13549 let select_state = SelectNextState {
13550 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13551 wordwise: true,
13552 done: is_empty,
13553 };
13554 self.select_prev_state = Some(select_state);
13555 } else {
13556 self.select_prev_state = None;
13557 }
13558 } else if let Some(selected_text) = selected_text {
13559 self.select_prev_state = Some(SelectNextState {
13560 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13561 wordwise: false,
13562 done: false,
13563 });
13564 self.select_previous(action, window, cx)?;
13565 }
13566 }
13567 Ok(())
13568 }
13569
13570 pub fn find_next_match(
13571 &mut self,
13572 _: &FindNextMatch,
13573 window: &mut Window,
13574 cx: &mut Context<Self>,
13575 ) -> Result<()> {
13576 let selections = self.selections.disjoint_anchors();
13577 match selections.first() {
13578 Some(first) if selections.len() >= 2 => {
13579 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13580 s.select_ranges([first.range()]);
13581 });
13582 }
13583 _ => self.select_next(
13584 &SelectNext {
13585 replace_newest: true,
13586 },
13587 window,
13588 cx,
13589 )?,
13590 }
13591 Ok(())
13592 }
13593
13594 pub fn find_previous_match(
13595 &mut self,
13596 _: &FindPreviousMatch,
13597 window: &mut Window,
13598 cx: &mut Context<Self>,
13599 ) -> Result<()> {
13600 let selections = self.selections.disjoint_anchors();
13601 match selections.last() {
13602 Some(last) if selections.len() >= 2 => {
13603 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13604 s.select_ranges([last.range()]);
13605 });
13606 }
13607 _ => self.select_previous(
13608 &SelectPrevious {
13609 replace_newest: true,
13610 },
13611 window,
13612 cx,
13613 )?,
13614 }
13615 Ok(())
13616 }
13617
13618 pub fn toggle_comments(
13619 &mut self,
13620 action: &ToggleComments,
13621 window: &mut Window,
13622 cx: &mut Context<Self>,
13623 ) {
13624 if self.read_only(cx) {
13625 return;
13626 }
13627 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13628 let text_layout_details = &self.text_layout_details(window);
13629 self.transact(window, cx, |this, window, cx| {
13630 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13631 let mut edits = Vec::new();
13632 let mut selection_edit_ranges = Vec::new();
13633 let mut last_toggled_row = None;
13634 let snapshot = this.buffer.read(cx).read(cx);
13635 let empty_str: Arc<str> = Arc::default();
13636 let mut suffixes_inserted = Vec::new();
13637 let ignore_indent = action.ignore_indent;
13638
13639 fn comment_prefix_range(
13640 snapshot: &MultiBufferSnapshot,
13641 row: MultiBufferRow,
13642 comment_prefix: &str,
13643 comment_prefix_whitespace: &str,
13644 ignore_indent: bool,
13645 ) -> Range<Point> {
13646 let indent_size = if ignore_indent {
13647 0
13648 } else {
13649 snapshot.indent_size_for_line(row).len
13650 };
13651
13652 let start = Point::new(row.0, indent_size);
13653
13654 let mut line_bytes = snapshot
13655 .bytes_in_range(start..snapshot.max_point())
13656 .flatten()
13657 .copied();
13658
13659 // If this line currently begins with the line comment prefix, then record
13660 // the range containing the prefix.
13661 if line_bytes
13662 .by_ref()
13663 .take(comment_prefix.len())
13664 .eq(comment_prefix.bytes())
13665 {
13666 // Include any whitespace that matches the comment prefix.
13667 let matching_whitespace_len = line_bytes
13668 .zip(comment_prefix_whitespace.bytes())
13669 .take_while(|(a, b)| a == b)
13670 .count() as u32;
13671 let end = Point::new(
13672 start.row,
13673 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13674 );
13675 start..end
13676 } else {
13677 start..start
13678 }
13679 }
13680
13681 fn comment_suffix_range(
13682 snapshot: &MultiBufferSnapshot,
13683 row: MultiBufferRow,
13684 comment_suffix: &str,
13685 comment_suffix_has_leading_space: bool,
13686 ) -> Range<Point> {
13687 let end = Point::new(row.0, snapshot.line_len(row));
13688 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13689
13690 let mut line_end_bytes = snapshot
13691 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13692 .flatten()
13693 .copied();
13694
13695 let leading_space_len = if suffix_start_column > 0
13696 && line_end_bytes.next() == Some(b' ')
13697 && comment_suffix_has_leading_space
13698 {
13699 1
13700 } else {
13701 0
13702 };
13703
13704 // If this line currently begins with the line comment prefix, then record
13705 // the range containing the prefix.
13706 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13707 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13708 start..end
13709 } else {
13710 end..end
13711 }
13712 }
13713
13714 // TODO: Handle selections that cross excerpts
13715 for selection in &mut selections {
13716 let start_column = snapshot
13717 .indent_size_for_line(MultiBufferRow(selection.start.row))
13718 .len;
13719 let language = if let Some(language) =
13720 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13721 {
13722 language
13723 } else {
13724 continue;
13725 };
13726
13727 selection_edit_ranges.clear();
13728
13729 // If multiple selections contain a given row, avoid processing that
13730 // row more than once.
13731 let mut start_row = MultiBufferRow(selection.start.row);
13732 if last_toggled_row == Some(start_row) {
13733 start_row = start_row.next_row();
13734 }
13735 let end_row =
13736 if selection.end.row > selection.start.row && selection.end.column == 0 {
13737 MultiBufferRow(selection.end.row - 1)
13738 } else {
13739 MultiBufferRow(selection.end.row)
13740 };
13741 last_toggled_row = Some(end_row);
13742
13743 if start_row > end_row {
13744 continue;
13745 }
13746
13747 // If the language has line comments, toggle those.
13748 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13749
13750 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13751 if ignore_indent {
13752 full_comment_prefixes = full_comment_prefixes
13753 .into_iter()
13754 .map(|s| Arc::from(s.trim_end()))
13755 .collect();
13756 }
13757
13758 if !full_comment_prefixes.is_empty() {
13759 let first_prefix = full_comment_prefixes
13760 .first()
13761 .expect("prefixes is non-empty");
13762 let prefix_trimmed_lengths = full_comment_prefixes
13763 .iter()
13764 .map(|p| p.trim_end_matches(' ').len())
13765 .collect::<SmallVec<[usize; 4]>>();
13766
13767 let mut all_selection_lines_are_comments = true;
13768
13769 for row in start_row.0..=end_row.0 {
13770 let row = MultiBufferRow(row);
13771 if start_row < end_row && snapshot.is_line_blank(row) {
13772 continue;
13773 }
13774
13775 let prefix_range = full_comment_prefixes
13776 .iter()
13777 .zip(prefix_trimmed_lengths.iter().copied())
13778 .map(|(prefix, trimmed_prefix_len)| {
13779 comment_prefix_range(
13780 snapshot.deref(),
13781 row,
13782 &prefix[..trimmed_prefix_len],
13783 &prefix[trimmed_prefix_len..],
13784 ignore_indent,
13785 )
13786 })
13787 .max_by_key(|range| range.end.column - range.start.column)
13788 .expect("prefixes is non-empty");
13789
13790 if prefix_range.is_empty() {
13791 all_selection_lines_are_comments = false;
13792 }
13793
13794 selection_edit_ranges.push(prefix_range);
13795 }
13796
13797 if all_selection_lines_are_comments {
13798 edits.extend(
13799 selection_edit_ranges
13800 .iter()
13801 .cloned()
13802 .map(|range| (range, empty_str.clone())),
13803 );
13804 } else {
13805 let min_column = selection_edit_ranges
13806 .iter()
13807 .map(|range| range.start.column)
13808 .min()
13809 .unwrap_or(0);
13810 edits.extend(selection_edit_ranges.iter().map(|range| {
13811 let position = Point::new(range.start.row, min_column);
13812 (position..position, first_prefix.clone())
13813 }));
13814 }
13815 } else if let Some((full_comment_prefix, comment_suffix)) =
13816 language.block_comment_delimiters()
13817 {
13818 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13819 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13820 let prefix_range = comment_prefix_range(
13821 snapshot.deref(),
13822 start_row,
13823 comment_prefix,
13824 comment_prefix_whitespace,
13825 ignore_indent,
13826 );
13827 let suffix_range = comment_suffix_range(
13828 snapshot.deref(),
13829 end_row,
13830 comment_suffix.trim_start_matches(' '),
13831 comment_suffix.starts_with(' '),
13832 );
13833
13834 if prefix_range.is_empty() || suffix_range.is_empty() {
13835 edits.push((
13836 prefix_range.start..prefix_range.start,
13837 full_comment_prefix.clone(),
13838 ));
13839 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13840 suffixes_inserted.push((end_row, comment_suffix.len()));
13841 } else {
13842 edits.push((prefix_range, empty_str.clone()));
13843 edits.push((suffix_range, empty_str.clone()));
13844 }
13845 } else {
13846 continue;
13847 }
13848 }
13849
13850 drop(snapshot);
13851 this.buffer.update(cx, |buffer, cx| {
13852 buffer.edit(edits, None, cx);
13853 });
13854
13855 // Adjust selections so that they end before any comment suffixes that
13856 // were inserted.
13857 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13858 let mut selections = this.selections.all::<Point>(cx);
13859 let snapshot = this.buffer.read(cx).read(cx);
13860 for selection in &mut selections {
13861 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13862 match row.cmp(&MultiBufferRow(selection.end.row)) {
13863 Ordering::Less => {
13864 suffixes_inserted.next();
13865 continue;
13866 }
13867 Ordering::Greater => break,
13868 Ordering::Equal => {
13869 if selection.end.column == snapshot.line_len(row) {
13870 if selection.is_empty() {
13871 selection.start.column -= suffix_len as u32;
13872 }
13873 selection.end.column -= suffix_len as u32;
13874 }
13875 break;
13876 }
13877 }
13878 }
13879 }
13880
13881 drop(snapshot);
13882 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13883 s.select(selections)
13884 });
13885
13886 let selections = this.selections.all::<Point>(cx);
13887 let selections_on_single_row = selections.windows(2).all(|selections| {
13888 selections[0].start.row == selections[1].start.row
13889 && selections[0].end.row == selections[1].end.row
13890 && selections[0].start.row == selections[0].end.row
13891 });
13892 let selections_selecting = selections
13893 .iter()
13894 .any(|selection| selection.start != selection.end);
13895 let advance_downwards = action.advance_downwards
13896 && selections_on_single_row
13897 && !selections_selecting
13898 && !matches!(this.mode, EditorMode::SingleLine { .. });
13899
13900 if advance_downwards {
13901 let snapshot = this.buffer.read(cx).snapshot(cx);
13902
13903 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13904 s.move_cursors_with(|display_snapshot, display_point, _| {
13905 let mut point = display_point.to_point(display_snapshot);
13906 point.row += 1;
13907 point = snapshot.clip_point(point, Bias::Left);
13908 let display_point = point.to_display_point(display_snapshot);
13909 let goal = SelectionGoal::HorizontalPosition(
13910 display_snapshot
13911 .x_for_display_point(display_point, text_layout_details)
13912 .into(),
13913 );
13914 (display_point, goal)
13915 })
13916 });
13917 }
13918 });
13919 }
13920
13921 pub fn select_enclosing_symbol(
13922 &mut self,
13923 _: &SelectEnclosingSymbol,
13924 window: &mut Window,
13925 cx: &mut Context<Self>,
13926 ) {
13927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13928
13929 let buffer = self.buffer.read(cx).snapshot(cx);
13930 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13931
13932 fn update_selection(
13933 selection: &Selection<usize>,
13934 buffer_snap: &MultiBufferSnapshot,
13935 ) -> Option<Selection<usize>> {
13936 let cursor = selection.head();
13937 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13938 for symbol in symbols.iter().rev() {
13939 let start = symbol.range.start.to_offset(buffer_snap);
13940 let end = symbol.range.end.to_offset(buffer_snap);
13941 let new_range = start..end;
13942 if start < selection.start || end > selection.end {
13943 return Some(Selection {
13944 id: selection.id,
13945 start: new_range.start,
13946 end: new_range.end,
13947 goal: SelectionGoal::None,
13948 reversed: selection.reversed,
13949 });
13950 }
13951 }
13952 None
13953 }
13954
13955 let mut selected_larger_symbol = false;
13956 let new_selections = old_selections
13957 .iter()
13958 .map(|selection| match update_selection(selection, &buffer) {
13959 Some(new_selection) => {
13960 if new_selection.range() != selection.range() {
13961 selected_larger_symbol = true;
13962 }
13963 new_selection
13964 }
13965 None => selection.clone(),
13966 })
13967 .collect::<Vec<_>>();
13968
13969 if selected_larger_symbol {
13970 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13971 s.select(new_selections);
13972 });
13973 }
13974 }
13975
13976 pub fn select_larger_syntax_node(
13977 &mut self,
13978 _: &SelectLargerSyntaxNode,
13979 window: &mut Window,
13980 cx: &mut Context<Self>,
13981 ) {
13982 let Some(visible_row_count) = self.visible_row_count() else {
13983 return;
13984 };
13985 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13986 if old_selections.is_empty() {
13987 return;
13988 }
13989
13990 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13991
13992 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13993 let buffer = self.buffer.read(cx).snapshot(cx);
13994
13995 let mut selected_larger_node = false;
13996 let mut new_selections = old_selections
13997 .iter()
13998 .map(|selection| {
13999 let old_range = selection.start..selection.end;
14000
14001 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
14002 // manually select word at selection
14003 if ["string_content", "inline"].contains(&node.kind()) {
14004 let word_range = {
14005 let display_point = buffer
14006 .offset_to_point(old_range.start)
14007 .to_display_point(&display_map);
14008 let Range { start, end } =
14009 movement::surrounding_word(&display_map, display_point);
14010 start.to_point(&display_map).to_offset(&buffer)
14011 ..end.to_point(&display_map).to_offset(&buffer)
14012 };
14013 // ignore if word is already selected
14014 if !word_range.is_empty() && old_range != word_range {
14015 let last_word_range = {
14016 let display_point = buffer
14017 .offset_to_point(old_range.end)
14018 .to_display_point(&display_map);
14019 let Range { start, end } =
14020 movement::surrounding_word(&display_map, display_point);
14021 start.to_point(&display_map).to_offset(&buffer)
14022 ..end.to_point(&display_map).to_offset(&buffer)
14023 };
14024 // only select word if start and end point belongs to same word
14025 if word_range == last_word_range {
14026 selected_larger_node = true;
14027 return Selection {
14028 id: selection.id,
14029 start: word_range.start,
14030 end: word_range.end,
14031 goal: SelectionGoal::None,
14032 reversed: selection.reversed,
14033 };
14034 }
14035 }
14036 }
14037 }
14038
14039 let mut new_range = old_range.clone();
14040 while let Some((_node, containing_range)) =
14041 buffer.syntax_ancestor(new_range.clone())
14042 {
14043 new_range = match containing_range {
14044 MultiOrSingleBufferOffsetRange::Single(_) => break,
14045 MultiOrSingleBufferOffsetRange::Multi(range) => range,
14046 };
14047 if !display_map.intersects_fold(new_range.start)
14048 && !display_map.intersects_fold(new_range.end)
14049 {
14050 break;
14051 }
14052 }
14053
14054 selected_larger_node |= new_range != old_range;
14055 Selection {
14056 id: selection.id,
14057 start: new_range.start,
14058 end: new_range.end,
14059 goal: SelectionGoal::None,
14060 reversed: selection.reversed,
14061 }
14062 })
14063 .collect::<Vec<_>>();
14064
14065 if !selected_larger_node {
14066 return; // don't put this call in the history
14067 }
14068
14069 // scroll based on transformation done to the last selection created by the user
14070 let (last_old, last_new) = old_selections
14071 .last()
14072 .zip(new_selections.last().cloned())
14073 .expect("old_selections isn't empty");
14074
14075 // revert selection
14076 let is_selection_reversed = {
14077 let should_newest_selection_be_reversed = last_old.start != last_new.start;
14078 new_selections.last_mut().expect("checked above").reversed =
14079 should_newest_selection_be_reversed;
14080 should_newest_selection_be_reversed
14081 };
14082
14083 if selected_larger_node {
14084 self.select_syntax_node_history.disable_clearing = true;
14085 self.change_selections(None, window, cx, |s| {
14086 s.select(new_selections.clone());
14087 });
14088 self.select_syntax_node_history.disable_clearing = false;
14089 }
14090
14091 let start_row = last_new.start.to_display_point(&display_map).row().0;
14092 let end_row = last_new.end.to_display_point(&display_map).row().0;
14093 let selection_height = end_row - start_row + 1;
14094 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14095
14096 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14097 let scroll_behavior = if fits_on_the_screen {
14098 self.request_autoscroll(Autoscroll::fit(), cx);
14099 SelectSyntaxNodeScrollBehavior::FitSelection
14100 } else if is_selection_reversed {
14101 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14102 SelectSyntaxNodeScrollBehavior::CursorTop
14103 } else {
14104 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14105 SelectSyntaxNodeScrollBehavior::CursorBottom
14106 };
14107
14108 self.select_syntax_node_history.push((
14109 old_selections,
14110 scroll_behavior,
14111 is_selection_reversed,
14112 ));
14113 }
14114
14115 pub fn select_smaller_syntax_node(
14116 &mut self,
14117 _: &SelectSmallerSyntaxNode,
14118 window: &mut Window,
14119 cx: &mut Context<Self>,
14120 ) {
14121 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14122
14123 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14124 self.select_syntax_node_history.pop()
14125 {
14126 if let Some(selection) = selections.last_mut() {
14127 selection.reversed = is_selection_reversed;
14128 }
14129
14130 self.select_syntax_node_history.disable_clearing = true;
14131 self.change_selections(None, window, cx, |s| {
14132 s.select(selections.to_vec());
14133 });
14134 self.select_syntax_node_history.disable_clearing = false;
14135
14136 match scroll_behavior {
14137 SelectSyntaxNodeScrollBehavior::CursorTop => {
14138 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14139 }
14140 SelectSyntaxNodeScrollBehavior::FitSelection => {
14141 self.request_autoscroll(Autoscroll::fit(), cx);
14142 }
14143 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14144 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14145 }
14146 }
14147 }
14148 }
14149
14150 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14151 if !EditorSettings::get_global(cx).gutter.runnables {
14152 self.clear_tasks();
14153 return Task::ready(());
14154 }
14155 let project = self.project.as_ref().map(Entity::downgrade);
14156 let task_sources = self.lsp_task_sources(cx);
14157 let multi_buffer = self.buffer.downgrade();
14158 cx.spawn_in(window, async move |editor, cx| {
14159 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14160 let Some(project) = project.and_then(|p| p.upgrade()) else {
14161 return;
14162 };
14163 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14164 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14165 }) else {
14166 return;
14167 };
14168
14169 let hide_runnables = project
14170 .update(cx, |project, cx| {
14171 // Do not display any test indicators in non-dev server remote projects.
14172 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14173 })
14174 .unwrap_or(true);
14175 if hide_runnables {
14176 return;
14177 }
14178 let new_rows =
14179 cx.background_spawn({
14180 let snapshot = display_snapshot.clone();
14181 async move {
14182 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14183 }
14184 })
14185 .await;
14186 let Ok(lsp_tasks) =
14187 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14188 else {
14189 return;
14190 };
14191 let lsp_tasks = lsp_tasks.await;
14192
14193 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14194 lsp_tasks
14195 .into_iter()
14196 .flat_map(|(kind, tasks)| {
14197 tasks.into_iter().filter_map(move |(location, task)| {
14198 Some((kind.clone(), location?, task))
14199 })
14200 })
14201 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14202 let buffer = location.target.buffer;
14203 let buffer_snapshot = buffer.read(cx).snapshot();
14204 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14205 |(excerpt_id, snapshot, _)| {
14206 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14207 display_snapshot
14208 .buffer_snapshot
14209 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14210 } else {
14211 None
14212 }
14213 },
14214 );
14215 if let Some(offset) = offset {
14216 let task_buffer_range =
14217 location.target.range.to_point(&buffer_snapshot);
14218 let context_buffer_range =
14219 task_buffer_range.to_offset(&buffer_snapshot);
14220 let context_range = BufferOffset(context_buffer_range.start)
14221 ..BufferOffset(context_buffer_range.end);
14222
14223 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14224 .or_insert_with(|| RunnableTasks {
14225 templates: Vec::new(),
14226 offset,
14227 column: task_buffer_range.start.column,
14228 extra_variables: HashMap::default(),
14229 context_range,
14230 })
14231 .templates
14232 .push((kind, task.original_task().clone()));
14233 }
14234
14235 acc
14236 })
14237 }) else {
14238 return;
14239 };
14240
14241 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14242 buffer.language_settings(cx).tasks.prefer_lsp
14243 }) else {
14244 return;
14245 };
14246
14247 let rows = Self::runnable_rows(
14248 project,
14249 display_snapshot,
14250 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14251 new_rows,
14252 cx.clone(),
14253 )
14254 .await;
14255 editor
14256 .update(cx, |editor, _| {
14257 editor.clear_tasks();
14258 for (key, mut value) in rows {
14259 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14260 value.templates.extend(lsp_tasks.templates);
14261 }
14262
14263 editor.insert_tasks(key, value);
14264 }
14265 for (key, value) in lsp_tasks_by_rows {
14266 editor.insert_tasks(key, value);
14267 }
14268 })
14269 .ok();
14270 })
14271 }
14272 fn fetch_runnable_ranges(
14273 snapshot: &DisplaySnapshot,
14274 range: Range<Anchor>,
14275 ) -> Vec<language::RunnableRange> {
14276 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14277 }
14278
14279 fn runnable_rows(
14280 project: Entity<Project>,
14281 snapshot: DisplaySnapshot,
14282 prefer_lsp: bool,
14283 runnable_ranges: Vec<RunnableRange>,
14284 cx: AsyncWindowContext,
14285 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14286 cx.spawn(async move |cx| {
14287 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14288 for mut runnable in runnable_ranges {
14289 let Some(tasks) = cx
14290 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14291 .ok()
14292 else {
14293 continue;
14294 };
14295 let mut tasks = tasks.await;
14296
14297 if prefer_lsp {
14298 tasks.retain(|(task_kind, _)| {
14299 !matches!(task_kind, TaskSourceKind::Language { .. })
14300 });
14301 }
14302 if tasks.is_empty() {
14303 continue;
14304 }
14305
14306 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14307 let Some(row) = snapshot
14308 .buffer_snapshot
14309 .buffer_line_for_row(MultiBufferRow(point.row))
14310 .map(|(_, range)| range.start.row)
14311 else {
14312 continue;
14313 };
14314
14315 let context_range =
14316 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14317 runnable_rows.push((
14318 (runnable.buffer_id, row),
14319 RunnableTasks {
14320 templates: tasks,
14321 offset: snapshot
14322 .buffer_snapshot
14323 .anchor_before(runnable.run_range.start),
14324 context_range,
14325 column: point.column,
14326 extra_variables: runnable.extra_captures,
14327 },
14328 ));
14329 }
14330 runnable_rows
14331 })
14332 }
14333
14334 fn templates_with_tags(
14335 project: &Entity<Project>,
14336 runnable: &mut Runnable,
14337 cx: &mut App,
14338 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14339 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14340 let (worktree_id, file) = project
14341 .buffer_for_id(runnable.buffer, cx)
14342 .and_then(|buffer| buffer.read(cx).file())
14343 .map(|file| (file.worktree_id(cx), file.clone()))
14344 .unzip();
14345
14346 (
14347 project.task_store().read(cx).task_inventory().cloned(),
14348 worktree_id,
14349 file,
14350 )
14351 });
14352
14353 let tags = mem::take(&mut runnable.tags);
14354 let language = runnable.language.clone();
14355 cx.spawn(async move |cx| {
14356 let mut templates_with_tags = Vec::new();
14357 if let Some(inventory) = inventory {
14358 for RunnableTag(tag) in tags {
14359 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14360 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14361 }) else {
14362 return templates_with_tags;
14363 };
14364 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14365 move |(_, template)| {
14366 template.tags.iter().any(|source_tag| source_tag == &tag)
14367 },
14368 ));
14369 }
14370 }
14371 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14372
14373 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14374 // Strongest source wins; if we have worktree tag binding, prefer that to
14375 // global and language bindings;
14376 // if we have a global binding, prefer that to language binding.
14377 let first_mismatch = templates_with_tags
14378 .iter()
14379 .position(|(tag_source, _)| tag_source != leading_tag_source);
14380 if let Some(index) = first_mismatch {
14381 templates_with_tags.truncate(index);
14382 }
14383 }
14384
14385 templates_with_tags
14386 })
14387 }
14388
14389 pub fn move_to_enclosing_bracket(
14390 &mut self,
14391 _: &MoveToEnclosingBracket,
14392 window: &mut Window,
14393 cx: &mut Context<Self>,
14394 ) {
14395 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14396 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14397 s.move_offsets_with(|snapshot, selection| {
14398 let Some(enclosing_bracket_ranges) =
14399 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14400 else {
14401 return;
14402 };
14403
14404 let mut best_length = usize::MAX;
14405 let mut best_inside = false;
14406 let mut best_in_bracket_range = false;
14407 let mut best_destination = None;
14408 for (open, close) in enclosing_bracket_ranges {
14409 let close = close.to_inclusive();
14410 let length = close.end() - open.start;
14411 let inside = selection.start >= open.end && selection.end <= *close.start();
14412 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14413 || close.contains(&selection.head());
14414
14415 // If best is next to a bracket and current isn't, skip
14416 if !in_bracket_range && best_in_bracket_range {
14417 continue;
14418 }
14419
14420 // Prefer smaller lengths unless best is inside and current isn't
14421 if length > best_length && (best_inside || !inside) {
14422 continue;
14423 }
14424
14425 best_length = length;
14426 best_inside = inside;
14427 best_in_bracket_range = in_bracket_range;
14428 best_destination = Some(
14429 if close.contains(&selection.start) && close.contains(&selection.end) {
14430 if inside { open.end } else { open.start }
14431 } else if inside {
14432 *close.start()
14433 } else {
14434 *close.end()
14435 },
14436 );
14437 }
14438
14439 if let Some(destination) = best_destination {
14440 selection.collapse_to(destination, SelectionGoal::None);
14441 }
14442 })
14443 });
14444 }
14445
14446 pub fn undo_selection(
14447 &mut self,
14448 _: &UndoSelection,
14449 window: &mut Window,
14450 cx: &mut Context<Self>,
14451 ) {
14452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14453 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14454 self.selection_history.mode = SelectionHistoryMode::Undoing;
14455 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14456 this.end_selection(window, cx);
14457 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14458 s.select_anchors(entry.selections.to_vec())
14459 });
14460 });
14461 self.selection_history.mode = SelectionHistoryMode::Normal;
14462
14463 self.select_next_state = entry.select_next_state;
14464 self.select_prev_state = entry.select_prev_state;
14465 self.add_selections_state = entry.add_selections_state;
14466 }
14467 }
14468
14469 pub fn redo_selection(
14470 &mut self,
14471 _: &RedoSelection,
14472 window: &mut Window,
14473 cx: &mut Context<Self>,
14474 ) {
14475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14476 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14477 self.selection_history.mode = SelectionHistoryMode::Redoing;
14478 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14479 this.end_selection(window, cx);
14480 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14481 s.select_anchors(entry.selections.to_vec())
14482 });
14483 });
14484 self.selection_history.mode = SelectionHistoryMode::Normal;
14485
14486 self.select_next_state = entry.select_next_state;
14487 self.select_prev_state = entry.select_prev_state;
14488 self.add_selections_state = entry.add_selections_state;
14489 }
14490 }
14491
14492 pub fn expand_excerpts(
14493 &mut self,
14494 action: &ExpandExcerpts,
14495 _: &mut Window,
14496 cx: &mut Context<Self>,
14497 ) {
14498 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14499 }
14500
14501 pub fn expand_excerpts_down(
14502 &mut self,
14503 action: &ExpandExcerptsDown,
14504 _: &mut Window,
14505 cx: &mut Context<Self>,
14506 ) {
14507 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14508 }
14509
14510 pub fn expand_excerpts_up(
14511 &mut self,
14512 action: &ExpandExcerptsUp,
14513 _: &mut Window,
14514 cx: &mut Context<Self>,
14515 ) {
14516 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14517 }
14518
14519 pub fn expand_excerpts_for_direction(
14520 &mut self,
14521 lines: u32,
14522 direction: ExpandExcerptDirection,
14523
14524 cx: &mut Context<Self>,
14525 ) {
14526 let selections = self.selections.disjoint_anchors();
14527
14528 let lines = if lines == 0 {
14529 EditorSettings::get_global(cx).expand_excerpt_lines
14530 } else {
14531 lines
14532 };
14533
14534 self.buffer.update(cx, |buffer, cx| {
14535 let snapshot = buffer.snapshot(cx);
14536 let mut excerpt_ids = selections
14537 .iter()
14538 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14539 .collect::<Vec<_>>();
14540 excerpt_ids.sort();
14541 excerpt_ids.dedup();
14542 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14543 })
14544 }
14545
14546 pub fn expand_excerpt(
14547 &mut self,
14548 excerpt: ExcerptId,
14549 direction: ExpandExcerptDirection,
14550 window: &mut Window,
14551 cx: &mut Context<Self>,
14552 ) {
14553 let current_scroll_position = self.scroll_position(cx);
14554 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14555 let mut should_scroll_up = false;
14556
14557 if direction == ExpandExcerptDirection::Down {
14558 let multi_buffer = self.buffer.read(cx);
14559 let snapshot = multi_buffer.snapshot(cx);
14560 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14561 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14562 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14563 let buffer_snapshot = buffer.read(cx).snapshot();
14564 let excerpt_end_row =
14565 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14566 let last_row = buffer_snapshot.max_point().row;
14567 let lines_below = last_row.saturating_sub(excerpt_end_row);
14568 should_scroll_up = lines_below >= lines_to_expand;
14569 }
14570 }
14571 }
14572 }
14573
14574 self.buffer.update(cx, |buffer, cx| {
14575 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14576 });
14577
14578 if should_scroll_up {
14579 let new_scroll_position =
14580 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14581 self.set_scroll_position(new_scroll_position, window, cx);
14582 }
14583 }
14584
14585 pub fn go_to_singleton_buffer_point(
14586 &mut self,
14587 point: Point,
14588 window: &mut Window,
14589 cx: &mut Context<Self>,
14590 ) {
14591 self.go_to_singleton_buffer_range(point..point, window, cx);
14592 }
14593
14594 pub fn go_to_singleton_buffer_range(
14595 &mut self,
14596 range: Range<Point>,
14597 window: &mut Window,
14598 cx: &mut Context<Self>,
14599 ) {
14600 let multibuffer = self.buffer().read(cx);
14601 let Some(buffer) = multibuffer.as_singleton() else {
14602 return;
14603 };
14604 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14605 return;
14606 };
14607 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14608 return;
14609 };
14610 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14611 s.select_anchor_ranges([start..end])
14612 });
14613 }
14614
14615 pub fn go_to_diagnostic(
14616 &mut self,
14617 _: &GoToDiagnostic,
14618 window: &mut Window,
14619 cx: &mut Context<Self>,
14620 ) {
14621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14622 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14623 }
14624
14625 pub fn go_to_prev_diagnostic(
14626 &mut self,
14627 _: &GoToPreviousDiagnostic,
14628 window: &mut Window,
14629 cx: &mut Context<Self>,
14630 ) {
14631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14632 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14633 }
14634
14635 pub fn go_to_diagnostic_impl(
14636 &mut self,
14637 direction: Direction,
14638 window: &mut Window,
14639 cx: &mut Context<Self>,
14640 ) {
14641 let buffer = self.buffer.read(cx).snapshot(cx);
14642 let selection = self.selections.newest::<usize>(cx);
14643
14644 let mut active_group_id = None;
14645 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14646 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14647 active_group_id = Some(active_group.group_id);
14648 }
14649 }
14650
14651 fn filtered(
14652 snapshot: EditorSnapshot,
14653 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14654 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14655 diagnostics
14656 .filter(|entry| entry.range.start != entry.range.end)
14657 .filter(|entry| !entry.diagnostic.is_unnecessary)
14658 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14659 }
14660
14661 let snapshot = self.snapshot(window, cx);
14662 let before = filtered(
14663 snapshot.clone(),
14664 buffer
14665 .diagnostics_in_range(0..selection.start)
14666 .filter(|entry| entry.range.start <= selection.start),
14667 );
14668 let after = filtered(
14669 snapshot,
14670 buffer
14671 .diagnostics_in_range(selection.start..buffer.len())
14672 .filter(|entry| entry.range.start >= selection.start),
14673 );
14674
14675 let mut found: Option<DiagnosticEntry<usize>> = None;
14676 if direction == Direction::Prev {
14677 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14678 {
14679 for diagnostic in prev_diagnostics.into_iter().rev() {
14680 if diagnostic.range.start != selection.start
14681 || active_group_id
14682 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14683 {
14684 found = Some(diagnostic);
14685 break 'outer;
14686 }
14687 }
14688 }
14689 } else {
14690 for diagnostic in after.chain(before) {
14691 if diagnostic.range.start != selection.start
14692 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14693 {
14694 found = Some(diagnostic);
14695 break;
14696 }
14697 }
14698 }
14699 let Some(next_diagnostic) = found else {
14700 return;
14701 };
14702
14703 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14704 return;
14705 };
14706 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14707 s.select_ranges(vec![
14708 next_diagnostic.range.start..next_diagnostic.range.start,
14709 ])
14710 });
14711 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14712 self.refresh_inline_completion(false, true, window, cx);
14713 }
14714
14715 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14716 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14717 let snapshot = self.snapshot(window, cx);
14718 let selection = self.selections.newest::<Point>(cx);
14719 self.go_to_hunk_before_or_after_position(
14720 &snapshot,
14721 selection.head(),
14722 Direction::Next,
14723 window,
14724 cx,
14725 );
14726 }
14727
14728 pub fn go_to_hunk_before_or_after_position(
14729 &mut self,
14730 snapshot: &EditorSnapshot,
14731 position: Point,
14732 direction: Direction,
14733 window: &mut Window,
14734 cx: &mut Context<Editor>,
14735 ) {
14736 let row = if direction == Direction::Next {
14737 self.hunk_after_position(snapshot, position)
14738 .map(|hunk| hunk.row_range.start)
14739 } else {
14740 self.hunk_before_position(snapshot, position)
14741 };
14742
14743 if let Some(row) = row {
14744 let destination = Point::new(row.0, 0);
14745 let autoscroll = Autoscroll::center();
14746
14747 self.unfold_ranges(&[destination..destination], false, false, cx);
14748 self.change_selections(Some(autoscroll), window, cx, |s| {
14749 s.select_ranges([destination..destination]);
14750 });
14751 }
14752 }
14753
14754 fn hunk_after_position(
14755 &mut self,
14756 snapshot: &EditorSnapshot,
14757 position: Point,
14758 ) -> Option<MultiBufferDiffHunk> {
14759 snapshot
14760 .buffer_snapshot
14761 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14762 .find(|hunk| hunk.row_range.start.0 > position.row)
14763 .or_else(|| {
14764 snapshot
14765 .buffer_snapshot
14766 .diff_hunks_in_range(Point::zero()..position)
14767 .find(|hunk| hunk.row_range.end.0 < position.row)
14768 })
14769 }
14770
14771 fn go_to_prev_hunk(
14772 &mut self,
14773 _: &GoToPreviousHunk,
14774 window: &mut Window,
14775 cx: &mut Context<Self>,
14776 ) {
14777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14778 let snapshot = self.snapshot(window, cx);
14779 let selection = self.selections.newest::<Point>(cx);
14780 self.go_to_hunk_before_or_after_position(
14781 &snapshot,
14782 selection.head(),
14783 Direction::Prev,
14784 window,
14785 cx,
14786 );
14787 }
14788
14789 fn hunk_before_position(
14790 &mut self,
14791 snapshot: &EditorSnapshot,
14792 position: Point,
14793 ) -> Option<MultiBufferRow> {
14794 snapshot
14795 .buffer_snapshot
14796 .diff_hunk_before(position)
14797 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14798 }
14799
14800 fn go_to_next_change(
14801 &mut self,
14802 _: &GoToNextChange,
14803 window: &mut Window,
14804 cx: &mut Context<Self>,
14805 ) {
14806 if let Some(selections) = self
14807 .change_list
14808 .next_change(1, Direction::Next)
14809 .map(|s| s.to_vec())
14810 {
14811 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14812 let map = s.display_map();
14813 s.select_display_ranges(selections.iter().map(|a| {
14814 let point = a.to_display_point(&map);
14815 point..point
14816 }))
14817 })
14818 }
14819 }
14820
14821 fn go_to_previous_change(
14822 &mut self,
14823 _: &GoToPreviousChange,
14824 window: &mut Window,
14825 cx: &mut Context<Self>,
14826 ) {
14827 if let Some(selections) = self
14828 .change_list
14829 .next_change(1, Direction::Prev)
14830 .map(|s| s.to_vec())
14831 {
14832 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14833 let map = s.display_map();
14834 s.select_display_ranges(selections.iter().map(|a| {
14835 let point = a.to_display_point(&map);
14836 point..point
14837 }))
14838 })
14839 }
14840 }
14841
14842 fn go_to_line<T: 'static>(
14843 &mut self,
14844 position: Anchor,
14845 highlight_color: Option<Hsla>,
14846 window: &mut Window,
14847 cx: &mut Context<Self>,
14848 ) {
14849 let snapshot = self.snapshot(window, cx).display_snapshot;
14850 let position = position.to_point(&snapshot.buffer_snapshot);
14851 let start = snapshot
14852 .buffer_snapshot
14853 .clip_point(Point::new(position.row, 0), Bias::Left);
14854 let end = start + Point::new(1, 0);
14855 let start = snapshot.buffer_snapshot.anchor_before(start);
14856 let end = snapshot.buffer_snapshot.anchor_before(end);
14857
14858 self.highlight_rows::<T>(
14859 start..end,
14860 highlight_color
14861 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14862 Default::default(),
14863 cx,
14864 );
14865
14866 if self.buffer.read(cx).is_singleton() {
14867 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14868 }
14869 }
14870
14871 pub fn go_to_definition(
14872 &mut self,
14873 _: &GoToDefinition,
14874 window: &mut Window,
14875 cx: &mut Context<Self>,
14876 ) -> Task<Result<Navigated>> {
14877 let definition =
14878 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14879 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14880 cx.spawn_in(window, async move |editor, cx| {
14881 if definition.await? == Navigated::Yes {
14882 return Ok(Navigated::Yes);
14883 }
14884 match fallback_strategy {
14885 GoToDefinitionFallback::None => Ok(Navigated::No),
14886 GoToDefinitionFallback::FindAllReferences => {
14887 match editor.update_in(cx, |editor, window, cx| {
14888 editor.find_all_references(&FindAllReferences, window, cx)
14889 })? {
14890 Some(references) => references.await,
14891 None => Ok(Navigated::No),
14892 }
14893 }
14894 }
14895 })
14896 }
14897
14898 pub fn go_to_declaration(
14899 &mut self,
14900 _: &GoToDeclaration,
14901 window: &mut Window,
14902 cx: &mut Context<Self>,
14903 ) -> Task<Result<Navigated>> {
14904 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14905 }
14906
14907 pub fn go_to_declaration_split(
14908 &mut self,
14909 _: &GoToDeclaration,
14910 window: &mut Window,
14911 cx: &mut Context<Self>,
14912 ) -> Task<Result<Navigated>> {
14913 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14914 }
14915
14916 pub fn go_to_implementation(
14917 &mut self,
14918 _: &GoToImplementation,
14919 window: &mut Window,
14920 cx: &mut Context<Self>,
14921 ) -> Task<Result<Navigated>> {
14922 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14923 }
14924
14925 pub fn go_to_implementation_split(
14926 &mut self,
14927 _: &GoToImplementationSplit,
14928 window: &mut Window,
14929 cx: &mut Context<Self>,
14930 ) -> Task<Result<Navigated>> {
14931 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14932 }
14933
14934 pub fn go_to_type_definition(
14935 &mut self,
14936 _: &GoToTypeDefinition,
14937 window: &mut Window,
14938 cx: &mut Context<Self>,
14939 ) -> Task<Result<Navigated>> {
14940 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14941 }
14942
14943 pub fn go_to_definition_split(
14944 &mut self,
14945 _: &GoToDefinitionSplit,
14946 window: &mut Window,
14947 cx: &mut Context<Self>,
14948 ) -> Task<Result<Navigated>> {
14949 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14950 }
14951
14952 pub fn go_to_type_definition_split(
14953 &mut self,
14954 _: &GoToTypeDefinitionSplit,
14955 window: &mut Window,
14956 cx: &mut Context<Self>,
14957 ) -> Task<Result<Navigated>> {
14958 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14959 }
14960
14961 fn go_to_definition_of_kind(
14962 &mut self,
14963 kind: GotoDefinitionKind,
14964 split: bool,
14965 window: &mut Window,
14966 cx: &mut Context<Self>,
14967 ) -> Task<Result<Navigated>> {
14968 let Some(provider) = self.semantics_provider.clone() else {
14969 return Task::ready(Ok(Navigated::No));
14970 };
14971 let head = self.selections.newest::<usize>(cx).head();
14972 let buffer = self.buffer.read(cx);
14973 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14974 text_anchor
14975 } else {
14976 return Task::ready(Ok(Navigated::No));
14977 };
14978
14979 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14980 return Task::ready(Ok(Navigated::No));
14981 };
14982
14983 cx.spawn_in(window, async move |editor, cx| {
14984 let definitions = definitions.await?;
14985 let navigated = editor
14986 .update_in(cx, |editor, window, cx| {
14987 editor.navigate_to_hover_links(
14988 Some(kind),
14989 definitions
14990 .into_iter()
14991 .filter(|location| {
14992 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14993 })
14994 .map(HoverLink::Text)
14995 .collect::<Vec<_>>(),
14996 split,
14997 window,
14998 cx,
14999 )
15000 })?
15001 .await?;
15002 anyhow::Ok(navigated)
15003 })
15004 }
15005
15006 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
15007 let selection = self.selections.newest_anchor();
15008 let head = selection.head();
15009 let tail = selection.tail();
15010
15011 let Some((buffer, start_position)) =
15012 self.buffer.read(cx).text_anchor_for_position(head, cx)
15013 else {
15014 return;
15015 };
15016
15017 let end_position = if head != tail {
15018 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
15019 return;
15020 };
15021 Some(pos)
15022 } else {
15023 None
15024 };
15025
15026 let url_finder = cx.spawn_in(window, async move |editor, cx| {
15027 let url = if let Some(end_pos) = end_position {
15028 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
15029 } else {
15030 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
15031 };
15032
15033 if let Some(url) = url {
15034 editor.update(cx, |_, cx| {
15035 cx.open_url(&url);
15036 })
15037 } else {
15038 Ok(())
15039 }
15040 });
15041
15042 url_finder.detach();
15043 }
15044
15045 pub fn open_selected_filename(
15046 &mut self,
15047 _: &OpenSelectedFilename,
15048 window: &mut Window,
15049 cx: &mut Context<Self>,
15050 ) {
15051 let Some(workspace) = self.workspace() else {
15052 return;
15053 };
15054
15055 let position = self.selections.newest_anchor().head();
15056
15057 let Some((buffer, buffer_position)) =
15058 self.buffer.read(cx).text_anchor_for_position(position, cx)
15059 else {
15060 return;
15061 };
15062
15063 let project = self.project.clone();
15064
15065 cx.spawn_in(window, async move |_, cx| {
15066 let result = find_file(&buffer, project, buffer_position, cx).await;
15067
15068 if let Some((_, path)) = result {
15069 workspace
15070 .update_in(cx, |workspace, window, cx| {
15071 workspace.open_resolved_path(path, window, cx)
15072 })?
15073 .await?;
15074 }
15075 anyhow::Ok(())
15076 })
15077 .detach();
15078 }
15079
15080 pub(crate) fn navigate_to_hover_links(
15081 &mut self,
15082 kind: Option<GotoDefinitionKind>,
15083 mut definitions: Vec<HoverLink>,
15084 split: bool,
15085 window: &mut Window,
15086 cx: &mut Context<Editor>,
15087 ) -> Task<Result<Navigated>> {
15088 // If there is one definition, just open it directly
15089 if definitions.len() == 1 {
15090 let definition = definitions.pop().unwrap();
15091
15092 enum TargetTaskResult {
15093 Location(Option<Location>),
15094 AlreadyNavigated,
15095 }
15096
15097 let target_task = match definition {
15098 HoverLink::Text(link) => {
15099 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15100 }
15101 HoverLink::InlayHint(lsp_location, server_id) => {
15102 let computation =
15103 self.compute_target_location(lsp_location, server_id, window, cx);
15104 cx.background_spawn(async move {
15105 let location = computation.await?;
15106 Ok(TargetTaskResult::Location(location))
15107 })
15108 }
15109 HoverLink::Url(url) => {
15110 cx.open_url(&url);
15111 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15112 }
15113 HoverLink::File(path) => {
15114 if let Some(workspace) = self.workspace() {
15115 cx.spawn_in(window, async move |_, cx| {
15116 workspace
15117 .update_in(cx, |workspace, window, cx| {
15118 workspace.open_resolved_path(path, window, cx)
15119 })?
15120 .await
15121 .map(|_| TargetTaskResult::AlreadyNavigated)
15122 })
15123 } else {
15124 Task::ready(Ok(TargetTaskResult::Location(None)))
15125 }
15126 }
15127 };
15128 cx.spawn_in(window, async move |editor, cx| {
15129 let target = match target_task.await.context("target resolution task")? {
15130 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15131 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15132 TargetTaskResult::Location(Some(target)) => target,
15133 };
15134
15135 editor.update_in(cx, |editor, window, cx| {
15136 let Some(workspace) = editor.workspace() else {
15137 return Navigated::No;
15138 };
15139 let pane = workspace.read(cx).active_pane().clone();
15140
15141 let range = target.range.to_point(target.buffer.read(cx));
15142 let range = editor.range_for_match(&range);
15143 let range = collapse_multiline_range(range);
15144
15145 if !split
15146 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15147 {
15148 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15149 } else {
15150 window.defer(cx, move |window, cx| {
15151 let target_editor: Entity<Self> =
15152 workspace.update(cx, |workspace, cx| {
15153 let pane = if split {
15154 workspace.adjacent_pane(window, cx)
15155 } else {
15156 workspace.active_pane().clone()
15157 };
15158
15159 workspace.open_project_item(
15160 pane,
15161 target.buffer.clone(),
15162 true,
15163 true,
15164 window,
15165 cx,
15166 )
15167 });
15168 target_editor.update(cx, |target_editor, cx| {
15169 // When selecting a definition in a different buffer, disable the nav history
15170 // to avoid creating a history entry at the previous cursor location.
15171 pane.update(cx, |pane, _| pane.disable_history());
15172 target_editor.go_to_singleton_buffer_range(range, window, cx);
15173 pane.update(cx, |pane, _| pane.enable_history());
15174 });
15175 });
15176 }
15177 Navigated::Yes
15178 })
15179 })
15180 } else if !definitions.is_empty() {
15181 cx.spawn_in(window, async move |editor, cx| {
15182 let (title, location_tasks, workspace) = editor
15183 .update_in(cx, |editor, window, cx| {
15184 let tab_kind = match kind {
15185 Some(GotoDefinitionKind::Implementation) => "Implementations",
15186 _ => "Definitions",
15187 };
15188 let title = definitions
15189 .iter()
15190 .find_map(|definition| match definition {
15191 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15192 let buffer = origin.buffer.read(cx);
15193 format!(
15194 "{} for {}",
15195 tab_kind,
15196 buffer
15197 .text_for_range(origin.range.clone())
15198 .collect::<String>()
15199 )
15200 }),
15201 HoverLink::InlayHint(_, _) => None,
15202 HoverLink::Url(_) => None,
15203 HoverLink::File(_) => None,
15204 })
15205 .unwrap_or(tab_kind.to_string());
15206 let location_tasks = definitions
15207 .into_iter()
15208 .map(|definition| match definition {
15209 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15210 HoverLink::InlayHint(lsp_location, server_id) => editor
15211 .compute_target_location(lsp_location, server_id, window, cx),
15212 HoverLink::Url(_) => Task::ready(Ok(None)),
15213 HoverLink::File(_) => Task::ready(Ok(None)),
15214 })
15215 .collect::<Vec<_>>();
15216 (title, location_tasks, editor.workspace().clone())
15217 })
15218 .context("location tasks preparation")?;
15219
15220 let locations: Vec<Location> = future::join_all(location_tasks)
15221 .await
15222 .into_iter()
15223 .filter_map(|location| location.transpose())
15224 .collect::<Result<_>>()
15225 .context("location tasks")?;
15226
15227 if locations.is_empty() {
15228 return Ok(Navigated::No);
15229 }
15230
15231 let Some(workspace) = workspace else {
15232 return Ok(Navigated::No);
15233 };
15234
15235 let opened = workspace
15236 .update_in(cx, |workspace, window, cx| {
15237 Self::open_locations_in_multibuffer(
15238 workspace,
15239 locations,
15240 title,
15241 split,
15242 MultibufferSelectionMode::First,
15243 window,
15244 cx,
15245 )
15246 })
15247 .ok();
15248
15249 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15250 })
15251 } else {
15252 Task::ready(Ok(Navigated::No))
15253 }
15254 }
15255
15256 fn compute_target_location(
15257 &self,
15258 lsp_location: lsp::Location,
15259 server_id: LanguageServerId,
15260 window: &mut Window,
15261 cx: &mut Context<Self>,
15262 ) -> Task<anyhow::Result<Option<Location>>> {
15263 let Some(project) = self.project.clone() else {
15264 return Task::ready(Ok(None));
15265 };
15266
15267 cx.spawn_in(window, async move |editor, cx| {
15268 let location_task = editor.update(cx, |_, cx| {
15269 project.update(cx, |project, cx| {
15270 let language_server_name = project
15271 .language_server_statuses(cx)
15272 .find(|(id, _)| server_id == *id)
15273 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15274 language_server_name.map(|language_server_name| {
15275 project.open_local_buffer_via_lsp(
15276 lsp_location.uri.clone(),
15277 server_id,
15278 language_server_name,
15279 cx,
15280 )
15281 })
15282 })
15283 })?;
15284 let location = match location_task {
15285 Some(task) => Some({
15286 let target_buffer_handle = task.await.context("open local buffer")?;
15287 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15288 let target_start = target_buffer
15289 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15290 let target_end = target_buffer
15291 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15292 target_buffer.anchor_after(target_start)
15293 ..target_buffer.anchor_before(target_end)
15294 })?;
15295 Location {
15296 buffer: target_buffer_handle,
15297 range,
15298 }
15299 }),
15300 None => None,
15301 };
15302 Ok(location)
15303 })
15304 }
15305
15306 pub fn find_all_references(
15307 &mut self,
15308 _: &FindAllReferences,
15309 window: &mut Window,
15310 cx: &mut Context<Self>,
15311 ) -> Option<Task<Result<Navigated>>> {
15312 let selection = self.selections.newest::<usize>(cx);
15313 let multi_buffer = self.buffer.read(cx);
15314 let head = selection.head();
15315
15316 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15317 let head_anchor = multi_buffer_snapshot.anchor_at(
15318 head,
15319 if head < selection.tail() {
15320 Bias::Right
15321 } else {
15322 Bias::Left
15323 },
15324 );
15325
15326 match self
15327 .find_all_references_task_sources
15328 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15329 {
15330 Ok(_) => {
15331 log::info!(
15332 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15333 );
15334 return None;
15335 }
15336 Err(i) => {
15337 self.find_all_references_task_sources.insert(i, head_anchor);
15338 }
15339 }
15340
15341 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15342 let workspace = self.workspace()?;
15343 let project = workspace.read(cx).project().clone();
15344 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15345 Some(cx.spawn_in(window, async move |editor, cx| {
15346 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15347 if let Ok(i) = editor
15348 .find_all_references_task_sources
15349 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15350 {
15351 editor.find_all_references_task_sources.remove(i);
15352 }
15353 });
15354
15355 let locations = references.await?;
15356 if locations.is_empty() {
15357 return anyhow::Ok(Navigated::No);
15358 }
15359
15360 workspace.update_in(cx, |workspace, window, cx| {
15361 let title = locations
15362 .first()
15363 .as_ref()
15364 .map(|location| {
15365 let buffer = location.buffer.read(cx);
15366 format!(
15367 "References to `{}`",
15368 buffer
15369 .text_for_range(location.range.clone())
15370 .collect::<String>()
15371 )
15372 })
15373 .unwrap();
15374 Self::open_locations_in_multibuffer(
15375 workspace,
15376 locations,
15377 title,
15378 false,
15379 MultibufferSelectionMode::First,
15380 window,
15381 cx,
15382 );
15383 Navigated::Yes
15384 })
15385 }))
15386 }
15387
15388 /// Opens a multibuffer with the given project locations in it
15389 pub fn open_locations_in_multibuffer(
15390 workspace: &mut Workspace,
15391 mut locations: Vec<Location>,
15392 title: String,
15393 split: bool,
15394 multibuffer_selection_mode: MultibufferSelectionMode,
15395 window: &mut Window,
15396 cx: &mut Context<Workspace>,
15397 ) {
15398 if locations.is_empty() {
15399 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15400 return;
15401 }
15402
15403 // If there are multiple definitions, open them in a multibuffer
15404 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15405 let mut locations = locations.into_iter().peekable();
15406 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15407 let capability = workspace.project().read(cx).capability();
15408
15409 let excerpt_buffer = cx.new(|cx| {
15410 let mut multibuffer = MultiBuffer::new(capability);
15411 while let Some(location) = locations.next() {
15412 let buffer = location.buffer.read(cx);
15413 let mut ranges_for_buffer = Vec::new();
15414 let range = location.range.to_point(buffer);
15415 ranges_for_buffer.push(range.clone());
15416
15417 while let Some(next_location) = locations.peek() {
15418 if next_location.buffer == location.buffer {
15419 ranges_for_buffer.push(next_location.range.to_point(buffer));
15420 locations.next();
15421 } else {
15422 break;
15423 }
15424 }
15425
15426 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15427 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15428 PathKey::for_buffer(&location.buffer, cx),
15429 location.buffer.clone(),
15430 ranges_for_buffer,
15431 DEFAULT_MULTIBUFFER_CONTEXT,
15432 cx,
15433 );
15434 ranges.extend(new_ranges)
15435 }
15436
15437 multibuffer.with_title(title)
15438 });
15439
15440 let editor = cx.new(|cx| {
15441 Editor::for_multibuffer(
15442 excerpt_buffer,
15443 Some(workspace.project().clone()),
15444 window,
15445 cx,
15446 )
15447 });
15448 editor.update(cx, |editor, cx| {
15449 match multibuffer_selection_mode {
15450 MultibufferSelectionMode::First => {
15451 if let Some(first_range) = ranges.first() {
15452 editor.change_selections(None, window, cx, |selections| {
15453 selections.clear_disjoint();
15454 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15455 });
15456 }
15457 editor.highlight_background::<Self>(
15458 &ranges,
15459 |theme| theme.colors().editor_highlighted_line_background,
15460 cx,
15461 );
15462 }
15463 MultibufferSelectionMode::All => {
15464 editor.change_selections(None, window, cx, |selections| {
15465 selections.clear_disjoint();
15466 selections.select_anchor_ranges(ranges);
15467 });
15468 }
15469 }
15470 editor.register_buffers_with_language_servers(cx);
15471 });
15472
15473 let item = Box::new(editor);
15474 let item_id = item.item_id();
15475
15476 if split {
15477 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15478 } else {
15479 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15480 let (preview_item_id, preview_item_idx) =
15481 workspace.active_pane().read_with(cx, |pane, _| {
15482 (pane.preview_item_id(), pane.preview_item_idx())
15483 });
15484
15485 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15486
15487 if let Some(preview_item_id) = preview_item_id {
15488 workspace.active_pane().update(cx, |pane, cx| {
15489 pane.remove_item(preview_item_id, false, false, window, cx);
15490 });
15491 }
15492 } else {
15493 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15494 }
15495 }
15496 workspace.active_pane().update(cx, |pane, cx| {
15497 pane.set_preview_item_id(Some(item_id), cx);
15498 });
15499 }
15500
15501 pub fn rename(
15502 &mut self,
15503 _: &Rename,
15504 window: &mut Window,
15505 cx: &mut Context<Self>,
15506 ) -> Option<Task<Result<()>>> {
15507 use language::ToOffset as _;
15508
15509 let provider = self.semantics_provider.clone()?;
15510 let selection = self.selections.newest_anchor().clone();
15511 let (cursor_buffer, cursor_buffer_position) = self
15512 .buffer
15513 .read(cx)
15514 .text_anchor_for_position(selection.head(), cx)?;
15515 let (tail_buffer, cursor_buffer_position_end) = self
15516 .buffer
15517 .read(cx)
15518 .text_anchor_for_position(selection.tail(), cx)?;
15519 if tail_buffer != cursor_buffer {
15520 return None;
15521 }
15522
15523 let snapshot = cursor_buffer.read(cx).snapshot();
15524 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15525 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15526 let prepare_rename = provider
15527 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15528 .unwrap_or_else(|| Task::ready(Ok(None)));
15529 drop(snapshot);
15530
15531 Some(cx.spawn_in(window, async move |this, cx| {
15532 let rename_range = if let Some(range) = prepare_rename.await? {
15533 Some(range)
15534 } else {
15535 this.update(cx, |this, cx| {
15536 let buffer = this.buffer.read(cx).snapshot(cx);
15537 let mut buffer_highlights = this
15538 .document_highlights_for_position(selection.head(), &buffer)
15539 .filter(|highlight| {
15540 highlight.start.excerpt_id == selection.head().excerpt_id
15541 && highlight.end.excerpt_id == selection.head().excerpt_id
15542 });
15543 buffer_highlights
15544 .next()
15545 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15546 })?
15547 };
15548 if let Some(rename_range) = rename_range {
15549 this.update_in(cx, |this, window, cx| {
15550 let snapshot = cursor_buffer.read(cx).snapshot();
15551 let rename_buffer_range = rename_range.to_offset(&snapshot);
15552 let cursor_offset_in_rename_range =
15553 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15554 let cursor_offset_in_rename_range_end =
15555 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15556
15557 this.take_rename(false, window, cx);
15558 let buffer = this.buffer.read(cx).read(cx);
15559 let cursor_offset = selection.head().to_offset(&buffer);
15560 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15561 let rename_end = rename_start + rename_buffer_range.len();
15562 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15563 let mut old_highlight_id = None;
15564 let old_name: Arc<str> = buffer
15565 .chunks(rename_start..rename_end, true)
15566 .map(|chunk| {
15567 if old_highlight_id.is_none() {
15568 old_highlight_id = chunk.syntax_highlight_id;
15569 }
15570 chunk.text
15571 })
15572 .collect::<String>()
15573 .into();
15574
15575 drop(buffer);
15576
15577 // Position the selection in the rename editor so that it matches the current selection.
15578 this.show_local_selections = false;
15579 let rename_editor = cx.new(|cx| {
15580 let mut editor = Editor::single_line(window, cx);
15581 editor.buffer.update(cx, |buffer, cx| {
15582 buffer.edit([(0..0, old_name.clone())], None, cx)
15583 });
15584 let rename_selection_range = match cursor_offset_in_rename_range
15585 .cmp(&cursor_offset_in_rename_range_end)
15586 {
15587 Ordering::Equal => {
15588 editor.select_all(&SelectAll, window, cx);
15589 return editor;
15590 }
15591 Ordering::Less => {
15592 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15593 }
15594 Ordering::Greater => {
15595 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15596 }
15597 };
15598 if rename_selection_range.end > old_name.len() {
15599 editor.select_all(&SelectAll, window, cx);
15600 } else {
15601 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15602 s.select_ranges([rename_selection_range]);
15603 });
15604 }
15605 editor
15606 });
15607 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15608 if e == &EditorEvent::Focused {
15609 cx.emit(EditorEvent::FocusedIn)
15610 }
15611 })
15612 .detach();
15613
15614 let write_highlights =
15615 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15616 let read_highlights =
15617 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15618 let ranges = write_highlights
15619 .iter()
15620 .flat_map(|(_, ranges)| ranges.iter())
15621 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15622 .cloned()
15623 .collect();
15624
15625 this.highlight_text::<Rename>(
15626 ranges,
15627 HighlightStyle {
15628 fade_out: Some(0.6),
15629 ..Default::default()
15630 },
15631 cx,
15632 );
15633 let rename_focus_handle = rename_editor.focus_handle(cx);
15634 window.focus(&rename_focus_handle);
15635 let block_id = this.insert_blocks(
15636 [BlockProperties {
15637 style: BlockStyle::Flex,
15638 placement: BlockPlacement::Below(range.start),
15639 height: Some(1),
15640 render: Arc::new({
15641 let rename_editor = rename_editor.clone();
15642 move |cx: &mut BlockContext| {
15643 let mut text_style = cx.editor_style.text.clone();
15644 if let Some(highlight_style) = old_highlight_id
15645 .and_then(|h| h.style(&cx.editor_style.syntax))
15646 {
15647 text_style = text_style.highlight(highlight_style);
15648 }
15649 div()
15650 .block_mouse_except_scroll()
15651 .pl(cx.anchor_x)
15652 .child(EditorElement::new(
15653 &rename_editor,
15654 EditorStyle {
15655 background: cx.theme().system().transparent,
15656 local_player: cx.editor_style.local_player,
15657 text: text_style,
15658 scrollbar_width: cx.editor_style.scrollbar_width,
15659 syntax: cx.editor_style.syntax.clone(),
15660 status: cx.editor_style.status.clone(),
15661 inlay_hints_style: HighlightStyle {
15662 font_weight: Some(FontWeight::BOLD),
15663 ..make_inlay_hints_style(cx.app)
15664 },
15665 inline_completion_styles: make_suggestion_styles(
15666 cx.app,
15667 ),
15668 ..EditorStyle::default()
15669 },
15670 ))
15671 .into_any_element()
15672 }
15673 }),
15674 priority: 0,
15675 render_in_minimap: true,
15676 }],
15677 Some(Autoscroll::fit()),
15678 cx,
15679 )[0];
15680 this.pending_rename = Some(RenameState {
15681 range,
15682 old_name,
15683 editor: rename_editor,
15684 block_id,
15685 });
15686 })?;
15687 }
15688
15689 Ok(())
15690 }))
15691 }
15692
15693 pub fn confirm_rename(
15694 &mut self,
15695 _: &ConfirmRename,
15696 window: &mut Window,
15697 cx: &mut Context<Self>,
15698 ) -> Option<Task<Result<()>>> {
15699 let rename = self.take_rename(false, window, cx)?;
15700 let workspace = self.workspace()?.downgrade();
15701 let (buffer, start) = self
15702 .buffer
15703 .read(cx)
15704 .text_anchor_for_position(rename.range.start, cx)?;
15705 let (end_buffer, _) = self
15706 .buffer
15707 .read(cx)
15708 .text_anchor_for_position(rename.range.end, cx)?;
15709 if buffer != end_buffer {
15710 return None;
15711 }
15712
15713 let old_name = rename.old_name;
15714 let new_name = rename.editor.read(cx).text(cx);
15715
15716 let rename = self.semantics_provider.as_ref()?.perform_rename(
15717 &buffer,
15718 start,
15719 new_name.clone(),
15720 cx,
15721 )?;
15722
15723 Some(cx.spawn_in(window, async move |editor, cx| {
15724 let project_transaction = rename.await?;
15725 Self::open_project_transaction(
15726 &editor,
15727 workspace,
15728 project_transaction,
15729 format!("Rename: {} → {}", old_name, new_name),
15730 cx,
15731 )
15732 .await?;
15733
15734 editor.update(cx, |editor, cx| {
15735 editor.refresh_document_highlights(cx);
15736 })?;
15737 Ok(())
15738 }))
15739 }
15740
15741 fn take_rename(
15742 &mut self,
15743 moving_cursor: bool,
15744 window: &mut Window,
15745 cx: &mut Context<Self>,
15746 ) -> Option<RenameState> {
15747 let rename = self.pending_rename.take()?;
15748 if rename.editor.focus_handle(cx).is_focused(window) {
15749 window.focus(&self.focus_handle);
15750 }
15751
15752 self.remove_blocks(
15753 [rename.block_id].into_iter().collect(),
15754 Some(Autoscroll::fit()),
15755 cx,
15756 );
15757 self.clear_highlights::<Rename>(cx);
15758 self.show_local_selections = true;
15759
15760 if moving_cursor {
15761 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15762 editor.selections.newest::<usize>(cx).head()
15763 });
15764
15765 // Update the selection to match the position of the selection inside
15766 // the rename editor.
15767 let snapshot = self.buffer.read(cx).read(cx);
15768 let rename_range = rename.range.to_offset(&snapshot);
15769 let cursor_in_editor = snapshot
15770 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15771 .min(rename_range.end);
15772 drop(snapshot);
15773
15774 self.change_selections(None, window, cx, |s| {
15775 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15776 });
15777 } else {
15778 self.refresh_document_highlights(cx);
15779 }
15780
15781 Some(rename)
15782 }
15783
15784 pub fn pending_rename(&self) -> Option<&RenameState> {
15785 self.pending_rename.as_ref()
15786 }
15787
15788 fn format(
15789 &mut self,
15790 _: &Format,
15791 window: &mut Window,
15792 cx: &mut Context<Self>,
15793 ) -> Option<Task<Result<()>>> {
15794 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15795
15796 let project = match &self.project {
15797 Some(project) => project.clone(),
15798 None => return None,
15799 };
15800
15801 Some(self.perform_format(
15802 project,
15803 FormatTrigger::Manual,
15804 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15805 window,
15806 cx,
15807 ))
15808 }
15809
15810 fn format_selections(
15811 &mut self,
15812 _: &FormatSelections,
15813 window: &mut Window,
15814 cx: &mut Context<Self>,
15815 ) -> Option<Task<Result<()>>> {
15816 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15817
15818 let project = match &self.project {
15819 Some(project) => project.clone(),
15820 None => return None,
15821 };
15822
15823 let ranges = self
15824 .selections
15825 .all_adjusted(cx)
15826 .into_iter()
15827 .map(|selection| selection.range())
15828 .collect_vec();
15829
15830 Some(self.perform_format(
15831 project,
15832 FormatTrigger::Manual,
15833 FormatTarget::Ranges(ranges),
15834 window,
15835 cx,
15836 ))
15837 }
15838
15839 fn perform_format(
15840 &mut self,
15841 project: Entity<Project>,
15842 trigger: FormatTrigger,
15843 target: FormatTarget,
15844 window: &mut Window,
15845 cx: &mut Context<Self>,
15846 ) -> Task<Result<()>> {
15847 let buffer = self.buffer.clone();
15848 let (buffers, target) = match target {
15849 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15850 FormatTarget::Ranges(selection_ranges) => {
15851 let multi_buffer = buffer.read(cx);
15852 let snapshot = multi_buffer.read(cx);
15853 let mut buffers = HashSet::default();
15854 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15855 BTreeMap::new();
15856 for selection_range in selection_ranges {
15857 for (buffer, buffer_range, _) in
15858 snapshot.range_to_buffer_ranges(selection_range)
15859 {
15860 let buffer_id = buffer.remote_id();
15861 let start = buffer.anchor_before(buffer_range.start);
15862 let end = buffer.anchor_after(buffer_range.end);
15863 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15864 buffer_id_to_ranges
15865 .entry(buffer_id)
15866 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15867 .or_insert_with(|| vec![start..end]);
15868 }
15869 }
15870 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15871 }
15872 };
15873
15874 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15875 let selections_prev = transaction_id_prev
15876 .and_then(|transaction_id_prev| {
15877 // default to selections as they were after the last edit, if we have them,
15878 // instead of how they are now.
15879 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15880 // will take you back to where you made the last edit, instead of staying where you scrolled
15881 self.selection_history
15882 .transaction(transaction_id_prev)
15883 .map(|t| t.0.clone())
15884 })
15885 .unwrap_or_else(|| {
15886 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15887 self.selections.disjoint_anchors()
15888 });
15889
15890 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15891 let format = project.update(cx, |project, cx| {
15892 project.format(buffers, target, true, trigger, cx)
15893 });
15894
15895 cx.spawn_in(window, async move |editor, cx| {
15896 let transaction = futures::select_biased! {
15897 transaction = format.log_err().fuse() => transaction,
15898 () = timeout => {
15899 log::warn!("timed out waiting for formatting");
15900 None
15901 }
15902 };
15903
15904 buffer
15905 .update(cx, |buffer, cx| {
15906 if let Some(transaction) = transaction {
15907 if !buffer.is_singleton() {
15908 buffer.push_transaction(&transaction.0, cx);
15909 }
15910 }
15911 cx.notify();
15912 })
15913 .ok();
15914
15915 if let Some(transaction_id_now) =
15916 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15917 {
15918 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15919 if has_new_transaction {
15920 _ = editor.update(cx, |editor, _| {
15921 editor
15922 .selection_history
15923 .insert_transaction(transaction_id_now, selections_prev);
15924 });
15925 }
15926 }
15927
15928 Ok(())
15929 })
15930 }
15931
15932 fn organize_imports(
15933 &mut self,
15934 _: &OrganizeImports,
15935 window: &mut Window,
15936 cx: &mut Context<Self>,
15937 ) -> Option<Task<Result<()>>> {
15938 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15939 let project = match &self.project {
15940 Some(project) => project.clone(),
15941 None => return None,
15942 };
15943 Some(self.perform_code_action_kind(
15944 project,
15945 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15946 window,
15947 cx,
15948 ))
15949 }
15950
15951 fn perform_code_action_kind(
15952 &mut self,
15953 project: Entity<Project>,
15954 kind: CodeActionKind,
15955 window: &mut Window,
15956 cx: &mut Context<Self>,
15957 ) -> Task<Result<()>> {
15958 let buffer = self.buffer.clone();
15959 let buffers = buffer.read(cx).all_buffers();
15960 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15961 let apply_action = project.update(cx, |project, cx| {
15962 project.apply_code_action_kind(buffers, kind, true, cx)
15963 });
15964 cx.spawn_in(window, async move |_, cx| {
15965 let transaction = futures::select_biased! {
15966 () = timeout => {
15967 log::warn!("timed out waiting for executing code action");
15968 None
15969 }
15970 transaction = apply_action.log_err().fuse() => transaction,
15971 };
15972 buffer
15973 .update(cx, |buffer, cx| {
15974 // check if we need this
15975 if let Some(transaction) = transaction {
15976 if !buffer.is_singleton() {
15977 buffer.push_transaction(&transaction.0, cx);
15978 }
15979 }
15980 cx.notify();
15981 })
15982 .ok();
15983 Ok(())
15984 })
15985 }
15986
15987 fn restart_language_server(
15988 &mut self,
15989 _: &RestartLanguageServer,
15990 _: &mut Window,
15991 cx: &mut Context<Self>,
15992 ) {
15993 if let Some(project) = self.project.clone() {
15994 self.buffer.update(cx, |multi_buffer, cx| {
15995 project.update(cx, |project, cx| {
15996 project.restart_language_servers_for_buffers(
15997 multi_buffer.all_buffers().into_iter().collect(),
15998 cx,
15999 );
16000 });
16001 })
16002 }
16003 }
16004
16005 fn stop_language_server(
16006 &mut self,
16007 _: &StopLanguageServer,
16008 _: &mut Window,
16009 cx: &mut Context<Self>,
16010 ) {
16011 if let Some(project) = self.project.clone() {
16012 self.buffer.update(cx, |multi_buffer, cx| {
16013 project.update(cx, |project, cx| {
16014 project.stop_language_servers_for_buffers(
16015 multi_buffer.all_buffers().into_iter().collect(),
16016 cx,
16017 );
16018 cx.emit(project::Event::RefreshInlayHints);
16019 });
16020 });
16021 }
16022 }
16023
16024 fn cancel_language_server_work(
16025 workspace: &mut Workspace,
16026 _: &actions::CancelLanguageServerWork,
16027 _: &mut Window,
16028 cx: &mut Context<Workspace>,
16029 ) {
16030 let project = workspace.project();
16031 let buffers = workspace
16032 .active_item(cx)
16033 .and_then(|item| item.act_as::<Editor>(cx))
16034 .map_or(HashSet::default(), |editor| {
16035 editor.read(cx).buffer.read(cx).all_buffers()
16036 });
16037 project.update(cx, |project, cx| {
16038 project.cancel_language_server_work_for_buffers(buffers, cx);
16039 });
16040 }
16041
16042 fn show_character_palette(
16043 &mut self,
16044 _: &ShowCharacterPalette,
16045 window: &mut Window,
16046 _: &mut Context<Self>,
16047 ) {
16048 window.show_character_palette();
16049 }
16050
16051 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
16052 if self.mode.is_minimap() {
16053 return;
16054 }
16055
16056 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
16057 let buffer = self.buffer.read(cx).snapshot(cx);
16058 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
16059 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
16060 let is_valid = buffer
16061 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
16062 .any(|entry| {
16063 entry.diagnostic.is_primary
16064 && !entry.range.is_empty()
16065 && entry.range.start == primary_range_start
16066 && entry.diagnostic.message == active_diagnostics.active_message
16067 });
16068
16069 if !is_valid {
16070 self.dismiss_diagnostics(cx);
16071 }
16072 }
16073 }
16074
16075 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
16076 match &self.active_diagnostics {
16077 ActiveDiagnostic::Group(group) => Some(group),
16078 _ => None,
16079 }
16080 }
16081
16082 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
16083 self.dismiss_diagnostics(cx);
16084 self.active_diagnostics = ActiveDiagnostic::All;
16085 }
16086
16087 fn activate_diagnostics(
16088 &mut self,
16089 buffer_id: BufferId,
16090 diagnostic: DiagnosticEntry<usize>,
16091 window: &mut Window,
16092 cx: &mut Context<Self>,
16093 ) {
16094 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16095 return;
16096 }
16097 self.dismiss_diagnostics(cx);
16098 let snapshot = self.snapshot(window, cx);
16099 let buffer = self.buffer.read(cx).snapshot(cx);
16100 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16101 return;
16102 };
16103
16104 let diagnostic_group = buffer
16105 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16106 .collect::<Vec<_>>();
16107
16108 let blocks =
16109 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16110
16111 let blocks = self.display_map.update(cx, |display_map, cx| {
16112 display_map.insert_blocks(blocks, cx).into_iter().collect()
16113 });
16114 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16115 active_range: buffer.anchor_before(diagnostic.range.start)
16116 ..buffer.anchor_after(diagnostic.range.end),
16117 active_message: diagnostic.diagnostic.message.clone(),
16118 group_id: diagnostic.diagnostic.group_id,
16119 blocks,
16120 });
16121 cx.notify();
16122 }
16123
16124 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16125 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16126 return;
16127 };
16128
16129 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16130 if let ActiveDiagnostic::Group(group) = prev {
16131 self.display_map.update(cx, |display_map, cx| {
16132 display_map.remove_blocks(group.blocks, cx);
16133 });
16134 cx.notify();
16135 }
16136 }
16137
16138 /// Disable inline diagnostics rendering for this editor.
16139 pub fn disable_inline_diagnostics(&mut self) {
16140 self.inline_diagnostics_enabled = false;
16141 self.inline_diagnostics_update = Task::ready(());
16142 self.inline_diagnostics.clear();
16143 }
16144
16145 pub fn diagnostics_enabled(&self) -> bool {
16146 self.mode.is_full()
16147 }
16148
16149 pub fn inline_diagnostics_enabled(&self) -> bool {
16150 self.diagnostics_enabled() && self.inline_diagnostics_enabled
16151 }
16152
16153 pub fn show_inline_diagnostics(&self) -> bool {
16154 self.show_inline_diagnostics
16155 }
16156
16157 pub fn toggle_inline_diagnostics(
16158 &mut self,
16159 _: &ToggleInlineDiagnostics,
16160 window: &mut Window,
16161 cx: &mut Context<Editor>,
16162 ) {
16163 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16164 self.refresh_inline_diagnostics(false, window, cx);
16165 }
16166
16167 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16168 self.diagnostics_max_severity = severity;
16169 self.display_map.update(cx, |display_map, _| {
16170 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16171 });
16172 }
16173
16174 pub fn toggle_diagnostics(
16175 &mut self,
16176 _: &ToggleDiagnostics,
16177 window: &mut Window,
16178 cx: &mut Context<Editor>,
16179 ) {
16180 if !self.diagnostics_enabled() {
16181 return;
16182 }
16183
16184 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16185 EditorSettings::get_global(cx)
16186 .diagnostics_max_severity
16187 .filter(|severity| severity != &DiagnosticSeverity::Off)
16188 .unwrap_or(DiagnosticSeverity::Hint)
16189 } else {
16190 DiagnosticSeverity::Off
16191 };
16192 self.set_max_diagnostics_severity(new_severity, cx);
16193 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16194 self.active_diagnostics = ActiveDiagnostic::None;
16195 self.inline_diagnostics_update = Task::ready(());
16196 self.inline_diagnostics.clear();
16197 } else {
16198 self.refresh_inline_diagnostics(false, window, cx);
16199 }
16200
16201 cx.notify();
16202 }
16203
16204 pub fn toggle_minimap(
16205 &mut self,
16206 _: &ToggleMinimap,
16207 window: &mut Window,
16208 cx: &mut Context<Editor>,
16209 ) {
16210 if self.supports_minimap(cx) {
16211 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16212 }
16213 }
16214
16215 fn refresh_inline_diagnostics(
16216 &mut self,
16217 debounce: bool,
16218 window: &mut Window,
16219 cx: &mut Context<Self>,
16220 ) {
16221 let max_severity = ProjectSettings::get_global(cx)
16222 .diagnostics
16223 .inline
16224 .max_severity
16225 .unwrap_or(self.diagnostics_max_severity);
16226
16227 if !self.inline_diagnostics_enabled()
16228 || !self.show_inline_diagnostics
16229 || max_severity == DiagnosticSeverity::Off
16230 {
16231 self.inline_diagnostics_update = Task::ready(());
16232 self.inline_diagnostics.clear();
16233 return;
16234 }
16235
16236 let debounce_ms = ProjectSettings::get_global(cx)
16237 .diagnostics
16238 .inline
16239 .update_debounce_ms;
16240 let debounce = if debounce && debounce_ms > 0 {
16241 Some(Duration::from_millis(debounce_ms))
16242 } else {
16243 None
16244 };
16245 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16246 if let Some(debounce) = debounce {
16247 cx.background_executor().timer(debounce).await;
16248 }
16249 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16250 editor
16251 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16252 .ok()
16253 }) else {
16254 return;
16255 };
16256
16257 let new_inline_diagnostics = cx
16258 .background_spawn(async move {
16259 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16260 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16261 let message = diagnostic_entry
16262 .diagnostic
16263 .message
16264 .split_once('\n')
16265 .map(|(line, _)| line)
16266 .map(SharedString::new)
16267 .unwrap_or_else(|| {
16268 SharedString::from(diagnostic_entry.diagnostic.message)
16269 });
16270 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16271 let (Ok(i) | Err(i)) = inline_diagnostics
16272 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16273 inline_diagnostics.insert(
16274 i,
16275 (
16276 start_anchor,
16277 InlineDiagnostic {
16278 message,
16279 group_id: diagnostic_entry.diagnostic.group_id,
16280 start: diagnostic_entry.range.start.to_point(&snapshot),
16281 is_primary: diagnostic_entry.diagnostic.is_primary,
16282 severity: diagnostic_entry.diagnostic.severity,
16283 },
16284 ),
16285 );
16286 }
16287 inline_diagnostics
16288 })
16289 .await;
16290
16291 editor
16292 .update(cx, |editor, cx| {
16293 editor.inline_diagnostics = new_inline_diagnostics;
16294 cx.notify();
16295 })
16296 .ok();
16297 });
16298 }
16299
16300 fn pull_diagnostics(
16301 &mut self,
16302 buffer_id: Option<BufferId>,
16303 window: &Window,
16304 cx: &mut Context<Self>,
16305 ) -> Option<()> {
16306 if !self.mode().is_full() {
16307 return None;
16308 }
16309 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16310 .diagnostics
16311 .lsp_pull_diagnostics;
16312 if !pull_diagnostics_settings.enabled {
16313 return None;
16314 }
16315 let project = self.project.as_ref()?.downgrade();
16316 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16317 let mut buffers = self.buffer.read(cx).all_buffers();
16318 if let Some(buffer_id) = buffer_id {
16319 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16320 }
16321
16322 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16323 cx.background_executor().timer(debounce).await;
16324
16325 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16326 buffers
16327 .into_iter()
16328 .filter_map(|buffer| {
16329 project
16330 .update(cx, |project, cx| {
16331 project.lsp_store().update(cx, |lsp_store, cx| {
16332 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
16333 })
16334 })
16335 .ok()
16336 })
16337 .collect::<FuturesUnordered<_>>()
16338 }) else {
16339 return;
16340 };
16341
16342 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16343 match pull_task {
16344 Ok(()) => {
16345 if editor
16346 .update_in(cx, |editor, window, cx| {
16347 editor.update_diagnostics_state(window, cx);
16348 })
16349 .is_err()
16350 {
16351 return;
16352 }
16353 }
16354 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16355 }
16356 }
16357 });
16358
16359 Some(())
16360 }
16361
16362 pub fn set_selections_from_remote(
16363 &mut self,
16364 selections: Vec<Selection<Anchor>>,
16365 pending_selection: Option<Selection<Anchor>>,
16366 window: &mut Window,
16367 cx: &mut Context<Self>,
16368 ) {
16369 let old_cursor_position = self.selections.newest_anchor().head();
16370 self.selections.change_with(cx, |s| {
16371 s.select_anchors(selections);
16372 if let Some(pending_selection) = pending_selection {
16373 s.set_pending(pending_selection, SelectMode::Character);
16374 } else {
16375 s.clear_pending();
16376 }
16377 });
16378 self.selections_did_change(
16379 false,
16380 &old_cursor_position,
16381 SelectionEffects::default(),
16382 window,
16383 cx,
16384 );
16385 }
16386
16387 pub fn transact(
16388 &mut self,
16389 window: &mut Window,
16390 cx: &mut Context<Self>,
16391 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16392 ) -> Option<TransactionId> {
16393 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16394 this.start_transaction_at(Instant::now(), window, cx);
16395 update(this, window, cx);
16396 this.end_transaction_at(Instant::now(), cx)
16397 })
16398 }
16399
16400 pub fn start_transaction_at(
16401 &mut self,
16402 now: Instant,
16403 window: &mut Window,
16404 cx: &mut Context<Self>,
16405 ) {
16406 self.end_selection(window, cx);
16407 if let Some(tx_id) = self
16408 .buffer
16409 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16410 {
16411 self.selection_history
16412 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16413 cx.emit(EditorEvent::TransactionBegun {
16414 transaction_id: tx_id,
16415 })
16416 }
16417 }
16418
16419 pub fn end_transaction_at(
16420 &mut self,
16421 now: Instant,
16422 cx: &mut Context<Self>,
16423 ) -> Option<TransactionId> {
16424 if let Some(transaction_id) = self
16425 .buffer
16426 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16427 {
16428 if let Some((_, end_selections)) =
16429 self.selection_history.transaction_mut(transaction_id)
16430 {
16431 *end_selections = Some(self.selections.disjoint_anchors());
16432 } else {
16433 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16434 }
16435
16436 cx.emit(EditorEvent::Edited { transaction_id });
16437 Some(transaction_id)
16438 } else {
16439 None
16440 }
16441 }
16442
16443 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16444 if self.selection_mark_mode {
16445 self.change_selections(None, window, cx, |s| {
16446 s.move_with(|_, sel| {
16447 sel.collapse_to(sel.head(), SelectionGoal::None);
16448 });
16449 })
16450 }
16451 self.selection_mark_mode = true;
16452 cx.notify();
16453 }
16454
16455 pub fn swap_selection_ends(
16456 &mut self,
16457 _: &actions::SwapSelectionEnds,
16458 window: &mut Window,
16459 cx: &mut Context<Self>,
16460 ) {
16461 self.change_selections(None, window, cx, |s| {
16462 s.move_with(|_, sel| {
16463 if sel.start != sel.end {
16464 sel.reversed = !sel.reversed
16465 }
16466 });
16467 });
16468 self.request_autoscroll(Autoscroll::newest(), cx);
16469 cx.notify();
16470 }
16471
16472 pub fn toggle_fold(
16473 &mut self,
16474 _: &actions::ToggleFold,
16475 window: &mut Window,
16476 cx: &mut Context<Self>,
16477 ) {
16478 if self.is_singleton(cx) {
16479 let selection = self.selections.newest::<Point>(cx);
16480
16481 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16482 let range = if selection.is_empty() {
16483 let point = selection.head().to_display_point(&display_map);
16484 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16485 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16486 .to_point(&display_map);
16487 start..end
16488 } else {
16489 selection.range()
16490 };
16491 if display_map.folds_in_range(range).next().is_some() {
16492 self.unfold_lines(&Default::default(), window, cx)
16493 } else {
16494 self.fold(&Default::default(), window, cx)
16495 }
16496 } else {
16497 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16498 let buffer_ids: HashSet<_> = self
16499 .selections
16500 .disjoint_anchor_ranges()
16501 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16502 .collect();
16503
16504 let should_unfold = buffer_ids
16505 .iter()
16506 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16507
16508 for buffer_id in buffer_ids {
16509 if should_unfold {
16510 self.unfold_buffer(buffer_id, cx);
16511 } else {
16512 self.fold_buffer(buffer_id, cx);
16513 }
16514 }
16515 }
16516 }
16517
16518 pub fn toggle_fold_recursive(
16519 &mut self,
16520 _: &actions::ToggleFoldRecursive,
16521 window: &mut Window,
16522 cx: &mut Context<Self>,
16523 ) {
16524 let selection = self.selections.newest::<Point>(cx);
16525
16526 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16527 let range = if selection.is_empty() {
16528 let point = selection.head().to_display_point(&display_map);
16529 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16530 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16531 .to_point(&display_map);
16532 start..end
16533 } else {
16534 selection.range()
16535 };
16536 if display_map.folds_in_range(range).next().is_some() {
16537 self.unfold_recursive(&Default::default(), window, cx)
16538 } else {
16539 self.fold_recursive(&Default::default(), window, cx)
16540 }
16541 }
16542
16543 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16544 if self.is_singleton(cx) {
16545 let mut to_fold = Vec::new();
16546 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16547 let selections = self.selections.all_adjusted(cx);
16548
16549 for selection in selections {
16550 let range = selection.range().sorted();
16551 let buffer_start_row = range.start.row;
16552
16553 if range.start.row != range.end.row {
16554 let mut found = false;
16555 let mut row = range.start.row;
16556 while row <= range.end.row {
16557 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16558 {
16559 found = true;
16560 row = crease.range().end.row + 1;
16561 to_fold.push(crease);
16562 } else {
16563 row += 1
16564 }
16565 }
16566 if found {
16567 continue;
16568 }
16569 }
16570
16571 for row in (0..=range.start.row).rev() {
16572 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16573 if crease.range().end.row >= buffer_start_row {
16574 to_fold.push(crease);
16575 if row <= range.start.row {
16576 break;
16577 }
16578 }
16579 }
16580 }
16581 }
16582
16583 self.fold_creases(to_fold, true, window, cx);
16584 } else {
16585 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16586 let buffer_ids = self
16587 .selections
16588 .disjoint_anchor_ranges()
16589 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16590 .collect::<HashSet<_>>();
16591 for buffer_id in buffer_ids {
16592 self.fold_buffer(buffer_id, cx);
16593 }
16594 }
16595 }
16596
16597 fn fold_at_level(
16598 &mut self,
16599 fold_at: &FoldAtLevel,
16600 window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) {
16603 if !self.buffer.read(cx).is_singleton() {
16604 return;
16605 }
16606
16607 let fold_at_level = fold_at.0;
16608 let snapshot = self.buffer.read(cx).snapshot(cx);
16609 let mut to_fold = Vec::new();
16610 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16611
16612 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16613 while start_row < end_row {
16614 match self
16615 .snapshot(window, cx)
16616 .crease_for_buffer_row(MultiBufferRow(start_row))
16617 {
16618 Some(crease) => {
16619 let nested_start_row = crease.range().start.row + 1;
16620 let nested_end_row = crease.range().end.row;
16621
16622 if current_level < fold_at_level {
16623 stack.push((nested_start_row, nested_end_row, current_level + 1));
16624 } else if current_level == fold_at_level {
16625 to_fold.push(crease);
16626 }
16627
16628 start_row = nested_end_row + 1;
16629 }
16630 None => start_row += 1,
16631 }
16632 }
16633 }
16634
16635 self.fold_creases(to_fold, true, window, cx);
16636 }
16637
16638 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16639 if self.buffer.read(cx).is_singleton() {
16640 let mut fold_ranges = Vec::new();
16641 let snapshot = self.buffer.read(cx).snapshot(cx);
16642
16643 for row in 0..snapshot.max_row().0 {
16644 if let Some(foldable_range) = self
16645 .snapshot(window, cx)
16646 .crease_for_buffer_row(MultiBufferRow(row))
16647 {
16648 fold_ranges.push(foldable_range);
16649 }
16650 }
16651
16652 self.fold_creases(fold_ranges, true, window, cx);
16653 } else {
16654 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16655 editor
16656 .update_in(cx, |editor, _, cx| {
16657 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16658 editor.fold_buffer(buffer_id, cx);
16659 }
16660 })
16661 .ok();
16662 });
16663 }
16664 }
16665
16666 pub fn fold_function_bodies(
16667 &mut self,
16668 _: &actions::FoldFunctionBodies,
16669 window: &mut Window,
16670 cx: &mut Context<Self>,
16671 ) {
16672 let snapshot = self.buffer.read(cx).snapshot(cx);
16673
16674 let ranges = snapshot
16675 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16676 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16677 .collect::<Vec<_>>();
16678
16679 let creases = ranges
16680 .into_iter()
16681 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16682 .collect();
16683
16684 self.fold_creases(creases, true, window, cx);
16685 }
16686
16687 pub fn fold_recursive(
16688 &mut self,
16689 _: &actions::FoldRecursive,
16690 window: &mut Window,
16691 cx: &mut Context<Self>,
16692 ) {
16693 let mut to_fold = Vec::new();
16694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16695 let selections = self.selections.all_adjusted(cx);
16696
16697 for selection in selections {
16698 let range = selection.range().sorted();
16699 let buffer_start_row = range.start.row;
16700
16701 if range.start.row != range.end.row {
16702 let mut found = false;
16703 for row in range.start.row..=range.end.row {
16704 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16705 found = true;
16706 to_fold.push(crease);
16707 }
16708 }
16709 if found {
16710 continue;
16711 }
16712 }
16713
16714 for row in (0..=range.start.row).rev() {
16715 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16716 if crease.range().end.row >= buffer_start_row {
16717 to_fold.push(crease);
16718 } else {
16719 break;
16720 }
16721 }
16722 }
16723 }
16724
16725 self.fold_creases(to_fold, true, window, cx);
16726 }
16727
16728 pub fn fold_at(
16729 &mut self,
16730 buffer_row: MultiBufferRow,
16731 window: &mut Window,
16732 cx: &mut Context<Self>,
16733 ) {
16734 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16735
16736 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16737 let autoscroll = self
16738 .selections
16739 .all::<Point>(cx)
16740 .iter()
16741 .any(|selection| crease.range().overlaps(&selection.range()));
16742
16743 self.fold_creases(vec![crease], autoscroll, window, cx);
16744 }
16745 }
16746
16747 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16748 if self.is_singleton(cx) {
16749 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16750 let buffer = &display_map.buffer_snapshot;
16751 let selections = self.selections.all::<Point>(cx);
16752 let ranges = selections
16753 .iter()
16754 .map(|s| {
16755 let range = s.display_range(&display_map).sorted();
16756 let mut start = range.start.to_point(&display_map);
16757 let mut end = range.end.to_point(&display_map);
16758 start.column = 0;
16759 end.column = buffer.line_len(MultiBufferRow(end.row));
16760 start..end
16761 })
16762 .collect::<Vec<_>>();
16763
16764 self.unfold_ranges(&ranges, true, true, cx);
16765 } else {
16766 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16767 let buffer_ids = self
16768 .selections
16769 .disjoint_anchor_ranges()
16770 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16771 .collect::<HashSet<_>>();
16772 for buffer_id in buffer_ids {
16773 self.unfold_buffer(buffer_id, cx);
16774 }
16775 }
16776 }
16777
16778 pub fn unfold_recursive(
16779 &mut self,
16780 _: &UnfoldRecursive,
16781 _window: &mut Window,
16782 cx: &mut Context<Self>,
16783 ) {
16784 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16785 let selections = self.selections.all::<Point>(cx);
16786 let ranges = selections
16787 .iter()
16788 .map(|s| {
16789 let mut range = s.display_range(&display_map).sorted();
16790 *range.start.column_mut() = 0;
16791 *range.end.column_mut() = display_map.line_len(range.end.row());
16792 let start = range.start.to_point(&display_map);
16793 let end = range.end.to_point(&display_map);
16794 start..end
16795 })
16796 .collect::<Vec<_>>();
16797
16798 self.unfold_ranges(&ranges, true, true, cx);
16799 }
16800
16801 pub fn unfold_at(
16802 &mut self,
16803 buffer_row: MultiBufferRow,
16804 _window: &mut Window,
16805 cx: &mut Context<Self>,
16806 ) {
16807 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16808
16809 let intersection_range = Point::new(buffer_row.0, 0)
16810 ..Point::new(
16811 buffer_row.0,
16812 display_map.buffer_snapshot.line_len(buffer_row),
16813 );
16814
16815 let autoscroll = self
16816 .selections
16817 .all::<Point>(cx)
16818 .iter()
16819 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16820
16821 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16822 }
16823
16824 pub fn unfold_all(
16825 &mut self,
16826 _: &actions::UnfoldAll,
16827 _window: &mut Window,
16828 cx: &mut Context<Self>,
16829 ) {
16830 if self.buffer.read(cx).is_singleton() {
16831 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16832 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16833 } else {
16834 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16835 editor
16836 .update(cx, |editor, cx| {
16837 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16838 editor.unfold_buffer(buffer_id, cx);
16839 }
16840 })
16841 .ok();
16842 });
16843 }
16844 }
16845
16846 pub fn fold_selected_ranges(
16847 &mut self,
16848 _: &FoldSelectedRanges,
16849 window: &mut Window,
16850 cx: &mut Context<Self>,
16851 ) {
16852 let selections = self.selections.all_adjusted(cx);
16853 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16854 let ranges = selections
16855 .into_iter()
16856 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16857 .collect::<Vec<_>>();
16858 self.fold_creases(ranges, true, window, cx);
16859 }
16860
16861 pub fn fold_ranges<T: ToOffset + Clone>(
16862 &mut self,
16863 ranges: Vec<Range<T>>,
16864 auto_scroll: bool,
16865 window: &mut Window,
16866 cx: &mut Context<Self>,
16867 ) {
16868 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16869 let ranges = ranges
16870 .into_iter()
16871 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16872 .collect::<Vec<_>>();
16873 self.fold_creases(ranges, auto_scroll, window, cx);
16874 }
16875
16876 pub fn fold_creases<T: ToOffset + Clone>(
16877 &mut self,
16878 creases: Vec<Crease<T>>,
16879 auto_scroll: bool,
16880 _window: &mut Window,
16881 cx: &mut Context<Self>,
16882 ) {
16883 if creases.is_empty() {
16884 return;
16885 }
16886
16887 let mut buffers_affected = HashSet::default();
16888 let multi_buffer = self.buffer().read(cx);
16889 for crease in &creases {
16890 if let Some((_, buffer, _)) =
16891 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16892 {
16893 buffers_affected.insert(buffer.read(cx).remote_id());
16894 };
16895 }
16896
16897 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16898
16899 if auto_scroll {
16900 self.request_autoscroll(Autoscroll::fit(), cx);
16901 }
16902
16903 cx.notify();
16904
16905 self.scrollbar_marker_state.dirty = true;
16906 self.folds_did_change(cx);
16907 }
16908
16909 /// Removes any folds whose ranges intersect any of the given ranges.
16910 pub fn unfold_ranges<T: ToOffset + Clone>(
16911 &mut self,
16912 ranges: &[Range<T>],
16913 inclusive: bool,
16914 auto_scroll: bool,
16915 cx: &mut Context<Self>,
16916 ) {
16917 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16918 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16919 });
16920 self.folds_did_change(cx);
16921 }
16922
16923 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16924 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16925 return;
16926 }
16927 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16928 self.display_map.update(cx, |display_map, cx| {
16929 display_map.fold_buffers([buffer_id], cx)
16930 });
16931 cx.emit(EditorEvent::BufferFoldToggled {
16932 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16933 folded: true,
16934 });
16935 cx.notify();
16936 }
16937
16938 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16939 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16940 return;
16941 }
16942 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16943 self.display_map.update(cx, |display_map, cx| {
16944 display_map.unfold_buffers([buffer_id], cx);
16945 });
16946 cx.emit(EditorEvent::BufferFoldToggled {
16947 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16948 folded: false,
16949 });
16950 cx.notify();
16951 }
16952
16953 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16954 self.display_map.read(cx).is_buffer_folded(buffer)
16955 }
16956
16957 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16958 self.display_map.read(cx).folded_buffers()
16959 }
16960
16961 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16962 self.display_map.update(cx, |display_map, cx| {
16963 display_map.disable_header_for_buffer(buffer_id, cx);
16964 });
16965 cx.notify();
16966 }
16967
16968 /// Removes any folds with the given ranges.
16969 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16970 &mut self,
16971 ranges: &[Range<T>],
16972 type_id: TypeId,
16973 auto_scroll: bool,
16974 cx: &mut Context<Self>,
16975 ) {
16976 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16977 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16978 });
16979 self.folds_did_change(cx);
16980 }
16981
16982 fn remove_folds_with<T: ToOffset + Clone>(
16983 &mut self,
16984 ranges: &[Range<T>],
16985 auto_scroll: bool,
16986 cx: &mut Context<Self>,
16987 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16988 ) {
16989 if ranges.is_empty() {
16990 return;
16991 }
16992
16993 let mut buffers_affected = HashSet::default();
16994 let multi_buffer = self.buffer().read(cx);
16995 for range in ranges {
16996 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16997 buffers_affected.insert(buffer.read(cx).remote_id());
16998 };
16999 }
17000
17001 self.display_map.update(cx, update);
17002
17003 if auto_scroll {
17004 self.request_autoscroll(Autoscroll::fit(), cx);
17005 }
17006
17007 cx.notify();
17008 self.scrollbar_marker_state.dirty = true;
17009 self.active_indent_guides_state.dirty = true;
17010 }
17011
17012 pub fn update_fold_widths(
17013 &mut self,
17014 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
17015 cx: &mut Context<Self>,
17016 ) -> bool {
17017 self.display_map
17018 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
17019 }
17020
17021 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
17022 self.display_map.read(cx).fold_placeholder.clone()
17023 }
17024
17025 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
17026 self.buffer.update(cx, |buffer, cx| {
17027 buffer.set_all_diff_hunks_expanded(cx);
17028 });
17029 }
17030
17031 pub fn expand_all_diff_hunks(
17032 &mut self,
17033 _: &ExpandAllDiffHunks,
17034 _window: &mut Window,
17035 cx: &mut Context<Self>,
17036 ) {
17037 self.buffer.update(cx, |buffer, cx| {
17038 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
17039 });
17040 }
17041
17042 pub fn toggle_selected_diff_hunks(
17043 &mut self,
17044 _: &ToggleSelectedDiffHunks,
17045 _window: &mut Window,
17046 cx: &mut Context<Self>,
17047 ) {
17048 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17049 self.toggle_diff_hunks_in_ranges(ranges, cx);
17050 }
17051
17052 pub fn diff_hunks_in_ranges<'a>(
17053 &'a self,
17054 ranges: &'a [Range<Anchor>],
17055 buffer: &'a MultiBufferSnapshot,
17056 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
17057 ranges.iter().flat_map(move |range| {
17058 let end_excerpt_id = range.end.excerpt_id;
17059 let range = range.to_point(buffer);
17060 let mut peek_end = range.end;
17061 if range.end.row < buffer.max_row().0 {
17062 peek_end = Point::new(range.end.row + 1, 0);
17063 }
17064 buffer
17065 .diff_hunks_in_range(range.start..peek_end)
17066 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
17067 })
17068 }
17069
17070 pub fn has_stageable_diff_hunks_in_ranges(
17071 &self,
17072 ranges: &[Range<Anchor>],
17073 snapshot: &MultiBufferSnapshot,
17074 ) -> bool {
17075 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
17076 hunks.any(|hunk| hunk.status().has_secondary_hunk())
17077 }
17078
17079 pub fn toggle_staged_selected_diff_hunks(
17080 &mut self,
17081 _: &::git::ToggleStaged,
17082 _: &mut Window,
17083 cx: &mut Context<Self>,
17084 ) {
17085 let snapshot = self.buffer.read(cx).snapshot(cx);
17086 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17087 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
17088 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17089 }
17090
17091 pub fn set_render_diff_hunk_controls(
17092 &mut self,
17093 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17094 cx: &mut Context<Self>,
17095 ) {
17096 self.render_diff_hunk_controls = render_diff_hunk_controls;
17097 cx.notify();
17098 }
17099
17100 pub fn stage_and_next(
17101 &mut self,
17102 _: &::git::StageAndNext,
17103 window: &mut Window,
17104 cx: &mut Context<Self>,
17105 ) {
17106 self.do_stage_or_unstage_and_next(true, window, cx);
17107 }
17108
17109 pub fn unstage_and_next(
17110 &mut self,
17111 _: &::git::UnstageAndNext,
17112 window: &mut Window,
17113 cx: &mut Context<Self>,
17114 ) {
17115 self.do_stage_or_unstage_and_next(false, window, cx);
17116 }
17117
17118 pub fn stage_or_unstage_diff_hunks(
17119 &mut self,
17120 stage: bool,
17121 ranges: Vec<Range<Anchor>>,
17122 cx: &mut Context<Self>,
17123 ) {
17124 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17125 cx.spawn(async move |this, cx| {
17126 task.await?;
17127 this.update(cx, |this, cx| {
17128 let snapshot = this.buffer.read(cx).snapshot(cx);
17129 let chunk_by = this
17130 .diff_hunks_in_ranges(&ranges, &snapshot)
17131 .chunk_by(|hunk| hunk.buffer_id);
17132 for (buffer_id, hunks) in &chunk_by {
17133 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17134 }
17135 })
17136 })
17137 .detach_and_log_err(cx);
17138 }
17139
17140 fn save_buffers_for_ranges_if_needed(
17141 &mut self,
17142 ranges: &[Range<Anchor>],
17143 cx: &mut Context<Editor>,
17144 ) -> Task<Result<()>> {
17145 let multibuffer = self.buffer.read(cx);
17146 let snapshot = multibuffer.read(cx);
17147 let buffer_ids: HashSet<_> = ranges
17148 .iter()
17149 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17150 .collect();
17151 drop(snapshot);
17152
17153 let mut buffers = HashSet::default();
17154 for buffer_id in buffer_ids {
17155 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17156 let buffer = buffer_entity.read(cx);
17157 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17158 {
17159 buffers.insert(buffer_entity);
17160 }
17161 }
17162 }
17163
17164 if let Some(project) = &self.project {
17165 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17166 } else {
17167 Task::ready(Ok(()))
17168 }
17169 }
17170
17171 fn do_stage_or_unstage_and_next(
17172 &mut self,
17173 stage: bool,
17174 window: &mut Window,
17175 cx: &mut Context<Self>,
17176 ) {
17177 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17178
17179 if ranges.iter().any(|range| range.start != range.end) {
17180 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17181 return;
17182 }
17183
17184 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17185 let snapshot = self.snapshot(window, cx);
17186 let position = self.selections.newest::<Point>(cx).head();
17187 let mut row = snapshot
17188 .buffer_snapshot
17189 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17190 .find(|hunk| hunk.row_range.start.0 > position.row)
17191 .map(|hunk| hunk.row_range.start);
17192
17193 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17194 // Outside of the project diff editor, wrap around to the beginning.
17195 if !all_diff_hunks_expanded {
17196 row = row.or_else(|| {
17197 snapshot
17198 .buffer_snapshot
17199 .diff_hunks_in_range(Point::zero()..position)
17200 .find(|hunk| hunk.row_range.end.0 < position.row)
17201 .map(|hunk| hunk.row_range.start)
17202 });
17203 }
17204
17205 if let Some(row) = row {
17206 let destination = Point::new(row.0, 0);
17207 let autoscroll = Autoscroll::center();
17208
17209 self.unfold_ranges(&[destination..destination], false, false, cx);
17210 self.change_selections(Some(autoscroll), window, cx, |s| {
17211 s.select_ranges([destination..destination]);
17212 });
17213 }
17214 }
17215
17216 fn do_stage_or_unstage(
17217 &self,
17218 stage: bool,
17219 buffer_id: BufferId,
17220 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17221 cx: &mut App,
17222 ) -> Option<()> {
17223 let project = self.project.as_ref()?;
17224 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17225 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17226 let buffer_snapshot = buffer.read(cx).snapshot();
17227 let file_exists = buffer_snapshot
17228 .file()
17229 .is_some_and(|file| file.disk_state().exists());
17230 diff.update(cx, |diff, cx| {
17231 diff.stage_or_unstage_hunks(
17232 stage,
17233 &hunks
17234 .map(|hunk| buffer_diff::DiffHunk {
17235 buffer_range: hunk.buffer_range,
17236 diff_base_byte_range: hunk.diff_base_byte_range,
17237 secondary_status: hunk.secondary_status,
17238 range: Point::zero()..Point::zero(), // unused
17239 })
17240 .collect::<Vec<_>>(),
17241 &buffer_snapshot,
17242 file_exists,
17243 cx,
17244 )
17245 });
17246 None
17247 }
17248
17249 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17250 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17251 self.buffer
17252 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17253 }
17254
17255 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17256 self.buffer.update(cx, |buffer, cx| {
17257 let ranges = vec![Anchor::min()..Anchor::max()];
17258 if !buffer.all_diff_hunks_expanded()
17259 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17260 {
17261 buffer.collapse_diff_hunks(ranges, cx);
17262 true
17263 } else {
17264 false
17265 }
17266 })
17267 }
17268
17269 fn toggle_diff_hunks_in_ranges(
17270 &mut self,
17271 ranges: Vec<Range<Anchor>>,
17272 cx: &mut Context<Editor>,
17273 ) {
17274 self.buffer.update(cx, |buffer, cx| {
17275 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17276 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17277 })
17278 }
17279
17280 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17281 self.buffer.update(cx, |buffer, cx| {
17282 let snapshot = buffer.snapshot(cx);
17283 let excerpt_id = range.end.excerpt_id;
17284 let point_range = range.to_point(&snapshot);
17285 let expand = !buffer.single_hunk_is_expanded(range, cx);
17286 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17287 })
17288 }
17289
17290 pub(crate) fn apply_all_diff_hunks(
17291 &mut self,
17292 _: &ApplyAllDiffHunks,
17293 window: &mut Window,
17294 cx: &mut Context<Self>,
17295 ) {
17296 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17297
17298 let buffers = self.buffer.read(cx).all_buffers();
17299 for branch_buffer in buffers {
17300 branch_buffer.update(cx, |branch_buffer, cx| {
17301 branch_buffer.merge_into_base(Vec::new(), cx);
17302 });
17303 }
17304
17305 if let Some(project) = self.project.clone() {
17306 self.save(
17307 SaveOptions {
17308 format: true,
17309 autosave: false,
17310 },
17311 project,
17312 window,
17313 cx,
17314 )
17315 .detach_and_log_err(cx);
17316 }
17317 }
17318
17319 pub(crate) fn apply_selected_diff_hunks(
17320 &mut self,
17321 _: &ApplyDiffHunk,
17322 window: &mut Window,
17323 cx: &mut Context<Self>,
17324 ) {
17325 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17326 let snapshot = self.snapshot(window, cx);
17327 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17328 let mut ranges_by_buffer = HashMap::default();
17329 self.transact(window, cx, |editor, _window, cx| {
17330 for hunk in hunks {
17331 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17332 ranges_by_buffer
17333 .entry(buffer.clone())
17334 .or_insert_with(Vec::new)
17335 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17336 }
17337 }
17338
17339 for (buffer, ranges) in ranges_by_buffer {
17340 buffer.update(cx, |buffer, cx| {
17341 buffer.merge_into_base(ranges, cx);
17342 });
17343 }
17344 });
17345
17346 if let Some(project) = self.project.clone() {
17347 self.save(
17348 SaveOptions {
17349 format: true,
17350 autosave: false,
17351 },
17352 project,
17353 window,
17354 cx,
17355 )
17356 .detach_and_log_err(cx);
17357 }
17358 }
17359
17360 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17361 if hovered != self.gutter_hovered {
17362 self.gutter_hovered = hovered;
17363 cx.notify();
17364 }
17365 }
17366
17367 pub fn insert_blocks(
17368 &mut self,
17369 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17370 autoscroll: Option<Autoscroll>,
17371 cx: &mut Context<Self>,
17372 ) -> Vec<CustomBlockId> {
17373 let blocks = self
17374 .display_map
17375 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17376 if let Some(autoscroll) = autoscroll {
17377 self.request_autoscroll(autoscroll, cx);
17378 }
17379 cx.notify();
17380 blocks
17381 }
17382
17383 pub fn resize_blocks(
17384 &mut self,
17385 heights: HashMap<CustomBlockId, u32>,
17386 autoscroll: Option<Autoscroll>,
17387 cx: &mut Context<Self>,
17388 ) {
17389 self.display_map
17390 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17391 if let Some(autoscroll) = autoscroll {
17392 self.request_autoscroll(autoscroll, cx);
17393 }
17394 cx.notify();
17395 }
17396
17397 pub fn replace_blocks(
17398 &mut self,
17399 renderers: HashMap<CustomBlockId, RenderBlock>,
17400 autoscroll: Option<Autoscroll>,
17401 cx: &mut Context<Self>,
17402 ) {
17403 self.display_map
17404 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17405 if let Some(autoscroll) = autoscroll {
17406 self.request_autoscroll(autoscroll, cx);
17407 }
17408 cx.notify();
17409 }
17410
17411 pub fn remove_blocks(
17412 &mut self,
17413 block_ids: HashSet<CustomBlockId>,
17414 autoscroll: Option<Autoscroll>,
17415 cx: &mut Context<Self>,
17416 ) {
17417 self.display_map.update(cx, |display_map, cx| {
17418 display_map.remove_blocks(block_ids, cx)
17419 });
17420 if let Some(autoscroll) = autoscroll {
17421 self.request_autoscroll(autoscroll, cx);
17422 }
17423 cx.notify();
17424 }
17425
17426 pub fn row_for_block(
17427 &self,
17428 block_id: CustomBlockId,
17429 cx: &mut Context<Self>,
17430 ) -> Option<DisplayRow> {
17431 self.display_map
17432 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17433 }
17434
17435 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17436 self.focused_block = Some(focused_block);
17437 }
17438
17439 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17440 self.focused_block.take()
17441 }
17442
17443 pub fn insert_creases(
17444 &mut self,
17445 creases: impl IntoIterator<Item = Crease<Anchor>>,
17446 cx: &mut Context<Self>,
17447 ) -> Vec<CreaseId> {
17448 self.display_map
17449 .update(cx, |map, cx| map.insert_creases(creases, cx))
17450 }
17451
17452 pub fn remove_creases(
17453 &mut self,
17454 ids: impl IntoIterator<Item = CreaseId>,
17455 cx: &mut Context<Self>,
17456 ) -> Vec<(CreaseId, Range<Anchor>)> {
17457 self.display_map
17458 .update(cx, |map, cx| map.remove_creases(ids, cx))
17459 }
17460
17461 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17462 self.display_map
17463 .update(cx, |map, cx| map.snapshot(cx))
17464 .longest_row()
17465 }
17466
17467 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17468 self.display_map
17469 .update(cx, |map, cx| map.snapshot(cx))
17470 .max_point()
17471 }
17472
17473 pub fn text(&self, cx: &App) -> String {
17474 self.buffer.read(cx).read(cx).text()
17475 }
17476
17477 pub fn is_empty(&self, cx: &App) -> bool {
17478 self.buffer.read(cx).read(cx).is_empty()
17479 }
17480
17481 pub fn text_option(&self, cx: &App) -> Option<String> {
17482 let text = self.text(cx);
17483 let text = text.trim();
17484
17485 if text.is_empty() {
17486 return None;
17487 }
17488
17489 Some(text.to_string())
17490 }
17491
17492 pub fn set_text(
17493 &mut self,
17494 text: impl Into<Arc<str>>,
17495 window: &mut Window,
17496 cx: &mut Context<Self>,
17497 ) {
17498 self.transact(window, cx, |this, _, cx| {
17499 this.buffer
17500 .read(cx)
17501 .as_singleton()
17502 .expect("you can only call set_text on editors for singleton buffers")
17503 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17504 });
17505 }
17506
17507 pub fn display_text(&self, cx: &mut App) -> String {
17508 self.display_map
17509 .update(cx, |map, cx| map.snapshot(cx))
17510 .text()
17511 }
17512
17513 fn create_minimap(
17514 &self,
17515 minimap_settings: MinimapSettings,
17516 window: &mut Window,
17517 cx: &mut Context<Self>,
17518 ) -> Option<Entity<Self>> {
17519 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17520 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17521 }
17522
17523 fn initialize_new_minimap(
17524 &self,
17525 minimap_settings: MinimapSettings,
17526 window: &mut Window,
17527 cx: &mut Context<Self>,
17528 ) -> Entity<Self> {
17529 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17530
17531 let mut minimap = Editor::new_internal(
17532 EditorMode::Minimap {
17533 parent: cx.weak_entity(),
17534 },
17535 self.buffer.clone(),
17536 self.project.clone(),
17537 Some(self.display_map.clone()),
17538 window,
17539 cx,
17540 );
17541 minimap.scroll_manager.clone_state(&self.scroll_manager);
17542 minimap.set_text_style_refinement(TextStyleRefinement {
17543 font_size: Some(MINIMAP_FONT_SIZE),
17544 font_weight: Some(MINIMAP_FONT_WEIGHT),
17545 ..Default::default()
17546 });
17547 minimap.update_minimap_configuration(minimap_settings, cx);
17548 cx.new(|_| minimap)
17549 }
17550
17551 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17552 let current_line_highlight = minimap_settings
17553 .current_line_highlight
17554 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17555 self.set_current_line_highlight(Some(current_line_highlight));
17556 }
17557
17558 pub fn minimap(&self) -> Option<&Entity<Self>> {
17559 self.minimap
17560 .as_ref()
17561 .filter(|_| self.minimap_visibility.visible())
17562 }
17563
17564 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17565 let mut wrap_guides = smallvec![];
17566
17567 if self.show_wrap_guides == Some(false) {
17568 return wrap_guides;
17569 }
17570
17571 let settings = self.buffer.read(cx).language_settings(cx);
17572 if settings.show_wrap_guides {
17573 match self.soft_wrap_mode(cx) {
17574 SoftWrap::Column(soft_wrap) => {
17575 wrap_guides.push((soft_wrap as usize, true));
17576 }
17577 SoftWrap::Bounded(soft_wrap) => {
17578 wrap_guides.push((soft_wrap as usize, true));
17579 }
17580 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17581 }
17582 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17583 }
17584
17585 wrap_guides
17586 }
17587
17588 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17589 let settings = self.buffer.read(cx).language_settings(cx);
17590 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17591 match mode {
17592 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17593 SoftWrap::None
17594 }
17595 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17596 language_settings::SoftWrap::PreferredLineLength => {
17597 SoftWrap::Column(settings.preferred_line_length)
17598 }
17599 language_settings::SoftWrap::Bounded => {
17600 SoftWrap::Bounded(settings.preferred_line_length)
17601 }
17602 }
17603 }
17604
17605 pub fn set_soft_wrap_mode(
17606 &mut self,
17607 mode: language_settings::SoftWrap,
17608
17609 cx: &mut Context<Self>,
17610 ) {
17611 self.soft_wrap_mode_override = Some(mode);
17612 cx.notify();
17613 }
17614
17615 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17616 self.hard_wrap = hard_wrap;
17617 cx.notify();
17618 }
17619
17620 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17621 self.text_style_refinement = Some(style);
17622 }
17623
17624 /// called by the Element so we know what style we were most recently rendered with.
17625 pub(crate) fn set_style(
17626 &mut self,
17627 style: EditorStyle,
17628 window: &mut Window,
17629 cx: &mut Context<Self>,
17630 ) {
17631 // We intentionally do not inform the display map about the minimap style
17632 // so that wrapping is not recalculated and stays consistent for the editor
17633 // and its linked minimap.
17634 if !self.mode.is_minimap() {
17635 let rem_size = window.rem_size();
17636 self.display_map.update(cx, |map, cx| {
17637 map.set_font(
17638 style.text.font(),
17639 style.text.font_size.to_pixels(rem_size),
17640 cx,
17641 )
17642 });
17643 }
17644 self.style = Some(style);
17645 }
17646
17647 pub fn style(&self) -> Option<&EditorStyle> {
17648 self.style.as_ref()
17649 }
17650
17651 // Called by the element. This method is not designed to be called outside of the editor
17652 // element's layout code because it does not notify when rewrapping is computed synchronously.
17653 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17654 self.display_map
17655 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17656 }
17657
17658 pub fn set_soft_wrap(&mut self) {
17659 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17660 }
17661
17662 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17663 if self.soft_wrap_mode_override.is_some() {
17664 self.soft_wrap_mode_override.take();
17665 } else {
17666 let soft_wrap = match self.soft_wrap_mode(cx) {
17667 SoftWrap::GitDiff => return,
17668 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17669 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17670 language_settings::SoftWrap::None
17671 }
17672 };
17673 self.soft_wrap_mode_override = Some(soft_wrap);
17674 }
17675 cx.notify();
17676 }
17677
17678 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17679 let Some(workspace) = self.workspace() else {
17680 return;
17681 };
17682 let fs = workspace.read(cx).app_state().fs.clone();
17683 let current_show = TabBarSettings::get_global(cx).show;
17684 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17685 setting.show = Some(!current_show);
17686 });
17687 }
17688
17689 pub fn toggle_indent_guides(
17690 &mut self,
17691 _: &ToggleIndentGuides,
17692 _: &mut Window,
17693 cx: &mut Context<Self>,
17694 ) {
17695 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17696 self.buffer
17697 .read(cx)
17698 .language_settings(cx)
17699 .indent_guides
17700 .enabled
17701 });
17702 self.show_indent_guides = Some(!currently_enabled);
17703 cx.notify();
17704 }
17705
17706 fn should_show_indent_guides(&self) -> Option<bool> {
17707 self.show_indent_guides
17708 }
17709
17710 pub fn toggle_line_numbers(
17711 &mut self,
17712 _: &ToggleLineNumbers,
17713 _: &mut Window,
17714 cx: &mut Context<Self>,
17715 ) {
17716 let mut editor_settings = EditorSettings::get_global(cx).clone();
17717 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17718 EditorSettings::override_global(editor_settings, cx);
17719 }
17720
17721 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17722 if let Some(show_line_numbers) = self.show_line_numbers {
17723 return show_line_numbers;
17724 }
17725 EditorSettings::get_global(cx).gutter.line_numbers
17726 }
17727
17728 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17729 self.use_relative_line_numbers
17730 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17731 }
17732
17733 pub fn toggle_relative_line_numbers(
17734 &mut self,
17735 _: &ToggleRelativeLineNumbers,
17736 _: &mut Window,
17737 cx: &mut Context<Self>,
17738 ) {
17739 let is_relative = self.should_use_relative_line_numbers(cx);
17740 self.set_relative_line_number(Some(!is_relative), cx)
17741 }
17742
17743 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17744 self.use_relative_line_numbers = is_relative;
17745 cx.notify();
17746 }
17747
17748 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17749 self.show_gutter = show_gutter;
17750 cx.notify();
17751 }
17752
17753 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17754 self.show_scrollbars = ScrollbarAxes {
17755 horizontal: show,
17756 vertical: show,
17757 };
17758 cx.notify();
17759 }
17760
17761 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17762 self.show_scrollbars.vertical = show;
17763 cx.notify();
17764 }
17765
17766 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17767 self.show_scrollbars.horizontal = show;
17768 cx.notify();
17769 }
17770
17771 pub fn set_minimap_visibility(
17772 &mut self,
17773 minimap_visibility: MinimapVisibility,
17774 window: &mut Window,
17775 cx: &mut Context<Self>,
17776 ) {
17777 if self.minimap_visibility != minimap_visibility {
17778 if minimap_visibility.visible() && self.minimap.is_none() {
17779 let minimap_settings = EditorSettings::get_global(cx).minimap;
17780 self.minimap =
17781 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17782 }
17783 self.minimap_visibility = minimap_visibility;
17784 cx.notify();
17785 }
17786 }
17787
17788 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17789 self.set_show_scrollbars(false, cx);
17790 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17791 }
17792
17793 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17794 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17795 }
17796
17797 /// Normally the text in full mode and auto height editors is padded on the
17798 /// left side by roughly half a character width for improved hit testing.
17799 ///
17800 /// Use this method to disable this for cases where this is not wanted (e.g.
17801 /// if you want to align the editor text with some other text above or below)
17802 /// or if you want to add this padding to single-line editors.
17803 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17804 self.offset_content = offset_content;
17805 cx.notify();
17806 }
17807
17808 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17809 self.show_line_numbers = Some(show_line_numbers);
17810 cx.notify();
17811 }
17812
17813 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17814 self.disable_expand_excerpt_buttons = true;
17815 cx.notify();
17816 }
17817
17818 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17819 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17820 cx.notify();
17821 }
17822
17823 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17824 self.show_code_actions = Some(show_code_actions);
17825 cx.notify();
17826 }
17827
17828 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17829 self.show_runnables = Some(show_runnables);
17830 cx.notify();
17831 }
17832
17833 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17834 self.show_breakpoints = Some(show_breakpoints);
17835 cx.notify();
17836 }
17837
17838 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17839 if self.display_map.read(cx).masked != masked {
17840 self.display_map.update(cx, |map, _| map.masked = masked);
17841 }
17842 cx.notify()
17843 }
17844
17845 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17846 self.show_wrap_guides = Some(show_wrap_guides);
17847 cx.notify();
17848 }
17849
17850 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17851 self.show_indent_guides = Some(show_indent_guides);
17852 cx.notify();
17853 }
17854
17855 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17856 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17857 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17858 if let Some(dir) = file.abs_path(cx).parent() {
17859 return Some(dir.to_owned());
17860 }
17861 }
17862
17863 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17864 return Some(project_path.path.to_path_buf());
17865 }
17866 }
17867
17868 None
17869 }
17870
17871 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17872 self.active_excerpt(cx)?
17873 .1
17874 .read(cx)
17875 .file()
17876 .and_then(|f| f.as_local())
17877 }
17878
17879 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17880 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17881 let buffer = buffer.read(cx);
17882 if let Some(project_path) = buffer.project_path(cx) {
17883 let project = self.project.as_ref()?.read(cx);
17884 project.absolute_path(&project_path, cx)
17885 } else {
17886 buffer
17887 .file()
17888 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17889 }
17890 })
17891 }
17892
17893 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17894 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17895 let project_path = buffer.read(cx).project_path(cx)?;
17896 let project = self.project.as_ref()?.read(cx);
17897 let entry = project.entry_for_path(&project_path, cx)?;
17898 let path = entry.path.to_path_buf();
17899 Some(path)
17900 })
17901 }
17902
17903 pub fn reveal_in_finder(
17904 &mut self,
17905 _: &RevealInFileManager,
17906 _window: &mut Window,
17907 cx: &mut Context<Self>,
17908 ) {
17909 if let Some(target) = self.target_file(cx) {
17910 cx.reveal_path(&target.abs_path(cx));
17911 }
17912 }
17913
17914 pub fn copy_path(
17915 &mut self,
17916 _: &zed_actions::workspace::CopyPath,
17917 _window: &mut Window,
17918 cx: &mut Context<Self>,
17919 ) {
17920 if let Some(path) = self.target_file_abs_path(cx) {
17921 if let Some(path) = path.to_str() {
17922 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17923 }
17924 }
17925 }
17926
17927 pub fn copy_relative_path(
17928 &mut self,
17929 _: &zed_actions::workspace::CopyRelativePath,
17930 _window: &mut Window,
17931 cx: &mut Context<Self>,
17932 ) {
17933 if let Some(path) = self.target_file_path(cx) {
17934 if let Some(path) = path.to_str() {
17935 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17936 }
17937 }
17938 }
17939
17940 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17941 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17942 buffer.read(cx).project_path(cx)
17943 } else {
17944 None
17945 }
17946 }
17947
17948 // Returns true if the editor handled a go-to-line request
17949 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17950 maybe!({
17951 let breakpoint_store = self.breakpoint_store.as_ref()?;
17952
17953 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17954 else {
17955 self.clear_row_highlights::<ActiveDebugLine>();
17956 return None;
17957 };
17958
17959 let position = active_stack_frame.position;
17960 let buffer_id = position.buffer_id?;
17961 let snapshot = self
17962 .project
17963 .as_ref()?
17964 .read(cx)
17965 .buffer_for_id(buffer_id, cx)?
17966 .read(cx)
17967 .snapshot();
17968
17969 let mut handled = false;
17970 for (id, ExcerptRange { context, .. }) in
17971 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17972 {
17973 if context.start.cmp(&position, &snapshot).is_ge()
17974 || context.end.cmp(&position, &snapshot).is_lt()
17975 {
17976 continue;
17977 }
17978 let snapshot = self.buffer.read(cx).snapshot(cx);
17979 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17980
17981 handled = true;
17982 self.clear_row_highlights::<ActiveDebugLine>();
17983
17984 self.go_to_line::<ActiveDebugLine>(
17985 multibuffer_anchor,
17986 Some(cx.theme().colors().editor_debugger_active_line_background),
17987 window,
17988 cx,
17989 );
17990
17991 cx.notify();
17992 }
17993
17994 handled.then_some(())
17995 })
17996 .is_some()
17997 }
17998
17999 pub fn copy_file_name_without_extension(
18000 &mut self,
18001 _: &CopyFileNameWithoutExtension,
18002 _: &mut Window,
18003 cx: &mut Context<Self>,
18004 ) {
18005 if let Some(file) = self.target_file(cx) {
18006 if let Some(file_stem) = file.path().file_stem() {
18007 if let Some(name) = file_stem.to_str() {
18008 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18009 }
18010 }
18011 }
18012 }
18013
18014 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
18015 if let Some(file) = self.target_file(cx) {
18016 if let Some(file_name) = file.path().file_name() {
18017 if let Some(name) = file_name.to_str() {
18018 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
18019 }
18020 }
18021 }
18022 }
18023
18024 pub fn toggle_git_blame(
18025 &mut self,
18026 _: &::git::Blame,
18027 window: &mut Window,
18028 cx: &mut Context<Self>,
18029 ) {
18030 self.show_git_blame_gutter = !self.show_git_blame_gutter;
18031
18032 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
18033 self.start_git_blame(true, window, cx);
18034 }
18035
18036 cx.notify();
18037 }
18038
18039 pub fn toggle_git_blame_inline(
18040 &mut self,
18041 _: &ToggleGitBlameInline,
18042 window: &mut Window,
18043 cx: &mut Context<Self>,
18044 ) {
18045 self.toggle_git_blame_inline_internal(true, window, cx);
18046 cx.notify();
18047 }
18048
18049 pub fn open_git_blame_commit(
18050 &mut self,
18051 _: &OpenGitBlameCommit,
18052 window: &mut Window,
18053 cx: &mut Context<Self>,
18054 ) {
18055 self.open_git_blame_commit_internal(window, cx);
18056 }
18057
18058 fn open_git_blame_commit_internal(
18059 &mut self,
18060 window: &mut Window,
18061 cx: &mut Context<Self>,
18062 ) -> Option<()> {
18063 let blame = self.blame.as_ref()?;
18064 let snapshot = self.snapshot(window, cx);
18065 let cursor = self.selections.newest::<Point>(cx).head();
18066 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
18067 let blame_entry = blame
18068 .update(cx, |blame, cx| {
18069 blame
18070 .blame_for_rows(
18071 &[RowInfo {
18072 buffer_id: Some(buffer.remote_id()),
18073 buffer_row: Some(point.row),
18074 ..Default::default()
18075 }],
18076 cx,
18077 )
18078 .next()
18079 })
18080 .flatten()?;
18081 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
18082 let repo = blame.read(cx).repository(cx)?;
18083 let workspace = self.workspace()?.downgrade();
18084 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
18085 None
18086 }
18087
18088 pub fn git_blame_inline_enabled(&self) -> bool {
18089 self.git_blame_inline_enabled
18090 }
18091
18092 pub fn toggle_selection_menu(
18093 &mut self,
18094 _: &ToggleSelectionMenu,
18095 _: &mut Window,
18096 cx: &mut Context<Self>,
18097 ) {
18098 self.show_selection_menu = self
18099 .show_selection_menu
18100 .map(|show_selections_menu| !show_selections_menu)
18101 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18102
18103 cx.notify();
18104 }
18105
18106 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18107 self.show_selection_menu
18108 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18109 }
18110
18111 fn start_git_blame(
18112 &mut self,
18113 user_triggered: bool,
18114 window: &mut Window,
18115 cx: &mut Context<Self>,
18116 ) {
18117 if let Some(project) = self.project.as_ref() {
18118 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18119 return;
18120 };
18121
18122 if buffer.read(cx).file().is_none() {
18123 return;
18124 }
18125
18126 let focused = self.focus_handle(cx).contains_focused(window, cx);
18127
18128 let project = project.clone();
18129 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18130 self.blame_subscription =
18131 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18132 self.blame = Some(blame);
18133 }
18134 }
18135
18136 fn toggle_git_blame_inline_internal(
18137 &mut self,
18138 user_triggered: bool,
18139 window: &mut Window,
18140 cx: &mut Context<Self>,
18141 ) {
18142 if self.git_blame_inline_enabled {
18143 self.git_blame_inline_enabled = false;
18144 self.show_git_blame_inline = false;
18145 self.show_git_blame_inline_delay_task.take();
18146 } else {
18147 self.git_blame_inline_enabled = true;
18148 self.start_git_blame_inline(user_triggered, window, cx);
18149 }
18150
18151 cx.notify();
18152 }
18153
18154 fn start_git_blame_inline(
18155 &mut self,
18156 user_triggered: bool,
18157 window: &mut Window,
18158 cx: &mut Context<Self>,
18159 ) {
18160 self.start_git_blame(user_triggered, window, cx);
18161
18162 if ProjectSettings::get_global(cx)
18163 .git
18164 .inline_blame_delay()
18165 .is_some()
18166 {
18167 self.start_inline_blame_timer(window, cx);
18168 } else {
18169 self.show_git_blame_inline = true
18170 }
18171 }
18172
18173 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18174 self.blame.as_ref()
18175 }
18176
18177 pub fn show_git_blame_gutter(&self) -> bool {
18178 self.show_git_blame_gutter
18179 }
18180
18181 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18182 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18183 }
18184
18185 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18186 self.show_git_blame_inline
18187 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18188 && !self.newest_selection_head_on_empty_line(cx)
18189 && self.has_blame_entries(cx)
18190 }
18191
18192 fn has_blame_entries(&self, cx: &App) -> bool {
18193 self.blame()
18194 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18195 }
18196
18197 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18198 let cursor_anchor = self.selections.newest_anchor().head();
18199
18200 let snapshot = self.buffer.read(cx).snapshot(cx);
18201 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18202
18203 snapshot.line_len(buffer_row) == 0
18204 }
18205
18206 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18207 let buffer_and_selection = maybe!({
18208 let selection = self.selections.newest::<Point>(cx);
18209 let selection_range = selection.range();
18210
18211 let multi_buffer = self.buffer().read(cx);
18212 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18213 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18214
18215 let (buffer, range, _) = if selection.reversed {
18216 buffer_ranges.first()
18217 } else {
18218 buffer_ranges.last()
18219 }?;
18220
18221 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18222 ..text::ToPoint::to_point(&range.end, &buffer).row;
18223 Some((
18224 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18225 selection,
18226 ))
18227 });
18228
18229 let Some((buffer, selection)) = buffer_and_selection else {
18230 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18231 };
18232
18233 let Some(project) = self.project.as_ref() else {
18234 return Task::ready(Err(anyhow!("editor does not have project")));
18235 };
18236
18237 project.update(cx, |project, cx| {
18238 project.get_permalink_to_line(&buffer, selection, cx)
18239 })
18240 }
18241
18242 pub fn copy_permalink_to_line(
18243 &mut self,
18244 _: &CopyPermalinkToLine,
18245 window: &mut Window,
18246 cx: &mut Context<Self>,
18247 ) {
18248 let permalink_task = self.get_permalink_to_line(cx);
18249 let workspace = self.workspace();
18250
18251 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18252 Ok(permalink) => {
18253 cx.update(|_, cx| {
18254 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18255 })
18256 .ok();
18257 }
18258 Err(err) => {
18259 let message = format!("Failed to copy permalink: {err}");
18260
18261 anyhow::Result::<()>::Err(err).log_err();
18262
18263 if let Some(workspace) = workspace {
18264 workspace
18265 .update_in(cx, |workspace, _, cx| {
18266 struct CopyPermalinkToLine;
18267
18268 workspace.show_toast(
18269 Toast::new(
18270 NotificationId::unique::<CopyPermalinkToLine>(),
18271 message,
18272 ),
18273 cx,
18274 )
18275 })
18276 .ok();
18277 }
18278 }
18279 })
18280 .detach();
18281 }
18282
18283 pub fn copy_file_location(
18284 &mut self,
18285 _: &CopyFileLocation,
18286 _: &mut Window,
18287 cx: &mut Context<Self>,
18288 ) {
18289 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18290 if let Some(file) = self.target_file(cx) {
18291 if let Some(path) = file.path().to_str() {
18292 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18293 }
18294 }
18295 }
18296
18297 pub fn open_permalink_to_line(
18298 &mut self,
18299 _: &OpenPermalinkToLine,
18300 window: &mut Window,
18301 cx: &mut Context<Self>,
18302 ) {
18303 let permalink_task = self.get_permalink_to_line(cx);
18304 let workspace = self.workspace();
18305
18306 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18307 Ok(permalink) => {
18308 cx.update(|_, cx| {
18309 cx.open_url(permalink.as_ref());
18310 })
18311 .ok();
18312 }
18313 Err(err) => {
18314 let message = format!("Failed to open permalink: {err}");
18315
18316 anyhow::Result::<()>::Err(err).log_err();
18317
18318 if let Some(workspace) = workspace {
18319 workspace
18320 .update(cx, |workspace, cx| {
18321 struct OpenPermalinkToLine;
18322
18323 workspace.show_toast(
18324 Toast::new(
18325 NotificationId::unique::<OpenPermalinkToLine>(),
18326 message,
18327 ),
18328 cx,
18329 )
18330 })
18331 .ok();
18332 }
18333 }
18334 })
18335 .detach();
18336 }
18337
18338 pub fn insert_uuid_v4(
18339 &mut self,
18340 _: &InsertUuidV4,
18341 window: &mut Window,
18342 cx: &mut Context<Self>,
18343 ) {
18344 self.insert_uuid(UuidVersion::V4, window, cx);
18345 }
18346
18347 pub fn insert_uuid_v7(
18348 &mut self,
18349 _: &InsertUuidV7,
18350 window: &mut Window,
18351 cx: &mut Context<Self>,
18352 ) {
18353 self.insert_uuid(UuidVersion::V7, window, cx);
18354 }
18355
18356 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18357 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18358 self.transact(window, cx, |this, window, cx| {
18359 let edits = this
18360 .selections
18361 .all::<Point>(cx)
18362 .into_iter()
18363 .map(|selection| {
18364 let uuid = match version {
18365 UuidVersion::V4 => uuid::Uuid::new_v4(),
18366 UuidVersion::V7 => uuid::Uuid::now_v7(),
18367 };
18368
18369 (selection.range(), uuid.to_string())
18370 });
18371 this.edit(edits, cx);
18372 this.refresh_inline_completion(true, false, window, cx);
18373 });
18374 }
18375
18376 pub fn open_selections_in_multibuffer(
18377 &mut self,
18378 _: &OpenSelectionsInMultibuffer,
18379 window: &mut Window,
18380 cx: &mut Context<Self>,
18381 ) {
18382 let multibuffer = self.buffer.read(cx);
18383
18384 let Some(buffer) = multibuffer.as_singleton() else {
18385 return;
18386 };
18387
18388 let Some(workspace) = self.workspace() else {
18389 return;
18390 };
18391
18392 let title = multibuffer.title(cx).to_string();
18393
18394 let locations = self
18395 .selections
18396 .all_anchors(cx)
18397 .into_iter()
18398 .map(|selection| Location {
18399 buffer: buffer.clone(),
18400 range: selection.start.text_anchor..selection.end.text_anchor,
18401 })
18402 .collect::<Vec<_>>();
18403
18404 cx.spawn_in(window, async move |_, cx| {
18405 workspace.update_in(cx, |workspace, window, cx| {
18406 Self::open_locations_in_multibuffer(
18407 workspace,
18408 locations,
18409 format!("Selections for '{title}'"),
18410 false,
18411 MultibufferSelectionMode::All,
18412 window,
18413 cx,
18414 );
18415 })
18416 })
18417 .detach();
18418 }
18419
18420 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18421 /// last highlight added will be used.
18422 ///
18423 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18424 pub fn highlight_rows<T: 'static>(
18425 &mut self,
18426 range: Range<Anchor>,
18427 color: Hsla,
18428 options: RowHighlightOptions,
18429 cx: &mut Context<Self>,
18430 ) {
18431 let snapshot = self.buffer().read(cx).snapshot(cx);
18432 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18433 let ix = row_highlights.binary_search_by(|highlight| {
18434 Ordering::Equal
18435 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18436 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18437 });
18438
18439 if let Err(mut ix) = ix {
18440 let index = post_inc(&mut self.highlight_order);
18441
18442 // If this range intersects with the preceding highlight, then merge it with
18443 // the preceding highlight. Otherwise insert a new highlight.
18444 let mut merged = false;
18445 if ix > 0 {
18446 let prev_highlight = &mut row_highlights[ix - 1];
18447 if prev_highlight
18448 .range
18449 .end
18450 .cmp(&range.start, &snapshot)
18451 .is_ge()
18452 {
18453 ix -= 1;
18454 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18455 prev_highlight.range.end = range.end;
18456 }
18457 merged = true;
18458 prev_highlight.index = index;
18459 prev_highlight.color = color;
18460 prev_highlight.options = options;
18461 }
18462 }
18463
18464 if !merged {
18465 row_highlights.insert(
18466 ix,
18467 RowHighlight {
18468 range: range.clone(),
18469 index,
18470 color,
18471 options,
18472 type_id: TypeId::of::<T>(),
18473 },
18474 );
18475 }
18476
18477 // If any of the following highlights intersect with this one, merge them.
18478 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18479 let highlight = &row_highlights[ix];
18480 if next_highlight
18481 .range
18482 .start
18483 .cmp(&highlight.range.end, &snapshot)
18484 .is_le()
18485 {
18486 if next_highlight
18487 .range
18488 .end
18489 .cmp(&highlight.range.end, &snapshot)
18490 .is_gt()
18491 {
18492 row_highlights[ix].range.end = next_highlight.range.end;
18493 }
18494 row_highlights.remove(ix + 1);
18495 } else {
18496 break;
18497 }
18498 }
18499 }
18500 }
18501
18502 /// Remove any highlighted row ranges of the given type that intersect the
18503 /// given ranges.
18504 pub fn remove_highlighted_rows<T: 'static>(
18505 &mut self,
18506 ranges_to_remove: Vec<Range<Anchor>>,
18507 cx: &mut Context<Self>,
18508 ) {
18509 let snapshot = self.buffer().read(cx).snapshot(cx);
18510 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18511 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18512 row_highlights.retain(|highlight| {
18513 while let Some(range_to_remove) = ranges_to_remove.peek() {
18514 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18515 Ordering::Less | Ordering::Equal => {
18516 ranges_to_remove.next();
18517 }
18518 Ordering::Greater => {
18519 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18520 Ordering::Less | Ordering::Equal => {
18521 return false;
18522 }
18523 Ordering::Greater => break,
18524 }
18525 }
18526 }
18527 }
18528
18529 true
18530 })
18531 }
18532
18533 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18534 pub fn clear_row_highlights<T: 'static>(&mut self) {
18535 self.highlighted_rows.remove(&TypeId::of::<T>());
18536 }
18537
18538 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18539 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18540 self.highlighted_rows
18541 .get(&TypeId::of::<T>())
18542 .map_or(&[] as &[_], |vec| vec.as_slice())
18543 .iter()
18544 .map(|highlight| (highlight.range.clone(), highlight.color))
18545 }
18546
18547 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18548 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18549 /// Allows to ignore certain kinds of highlights.
18550 pub fn highlighted_display_rows(
18551 &self,
18552 window: &mut Window,
18553 cx: &mut App,
18554 ) -> BTreeMap<DisplayRow, LineHighlight> {
18555 let snapshot = self.snapshot(window, cx);
18556 let mut used_highlight_orders = HashMap::default();
18557 self.highlighted_rows
18558 .iter()
18559 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18560 .fold(
18561 BTreeMap::<DisplayRow, LineHighlight>::new(),
18562 |mut unique_rows, highlight| {
18563 let start = highlight.range.start.to_display_point(&snapshot);
18564 let end = highlight.range.end.to_display_point(&snapshot);
18565 let start_row = start.row().0;
18566 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18567 && end.column() == 0
18568 {
18569 end.row().0.saturating_sub(1)
18570 } else {
18571 end.row().0
18572 };
18573 for row in start_row..=end_row {
18574 let used_index =
18575 used_highlight_orders.entry(row).or_insert(highlight.index);
18576 if highlight.index >= *used_index {
18577 *used_index = highlight.index;
18578 unique_rows.insert(
18579 DisplayRow(row),
18580 LineHighlight {
18581 include_gutter: highlight.options.include_gutter,
18582 border: None,
18583 background: highlight.color.into(),
18584 type_id: Some(highlight.type_id),
18585 },
18586 );
18587 }
18588 }
18589 unique_rows
18590 },
18591 )
18592 }
18593
18594 pub fn highlighted_display_row_for_autoscroll(
18595 &self,
18596 snapshot: &DisplaySnapshot,
18597 ) -> Option<DisplayRow> {
18598 self.highlighted_rows
18599 .values()
18600 .flat_map(|highlighted_rows| highlighted_rows.iter())
18601 .filter_map(|highlight| {
18602 if highlight.options.autoscroll {
18603 Some(highlight.range.start.to_display_point(snapshot).row())
18604 } else {
18605 None
18606 }
18607 })
18608 .min()
18609 }
18610
18611 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18612 self.highlight_background::<SearchWithinRange>(
18613 ranges,
18614 |colors| colors.colors().editor_document_highlight_read_background,
18615 cx,
18616 )
18617 }
18618
18619 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18620 self.breadcrumb_header = Some(new_header);
18621 }
18622
18623 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18624 self.clear_background_highlights::<SearchWithinRange>(cx);
18625 }
18626
18627 pub fn highlight_background<T: 'static>(
18628 &mut self,
18629 ranges: &[Range<Anchor>],
18630 color_fetcher: fn(&Theme) -> Hsla,
18631 cx: &mut Context<Self>,
18632 ) {
18633 self.background_highlights.insert(
18634 HighlightKey::Type(TypeId::of::<T>()),
18635 (color_fetcher, Arc::from(ranges)),
18636 );
18637 self.scrollbar_marker_state.dirty = true;
18638 cx.notify();
18639 }
18640
18641 pub fn highlight_background_key<T: 'static>(
18642 &mut self,
18643 key: usize,
18644 ranges: &[Range<Anchor>],
18645 color_fetcher: fn(&Theme) -> Hsla,
18646 cx: &mut Context<Self>,
18647 ) {
18648 self.background_highlights.insert(
18649 HighlightKey::TypePlus(TypeId::of::<T>(), key),
18650 (color_fetcher, Arc::from(ranges)),
18651 );
18652 self.scrollbar_marker_state.dirty = true;
18653 cx.notify();
18654 }
18655
18656 pub fn clear_background_highlights<T: 'static>(
18657 &mut self,
18658 cx: &mut Context<Self>,
18659 ) -> Option<BackgroundHighlight> {
18660 let text_highlights = self
18661 .background_highlights
18662 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
18663 if !text_highlights.1.is_empty() {
18664 self.scrollbar_marker_state.dirty = true;
18665 cx.notify();
18666 }
18667 Some(text_highlights)
18668 }
18669
18670 pub fn highlight_gutter<T: 'static>(
18671 &mut self,
18672 ranges: impl Into<Vec<Range<Anchor>>>,
18673 color_fetcher: fn(&App) -> Hsla,
18674 cx: &mut Context<Self>,
18675 ) {
18676 self.gutter_highlights
18677 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18678 cx.notify();
18679 }
18680
18681 pub fn clear_gutter_highlights<T: 'static>(
18682 &mut self,
18683 cx: &mut Context<Self>,
18684 ) -> Option<GutterHighlight> {
18685 cx.notify();
18686 self.gutter_highlights.remove(&TypeId::of::<T>())
18687 }
18688
18689 pub fn insert_gutter_highlight<T: 'static>(
18690 &mut self,
18691 range: Range<Anchor>,
18692 color_fetcher: fn(&App) -> Hsla,
18693 cx: &mut Context<Self>,
18694 ) {
18695 let snapshot = self.buffer().read(cx).snapshot(cx);
18696 let mut highlights = self
18697 .gutter_highlights
18698 .remove(&TypeId::of::<T>())
18699 .map(|(_, highlights)| highlights)
18700 .unwrap_or_default();
18701 let ix = highlights.binary_search_by(|highlight| {
18702 Ordering::Equal
18703 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18704 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18705 });
18706 if let Err(ix) = ix {
18707 highlights.insert(ix, range);
18708 }
18709 self.gutter_highlights
18710 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18711 }
18712
18713 pub fn remove_gutter_highlights<T: 'static>(
18714 &mut self,
18715 ranges_to_remove: Vec<Range<Anchor>>,
18716 cx: &mut Context<Self>,
18717 ) {
18718 let snapshot = self.buffer().read(cx).snapshot(cx);
18719 let Some((color_fetcher, mut gutter_highlights)) =
18720 self.gutter_highlights.remove(&TypeId::of::<T>())
18721 else {
18722 return;
18723 };
18724 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18725 gutter_highlights.retain(|highlight| {
18726 while let Some(range_to_remove) = ranges_to_remove.peek() {
18727 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18728 Ordering::Less | Ordering::Equal => {
18729 ranges_to_remove.next();
18730 }
18731 Ordering::Greater => {
18732 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18733 Ordering::Less | Ordering::Equal => {
18734 return false;
18735 }
18736 Ordering::Greater => break,
18737 }
18738 }
18739 }
18740 }
18741
18742 true
18743 });
18744 self.gutter_highlights
18745 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18746 }
18747
18748 #[cfg(feature = "test-support")]
18749 pub fn all_text_highlights(
18750 &self,
18751 window: &mut Window,
18752 cx: &mut Context<Self>,
18753 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
18754 let snapshot = self.snapshot(window, cx);
18755 self.display_map.update(cx, |display_map, _| {
18756 display_map
18757 .all_text_highlights()
18758 .map(|highlight| {
18759 let (style, ranges) = highlight.as_ref();
18760 (
18761 *style,
18762 ranges
18763 .iter()
18764 .map(|range| range.clone().to_display_points(&snapshot))
18765 .collect(),
18766 )
18767 })
18768 .collect()
18769 })
18770 }
18771
18772 #[cfg(feature = "test-support")]
18773 pub fn all_text_background_highlights(
18774 &self,
18775 window: &mut Window,
18776 cx: &mut Context<Self>,
18777 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18778 let snapshot = self.snapshot(window, cx);
18779 let buffer = &snapshot.buffer_snapshot;
18780 let start = buffer.anchor_before(0);
18781 let end = buffer.anchor_after(buffer.len());
18782 self.background_highlights_in_range(start..end, &snapshot, cx.theme())
18783 }
18784
18785 #[cfg(feature = "test-support")]
18786 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18787 let snapshot = self.buffer().read(cx).snapshot(cx);
18788
18789 let highlights = self
18790 .background_highlights
18791 .get(&HighlightKey::Type(TypeId::of::<
18792 items::BufferSearchHighlights,
18793 >()));
18794
18795 if let Some((_color, ranges)) = highlights {
18796 ranges
18797 .iter()
18798 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18799 .collect_vec()
18800 } else {
18801 vec![]
18802 }
18803 }
18804
18805 fn document_highlights_for_position<'a>(
18806 &'a self,
18807 position: Anchor,
18808 buffer: &'a MultiBufferSnapshot,
18809 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18810 let read_highlights = self
18811 .background_highlights
18812 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
18813 .map(|h| &h.1);
18814 let write_highlights = self
18815 .background_highlights
18816 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
18817 .map(|h| &h.1);
18818 let left_position = position.bias_left(buffer);
18819 let right_position = position.bias_right(buffer);
18820 read_highlights
18821 .into_iter()
18822 .chain(write_highlights)
18823 .flat_map(move |ranges| {
18824 let start_ix = match ranges.binary_search_by(|probe| {
18825 let cmp = probe.end.cmp(&left_position, buffer);
18826 if cmp.is_ge() {
18827 Ordering::Greater
18828 } else {
18829 Ordering::Less
18830 }
18831 }) {
18832 Ok(i) | Err(i) => i,
18833 };
18834
18835 ranges[start_ix..]
18836 .iter()
18837 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18838 })
18839 }
18840
18841 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18842 self.background_highlights
18843 .get(&HighlightKey::Type(TypeId::of::<T>()))
18844 .map_or(false, |(_, highlights)| !highlights.is_empty())
18845 }
18846
18847 pub fn background_highlights_in_range(
18848 &self,
18849 search_range: Range<Anchor>,
18850 display_snapshot: &DisplaySnapshot,
18851 theme: &Theme,
18852 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18853 let mut results = Vec::new();
18854 for (color_fetcher, ranges) in self.background_highlights.values() {
18855 let color = color_fetcher(theme);
18856 let start_ix = match ranges.binary_search_by(|probe| {
18857 let cmp = probe
18858 .end
18859 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18860 if cmp.is_gt() {
18861 Ordering::Greater
18862 } else {
18863 Ordering::Less
18864 }
18865 }) {
18866 Ok(i) | Err(i) => i,
18867 };
18868 for range in &ranges[start_ix..] {
18869 if range
18870 .start
18871 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18872 .is_ge()
18873 {
18874 break;
18875 }
18876
18877 let start = range.start.to_display_point(display_snapshot);
18878 let end = range.end.to_display_point(display_snapshot);
18879 results.push((start..end, color))
18880 }
18881 }
18882 results
18883 }
18884
18885 pub fn background_highlight_row_ranges<T: 'static>(
18886 &self,
18887 search_range: Range<Anchor>,
18888 display_snapshot: &DisplaySnapshot,
18889 count: usize,
18890 ) -> Vec<RangeInclusive<DisplayPoint>> {
18891 let mut results = Vec::new();
18892 let Some((_, ranges)) = self
18893 .background_highlights
18894 .get(&HighlightKey::Type(TypeId::of::<T>()))
18895 else {
18896 return vec![];
18897 };
18898
18899 let start_ix = match ranges.binary_search_by(|probe| {
18900 let cmp = probe
18901 .end
18902 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18903 if cmp.is_gt() {
18904 Ordering::Greater
18905 } else {
18906 Ordering::Less
18907 }
18908 }) {
18909 Ok(i) | Err(i) => i,
18910 };
18911 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18912 if let (Some(start_display), Some(end_display)) = (start, end) {
18913 results.push(
18914 start_display.to_display_point(display_snapshot)
18915 ..=end_display.to_display_point(display_snapshot),
18916 );
18917 }
18918 };
18919 let mut start_row: Option<Point> = None;
18920 let mut end_row: Option<Point> = None;
18921 if ranges.len() > count {
18922 return Vec::new();
18923 }
18924 for range in &ranges[start_ix..] {
18925 if range
18926 .start
18927 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18928 .is_ge()
18929 {
18930 break;
18931 }
18932 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18933 if let Some(current_row) = &end_row {
18934 if end.row == current_row.row {
18935 continue;
18936 }
18937 }
18938 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18939 if start_row.is_none() {
18940 assert_eq!(end_row, None);
18941 start_row = Some(start);
18942 end_row = Some(end);
18943 continue;
18944 }
18945 if let Some(current_end) = end_row.as_mut() {
18946 if start.row > current_end.row + 1 {
18947 push_region(start_row, end_row);
18948 start_row = Some(start);
18949 end_row = Some(end);
18950 } else {
18951 // Merge two hunks.
18952 *current_end = end;
18953 }
18954 } else {
18955 unreachable!();
18956 }
18957 }
18958 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18959 push_region(start_row, end_row);
18960 results
18961 }
18962
18963 pub fn gutter_highlights_in_range(
18964 &self,
18965 search_range: Range<Anchor>,
18966 display_snapshot: &DisplaySnapshot,
18967 cx: &App,
18968 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18969 let mut results = Vec::new();
18970 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18971 let color = color_fetcher(cx);
18972 let start_ix = match ranges.binary_search_by(|probe| {
18973 let cmp = probe
18974 .end
18975 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18976 if cmp.is_gt() {
18977 Ordering::Greater
18978 } else {
18979 Ordering::Less
18980 }
18981 }) {
18982 Ok(i) | Err(i) => i,
18983 };
18984 for range in &ranges[start_ix..] {
18985 if range
18986 .start
18987 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18988 .is_ge()
18989 {
18990 break;
18991 }
18992
18993 let start = range.start.to_display_point(display_snapshot);
18994 let end = range.end.to_display_point(display_snapshot);
18995 results.push((start..end, color))
18996 }
18997 }
18998 results
18999 }
19000
19001 /// Get the text ranges corresponding to the redaction query
19002 pub fn redacted_ranges(
19003 &self,
19004 search_range: Range<Anchor>,
19005 display_snapshot: &DisplaySnapshot,
19006 cx: &App,
19007 ) -> Vec<Range<DisplayPoint>> {
19008 display_snapshot
19009 .buffer_snapshot
19010 .redacted_ranges(search_range, |file| {
19011 if let Some(file) = file {
19012 file.is_private()
19013 && EditorSettings::get(
19014 Some(SettingsLocation {
19015 worktree_id: file.worktree_id(cx),
19016 path: file.path().as_ref(),
19017 }),
19018 cx,
19019 )
19020 .redact_private_values
19021 } else {
19022 false
19023 }
19024 })
19025 .map(|range| {
19026 range.start.to_display_point(display_snapshot)
19027 ..range.end.to_display_point(display_snapshot)
19028 })
19029 .collect()
19030 }
19031
19032 pub fn highlight_text_key<T: 'static>(
19033 &mut self,
19034 key: usize,
19035 ranges: Vec<Range<Anchor>>,
19036 style: HighlightStyle,
19037 cx: &mut Context<Self>,
19038 ) {
19039 self.display_map.update(cx, |map, _| {
19040 map.highlight_text(
19041 HighlightKey::TypePlus(TypeId::of::<T>(), key),
19042 ranges,
19043 style,
19044 );
19045 });
19046 cx.notify();
19047 }
19048
19049 pub fn highlight_text<T: 'static>(
19050 &mut self,
19051 ranges: Vec<Range<Anchor>>,
19052 style: HighlightStyle,
19053 cx: &mut Context<Self>,
19054 ) {
19055 self.display_map.update(cx, |map, _| {
19056 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
19057 });
19058 cx.notify();
19059 }
19060
19061 pub(crate) fn highlight_inlays<T: 'static>(
19062 &mut self,
19063 highlights: Vec<InlayHighlight>,
19064 style: HighlightStyle,
19065 cx: &mut Context<Self>,
19066 ) {
19067 self.display_map.update(cx, |map, _| {
19068 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
19069 });
19070 cx.notify();
19071 }
19072
19073 pub fn text_highlights<'a, T: 'static>(
19074 &'a self,
19075 cx: &'a App,
19076 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
19077 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
19078 }
19079
19080 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
19081 let cleared = self
19082 .display_map
19083 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
19084 if cleared {
19085 cx.notify();
19086 }
19087 }
19088
19089 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
19090 (self.read_only(cx) || self.blink_manager.read(cx).visible())
19091 && self.focus_handle.is_focused(window)
19092 }
19093
19094 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
19095 self.show_cursor_when_unfocused = is_enabled;
19096 cx.notify();
19097 }
19098
19099 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
19100 cx.notify();
19101 }
19102
19103 fn on_debug_session_event(
19104 &mut self,
19105 _session: Entity<Session>,
19106 event: &SessionEvent,
19107 cx: &mut Context<Self>,
19108 ) {
19109 match event {
19110 SessionEvent::InvalidateInlineValue => {
19111 self.refresh_inline_values(cx);
19112 }
19113 _ => {}
19114 }
19115 }
19116
19117 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19118 let Some(project) = self.project.clone() else {
19119 return;
19120 };
19121
19122 if !self.inline_value_cache.enabled {
19123 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19124 self.splice_inlays(&inlays, Vec::new(), cx);
19125 return;
19126 }
19127
19128 let current_execution_position = self
19129 .highlighted_rows
19130 .get(&TypeId::of::<ActiveDebugLine>())
19131 .and_then(|lines| lines.last().map(|line| line.range.start));
19132
19133 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19134 let inline_values = editor
19135 .update(cx, |editor, cx| {
19136 let Some(current_execution_position) = current_execution_position else {
19137 return Some(Task::ready(Ok(Vec::new())));
19138 };
19139
19140 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19141 let snapshot = buffer.snapshot(cx);
19142
19143 let excerpt = snapshot.excerpt_containing(
19144 current_execution_position..current_execution_position,
19145 )?;
19146
19147 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19148 })?;
19149
19150 let range =
19151 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19152
19153 project.inline_values(buffer, range, cx)
19154 })
19155 .ok()
19156 .flatten()?
19157 .await
19158 .context("refreshing debugger inlays")
19159 .log_err()?;
19160
19161 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19162
19163 for (buffer_id, inline_value) in inline_values
19164 .into_iter()
19165 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19166 {
19167 buffer_inline_values
19168 .entry(buffer_id)
19169 .or_default()
19170 .push(inline_value);
19171 }
19172
19173 editor
19174 .update(cx, |editor, cx| {
19175 let snapshot = editor.buffer.read(cx).snapshot(cx);
19176 let mut new_inlays = Vec::default();
19177
19178 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19179 let buffer_id = buffer_snapshot.remote_id();
19180 buffer_inline_values
19181 .get(&buffer_id)
19182 .into_iter()
19183 .flatten()
19184 .for_each(|hint| {
19185 let inlay = Inlay::debugger(
19186 post_inc(&mut editor.next_inlay_id),
19187 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19188 hint.text(),
19189 );
19190
19191 new_inlays.push(inlay);
19192 });
19193 }
19194
19195 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19196 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19197
19198 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19199 })
19200 .ok()?;
19201 Some(())
19202 });
19203 }
19204
19205 fn on_buffer_event(
19206 &mut self,
19207 multibuffer: &Entity<MultiBuffer>,
19208 event: &multi_buffer::Event,
19209 window: &mut Window,
19210 cx: &mut Context<Self>,
19211 ) {
19212 match event {
19213 multi_buffer::Event::Edited {
19214 singleton_buffer_edited,
19215 edited_buffer,
19216 } => {
19217 self.scrollbar_marker_state.dirty = true;
19218 self.active_indent_guides_state.dirty = true;
19219 self.refresh_active_diagnostics(cx);
19220 self.refresh_code_actions(window, cx);
19221 self.refresh_selected_text_highlights(true, window, cx);
19222 refresh_matching_bracket_highlights(self, window, cx);
19223 if self.has_active_inline_completion() {
19224 self.update_visible_inline_completion(window, cx);
19225 }
19226 if let Some(project) = self.project.as_ref() {
19227 if let Some(edited_buffer) = edited_buffer {
19228 project.update(cx, |project, cx| {
19229 self.registered_buffers
19230 .entry(edited_buffer.read(cx).remote_id())
19231 .or_insert_with(|| {
19232 project
19233 .register_buffer_with_language_servers(&edited_buffer, cx)
19234 });
19235 });
19236 }
19237 }
19238 cx.emit(EditorEvent::BufferEdited);
19239 cx.emit(SearchEvent::MatchesInvalidated);
19240
19241 if let Some(buffer) = edited_buffer {
19242 self.update_lsp_data(None, Some(buffer.read(cx).remote_id()), window, cx);
19243 }
19244
19245 if *singleton_buffer_edited {
19246 if let Some(buffer) = edited_buffer {
19247 if buffer.read(cx).file().is_none() {
19248 cx.emit(EditorEvent::TitleChanged);
19249 }
19250 }
19251 if let Some(project) = &self.project {
19252 #[allow(clippy::mutable_key_type)]
19253 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19254 multibuffer
19255 .all_buffers()
19256 .into_iter()
19257 .filter_map(|buffer| {
19258 buffer.update(cx, |buffer, cx| {
19259 let language = buffer.language()?;
19260 let should_discard = project.update(cx, |project, cx| {
19261 project.is_local()
19262 && !project.has_language_servers_for(buffer, cx)
19263 });
19264 should_discard.not().then_some(language.clone())
19265 })
19266 })
19267 .collect::<HashSet<_>>()
19268 });
19269 if !languages_affected.is_empty() {
19270 self.refresh_inlay_hints(
19271 InlayHintRefreshReason::BufferEdited(languages_affected),
19272 cx,
19273 );
19274 }
19275 }
19276 }
19277
19278 let Some(project) = &self.project else { return };
19279 let (telemetry, is_via_ssh) = {
19280 let project = project.read(cx);
19281 let telemetry = project.client().telemetry().clone();
19282 let is_via_ssh = project.is_via_ssh();
19283 (telemetry, is_via_ssh)
19284 };
19285 refresh_linked_ranges(self, window, cx);
19286 telemetry.log_edit_event("editor", is_via_ssh);
19287 }
19288 multi_buffer::Event::ExcerptsAdded {
19289 buffer,
19290 predecessor,
19291 excerpts,
19292 } => {
19293 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19294 let buffer_id = buffer.read(cx).remote_id();
19295 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19296 if let Some(project) = &self.project {
19297 update_uncommitted_diff_for_buffer(
19298 cx.entity(),
19299 project,
19300 [buffer.clone()],
19301 self.buffer.clone(),
19302 cx,
19303 )
19304 .detach();
19305 }
19306 }
19307 self.update_lsp_data(None, Some(buffer_id), window, cx);
19308 cx.emit(EditorEvent::ExcerptsAdded {
19309 buffer: buffer.clone(),
19310 predecessor: *predecessor,
19311 excerpts: excerpts.clone(),
19312 });
19313 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19314 }
19315 multi_buffer::Event::ExcerptsRemoved {
19316 ids,
19317 removed_buffer_ids,
19318 } => {
19319 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19320 let buffer = self.buffer.read(cx);
19321 self.registered_buffers
19322 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19323 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19324 cx.emit(EditorEvent::ExcerptsRemoved {
19325 ids: ids.clone(),
19326 removed_buffer_ids: removed_buffer_ids.clone(),
19327 });
19328 }
19329 multi_buffer::Event::ExcerptsEdited {
19330 excerpt_ids,
19331 buffer_ids,
19332 } => {
19333 self.display_map.update(cx, |map, cx| {
19334 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19335 });
19336 cx.emit(EditorEvent::ExcerptsEdited {
19337 ids: excerpt_ids.clone(),
19338 });
19339 }
19340 multi_buffer::Event::ExcerptsExpanded { ids } => {
19341 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19342 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19343 }
19344 multi_buffer::Event::Reparsed(buffer_id) => {
19345 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19346 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19347
19348 cx.emit(EditorEvent::Reparsed(*buffer_id));
19349 }
19350 multi_buffer::Event::DiffHunksToggled => {
19351 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19352 }
19353 multi_buffer::Event::LanguageChanged(buffer_id) => {
19354 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19355 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19356 cx.emit(EditorEvent::Reparsed(*buffer_id));
19357 cx.notify();
19358 }
19359 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19360 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19361 multi_buffer::Event::FileHandleChanged
19362 | multi_buffer::Event::Reloaded
19363 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19364 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19365 multi_buffer::Event::DiagnosticsUpdated => {
19366 self.update_diagnostics_state(window, cx);
19367 }
19368 _ => {}
19369 };
19370 }
19371
19372 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19373 self.refresh_active_diagnostics(cx);
19374 self.refresh_inline_diagnostics(true, window, cx);
19375 self.scrollbar_marker_state.dirty = true;
19376 cx.notify();
19377 }
19378
19379 pub fn start_temporary_diff_override(&mut self) {
19380 self.load_diff_task.take();
19381 self.temporary_diff_override = true;
19382 }
19383
19384 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19385 self.temporary_diff_override = false;
19386 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19387 self.buffer.update(cx, |buffer, cx| {
19388 buffer.set_all_diff_hunks_collapsed(cx);
19389 });
19390
19391 if let Some(project) = self.project.clone() {
19392 self.load_diff_task = Some(
19393 update_uncommitted_diff_for_buffer(
19394 cx.entity(),
19395 &project,
19396 self.buffer.read(cx).all_buffers(),
19397 self.buffer.clone(),
19398 cx,
19399 )
19400 .shared(),
19401 );
19402 }
19403 }
19404
19405 fn on_display_map_changed(
19406 &mut self,
19407 _: Entity<DisplayMap>,
19408 _: &mut Window,
19409 cx: &mut Context<Self>,
19410 ) {
19411 cx.notify();
19412 }
19413
19414 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19415 let new_severity = if self.diagnostics_enabled() {
19416 EditorSettings::get_global(cx)
19417 .diagnostics_max_severity
19418 .unwrap_or(DiagnosticSeverity::Hint)
19419 } else {
19420 DiagnosticSeverity::Off
19421 };
19422 self.set_max_diagnostics_severity(new_severity, cx);
19423 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19424 self.update_edit_prediction_settings(cx);
19425 self.refresh_inline_completion(true, false, window, cx);
19426 self.refresh_inlay_hints(
19427 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19428 self.selections.newest_anchor().head(),
19429 &self.buffer.read(cx).snapshot(cx),
19430 cx,
19431 )),
19432 cx,
19433 );
19434
19435 let old_cursor_shape = self.cursor_shape;
19436
19437 {
19438 let editor_settings = EditorSettings::get_global(cx);
19439 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19440 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19441 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19442 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19443 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19444 }
19445
19446 if old_cursor_shape != self.cursor_shape {
19447 cx.emit(EditorEvent::CursorShapeChanged);
19448 }
19449
19450 let project_settings = ProjectSettings::get_global(cx);
19451 self.serialize_dirty_buffers =
19452 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19453
19454 if self.mode.is_full() {
19455 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19456 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19457 if self.show_inline_diagnostics != show_inline_diagnostics {
19458 self.show_inline_diagnostics = show_inline_diagnostics;
19459 self.refresh_inline_diagnostics(false, window, cx);
19460 }
19461
19462 if self.git_blame_inline_enabled != inline_blame_enabled {
19463 self.toggle_git_blame_inline_internal(false, window, cx);
19464 }
19465
19466 let minimap_settings = EditorSettings::get_global(cx).minimap;
19467 if self.minimap_visibility != MinimapVisibility::Disabled {
19468 if self.minimap_visibility.settings_visibility()
19469 != minimap_settings.minimap_enabled()
19470 {
19471 self.set_minimap_visibility(
19472 MinimapVisibility::for_mode(self.mode(), cx),
19473 window,
19474 cx,
19475 );
19476 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19477 minimap_entity.update(cx, |minimap_editor, cx| {
19478 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19479 })
19480 }
19481 }
19482 }
19483
19484 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
19485 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
19486 }) {
19487 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
19488 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
19489 }
19490 self.refresh_colors(None, None, window, cx);
19491 }
19492
19493 cx.notify();
19494 }
19495
19496 pub fn set_searchable(&mut self, searchable: bool) {
19497 self.searchable = searchable;
19498 }
19499
19500 pub fn searchable(&self) -> bool {
19501 self.searchable
19502 }
19503
19504 fn open_proposed_changes_editor(
19505 &mut self,
19506 _: &OpenProposedChangesEditor,
19507 window: &mut Window,
19508 cx: &mut Context<Self>,
19509 ) {
19510 let Some(workspace) = self.workspace() else {
19511 cx.propagate();
19512 return;
19513 };
19514
19515 let selections = self.selections.all::<usize>(cx);
19516 let multi_buffer = self.buffer.read(cx);
19517 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19518 let mut new_selections_by_buffer = HashMap::default();
19519 for selection in selections {
19520 for (buffer, range, _) in
19521 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19522 {
19523 let mut range = range.to_point(buffer);
19524 range.start.column = 0;
19525 range.end.column = buffer.line_len(range.end.row);
19526 new_selections_by_buffer
19527 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19528 .or_insert(Vec::new())
19529 .push(range)
19530 }
19531 }
19532
19533 let proposed_changes_buffers = new_selections_by_buffer
19534 .into_iter()
19535 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19536 .collect::<Vec<_>>();
19537 let proposed_changes_editor = cx.new(|cx| {
19538 ProposedChangesEditor::new(
19539 "Proposed changes",
19540 proposed_changes_buffers,
19541 self.project.clone(),
19542 window,
19543 cx,
19544 )
19545 });
19546
19547 window.defer(cx, move |window, cx| {
19548 workspace.update(cx, |workspace, cx| {
19549 workspace.active_pane().update(cx, |pane, cx| {
19550 pane.add_item(
19551 Box::new(proposed_changes_editor),
19552 true,
19553 true,
19554 None,
19555 window,
19556 cx,
19557 );
19558 });
19559 });
19560 });
19561 }
19562
19563 pub fn open_excerpts_in_split(
19564 &mut self,
19565 _: &OpenExcerptsSplit,
19566 window: &mut Window,
19567 cx: &mut Context<Self>,
19568 ) {
19569 self.open_excerpts_common(None, true, window, cx)
19570 }
19571
19572 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19573 self.open_excerpts_common(None, false, window, cx)
19574 }
19575
19576 fn open_excerpts_common(
19577 &mut self,
19578 jump_data: Option<JumpData>,
19579 split: bool,
19580 window: &mut Window,
19581 cx: &mut Context<Self>,
19582 ) {
19583 let Some(workspace) = self.workspace() else {
19584 cx.propagate();
19585 return;
19586 };
19587
19588 if self.buffer.read(cx).is_singleton() {
19589 cx.propagate();
19590 return;
19591 }
19592
19593 let mut new_selections_by_buffer = HashMap::default();
19594 match &jump_data {
19595 Some(JumpData::MultiBufferPoint {
19596 excerpt_id,
19597 position,
19598 anchor,
19599 line_offset_from_top,
19600 }) => {
19601 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19602 if let Some(buffer) = multi_buffer_snapshot
19603 .buffer_id_for_excerpt(*excerpt_id)
19604 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19605 {
19606 let buffer_snapshot = buffer.read(cx).snapshot();
19607 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19608 language::ToPoint::to_point(anchor, &buffer_snapshot)
19609 } else {
19610 buffer_snapshot.clip_point(*position, Bias::Left)
19611 };
19612 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19613 new_selections_by_buffer.insert(
19614 buffer,
19615 (
19616 vec![jump_to_offset..jump_to_offset],
19617 Some(*line_offset_from_top),
19618 ),
19619 );
19620 }
19621 }
19622 Some(JumpData::MultiBufferRow {
19623 row,
19624 line_offset_from_top,
19625 }) => {
19626 let point = MultiBufferPoint::new(row.0, 0);
19627 if let Some((buffer, buffer_point, _)) =
19628 self.buffer.read(cx).point_to_buffer_point(point, cx)
19629 {
19630 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19631 new_selections_by_buffer
19632 .entry(buffer)
19633 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19634 .0
19635 .push(buffer_offset..buffer_offset)
19636 }
19637 }
19638 None => {
19639 let selections = self.selections.all::<usize>(cx);
19640 let multi_buffer = self.buffer.read(cx);
19641 for selection in selections {
19642 for (snapshot, range, _, anchor) in multi_buffer
19643 .snapshot(cx)
19644 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19645 {
19646 if let Some(anchor) = anchor {
19647 // selection is in a deleted hunk
19648 let Some(buffer_id) = anchor.buffer_id else {
19649 continue;
19650 };
19651 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19652 continue;
19653 };
19654 let offset = text::ToOffset::to_offset(
19655 &anchor.text_anchor,
19656 &buffer_handle.read(cx).snapshot(),
19657 );
19658 let range = offset..offset;
19659 new_selections_by_buffer
19660 .entry(buffer_handle)
19661 .or_insert((Vec::new(), None))
19662 .0
19663 .push(range)
19664 } else {
19665 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19666 else {
19667 continue;
19668 };
19669 new_selections_by_buffer
19670 .entry(buffer_handle)
19671 .or_insert((Vec::new(), None))
19672 .0
19673 .push(range)
19674 }
19675 }
19676 }
19677 }
19678 }
19679
19680 new_selections_by_buffer
19681 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19682
19683 if new_selections_by_buffer.is_empty() {
19684 return;
19685 }
19686
19687 // We defer the pane interaction because we ourselves are a workspace item
19688 // and activating a new item causes the pane to call a method on us reentrantly,
19689 // which panics if we're on the stack.
19690 window.defer(cx, move |window, cx| {
19691 workspace.update(cx, |workspace, cx| {
19692 let pane = if split {
19693 workspace.adjacent_pane(window, cx)
19694 } else {
19695 workspace.active_pane().clone()
19696 };
19697
19698 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19699 let editor = buffer
19700 .read(cx)
19701 .file()
19702 .is_none()
19703 .then(|| {
19704 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19705 // so `workspace.open_project_item` will never find them, always opening a new editor.
19706 // Instead, we try to activate the existing editor in the pane first.
19707 let (editor, pane_item_index) =
19708 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19709 let editor = item.downcast::<Editor>()?;
19710 let singleton_buffer =
19711 editor.read(cx).buffer().read(cx).as_singleton()?;
19712 if singleton_buffer == buffer {
19713 Some((editor, i))
19714 } else {
19715 None
19716 }
19717 })?;
19718 pane.update(cx, |pane, cx| {
19719 pane.activate_item(pane_item_index, true, true, window, cx)
19720 });
19721 Some(editor)
19722 })
19723 .flatten()
19724 .unwrap_or_else(|| {
19725 workspace.open_project_item::<Self>(
19726 pane.clone(),
19727 buffer,
19728 true,
19729 true,
19730 window,
19731 cx,
19732 )
19733 });
19734
19735 editor.update(cx, |editor, cx| {
19736 let autoscroll = match scroll_offset {
19737 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19738 None => Autoscroll::newest(),
19739 };
19740 let nav_history = editor.nav_history.take();
19741 editor.change_selections(Some(autoscroll), window, cx, |s| {
19742 s.select_ranges(ranges);
19743 });
19744 editor.nav_history = nav_history;
19745 });
19746 }
19747 })
19748 });
19749 }
19750
19751 // For now, don't allow opening excerpts in buffers that aren't backed by
19752 // regular project files.
19753 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19754 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19755 }
19756
19757 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19758 let snapshot = self.buffer.read(cx).read(cx);
19759 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19760 Some(
19761 ranges
19762 .iter()
19763 .map(move |range| {
19764 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19765 })
19766 .collect(),
19767 )
19768 }
19769
19770 fn selection_replacement_ranges(
19771 &self,
19772 range: Range<OffsetUtf16>,
19773 cx: &mut App,
19774 ) -> Vec<Range<OffsetUtf16>> {
19775 let selections = self.selections.all::<OffsetUtf16>(cx);
19776 let newest_selection = selections
19777 .iter()
19778 .max_by_key(|selection| selection.id)
19779 .unwrap();
19780 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19781 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19782 let snapshot = self.buffer.read(cx).read(cx);
19783 selections
19784 .into_iter()
19785 .map(|mut selection| {
19786 selection.start.0 =
19787 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19788 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19789 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19790 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19791 })
19792 .collect()
19793 }
19794
19795 fn report_editor_event(
19796 &self,
19797 event_type: &'static str,
19798 file_extension: Option<String>,
19799 cx: &App,
19800 ) {
19801 if cfg!(any(test, feature = "test-support")) {
19802 return;
19803 }
19804
19805 let Some(project) = &self.project else { return };
19806
19807 // If None, we are in a file without an extension
19808 let file = self
19809 .buffer
19810 .read(cx)
19811 .as_singleton()
19812 .and_then(|b| b.read(cx).file());
19813 let file_extension = file_extension.or(file
19814 .as_ref()
19815 .and_then(|file| Path::new(file.file_name(cx)).extension())
19816 .and_then(|e| e.to_str())
19817 .map(|a| a.to_string()));
19818
19819 let vim_mode = vim_enabled(cx);
19820
19821 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19822 let copilot_enabled = edit_predictions_provider
19823 == language::language_settings::EditPredictionProvider::Copilot;
19824 let copilot_enabled_for_language = self
19825 .buffer
19826 .read(cx)
19827 .language_settings(cx)
19828 .show_edit_predictions;
19829
19830 let project = project.read(cx);
19831 telemetry::event!(
19832 event_type,
19833 file_extension,
19834 vim_mode,
19835 copilot_enabled,
19836 copilot_enabled_for_language,
19837 edit_predictions_provider,
19838 is_via_ssh = project.is_via_ssh(),
19839 );
19840 }
19841
19842 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19843 /// with each line being an array of {text, highlight} objects.
19844 fn copy_highlight_json(
19845 &mut self,
19846 _: &CopyHighlightJson,
19847 window: &mut Window,
19848 cx: &mut Context<Self>,
19849 ) {
19850 #[derive(Serialize)]
19851 struct Chunk<'a> {
19852 text: String,
19853 highlight: Option<&'a str>,
19854 }
19855
19856 let snapshot = self.buffer.read(cx).snapshot(cx);
19857 let range = self
19858 .selected_text_range(false, window, cx)
19859 .and_then(|selection| {
19860 if selection.range.is_empty() {
19861 None
19862 } else {
19863 Some(selection.range)
19864 }
19865 })
19866 .unwrap_or_else(|| 0..snapshot.len());
19867
19868 let chunks = snapshot.chunks(range, true);
19869 let mut lines = Vec::new();
19870 let mut line: VecDeque<Chunk> = VecDeque::new();
19871
19872 let Some(style) = self.style.as_ref() else {
19873 return;
19874 };
19875
19876 for chunk in chunks {
19877 let highlight = chunk
19878 .syntax_highlight_id
19879 .and_then(|id| id.name(&style.syntax));
19880 let mut chunk_lines = chunk.text.split('\n').peekable();
19881 while let Some(text) = chunk_lines.next() {
19882 let mut merged_with_last_token = false;
19883 if let Some(last_token) = line.back_mut() {
19884 if last_token.highlight == highlight {
19885 last_token.text.push_str(text);
19886 merged_with_last_token = true;
19887 }
19888 }
19889
19890 if !merged_with_last_token {
19891 line.push_back(Chunk {
19892 text: text.into(),
19893 highlight,
19894 });
19895 }
19896
19897 if chunk_lines.peek().is_some() {
19898 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19899 line.pop_front();
19900 }
19901 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19902 line.pop_back();
19903 }
19904
19905 lines.push(mem::take(&mut line));
19906 }
19907 }
19908 }
19909
19910 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19911 return;
19912 };
19913 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19914 }
19915
19916 pub fn open_context_menu(
19917 &mut self,
19918 _: &OpenContextMenu,
19919 window: &mut Window,
19920 cx: &mut Context<Self>,
19921 ) {
19922 self.request_autoscroll(Autoscroll::newest(), cx);
19923 let position = self.selections.newest_display(cx).start;
19924 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19925 }
19926
19927 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19928 &self.inlay_hint_cache
19929 }
19930
19931 pub fn replay_insert_event(
19932 &mut self,
19933 text: &str,
19934 relative_utf16_range: Option<Range<isize>>,
19935 window: &mut Window,
19936 cx: &mut Context<Self>,
19937 ) {
19938 if !self.input_enabled {
19939 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19940 return;
19941 }
19942 if let Some(relative_utf16_range) = relative_utf16_range {
19943 let selections = self.selections.all::<OffsetUtf16>(cx);
19944 self.change_selections(None, window, cx, |s| {
19945 let new_ranges = selections.into_iter().map(|range| {
19946 let start = OffsetUtf16(
19947 range
19948 .head()
19949 .0
19950 .saturating_add_signed(relative_utf16_range.start),
19951 );
19952 let end = OffsetUtf16(
19953 range
19954 .head()
19955 .0
19956 .saturating_add_signed(relative_utf16_range.end),
19957 );
19958 start..end
19959 });
19960 s.select_ranges(new_ranges);
19961 });
19962 }
19963
19964 self.handle_input(text, window, cx);
19965 }
19966
19967 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19968 let Some(provider) = self.semantics_provider.as_ref() else {
19969 return false;
19970 };
19971
19972 let mut supports = false;
19973 self.buffer().update(cx, |this, cx| {
19974 this.for_each_buffer(|buffer| {
19975 supports |= provider.supports_inlay_hints(buffer, cx);
19976 });
19977 });
19978
19979 supports
19980 }
19981
19982 pub fn is_focused(&self, window: &Window) -> bool {
19983 self.focus_handle.is_focused(window)
19984 }
19985
19986 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19987 cx.emit(EditorEvent::Focused);
19988
19989 if let Some(descendant) = self
19990 .last_focused_descendant
19991 .take()
19992 .and_then(|descendant| descendant.upgrade())
19993 {
19994 window.focus(&descendant);
19995 } else {
19996 if let Some(blame) = self.blame.as_ref() {
19997 blame.update(cx, GitBlame::focus)
19998 }
19999
20000 self.blink_manager.update(cx, BlinkManager::enable);
20001 self.show_cursor_names(window, cx);
20002 self.buffer.update(cx, |buffer, cx| {
20003 buffer.finalize_last_transaction(cx);
20004 if self.leader_id.is_none() {
20005 buffer.set_active_selections(
20006 &self.selections.disjoint_anchors(),
20007 self.selections.line_mode,
20008 self.cursor_shape,
20009 cx,
20010 );
20011 }
20012 });
20013 }
20014 }
20015
20016 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20017 cx.emit(EditorEvent::FocusedIn)
20018 }
20019
20020 fn handle_focus_out(
20021 &mut self,
20022 event: FocusOutEvent,
20023 _window: &mut Window,
20024 cx: &mut Context<Self>,
20025 ) {
20026 if event.blurred != self.focus_handle {
20027 self.last_focused_descendant = Some(event.blurred);
20028 }
20029 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
20030 }
20031
20032 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20033 self.blink_manager.update(cx, BlinkManager::disable);
20034 self.buffer
20035 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
20036
20037 if let Some(blame) = self.blame.as_ref() {
20038 blame.update(cx, GitBlame::blur)
20039 }
20040 if !self.hover_state.focused(window, cx) {
20041 hide_hover(self, cx);
20042 }
20043 if !self
20044 .context_menu
20045 .borrow()
20046 .as_ref()
20047 .is_some_and(|context_menu| context_menu.focused(window, cx))
20048 {
20049 self.hide_context_menu(window, cx);
20050 }
20051 self.discard_inline_completion(false, cx);
20052 cx.emit(EditorEvent::Blurred);
20053 cx.notify();
20054 }
20055
20056 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20057 let mut pending: String = window
20058 .pending_input_keystrokes()
20059 .into_iter()
20060 .flatten()
20061 .filter_map(|keystroke| {
20062 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
20063 keystroke.key_char.clone()
20064 } else {
20065 None
20066 }
20067 })
20068 .collect();
20069
20070 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
20071 pending = "".to_string();
20072 }
20073
20074 let existing_pending = self
20075 .text_highlights::<PendingInput>(cx)
20076 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
20077 if existing_pending.is_none() && pending.is_empty() {
20078 return;
20079 }
20080 let transaction =
20081 self.transact(window, cx, |this, window, cx| {
20082 let selections = this.selections.all::<usize>(cx);
20083 let edits = selections
20084 .iter()
20085 .map(|selection| (selection.end..selection.end, pending.clone()));
20086 this.edit(edits, cx);
20087 this.change_selections(None, window, cx, |s| {
20088 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
20089 sel.start + ix * pending.len()..sel.end + ix * pending.len()
20090 }));
20091 });
20092 if let Some(existing_ranges) = existing_pending {
20093 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
20094 this.edit(edits, cx);
20095 }
20096 });
20097
20098 let snapshot = self.snapshot(window, cx);
20099 let ranges = self
20100 .selections
20101 .all::<usize>(cx)
20102 .into_iter()
20103 .map(|selection| {
20104 snapshot.buffer_snapshot.anchor_after(selection.end)
20105 ..snapshot
20106 .buffer_snapshot
20107 .anchor_before(selection.end + pending.len())
20108 })
20109 .collect();
20110
20111 if pending.is_empty() {
20112 self.clear_highlights::<PendingInput>(cx);
20113 } else {
20114 self.highlight_text::<PendingInput>(
20115 ranges,
20116 HighlightStyle {
20117 underline: Some(UnderlineStyle {
20118 thickness: px(1.),
20119 color: None,
20120 wavy: false,
20121 }),
20122 ..Default::default()
20123 },
20124 cx,
20125 );
20126 }
20127
20128 self.ime_transaction = self.ime_transaction.or(transaction);
20129 if let Some(transaction) = self.ime_transaction {
20130 self.buffer.update(cx, |buffer, cx| {
20131 buffer.group_until_transaction(transaction, cx);
20132 });
20133 }
20134
20135 if self.text_highlights::<PendingInput>(cx).is_none() {
20136 self.ime_transaction.take();
20137 }
20138 }
20139
20140 pub fn register_action_renderer(
20141 &mut self,
20142 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20143 ) -> Subscription {
20144 let id = self.next_editor_action_id.post_inc();
20145 self.editor_actions
20146 .borrow_mut()
20147 .insert(id, Box::new(listener));
20148
20149 let editor_actions = self.editor_actions.clone();
20150 Subscription::new(move || {
20151 editor_actions.borrow_mut().remove(&id);
20152 })
20153 }
20154
20155 pub fn register_action<A: Action>(
20156 &mut self,
20157 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20158 ) -> Subscription {
20159 let id = self.next_editor_action_id.post_inc();
20160 let listener = Arc::new(listener);
20161 self.editor_actions.borrow_mut().insert(
20162 id,
20163 Box::new(move |_, window, _| {
20164 let listener = listener.clone();
20165 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20166 let action = action.downcast_ref().unwrap();
20167 if phase == DispatchPhase::Bubble {
20168 listener(action, window, cx)
20169 }
20170 })
20171 }),
20172 );
20173
20174 let editor_actions = self.editor_actions.clone();
20175 Subscription::new(move || {
20176 editor_actions.borrow_mut().remove(&id);
20177 })
20178 }
20179
20180 pub fn file_header_size(&self) -> u32 {
20181 FILE_HEADER_HEIGHT
20182 }
20183
20184 pub fn restore(
20185 &mut self,
20186 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20187 window: &mut Window,
20188 cx: &mut Context<Self>,
20189 ) {
20190 let workspace = self.workspace();
20191 let project = self.project.as_ref();
20192 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20193 let mut tasks = Vec::new();
20194 for (buffer_id, changes) in revert_changes {
20195 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20196 buffer.update(cx, |buffer, cx| {
20197 buffer.edit(
20198 changes
20199 .into_iter()
20200 .map(|(range, text)| (range, text.to_string())),
20201 None,
20202 cx,
20203 );
20204 });
20205
20206 if let Some(project) =
20207 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20208 {
20209 project.update(cx, |project, cx| {
20210 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20211 })
20212 }
20213 }
20214 }
20215 tasks
20216 });
20217 cx.spawn_in(window, async move |_, cx| {
20218 for (buffer, task) in save_tasks {
20219 let result = task.await;
20220 if result.is_err() {
20221 let Some(path) = buffer
20222 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20223 .ok()
20224 else {
20225 continue;
20226 };
20227 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20228 let Some(task) = cx
20229 .update_window_entity(&workspace, |workspace, window, cx| {
20230 workspace
20231 .open_path_preview(path, None, false, false, false, window, cx)
20232 })
20233 .ok()
20234 else {
20235 continue;
20236 };
20237 task.await.log_err();
20238 }
20239 }
20240 }
20241 })
20242 .detach();
20243 self.change_selections(None, window, cx, |selections| selections.refresh());
20244 }
20245
20246 pub fn to_pixel_point(
20247 &self,
20248 source: multi_buffer::Anchor,
20249 editor_snapshot: &EditorSnapshot,
20250 window: &mut Window,
20251 ) -> Option<gpui::Point<Pixels>> {
20252 let source_point = source.to_display_point(editor_snapshot);
20253 self.display_to_pixel_point(source_point, editor_snapshot, window)
20254 }
20255
20256 pub fn display_to_pixel_point(
20257 &self,
20258 source: DisplayPoint,
20259 editor_snapshot: &EditorSnapshot,
20260 window: &mut Window,
20261 ) -> Option<gpui::Point<Pixels>> {
20262 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20263 let text_layout_details = self.text_layout_details(window);
20264 let scroll_top = text_layout_details
20265 .scroll_anchor
20266 .scroll_position(editor_snapshot)
20267 .y;
20268
20269 if source.row().as_f32() < scroll_top.floor() {
20270 return None;
20271 }
20272 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20273 let source_y = line_height * (source.row().as_f32() - scroll_top);
20274 Some(gpui::Point::new(source_x, source_y))
20275 }
20276
20277 pub fn has_visible_completions_menu(&self) -> bool {
20278 !self.edit_prediction_preview_is_active()
20279 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20280 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20281 })
20282 }
20283
20284 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20285 if self.mode.is_minimap() {
20286 return;
20287 }
20288 self.addons
20289 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20290 }
20291
20292 pub fn unregister_addon<T: Addon>(&mut self) {
20293 self.addons.remove(&std::any::TypeId::of::<T>());
20294 }
20295
20296 pub fn addon<T: Addon>(&self) -> Option<&T> {
20297 let type_id = std::any::TypeId::of::<T>();
20298 self.addons
20299 .get(&type_id)
20300 .and_then(|item| item.to_any().downcast_ref::<T>())
20301 }
20302
20303 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20304 let type_id = std::any::TypeId::of::<T>();
20305 self.addons
20306 .get_mut(&type_id)
20307 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20308 }
20309
20310 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20311 let text_layout_details = self.text_layout_details(window);
20312 let style = &text_layout_details.editor_style;
20313 let font_id = window.text_system().resolve_font(&style.text.font());
20314 let font_size = style.text.font_size.to_pixels(window.rem_size());
20315 let line_height = style.text.line_height_in_pixels(window.rem_size());
20316 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20317
20318 gpui::Size::new(em_width, line_height)
20319 }
20320
20321 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20322 self.load_diff_task.clone()
20323 }
20324
20325 fn read_metadata_from_db(
20326 &mut self,
20327 item_id: u64,
20328 workspace_id: WorkspaceId,
20329 window: &mut Window,
20330 cx: &mut Context<Editor>,
20331 ) {
20332 if self.is_singleton(cx)
20333 && !self.mode.is_minimap()
20334 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20335 {
20336 let buffer_snapshot = OnceCell::new();
20337
20338 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20339 if !folds.is_empty() {
20340 let snapshot =
20341 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20342 self.fold_ranges(
20343 folds
20344 .into_iter()
20345 .map(|(start, end)| {
20346 snapshot.clip_offset(start, Bias::Left)
20347 ..snapshot.clip_offset(end, Bias::Right)
20348 })
20349 .collect(),
20350 false,
20351 window,
20352 cx,
20353 );
20354 }
20355 }
20356
20357 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20358 if !selections.is_empty() {
20359 let snapshot =
20360 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20361 // skip adding the initial selection to selection history
20362 self.selection_history.mode = SelectionHistoryMode::Skipping;
20363 self.change_selections(None, window, cx, |s| {
20364 s.select_ranges(selections.into_iter().map(|(start, end)| {
20365 snapshot.clip_offset(start, Bias::Left)
20366 ..snapshot.clip_offset(end, Bias::Right)
20367 }));
20368 });
20369 self.selection_history.mode = SelectionHistoryMode::Normal;
20370 }
20371 };
20372 }
20373
20374 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20375 }
20376
20377 fn update_lsp_data(
20378 &mut self,
20379 for_server_id: Option<LanguageServerId>,
20380 for_buffer: Option<BufferId>,
20381 window: &mut Window,
20382 cx: &mut Context<'_, Self>,
20383 ) {
20384 self.pull_diagnostics(for_buffer, window, cx);
20385 self.refresh_colors(for_server_id, for_buffer, window, cx);
20386 }
20387}
20388
20389fn vim_enabled(cx: &App) -> bool {
20390 cx.global::<SettingsStore>()
20391 .raw_user_settings()
20392 .get("vim_mode")
20393 == Some(&serde_json::Value::Bool(true))
20394}
20395
20396fn process_completion_for_edit(
20397 completion: &Completion,
20398 intent: CompletionIntent,
20399 buffer: &Entity<Buffer>,
20400 cursor_position: &text::Anchor,
20401 cx: &mut Context<Editor>,
20402) -> CompletionEdit {
20403 let buffer = buffer.read(cx);
20404 let buffer_snapshot = buffer.snapshot();
20405 let (snippet, new_text) = if completion.is_snippet() {
20406 // Workaround for typescript language server issues so that methods don't expand within
20407 // strings and functions with type expressions. The previous point is used because the query
20408 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20409 let mut snippet_source = completion.new_text.clone();
20410 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20411 previous_point.column = previous_point.column.saturating_sub(1);
20412 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20413 if scope.prefers_label_for_snippet_in_completion() {
20414 if let Some(label) = completion.label() {
20415 if matches!(
20416 completion.kind(),
20417 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20418 ) {
20419 snippet_source = label;
20420 }
20421 }
20422 }
20423 }
20424 match Snippet::parse(&snippet_source).log_err() {
20425 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20426 None => (None, completion.new_text.clone()),
20427 }
20428 } else {
20429 (None, completion.new_text.clone())
20430 };
20431
20432 let mut range_to_replace = {
20433 let replace_range = &completion.replace_range;
20434 if let CompletionSource::Lsp {
20435 insert_range: Some(insert_range),
20436 ..
20437 } = &completion.source
20438 {
20439 debug_assert_eq!(
20440 insert_range.start, replace_range.start,
20441 "insert_range and replace_range should start at the same position"
20442 );
20443 debug_assert!(
20444 insert_range
20445 .start
20446 .cmp(&cursor_position, &buffer_snapshot)
20447 .is_le(),
20448 "insert_range should start before or at cursor position"
20449 );
20450 debug_assert!(
20451 replace_range
20452 .start
20453 .cmp(&cursor_position, &buffer_snapshot)
20454 .is_le(),
20455 "replace_range should start before or at cursor position"
20456 );
20457 debug_assert!(
20458 insert_range
20459 .end
20460 .cmp(&cursor_position, &buffer_snapshot)
20461 .is_le(),
20462 "insert_range should end before or at cursor position"
20463 );
20464
20465 let should_replace = match intent {
20466 CompletionIntent::CompleteWithInsert => false,
20467 CompletionIntent::CompleteWithReplace => true,
20468 CompletionIntent::Complete | CompletionIntent::Compose => {
20469 let insert_mode =
20470 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20471 .completions
20472 .lsp_insert_mode;
20473 match insert_mode {
20474 LspInsertMode::Insert => false,
20475 LspInsertMode::Replace => true,
20476 LspInsertMode::ReplaceSubsequence => {
20477 let mut text_to_replace = buffer.chars_for_range(
20478 buffer.anchor_before(replace_range.start)
20479 ..buffer.anchor_after(replace_range.end),
20480 );
20481 let mut current_needle = text_to_replace.next();
20482 for haystack_ch in completion.label.text.chars() {
20483 if let Some(needle_ch) = current_needle {
20484 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20485 current_needle = text_to_replace.next();
20486 }
20487 }
20488 }
20489 current_needle.is_none()
20490 }
20491 LspInsertMode::ReplaceSuffix => {
20492 if replace_range
20493 .end
20494 .cmp(&cursor_position, &buffer_snapshot)
20495 .is_gt()
20496 {
20497 let range_after_cursor = *cursor_position..replace_range.end;
20498 let text_after_cursor = buffer
20499 .text_for_range(
20500 buffer.anchor_before(range_after_cursor.start)
20501 ..buffer.anchor_after(range_after_cursor.end),
20502 )
20503 .collect::<String>()
20504 .to_ascii_lowercase();
20505 completion
20506 .label
20507 .text
20508 .to_ascii_lowercase()
20509 .ends_with(&text_after_cursor)
20510 } else {
20511 true
20512 }
20513 }
20514 }
20515 }
20516 };
20517
20518 if should_replace {
20519 replace_range.clone()
20520 } else {
20521 insert_range.clone()
20522 }
20523 } else {
20524 replace_range.clone()
20525 }
20526 };
20527
20528 if range_to_replace
20529 .end
20530 .cmp(&cursor_position, &buffer_snapshot)
20531 .is_lt()
20532 {
20533 range_to_replace.end = *cursor_position;
20534 }
20535
20536 CompletionEdit {
20537 new_text,
20538 replace_range: range_to_replace.to_offset(&buffer),
20539 snippet,
20540 }
20541}
20542
20543struct CompletionEdit {
20544 new_text: String,
20545 replace_range: Range<usize>,
20546 snippet: Option<Snippet>,
20547}
20548
20549fn insert_extra_newline_brackets(
20550 buffer: &MultiBufferSnapshot,
20551 range: Range<usize>,
20552 language: &language::LanguageScope,
20553) -> bool {
20554 let leading_whitespace_len = buffer
20555 .reversed_chars_at(range.start)
20556 .take_while(|c| c.is_whitespace() && *c != '\n')
20557 .map(|c| c.len_utf8())
20558 .sum::<usize>();
20559 let trailing_whitespace_len = buffer
20560 .chars_at(range.end)
20561 .take_while(|c| c.is_whitespace() && *c != '\n')
20562 .map(|c| c.len_utf8())
20563 .sum::<usize>();
20564 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20565
20566 language.brackets().any(|(pair, enabled)| {
20567 let pair_start = pair.start.trim_end();
20568 let pair_end = pair.end.trim_start();
20569
20570 enabled
20571 && pair.newline
20572 && buffer.contains_str_at(range.end, pair_end)
20573 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20574 })
20575}
20576
20577fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20578 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20579 [(buffer, range, _)] => (*buffer, range.clone()),
20580 _ => return false,
20581 };
20582 let pair = {
20583 let mut result: Option<BracketMatch> = None;
20584
20585 for pair in buffer
20586 .all_bracket_ranges(range.clone())
20587 .filter(move |pair| {
20588 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20589 })
20590 {
20591 let len = pair.close_range.end - pair.open_range.start;
20592
20593 if let Some(existing) = &result {
20594 let existing_len = existing.close_range.end - existing.open_range.start;
20595 if len > existing_len {
20596 continue;
20597 }
20598 }
20599
20600 result = Some(pair);
20601 }
20602
20603 result
20604 };
20605 let Some(pair) = pair else {
20606 return false;
20607 };
20608 pair.newline_only
20609 && buffer
20610 .chars_for_range(pair.open_range.end..range.start)
20611 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20612 .all(|c| c.is_whitespace() && c != '\n')
20613}
20614
20615fn update_uncommitted_diff_for_buffer(
20616 editor: Entity<Editor>,
20617 project: &Entity<Project>,
20618 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20619 buffer: Entity<MultiBuffer>,
20620 cx: &mut App,
20621) -> Task<()> {
20622 let mut tasks = Vec::new();
20623 project.update(cx, |project, cx| {
20624 for buffer in buffers {
20625 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20626 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20627 }
20628 }
20629 });
20630 cx.spawn(async move |cx| {
20631 let diffs = future::join_all(tasks).await;
20632 if editor
20633 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20634 .unwrap_or(false)
20635 {
20636 return;
20637 }
20638
20639 buffer
20640 .update(cx, |buffer, cx| {
20641 for diff in diffs.into_iter().flatten() {
20642 buffer.add_diff(diff, cx);
20643 }
20644 })
20645 .ok();
20646 })
20647}
20648
20649fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20650 let tab_size = tab_size.get() as usize;
20651 let mut width = offset;
20652
20653 for ch in text.chars() {
20654 width += if ch == '\t' {
20655 tab_size - (width % tab_size)
20656 } else {
20657 1
20658 };
20659 }
20660
20661 width - offset
20662}
20663
20664#[cfg(test)]
20665mod tests {
20666 use super::*;
20667
20668 #[test]
20669 fn test_string_size_with_expanded_tabs() {
20670 let nz = |val| NonZeroU32::new(val).unwrap();
20671 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20672 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20673 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20674 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20675 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20676 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20677 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20678 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20679 }
20680}
20681
20682/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20683struct WordBreakingTokenizer<'a> {
20684 input: &'a str,
20685}
20686
20687impl<'a> WordBreakingTokenizer<'a> {
20688 fn new(input: &'a str) -> Self {
20689 Self { input }
20690 }
20691}
20692
20693fn is_char_ideographic(ch: char) -> bool {
20694 use unicode_script::Script::*;
20695 use unicode_script::UnicodeScript;
20696 matches!(ch.script(), Han | Tangut | Yi)
20697}
20698
20699fn is_grapheme_ideographic(text: &str) -> bool {
20700 text.chars().any(is_char_ideographic)
20701}
20702
20703fn is_grapheme_whitespace(text: &str) -> bool {
20704 text.chars().any(|x| x.is_whitespace())
20705}
20706
20707fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20708 text.chars().next().map_or(false, |ch| {
20709 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20710 })
20711}
20712
20713#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20714enum WordBreakToken<'a> {
20715 Word { token: &'a str, grapheme_len: usize },
20716 InlineWhitespace { token: &'a str, grapheme_len: usize },
20717 Newline,
20718}
20719
20720impl<'a> Iterator for WordBreakingTokenizer<'a> {
20721 /// Yields a span, the count of graphemes in the token, and whether it was
20722 /// whitespace. Note that it also breaks at word boundaries.
20723 type Item = WordBreakToken<'a>;
20724
20725 fn next(&mut self) -> Option<Self::Item> {
20726 use unicode_segmentation::UnicodeSegmentation;
20727 if self.input.is_empty() {
20728 return None;
20729 }
20730
20731 let mut iter = self.input.graphemes(true).peekable();
20732 let mut offset = 0;
20733 let mut grapheme_len = 0;
20734 if let Some(first_grapheme) = iter.next() {
20735 let is_newline = first_grapheme == "\n";
20736 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20737 offset += first_grapheme.len();
20738 grapheme_len += 1;
20739 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20740 if let Some(grapheme) = iter.peek().copied() {
20741 if should_stay_with_preceding_ideograph(grapheme) {
20742 offset += grapheme.len();
20743 grapheme_len += 1;
20744 }
20745 }
20746 } else {
20747 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20748 let mut next_word_bound = words.peek().copied();
20749 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20750 next_word_bound = words.next();
20751 }
20752 while let Some(grapheme) = iter.peek().copied() {
20753 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20754 break;
20755 };
20756 if is_grapheme_whitespace(grapheme) != is_whitespace
20757 || (grapheme == "\n") != is_newline
20758 {
20759 break;
20760 };
20761 offset += grapheme.len();
20762 grapheme_len += 1;
20763 iter.next();
20764 }
20765 }
20766 let token = &self.input[..offset];
20767 self.input = &self.input[offset..];
20768 if token == "\n" {
20769 Some(WordBreakToken::Newline)
20770 } else if is_whitespace {
20771 Some(WordBreakToken::InlineWhitespace {
20772 token,
20773 grapheme_len,
20774 })
20775 } else {
20776 Some(WordBreakToken::Word {
20777 token,
20778 grapheme_len,
20779 })
20780 }
20781 } else {
20782 None
20783 }
20784 }
20785}
20786
20787#[test]
20788fn test_word_breaking_tokenizer() {
20789 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20790 ("", &[]),
20791 (" ", &[whitespace(" ", 2)]),
20792 ("Ʒ", &[word("Ʒ", 1)]),
20793 ("Ǽ", &[word("Ǽ", 1)]),
20794 ("⋑", &[word("⋑", 1)]),
20795 ("⋑⋑", &[word("⋑⋑", 2)]),
20796 (
20797 "原理,进而",
20798 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20799 ),
20800 (
20801 "hello world",
20802 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20803 ),
20804 (
20805 "hello, world",
20806 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20807 ),
20808 (
20809 " hello world",
20810 &[
20811 whitespace(" ", 2),
20812 word("hello", 5),
20813 whitespace(" ", 1),
20814 word("world", 5),
20815 ],
20816 ),
20817 (
20818 "这是什么 \n 钢笔",
20819 &[
20820 word("这", 1),
20821 word("是", 1),
20822 word("什", 1),
20823 word("么", 1),
20824 whitespace(" ", 1),
20825 newline(),
20826 whitespace(" ", 1),
20827 word("钢", 1),
20828 word("笔", 1),
20829 ],
20830 ),
20831 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20832 ];
20833
20834 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20835 WordBreakToken::Word {
20836 token,
20837 grapheme_len,
20838 }
20839 }
20840
20841 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20842 WordBreakToken::InlineWhitespace {
20843 token,
20844 grapheme_len,
20845 }
20846 }
20847
20848 fn newline() -> WordBreakToken<'static> {
20849 WordBreakToken::Newline
20850 }
20851
20852 for (input, result) in tests {
20853 assert_eq!(
20854 WordBreakingTokenizer::new(input)
20855 .collect::<Vec<_>>()
20856 .as_slice(),
20857 *result,
20858 );
20859 }
20860}
20861
20862fn wrap_with_prefix(
20863 line_prefix: String,
20864 unwrapped_text: String,
20865 wrap_column: usize,
20866 tab_size: NonZeroU32,
20867 preserve_existing_whitespace: bool,
20868) -> String {
20869 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20870 let mut wrapped_text = String::new();
20871 let mut current_line = line_prefix.clone();
20872
20873 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20874 let mut current_line_len = line_prefix_len;
20875 let mut in_whitespace = false;
20876 for token in tokenizer {
20877 let have_preceding_whitespace = in_whitespace;
20878 match token {
20879 WordBreakToken::Word {
20880 token,
20881 grapheme_len,
20882 } => {
20883 in_whitespace = false;
20884 if current_line_len + grapheme_len > wrap_column
20885 && current_line_len != line_prefix_len
20886 {
20887 wrapped_text.push_str(current_line.trim_end());
20888 wrapped_text.push('\n');
20889 current_line.truncate(line_prefix.len());
20890 current_line_len = line_prefix_len;
20891 }
20892 current_line.push_str(token);
20893 current_line_len += grapheme_len;
20894 }
20895 WordBreakToken::InlineWhitespace {
20896 mut token,
20897 mut grapheme_len,
20898 } => {
20899 in_whitespace = true;
20900 if have_preceding_whitespace && !preserve_existing_whitespace {
20901 continue;
20902 }
20903 if !preserve_existing_whitespace {
20904 token = " ";
20905 grapheme_len = 1;
20906 }
20907 if current_line_len + grapheme_len > wrap_column {
20908 wrapped_text.push_str(current_line.trim_end());
20909 wrapped_text.push('\n');
20910 current_line.truncate(line_prefix.len());
20911 current_line_len = line_prefix_len;
20912 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20913 current_line.push_str(token);
20914 current_line_len += grapheme_len;
20915 }
20916 }
20917 WordBreakToken::Newline => {
20918 in_whitespace = true;
20919 if preserve_existing_whitespace {
20920 wrapped_text.push_str(current_line.trim_end());
20921 wrapped_text.push('\n');
20922 current_line.truncate(line_prefix.len());
20923 current_line_len = line_prefix_len;
20924 } else if have_preceding_whitespace {
20925 continue;
20926 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20927 {
20928 wrapped_text.push_str(current_line.trim_end());
20929 wrapped_text.push('\n');
20930 current_line.truncate(line_prefix.len());
20931 current_line_len = line_prefix_len;
20932 } else if current_line_len != line_prefix_len {
20933 current_line.push(' ');
20934 current_line_len += 1;
20935 }
20936 }
20937 }
20938 }
20939
20940 if !current_line.is_empty() {
20941 wrapped_text.push_str(¤t_line);
20942 }
20943 wrapped_text
20944}
20945
20946#[test]
20947fn test_wrap_with_prefix() {
20948 assert_eq!(
20949 wrap_with_prefix(
20950 "# ".to_string(),
20951 "abcdefg".to_string(),
20952 4,
20953 NonZeroU32::new(4).unwrap(),
20954 false,
20955 ),
20956 "# abcdefg"
20957 );
20958 assert_eq!(
20959 wrap_with_prefix(
20960 "".to_string(),
20961 "\thello world".to_string(),
20962 8,
20963 NonZeroU32::new(4).unwrap(),
20964 false,
20965 ),
20966 "hello\nworld"
20967 );
20968 assert_eq!(
20969 wrap_with_prefix(
20970 "// ".to_string(),
20971 "xx \nyy zz aa bb cc".to_string(),
20972 12,
20973 NonZeroU32::new(4).unwrap(),
20974 false,
20975 ),
20976 "// xx yy zz\n// aa bb cc"
20977 );
20978 assert_eq!(
20979 wrap_with_prefix(
20980 String::new(),
20981 "这是什么 \n 钢笔".to_string(),
20982 3,
20983 NonZeroU32::new(4).unwrap(),
20984 false,
20985 ),
20986 "这是什\n么 钢\n笔"
20987 );
20988}
20989
20990pub trait CollaborationHub {
20991 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20992 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20993 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20994}
20995
20996impl CollaborationHub for Entity<Project> {
20997 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20998 self.read(cx).collaborators()
20999 }
21000
21001 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
21002 self.read(cx).user_store().read(cx).participant_indices()
21003 }
21004
21005 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
21006 let this = self.read(cx);
21007 let user_ids = this.collaborators().values().map(|c| c.user_id);
21008 this.user_store().read(cx).participant_names(user_ids, cx)
21009 }
21010}
21011
21012pub trait SemanticsProvider {
21013 fn hover(
21014 &self,
21015 buffer: &Entity<Buffer>,
21016 position: text::Anchor,
21017 cx: &mut App,
21018 ) -> Option<Task<Vec<project::Hover>>>;
21019
21020 fn inline_values(
21021 &self,
21022 buffer_handle: Entity<Buffer>,
21023 range: Range<text::Anchor>,
21024 cx: &mut App,
21025 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21026
21027 fn inlay_hints(
21028 &self,
21029 buffer_handle: Entity<Buffer>,
21030 range: Range<text::Anchor>,
21031 cx: &mut App,
21032 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
21033
21034 fn resolve_inlay_hint(
21035 &self,
21036 hint: InlayHint,
21037 buffer_handle: Entity<Buffer>,
21038 server_id: LanguageServerId,
21039 cx: &mut App,
21040 ) -> Option<Task<anyhow::Result<InlayHint>>>;
21041
21042 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
21043
21044 fn document_highlights(
21045 &self,
21046 buffer: &Entity<Buffer>,
21047 position: text::Anchor,
21048 cx: &mut App,
21049 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
21050
21051 fn definitions(
21052 &self,
21053 buffer: &Entity<Buffer>,
21054 position: text::Anchor,
21055 kind: GotoDefinitionKind,
21056 cx: &mut App,
21057 ) -> Option<Task<Result<Vec<LocationLink>>>>;
21058
21059 fn range_for_rename(
21060 &self,
21061 buffer: &Entity<Buffer>,
21062 position: text::Anchor,
21063 cx: &mut App,
21064 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
21065
21066 fn perform_rename(
21067 &self,
21068 buffer: &Entity<Buffer>,
21069 position: text::Anchor,
21070 new_name: String,
21071 cx: &mut App,
21072 ) -> Option<Task<Result<ProjectTransaction>>>;
21073}
21074
21075pub trait CompletionProvider {
21076 fn completions(
21077 &self,
21078 excerpt_id: ExcerptId,
21079 buffer: &Entity<Buffer>,
21080 buffer_position: text::Anchor,
21081 trigger: CompletionContext,
21082 window: &mut Window,
21083 cx: &mut Context<Editor>,
21084 ) -> Task<Result<Vec<CompletionResponse>>>;
21085
21086 fn resolve_completions(
21087 &self,
21088 _buffer: Entity<Buffer>,
21089 _completion_indices: Vec<usize>,
21090 _completions: Rc<RefCell<Box<[Completion]>>>,
21091 _cx: &mut Context<Editor>,
21092 ) -> Task<Result<bool>> {
21093 Task::ready(Ok(false))
21094 }
21095
21096 fn apply_additional_edits_for_completion(
21097 &self,
21098 _buffer: Entity<Buffer>,
21099 _completions: Rc<RefCell<Box<[Completion]>>>,
21100 _completion_index: usize,
21101 _push_to_history: bool,
21102 _cx: &mut Context<Editor>,
21103 ) -> Task<Result<Option<language::Transaction>>> {
21104 Task::ready(Ok(None))
21105 }
21106
21107 fn is_completion_trigger(
21108 &self,
21109 buffer: &Entity<Buffer>,
21110 position: language::Anchor,
21111 text: &str,
21112 trigger_in_words: bool,
21113 menu_is_open: bool,
21114 cx: &mut Context<Editor>,
21115 ) -> bool;
21116
21117 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
21118
21119 fn sort_completions(&self) -> bool {
21120 true
21121 }
21122
21123 fn filter_completions(&self) -> bool {
21124 true
21125 }
21126}
21127
21128pub trait CodeActionProvider {
21129 fn id(&self) -> Arc<str>;
21130
21131 fn code_actions(
21132 &self,
21133 buffer: &Entity<Buffer>,
21134 range: Range<text::Anchor>,
21135 window: &mut Window,
21136 cx: &mut App,
21137 ) -> Task<Result<Vec<CodeAction>>>;
21138
21139 fn apply_code_action(
21140 &self,
21141 buffer_handle: Entity<Buffer>,
21142 action: CodeAction,
21143 excerpt_id: ExcerptId,
21144 push_to_history: bool,
21145 window: &mut Window,
21146 cx: &mut App,
21147 ) -> Task<Result<ProjectTransaction>>;
21148}
21149
21150impl CodeActionProvider for Entity<Project> {
21151 fn id(&self) -> Arc<str> {
21152 "project".into()
21153 }
21154
21155 fn code_actions(
21156 &self,
21157 buffer: &Entity<Buffer>,
21158 range: Range<text::Anchor>,
21159 _window: &mut Window,
21160 cx: &mut App,
21161 ) -> Task<Result<Vec<CodeAction>>> {
21162 self.update(cx, |project, cx| {
21163 let code_lens = project.code_lens(buffer, range.clone(), cx);
21164 let code_actions = project.code_actions(buffer, range, None, cx);
21165 cx.background_spawn(async move {
21166 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21167 Ok(code_lens
21168 .context("code lens fetch")?
21169 .into_iter()
21170 .chain(code_actions.context("code action fetch")?)
21171 .collect())
21172 })
21173 })
21174 }
21175
21176 fn apply_code_action(
21177 &self,
21178 buffer_handle: Entity<Buffer>,
21179 action: CodeAction,
21180 _excerpt_id: ExcerptId,
21181 push_to_history: bool,
21182 _window: &mut Window,
21183 cx: &mut App,
21184 ) -> Task<Result<ProjectTransaction>> {
21185 self.update(cx, |project, cx| {
21186 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21187 })
21188 }
21189}
21190
21191fn snippet_completions(
21192 project: &Project,
21193 buffer: &Entity<Buffer>,
21194 buffer_position: text::Anchor,
21195 cx: &mut App,
21196) -> Task<Result<CompletionResponse>> {
21197 let languages = buffer.read(cx).languages_at(buffer_position);
21198 let snippet_store = project.snippets().read(cx);
21199
21200 let scopes: Vec<_> = languages
21201 .iter()
21202 .filter_map(|language| {
21203 let language_name = language.lsp_id();
21204 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21205
21206 if snippets.is_empty() {
21207 None
21208 } else {
21209 Some((language.default_scope(), snippets))
21210 }
21211 })
21212 .collect();
21213
21214 if scopes.is_empty() {
21215 return Task::ready(Ok(CompletionResponse {
21216 completions: vec![],
21217 is_incomplete: false,
21218 }));
21219 }
21220
21221 let snapshot = buffer.read(cx).text_snapshot();
21222 let chars: String = snapshot
21223 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21224 .collect();
21225 let executor = cx.background_executor().clone();
21226
21227 cx.background_spawn(async move {
21228 let mut is_incomplete = false;
21229 let mut completions: Vec<Completion> = Vec::new();
21230 for (scope, snippets) in scopes.into_iter() {
21231 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21232 let mut last_word = chars
21233 .chars()
21234 .take_while(|c| classifier.is_word(*c))
21235 .collect::<String>();
21236 last_word = last_word.chars().rev().collect();
21237
21238 if last_word.is_empty() {
21239 return Ok(CompletionResponse {
21240 completions: vec![],
21241 is_incomplete: true,
21242 });
21243 }
21244
21245 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21246 let to_lsp = |point: &text::Anchor| {
21247 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21248 point_to_lsp(end)
21249 };
21250 let lsp_end = to_lsp(&buffer_position);
21251
21252 let candidates = snippets
21253 .iter()
21254 .enumerate()
21255 .flat_map(|(ix, snippet)| {
21256 snippet
21257 .prefix
21258 .iter()
21259 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21260 })
21261 .collect::<Vec<StringMatchCandidate>>();
21262
21263 const MAX_RESULTS: usize = 100;
21264 let mut matches = fuzzy::match_strings(
21265 &candidates,
21266 &last_word,
21267 last_word.chars().any(|c| c.is_uppercase()),
21268 true,
21269 MAX_RESULTS,
21270 &Default::default(),
21271 executor.clone(),
21272 )
21273 .await;
21274
21275 if matches.len() >= MAX_RESULTS {
21276 is_incomplete = true;
21277 }
21278
21279 // Remove all candidates where the query's start does not match the start of any word in the candidate
21280 if let Some(query_start) = last_word.chars().next() {
21281 matches.retain(|string_match| {
21282 split_words(&string_match.string).any(|word| {
21283 // Check that the first codepoint of the word as lowercase matches the first
21284 // codepoint of the query as lowercase
21285 word.chars()
21286 .flat_map(|codepoint| codepoint.to_lowercase())
21287 .zip(query_start.to_lowercase())
21288 .all(|(word_cp, query_cp)| word_cp == query_cp)
21289 })
21290 });
21291 }
21292
21293 let matched_strings = matches
21294 .into_iter()
21295 .map(|m| m.string)
21296 .collect::<HashSet<_>>();
21297
21298 completions.extend(snippets.iter().filter_map(|snippet| {
21299 let matching_prefix = snippet
21300 .prefix
21301 .iter()
21302 .find(|prefix| matched_strings.contains(*prefix))?;
21303 let start = as_offset - last_word.len();
21304 let start = snapshot.anchor_before(start);
21305 let range = start..buffer_position;
21306 let lsp_start = to_lsp(&start);
21307 let lsp_range = lsp::Range {
21308 start: lsp_start,
21309 end: lsp_end,
21310 };
21311 Some(Completion {
21312 replace_range: range,
21313 new_text: snippet.body.clone(),
21314 source: CompletionSource::Lsp {
21315 insert_range: None,
21316 server_id: LanguageServerId(usize::MAX),
21317 resolved: true,
21318 lsp_completion: Box::new(lsp::CompletionItem {
21319 label: snippet.prefix.first().unwrap().clone(),
21320 kind: Some(CompletionItemKind::SNIPPET),
21321 label_details: snippet.description.as_ref().map(|description| {
21322 lsp::CompletionItemLabelDetails {
21323 detail: Some(description.clone()),
21324 description: None,
21325 }
21326 }),
21327 insert_text_format: Some(InsertTextFormat::SNIPPET),
21328 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21329 lsp::InsertReplaceEdit {
21330 new_text: snippet.body.clone(),
21331 insert: lsp_range,
21332 replace: lsp_range,
21333 },
21334 )),
21335 filter_text: Some(snippet.body.clone()),
21336 sort_text: Some(char::MAX.to_string()),
21337 ..lsp::CompletionItem::default()
21338 }),
21339 lsp_defaults: None,
21340 },
21341 label: CodeLabel {
21342 text: matching_prefix.clone(),
21343 runs: Vec::new(),
21344 filter_range: 0..matching_prefix.len(),
21345 },
21346 icon_path: None,
21347 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21348 single_line: snippet.name.clone().into(),
21349 plain_text: snippet
21350 .description
21351 .clone()
21352 .map(|description| description.into()),
21353 }),
21354 insert_text_mode: None,
21355 confirm: None,
21356 })
21357 }))
21358 }
21359
21360 Ok(CompletionResponse {
21361 completions,
21362 is_incomplete,
21363 })
21364 })
21365}
21366
21367impl CompletionProvider for Entity<Project> {
21368 fn completions(
21369 &self,
21370 _excerpt_id: ExcerptId,
21371 buffer: &Entity<Buffer>,
21372 buffer_position: text::Anchor,
21373 options: CompletionContext,
21374 _window: &mut Window,
21375 cx: &mut Context<Editor>,
21376 ) -> Task<Result<Vec<CompletionResponse>>> {
21377 self.update(cx, |project, cx| {
21378 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21379 let project_completions = project.completions(buffer, buffer_position, options, cx);
21380 cx.background_spawn(async move {
21381 let mut responses = project_completions.await?;
21382 let snippets = snippets.await?;
21383 if !snippets.completions.is_empty() {
21384 responses.push(snippets);
21385 }
21386 Ok(responses)
21387 })
21388 })
21389 }
21390
21391 fn resolve_completions(
21392 &self,
21393 buffer: Entity<Buffer>,
21394 completion_indices: Vec<usize>,
21395 completions: Rc<RefCell<Box<[Completion]>>>,
21396 cx: &mut Context<Editor>,
21397 ) -> Task<Result<bool>> {
21398 self.update(cx, |project, cx| {
21399 project.lsp_store().update(cx, |lsp_store, cx| {
21400 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21401 })
21402 })
21403 }
21404
21405 fn apply_additional_edits_for_completion(
21406 &self,
21407 buffer: Entity<Buffer>,
21408 completions: Rc<RefCell<Box<[Completion]>>>,
21409 completion_index: usize,
21410 push_to_history: bool,
21411 cx: &mut Context<Editor>,
21412 ) -> Task<Result<Option<language::Transaction>>> {
21413 self.update(cx, |project, cx| {
21414 project.lsp_store().update(cx, |lsp_store, cx| {
21415 lsp_store.apply_additional_edits_for_completion(
21416 buffer,
21417 completions,
21418 completion_index,
21419 push_to_history,
21420 cx,
21421 )
21422 })
21423 })
21424 }
21425
21426 fn is_completion_trigger(
21427 &self,
21428 buffer: &Entity<Buffer>,
21429 position: language::Anchor,
21430 text: &str,
21431 trigger_in_words: bool,
21432 menu_is_open: bool,
21433 cx: &mut Context<Editor>,
21434 ) -> bool {
21435 let mut chars = text.chars();
21436 let char = if let Some(char) = chars.next() {
21437 char
21438 } else {
21439 return false;
21440 };
21441 if chars.next().is_some() {
21442 return false;
21443 }
21444
21445 let buffer = buffer.read(cx);
21446 let snapshot = buffer.snapshot();
21447 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21448 return false;
21449 }
21450 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21451 if trigger_in_words && classifier.is_word(char) {
21452 return true;
21453 }
21454
21455 buffer.completion_triggers().contains(text)
21456 }
21457}
21458
21459impl SemanticsProvider for Entity<Project> {
21460 fn hover(
21461 &self,
21462 buffer: &Entity<Buffer>,
21463 position: text::Anchor,
21464 cx: &mut App,
21465 ) -> Option<Task<Vec<project::Hover>>> {
21466 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21467 }
21468
21469 fn document_highlights(
21470 &self,
21471 buffer: &Entity<Buffer>,
21472 position: text::Anchor,
21473 cx: &mut App,
21474 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21475 Some(self.update(cx, |project, cx| {
21476 project.document_highlights(buffer, position, cx)
21477 }))
21478 }
21479
21480 fn definitions(
21481 &self,
21482 buffer: &Entity<Buffer>,
21483 position: text::Anchor,
21484 kind: GotoDefinitionKind,
21485 cx: &mut App,
21486 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21487 Some(self.update(cx, |project, cx| match kind {
21488 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21489 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21490 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21491 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21492 }))
21493 }
21494
21495 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21496 // TODO: make this work for remote projects
21497 self.update(cx, |project, cx| {
21498 if project
21499 .active_debug_session(cx)
21500 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21501 {
21502 return true;
21503 }
21504
21505 buffer.update(cx, |buffer, cx| {
21506 project.any_language_server_supports_inlay_hints(buffer, cx)
21507 })
21508 })
21509 }
21510
21511 fn inline_values(
21512 &self,
21513 buffer_handle: Entity<Buffer>,
21514
21515 range: Range<text::Anchor>,
21516 cx: &mut App,
21517 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21518 self.update(cx, |project, cx| {
21519 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21520
21521 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21522 })
21523 }
21524
21525 fn inlay_hints(
21526 &self,
21527 buffer_handle: Entity<Buffer>,
21528 range: Range<text::Anchor>,
21529 cx: &mut App,
21530 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21531 Some(self.update(cx, |project, cx| {
21532 project.inlay_hints(buffer_handle, range, cx)
21533 }))
21534 }
21535
21536 fn resolve_inlay_hint(
21537 &self,
21538 hint: InlayHint,
21539 buffer_handle: Entity<Buffer>,
21540 server_id: LanguageServerId,
21541 cx: &mut App,
21542 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21543 Some(self.update(cx, |project, cx| {
21544 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21545 }))
21546 }
21547
21548 fn range_for_rename(
21549 &self,
21550 buffer: &Entity<Buffer>,
21551 position: text::Anchor,
21552 cx: &mut App,
21553 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21554 Some(self.update(cx, |project, cx| {
21555 let buffer = buffer.clone();
21556 let task = project.prepare_rename(buffer.clone(), position, cx);
21557 cx.spawn(async move |_, cx| {
21558 Ok(match task.await? {
21559 PrepareRenameResponse::Success(range) => Some(range),
21560 PrepareRenameResponse::InvalidPosition => None,
21561 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21562 // Fallback on using TreeSitter info to determine identifier range
21563 buffer.read_with(cx, |buffer, _| {
21564 let snapshot = buffer.snapshot();
21565 let (range, kind) = snapshot.surrounding_word(position);
21566 if kind != Some(CharKind::Word) {
21567 return None;
21568 }
21569 Some(
21570 snapshot.anchor_before(range.start)
21571 ..snapshot.anchor_after(range.end),
21572 )
21573 })?
21574 }
21575 })
21576 })
21577 }))
21578 }
21579
21580 fn perform_rename(
21581 &self,
21582 buffer: &Entity<Buffer>,
21583 position: text::Anchor,
21584 new_name: String,
21585 cx: &mut App,
21586 ) -> Option<Task<Result<ProjectTransaction>>> {
21587 Some(self.update(cx, |project, cx| {
21588 project.perform_rename(buffer.clone(), position, new_name, cx)
21589 }))
21590 }
21591}
21592
21593fn inlay_hint_settings(
21594 location: Anchor,
21595 snapshot: &MultiBufferSnapshot,
21596 cx: &mut Context<Editor>,
21597) -> InlayHintSettings {
21598 let file = snapshot.file_at(location);
21599 let language = snapshot.language_at(location).map(|l| l.name());
21600 language_settings(language, file, cx).inlay_hints
21601}
21602
21603fn consume_contiguous_rows(
21604 contiguous_row_selections: &mut Vec<Selection<Point>>,
21605 selection: &Selection<Point>,
21606 display_map: &DisplaySnapshot,
21607 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21608) -> (MultiBufferRow, MultiBufferRow) {
21609 contiguous_row_selections.push(selection.clone());
21610 let start_row = MultiBufferRow(selection.start.row);
21611 let mut end_row = ending_row(selection, display_map);
21612
21613 while let Some(next_selection) = selections.peek() {
21614 if next_selection.start.row <= end_row.0 {
21615 end_row = ending_row(next_selection, display_map);
21616 contiguous_row_selections.push(selections.next().unwrap().clone());
21617 } else {
21618 break;
21619 }
21620 }
21621 (start_row, end_row)
21622}
21623
21624fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21625 if next_selection.end.column > 0 || next_selection.is_empty() {
21626 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21627 } else {
21628 MultiBufferRow(next_selection.end.row)
21629 }
21630}
21631
21632impl EditorSnapshot {
21633 pub fn remote_selections_in_range<'a>(
21634 &'a self,
21635 range: &'a Range<Anchor>,
21636 collaboration_hub: &dyn CollaborationHub,
21637 cx: &'a App,
21638 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21639 let participant_names = collaboration_hub.user_names(cx);
21640 let participant_indices = collaboration_hub.user_participant_indices(cx);
21641 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21642 let collaborators_by_replica_id = collaborators_by_peer_id
21643 .values()
21644 .map(|collaborator| (collaborator.replica_id, collaborator))
21645 .collect::<HashMap<_, _>>();
21646 self.buffer_snapshot
21647 .selections_in_range(range, false)
21648 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21649 if replica_id == AGENT_REPLICA_ID {
21650 Some(RemoteSelection {
21651 replica_id,
21652 selection,
21653 cursor_shape,
21654 line_mode,
21655 collaborator_id: CollaboratorId::Agent,
21656 user_name: Some("Agent".into()),
21657 color: cx.theme().players().agent(),
21658 })
21659 } else {
21660 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21661 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21662 let user_name = participant_names.get(&collaborator.user_id).cloned();
21663 Some(RemoteSelection {
21664 replica_id,
21665 selection,
21666 cursor_shape,
21667 line_mode,
21668 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21669 user_name,
21670 color: if let Some(index) = participant_index {
21671 cx.theme().players().color_for_participant(index.0)
21672 } else {
21673 cx.theme().players().absent()
21674 },
21675 })
21676 }
21677 })
21678 }
21679
21680 pub fn hunks_for_ranges(
21681 &self,
21682 ranges: impl IntoIterator<Item = Range<Point>>,
21683 ) -> Vec<MultiBufferDiffHunk> {
21684 let mut hunks = Vec::new();
21685 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21686 HashMap::default();
21687 for query_range in ranges {
21688 let query_rows =
21689 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21690 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21691 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21692 ) {
21693 // Include deleted hunks that are adjacent to the query range, because
21694 // otherwise they would be missed.
21695 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21696 if hunk.status().is_deleted() {
21697 intersects_range |= hunk.row_range.start == query_rows.end;
21698 intersects_range |= hunk.row_range.end == query_rows.start;
21699 }
21700 if intersects_range {
21701 if !processed_buffer_rows
21702 .entry(hunk.buffer_id)
21703 .or_default()
21704 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21705 {
21706 continue;
21707 }
21708 hunks.push(hunk);
21709 }
21710 }
21711 }
21712
21713 hunks
21714 }
21715
21716 fn display_diff_hunks_for_rows<'a>(
21717 &'a self,
21718 display_rows: Range<DisplayRow>,
21719 folded_buffers: &'a HashSet<BufferId>,
21720 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21721 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21722 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21723
21724 self.buffer_snapshot
21725 .diff_hunks_in_range(buffer_start..buffer_end)
21726 .filter_map(|hunk| {
21727 if folded_buffers.contains(&hunk.buffer_id) {
21728 return None;
21729 }
21730
21731 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21732 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21733
21734 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21735 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21736
21737 let display_hunk = if hunk_display_start.column() != 0 {
21738 DisplayDiffHunk::Folded {
21739 display_row: hunk_display_start.row(),
21740 }
21741 } else {
21742 let mut end_row = hunk_display_end.row();
21743 if hunk_display_end.column() > 0 {
21744 end_row.0 += 1;
21745 }
21746 let is_created_file = hunk.is_created_file();
21747 DisplayDiffHunk::Unfolded {
21748 status: hunk.status(),
21749 diff_base_byte_range: hunk.diff_base_byte_range,
21750 display_row_range: hunk_display_start.row()..end_row,
21751 multi_buffer_range: Anchor::range_in_buffer(
21752 hunk.excerpt_id,
21753 hunk.buffer_id,
21754 hunk.buffer_range,
21755 ),
21756 is_created_file,
21757 }
21758 };
21759
21760 Some(display_hunk)
21761 })
21762 }
21763
21764 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21765 self.display_snapshot.buffer_snapshot.language_at(position)
21766 }
21767
21768 pub fn is_focused(&self) -> bool {
21769 self.is_focused
21770 }
21771
21772 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21773 self.placeholder_text.as_ref()
21774 }
21775
21776 pub fn scroll_position(&self) -> gpui::Point<f32> {
21777 self.scroll_anchor.scroll_position(&self.display_snapshot)
21778 }
21779
21780 fn gutter_dimensions(
21781 &self,
21782 font_id: FontId,
21783 font_size: Pixels,
21784 max_line_number_width: Pixels,
21785 cx: &App,
21786 ) -> Option<GutterDimensions> {
21787 if !self.show_gutter {
21788 return None;
21789 }
21790
21791 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21792 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21793
21794 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21795 matches!(
21796 ProjectSettings::get_global(cx).git.git_gutter,
21797 Some(GitGutterSetting::TrackedFiles)
21798 )
21799 });
21800 let gutter_settings = EditorSettings::get_global(cx).gutter;
21801 let show_line_numbers = self
21802 .show_line_numbers
21803 .unwrap_or(gutter_settings.line_numbers);
21804 let line_gutter_width = if show_line_numbers {
21805 // Avoid flicker-like gutter resizes when the line number gains another digit by
21806 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21807 let min_width_for_number_on_gutter =
21808 ch_advance * gutter_settings.min_line_number_digits as f32;
21809 max_line_number_width.max(min_width_for_number_on_gutter)
21810 } else {
21811 0.0.into()
21812 };
21813
21814 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21815 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21816
21817 let git_blame_entries_width =
21818 self.git_blame_gutter_max_author_length
21819 .map(|max_author_length| {
21820 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21821 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21822
21823 /// The number of characters to dedicate to gaps and margins.
21824 const SPACING_WIDTH: usize = 4;
21825
21826 let max_char_count = max_author_length.min(renderer.max_author_length())
21827 + ::git::SHORT_SHA_LENGTH
21828 + MAX_RELATIVE_TIMESTAMP.len()
21829 + SPACING_WIDTH;
21830
21831 ch_advance * max_char_count
21832 });
21833
21834 let is_singleton = self.buffer_snapshot.is_singleton();
21835
21836 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21837 left_padding += if !is_singleton {
21838 ch_width * 4.0
21839 } else if show_runnables || show_breakpoints {
21840 ch_width * 3.0
21841 } else if show_git_gutter && show_line_numbers {
21842 ch_width * 2.0
21843 } else if show_git_gutter || show_line_numbers {
21844 ch_width
21845 } else {
21846 px(0.)
21847 };
21848
21849 let shows_folds = is_singleton && gutter_settings.folds;
21850
21851 let right_padding = if shows_folds && show_line_numbers {
21852 ch_width * 4.0
21853 } else if shows_folds || (!is_singleton && show_line_numbers) {
21854 ch_width * 3.0
21855 } else if show_line_numbers {
21856 ch_width
21857 } else {
21858 px(0.)
21859 };
21860
21861 Some(GutterDimensions {
21862 left_padding,
21863 right_padding,
21864 width: line_gutter_width + left_padding + right_padding,
21865 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21866 git_blame_entries_width,
21867 })
21868 }
21869
21870 pub fn render_crease_toggle(
21871 &self,
21872 buffer_row: MultiBufferRow,
21873 row_contains_cursor: bool,
21874 editor: Entity<Editor>,
21875 window: &mut Window,
21876 cx: &mut App,
21877 ) -> Option<AnyElement> {
21878 let folded = self.is_line_folded(buffer_row);
21879 let mut is_foldable = false;
21880
21881 if let Some(crease) = self
21882 .crease_snapshot
21883 .query_row(buffer_row, &self.buffer_snapshot)
21884 {
21885 is_foldable = true;
21886 match crease {
21887 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21888 if let Some(render_toggle) = render_toggle {
21889 let toggle_callback =
21890 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21891 if folded {
21892 editor.update(cx, |editor, cx| {
21893 editor.fold_at(buffer_row, window, cx)
21894 });
21895 } else {
21896 editor.update(cx, |editor, cx| {
21897 editor.unfold_at(buffer_row, window, cx)
21898 });
21899 }
21900 });
21901 return Some((render_toggle)(
21902 buffer_row,
21903 folded,
21904 toggle_callback,
21905 window,
21906 cx,
21907 ));
21908 }
21909 }
21910 }
21911 }
21912
21913 is_foldable |= self.starts_indent(buffer_row);
21914
21915 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21916 Some(
21917 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21918 .toggle_state(folded)
21919 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21920 if folded {
21921 this.unfold_at(buffer_row, window, cx);
21922 } else {
21923 this.fold_at(buffer_row, window, cx);
21924 }
21925 }))
21926 .into_any_element(),
21927 )
21928 } else {
21929 None
21930 }
21931 }
21932
21933 pub fn render_crease_trailer(
21934 &self,
21935 buffer_row: MultiBufferRow,
21936 window: &mut Window,
21937 cx: &mut App,
21938 ) -> Option<AnyElement> {
21939 let folded = self.is_line_folded(buffer_row);
21940 if let Crease::Inline { render_trailer, .. } = self
21941 .crease_snapshot
21942 .query_row(buffer_row, &self.buffer_snapshot)?
21943 {
21944 let render_trailer = render_trailer.as_ref()?;
21945 Some(render_trailer(buffer_row, folded, window, cx))
21946 } else {
21947 None
21948 }
21949 }
21950}
21951
21952impl Deref for EditorSnapshot {
21953 type Target = DisplaySnapshot;
21954
21955 fn deref(&self) -> &Self::Target {
21956 &self.display_snapshot
21957 }
21958}
21959
21960#[derive(Clone, Debug, PartialEq, Eq)]
21961pub enum EditorEvent {
21962 InputIgnored {
21963 text: Arc<str>,
21964 },
21965 InputHandled {
21966 utf16_range_to_replace: Option<Range<isize>>,
21967 text: Arc<str>,
21968 },
21969 ExcerptsAdded {
21970 buffer: Entity<Buffer>,
21971 predecessor: ExcerptId,
21972 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21973 },
21974 ExcerptsRemoved {
21975 ids: Vec<ExcerptId>,
21976 removed_buffer_ids: Vec<BufferId>,
21977 },
21978 BufferFoldToggled {
21979 ids: Vec<ExcerptId>,
21980 folded: bool,
21981 },
21982 ExcerptsEdited {
21983 ids: Vec<ExcerptId>,
21984 },
21985 ExcerptsExpanded {
21986 ids: Vec<ExcerptId>,
21987 },
21988 BufferEdited,
21989 Edited {
21990 transaction_id: clock::Lamport,
21991 },
21992 Reparsed(BufferId),
21993 Focused,
21994 FocusedIn,
21995 Blurred,
21996 DirtyChanged,
21997 Saved,
21998 TitleChanged,
21999 DiffBaseChanged,
22000 SelectionsChanged {
22001 local: bool,
22002 },
22003 ScrollPositionChanged {
22004 local: bool,
22005 autoscroll: bool,
22006 },
22007 Closed,
22008 TransactionUndone {
22009 transaction_id: clock::Lamport,
22010 },
22011 TransactionBegun {
22012 transaction_id: clock::Lamport,
22013 },
22014 Reloaded,
22015 CursorShapeChanged,
22016 PushedToNavHistory {
22017 anchor: Anchor,
22018 is_deactivate: bool,
22019 },
22020}
22021
22022impl EventEmitter<EditorEvent> for Editor {}
22023
22024impl Focusable for Editor {
22025 fn focus_handle(&self, _cx: &App) -> FocusHandle {
22026 self.focus_handle.clone()
22027 }
22028}
22029
22030impl Render for Editor {
22031 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22032 let settings = ThemeSettings::get_global(cx);
22033
22034 let mut text_style = match self.mode {
22035 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
22036 color: cx.theme().colors().editor_foreground,
22037 font_family: settings.ui_font.family.clone(),
22038 font_features: settings.ui_font.features.clone(),
22039 font_fallbacks: settings.ui_font.fallbacks.clone(),
22040 font_size: rems(0.875).into(),
22041 font_weight: settings.ui_font.weight,
22042 line_height: relative(settings.buffer_line_height.value()),
22043 ..Default::default()
22044 },
22045 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
22046 color: cx.theme().colors().editor_foreground,
22047 font_family: settings.buffer_font.family.clone(),
22048 font_features: settings.buffer_font.features.clone(),
22049 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22050 font_size: settings.buffer_font_size(cx).into(),
22051 font_weight: settings.buffer_font.weight,
22052 line_height: relative(settings.buffer_line_height.value()),
22053 ..Default::default()
22054 },
22055 };
22056 if let Some(text_style_refinement) = &self.text_style_refinement {
22057 text_style.refine(text_style_refinement)
22058 }
22059
22060 let background = match self.mode {
22061 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22062 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22063 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22064 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22065 };
22066
22067 EditorElement::new(
22068 &cx.entity(),
22069 EditorStyle {
22070 background,
22071 local_player: cx.theme().players().local(),
22072 text: text_style,
22073 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22074 syntax: cx.theme().syntax().clone(),
22075 status: cx.theme().status().clone(),
22076 inlay_hints_style: make_inlay_hints_style(cx),
22077 inline_completion_styles: make_suggestion_styles(cx),
22078 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22079 show_underlines: !self.mode.is_minimap(),
22080 },
22081 )
22082 }
22083}
22084
22085impl EntityInputHandler for Editor {
22086 fn text_for_range(
22087 &mut self,
22088 range_utf16: Range<usize>,
22089 adjusted_range: &mut Option<Range<usize>>,
22090 _: &mut Window,
22091 cx: &mut Context<Self>,
22092 ) -> Option<String> {
22093 let snapshot = self.buffer.read(cx).read(cx);
22094 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22095 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22096 if (start.0..end.0) != range_utf16 {
22097 adjusted_range.replace(start.0..end.0);
22098 }
22099 Some(snapshot.text_for_range(start..end).collect())
22100 }
22101
22102 fn selected_text_range(
22103 &mut self,
22104 ignore_disabled_input: bool,
22105 _: &mut Window,
22106 cx: &mut Context<Self>,
22107 ) -> Option<UTF16Selection> {
22108 // Prevent the IME menu from appearing when holding down an alphabetic key
22109 // while input is disabled.
22110 if !ignore_disabled_input && !self.input_enabled {
22111 return None;
22112 }
22113
22114 let selection = self.selections.newest::<OffsetUtf16>(cx);
22115 let range = selection.range();
22116
22117 Some(UTF16Selection {
22118 range: range.start.0..range.end.0,
22119 reversed: selection.reversed,
22120 })
22121 }
22122
22123 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22124 let snapshot = self.buffer.read(cx).read(cx);
22125 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
22126 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22127 }
22128
22129 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22130 self.clear_highlights::<InputComposition>(cx);
22131 self.ime_transaction.take();
22132 }
22133
22134 fn replace_text_in_range(
22135 &mut self,
22136 range_utf16: Option<Range<usize>>,
22137 text: &str,
22138 window: &mut Window,
22139 cx: &mut Context<Self>,
22140 ) {
22141 if !self.input_enabled {
22142 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22143 return;
22144 }
22145
22146 self.transact(window, cx, |this, window, cx| {
22147 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22148 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22149 Some(this.selection_replacement_ranges(range_utf16, cx))
22150 } else {
22151 this.marked_text_ranges(cx)
22152 };
22153
22154 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22155 let newest_selection_id = this.selections.newest_anchor().id;
22156 this.selections
22157 .all::<OffsetUtf16>(cx)
22158 .iter()
22159 .zip(ranges_to_replace.iter())
22160 .find_map(|(selection, range)| {
22161 if selection.id == newest_selection_id {
22162 Some(
22163 (range.start.0 as isize - selection.head().0 as isize)
22164 ..(range.end.0 as isize - selection.head().0 as isize),
22165 )
22166 } else {
22167 None
22168 }
22169 })
22170 });
22171
22172 cx.emit(EditorEvent::InputHandled {
22173 utf16_range_to_replace: range_to_replace,
22174 text: text.into(),
22175 });
22176
22177 if let Some(new_selected_ranges) = new_selected_ranges {
22178 this.change_selections(None, window, cx, |selections| {
22179 selections.select_ranges(new_selected_ranges)
22180 });
22181 this.backspace(&Default::default(), window, cx);
22182 }
22183
22184 this.handle_input(text, window, cx);
22185 });
22186
22187 if let Some(transaction) = self.ime_transaction {
22188 self.buffer.update(cx, |buffer, cx| {
22189 buffer.group_until_transaction(transaction, cx);
22190 });
22191 }
22192
22193 self.unmark_text(window, cx);
22194 }
22195
22196 fn replace_and_mark_text_in_range(
22197 &mut self,
22198 range_utf16: Option<Range<usize>>,
22199 text: &str,
22200 new_selected_range_utf16: Option<Range<usize>>,
22201 window: &mut Window,
22202 cx: &mut Context<Self>,
22203 ) {
22204 if !self.input_enabled {
22205 return;
22206 }
22207
22208 let transaction = self.transact(window, cx, |this, window, cx| {
22209 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22210 let snapshot = this.buffer.read(cx).read(cx);
22211 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22212 for marked_range in &mut marked_ranges {
22213 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22214 marked_range.start.0 += relative_range_utf16.start;
22215 marked_range.start =
22216 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22217 marked_range.end =
22218 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22219 }
22220 }
22221 Some(marked_ranges)
22222 } else if let Some(range_utf16) = range_utf16 {
22223 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22224 Some(this.selection_replacement_ranges(range_utf16, cx))
22225 } else {
22226 None
22227 };
22228
22229 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22230 let newest_selection_id = this.selections.newest_anchor().id;
22231 this.selections
22232 .all::<OffsetUtf16>(cx)
22233 .iter()
22234 .zip(ranges_to_replace.iter())
22235 .find_map(|(selection, range)| {
22236 if selection.id == newest_selection_id {
22237 Some(
22238 (range.start.0 as isize - selection.head().0 as isize)
22239 ..(range.end.0 as isize - selection.head().0 as isize),
22240 )
22241 } else {
22242 None
22243 }
22244 })
22245 });
22246
22247 cx.emit(EditorEvent::InputHandled {
22248 utf16_range_to_replace: range_to_replace,
22249 text: text.into(),
22250 });
22251
22252 if let Some(ranges) = ranges_to_replace {
22253 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22254 }
22255
22256 let marked_ranges = {
22257 let snapshot = this.buffer.read(cx).read(cx);
22258 this.selections
22259 .disjoint_anchors()
22260 .iter()
22261 .map(|selection| {
22262 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22263 })
22264 .collect::<Vec<_>>()
22265 };
22266
22267 if text.is_empty() {
22268 this.unmark_text(window, cx);
22269 } else {
22270 this.highlight_text::<InputComposition>(
22271 marked_ranges.clone(),
22272 HighlightStyle {
22273 underline: Some(UnderlineStyle {
22274 thickness: px(1.),
22275 color: None,
22276 wavy: false,
22277 }),
22278 ..Default::default()
22279 },
22280 cx,
22281 );
22282 }
22283
22284 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22285 let use_autoclose = this.use_autoclose;
22286 let use_auto_surround = this.use_auto_surround;
22287 this.set_use_autoclose(false);
22288 this.set_use_auto_surround(false);
22289 this.handle_input(text, window, cx);
22290 this.set_use_autoclose(use_autoclose);
22291 this.set_use_auto_surround(use_auto_surround);
22292
22293 if let Some(new_selected_range) = new_selected_range_utf16 {
22294 let snapshot = this.buffer.read(cx).read(cx);
22295 let new_selected_ranges = marked_ranges
22296 .into_iter()
22297 .map(|marked_range| {
22298 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22299 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22300 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22301 snapshot.clip_offset_utf16(new_start, Bias::Left)
22302 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22303 })
22304 .collect::<Vec<_>>();
22305
22306 drop(snapshot);
22307 this.change_selections(None, window, cx, |selections| {
22308 selections.select_ranges(new_selected_ranges)
22309 });
22310 }
22311 });
22312
22313 self.ime_transaction = self.ime_transaction.or(transaction);
22314 if let Some(transaction) = self.ime_transaction {
22315 self.buffer.update(cx, |buffer, cx| {
22316 buffer.group_until_transaction(transaction, cx);
22317 });
22318 }
22319
22320 if self.text_highlights::<InputComposition>(cx).is_none() {
22321 self.ime_transaction.take();
22322 }
22323 }
22324
22325 fn bounds_for_range(
22326 &mut self,
22327 range_utf16: Range<usize>,
22328 element_bounds: gpui::Bounds<Pixels>,
22329 window: &mut Window,
22330 cx: &mut Context<Self>,
22331 ) -> Option<gpui::Bounds<Pixels>> {
22332 let text_layout_details = self.text_layout_details(window);
22333 let gpui::Size {
22334 width: em_width,
22335 height: line_height,
22336 } = self.character_size(window);
22337
22338 let snapshot = self.snapshot(window, cx);
22339 let scroll_position = snapshot.scroll_position();
22340 let scroll_left = scroll_position.x * em_width;
22341
22342 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22343 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22344 + self.gutter_dimensions.width
22345 + self.gutter_dimensions.margin;
22346 let y = line_height * (start.row().as_f32() - scroll_position.y);
22347
22348 Some(Bounds {
22349 origin: element_bounds.origin + point(x, y),
22350 size: size(em_width, line_height),
22351 })
22352 }
22353
22354 fn character_index_for_point(
22355 &mut self,
22356 point: gpui::Point<Pixels>,
22357 _window: &mut Window,
22358 _cx: &mut Context<Self>,
22359 ) -> Option<usize> {
22360 let position_map = self.last_position_map.as_ref()?;
22361 if !position_map.text_hitbox.contains(&point) {
22362 return None;
22363 }
22364 let display_point = position_map.point_for_position(point).previous_valid;
22365 let anchor = position_map
22366 .snapshot
22367 .display_point_to_anchor(display_point, Bias::Left);
22368 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22369 Some(utf16_offset.0)
22370 }
22371}
22372
22373trait SelectionExt {
22374 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22375 fn spanned_rows(
22376 &self,
22377 include_end_if_at_line_start: bool,
22378 map: &DisplaySnapshot,
22379 ) -> Range<MultiBufferRow>;
22380}
22381
22382impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22383 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22384 let start = self
22385 .start
22386 .to_point(&map.buffer_snapshot)
22387 .to_display_point(map);
22388 let end = self
22389 .end
22390 .to_point(&map.buffer_snapshot)
22391 .to_display_point(map);
22392 if self.reversed {
22393 end..start
22394 } else {
22395 start..end
22396 }
22397 }
22398
22399 fn spanned_rows(
22400 &self,
22401 include_end_if_at_line_start: bool,
22402 map: &DisplaySnapshot,
22403 ) -> Range<MultiBufferRow> {
22404 let start = self.start.to_point(&map.buffer_snapshot);
22405 let mut end = self.end.to_point(&map.buffer_snapshot);
22406 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22407 end.row -= 1;
22408 }
22409
22410 let buffer_start = map.prev_line_boundary(start).0;
22411 let buffer_end = map.next_line_boundary(end).0;
22412 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22413 }
22414}
22415
22416impl<T: InvalidationRegion> InvalidationStack<T> {
22417 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22418 where
22419 S: Clone + ToOffset,
22420 {
22421 while let Some(region) = self.last() {
22422 let all_selections_inside_invalidation_ranges =
22423 if selections.len() == region.ranges().len() {
22424 selections
22425 .iter()
22426 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22427 .all(|(selection, invalidation_range)| {
22428 let head = selection.head().to_offset(buffer);
22429 invalidation_range.start <= head && invalidation_range.end >= head
22430 })
22431 } else {
22432 false
22433 };
22434
22435 if all_selections_inside_invalidation_ranges {
22436 break;
22437 } else {
22438 self.pop();
22439 }
22440 }
22441 }
22442}
22443
22444impl<T> Default for InvalidationStack<T> {
22445 fn default() -> Self {
22446 Self(Default::default())
22447 }
22448}
22449
22450impl<T> Deref for InvalidationStack<T> {
22451 type Target = Vec<T>;
22452
22453 fn deref(&self) -> &Self::Target {
22454 &self.0
22455 }
22456}
22457
22458impl<T> DerefMut for InvalidationStack<T> {
22459 fn deref_mut(&mut self) -> &mut Self::Target {
22460 &mut self.0
22461 }
22462}
22463
22464impl InvalidationRegion for SnippetState {
22465 fn ranges(&self) -> &[Range<Anchor>] {
22466 &self.ranges[self.active_index]
22467 }
22468}
22469
22470fn inline_completion_edit_text(
22471 current_snapshot: &BufferSnapshot,
22472 edits: &[(Range<Anchor>, String)],
22473 edit_preview: &EditPreview,
22474 include_deletions: bool,
22475 cx: &App,
22476) -> HighlightedText {
22477 let edits = edits
22478 .iter()
22479 .map(|(anchor, text)| {
22480 (
22481 anchor.start.text_anchor..anchor.end.text_anchor,
22482 text.clone(),
22483 )
22484 })
22485 .collect::<Vec<_>>();
22486
22487 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22488}
22489
22490pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22491 match severity {
22492 lsp::DiagnosticSeverity::ERROR => colors.error,
22493 lsp::DiagnosticSeverity::WARNING => colors.warning,
22494 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22495 lsp::DiagnosticSeverity::HINT => colors.info,
22496 _ => colors.ignored,
22497 }
22498}
22499
22500pub fn styled_runs_for_code_label<'a>(
22501 label: &'a CodeLabel,
22502 syntax_theme: &'a theme::SyntaxTheme,
22503) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22504 let fade_out = HighlightStyle {
22505 fade_out: Some(0.35),
22506 ..Default::default()
22507 };
22508
22509 let mut prev_end = label.filter_range.end;
22510 label
22511 .runs
22512 .iter()
22513 .enumerate()
22514 .flat_map(move |(ix, (range, highlight_id))| {
22515 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22516 style
22517 } else {
22518 return Default::default();
22519 };
22520 let mut muted_style = style;
22521 muted_style.highlight(fade_out);
22522
22523 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22524 if range.start >= label.filter_range.end {
22525 if range.start > prev_end {
22526 runs.push((prev_end..range.start, fade_out));
22527 }
22528 runs.push((range.clone(), muted_style));
22529 } else if range.end <= label.filter_range.end {
22530 runs.push((range.clone(), style));
22531 } else {
22532 runs.push((range.start..label.filter_range.end, style));
22533 runs.push((label.filter_range.end..range.end, muted_style));
22534 }
22535 prev_end = cmp::max(prev_end, range.end);
22536
22537 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22538 runs.push((prev_end..label.text.len(), fade_out));
22539 }
22540
22541 runs
22542 })
22543}
22544
22545pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22546 let mut prev_index = 0;
22547 let mut prev_codepoint: Option<char> = None;
22548 text.char_indices()
22549 .chain([(text.len(), '\0')])
22550 .filter_map(move |(index, codepoint)| {
22551 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22552 let is_boundary = index == text.len()
22553 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22554 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22555 if is_boundary {
22556 let chunk = &text[prev_index..index];
22557 prev_index = index;
22558 Some(chunk)
22559 } else {
22560 None
22561 }
22562 })
22563}
22564
22565pub trait RangeToAnchorExt: Sized {
22566 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22567
22568 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22569 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22570 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22571 }
22572}
22573
22574impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22575 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22576 let start_offset = self.start.to_offset(snapshot);
22577 let end_offset = self.end.to_offset(snapshot);
22578 if start_offset == end_offset {
22579 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22580 } else {
22581 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22582 }
22583 }
22584}
22585
22586pub trait RowExt {
22587 fn as_f32(&self) -> f32;
22588
22589 fn next_row(&self) -> Self;
22590
22591 fn previous_row(&self) -> Self;
22592
22593 fn minus(&self, other: Self) -> u32;
22594}
22595
22596impl RowExt for DisplayRow {
22597 fn as_f32(&self) -> f32 {
22598 self.0 as f32
22599 }
22600
22601 fn next_row(&self) -> Self {
22602 Self(self.0 + 1)
22603 }
22604
22605 fn previous_row(&self) -> Self {
22606 Self(self.0.saturating_sub(1))
22607 }
22608
22609 fn minus(&self, other: Self) -> u32 {
22610 self.0 - other.0
22611 }
22612}
22613
22614impl RowExt for MultiBufferRow {
22615 fn as_f32(&self) -> f32 {
22616 self.0 as f32
22617 }
22618
22619 fn next_row(&self) -> Self {
22620 Self(self.0 + 1)
22621 }
22622
22623 fn previous_row(&self) -> Self {
22624 Self(self.0.saturating_sub(1))
22625 }
22626
22627 fn minus(&self, other: Self) -> u32 {
22628 self.0 - other.0
22629 }
22630}
22631
22632trait RowRangeExt {
22633 type Row;
22634
22635 fn len(&self) -> usize;
22636
22637 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22638}
22639
22640impl RowRangeExt for Range<MultiBufferRow> {
22641 type Row = MultiBufferRow;
22642
22643 fn len(&self) -> usize {
22644 (self.end.0 - self.start.0) as usize
22645 }
22646
22647 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22648 (self.start.0..self.end.0).map(MultiBufferRow)
22649 }
22650}
22651
22652impl RowRangeExt for Range<DisplayRow> {
22653 type Row = DisplayRow;
22654
22655 fn len(&self) -> usize {
22656 (self.end.0 - self.start.0) as usize
22657 }
22658
22659 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22660 (self.start.0..self.end.0).map(DisplayRow)
22661 }
22662}
22663
22664/// If select range has more than one line, we
22665/// just point the cursor to range.start.
22666fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22667 if range.start.row == range.end.row {
22668 range
22669 } else {
22670 range.start..range.start
22671 }
22672}
22673pub struct KillRing(ClipboardItem);
22674impl Global for KillRing {}
22675
22676const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22677
22678enum BreakpointPromptEditAction {
22679 Log,
22680 Condition,
22681 HitCondition,
22682}
22683
22684struct BreakpointPromptEditor {
22685 pub(crate) prompt: Entity<Editor>,
22686 editor: WeakEntity<Editor>,
22687 breakpoint_anchor: Anchor,
22688 breakpoint: Breakpoint,
22689 edit_action: BreakpointPromptEditAction,
22690 block_ids: HashSet<CustomBlockId>,
22691 editor_margins: Arc<Mutex<EditorMargins>>,
22692 _subscriptions: Vec<Subscription>,
22693}
22694
22695impl BreakpointPromptEditor {
22696 const MAX_LINES: u8 = 4;
22697
22698 fn new(
22699 editor: WeakEntity<Editor>,
22700 breakpoint_anchor: Anchor,
22701 breakpoint: Breakpoint,
22702 edit_action: BreakpointPromptEditAction,
22703 window: &mut Window,
22704 cx: &mut Context<Self>,
22705 ) -> Self {
22706 let base_text = match edit_action {
22707 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22708 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22709 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22710 }
22711 .map(|msg| msg.to_string())
22712 .unwrap_or_default();
22713
22714 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22715 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22716
22717 let prompt = cx.new(|cx| {
22718 let mut prompt = Editor::new(
22719 EditorMode::AutoHeight {
22720 min_lines: 1,
22721 max_lines: Self::MAX_LINES as usize,
22722 },
22723 buffer,
22724 None,
22725 window,
22726 cx,
22727 );
22728 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22729 prompt.set_show_cursor_when_unfocused(false, cx);
22730 prompt.set_placeholder_text(
22731 match edit_action {
22732 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22733 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22734 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22735 },
22736 cx,
22737 );
22738
22739 prompt
22740 });
22741
22742 Self {
22743 prompt,
22744 editor,
22745 breakpoint_anchor,
22746 breakpoint,
22747 edit_action,
22748 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22749 block_ids: Default::default(),
22750 _subscriptions: vec![],
22751 }
22752 }
22753
22754 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22755 self.block_ids.extend(block_ids)
22756 }
22757
22758 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22759 if let Some(editor) = self.editor.upgrade() {
22760 let message = self
22761 .prompt
22762 .read(cx)
22763 .buffer
22764 .read(cx)
22765 .as_singleton()
22766 .expect("A multi buffer in breakpoint prompt isn't possible")
22767 .read(cx)
22768 .as_rope()
22769 .to_string();
22770
22771 editor.update(cx, |editor, cx| {
22772 editor.edit_breakpoint_at_anchor(
22773 self.breakpoint_anchor,
22774 self.breakpoint.clone(),
22775 match self.edit_action {
22776 BreakpointPromptEditAction::Log => {
22777 BreakpointEditAction::EditLogMessage(message.into())
22778 }
22779 BreakpointPromptEditAction::Condition => {
22780 BreakpointEditAction::EditCondition(message.into())
22781 }
22782 BreakpointPromptEditAction::HitCondition => {
22783 BreakpointEditAction::EditHitCondition(message.into())
22784 }
22785 },
22786 cx,
22787 );
22788
22789 editor.remove_blocks(self.block_ids.clone(), None, cx);
22790 cx.focus_self(window);
22791 });
22792 }
22793 }
22794
22795 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22796 self.editor
22797 .update(cx, |editor, cx| {
22798 editor.remove_blocks(self.block_ids.clone(), None, cx);
22799 window.focus(&editor.focus_handle);
22800 })
22801 .log_err();
22802 }
22803
22804 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22805 let settings = ThemeSettings::get_global(cx);
22806 let text_style = TextStyle {
22807 color: if self.prompt.read(cx).read_only(cx) {
22808 cx.theme().colors().text_disabled
22809 } else {
22810 cx.theme().colors().text
22811 },
22812 font_family: settings.buffer_font.family.clone(),
22813 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22814 font_size: settings.buffer_font_size(cx).into(),
22815 font_weight: settings.buffer_font.weight,
22816 line_height: relative(settings.buffer_line_height.value()),
22817 ..Default::default()
22818 };
22819 EditorElement::new(
22820 &self.prompt,
22821 EditorStyle {
22822 background: cx.theme().colors().editor_background,
22823 local_player: cx.theme().players().local(),
22824 text: text_style,
22825 ..Default::default()
22826 },
22827 )
22828 }
22829}
22830
22831impl Render for BreakpointPromptEditor {
22832 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22833 let editor_margins = *self.editor_margins.lock();
22834 let gutter_dimensions = editor_margins.gutter;
22835 h_flex()
22836 .key_context("Editor")
22837 .bg(cx.theme().colors().editor_background)
22838 .border_y_1()
22839 .border_color(cx.theme().status().info_border)
22840 .size_full()
22841 .py(window.line_height() / 2.5)
22842 .on_action(cx.listener(Self::confirm))
22843 .on_action(cx.listener(Self::cancel))
22844 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22845 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22846 }
22847}
22848
22849impl Focusable for BreakpointPromptEditor {
22850 fn focus_handle(&self, cx: &App) -> FocusHandle {
22851 self.prompt.focus_handle(cx)
22852 }
22853}
22854
22855fn all_edits_insertions_or_deletions(
22856 edits: &Vec<(Range<Anchor>, String)>,
22857 snapshot: &MultiBufferSnapshot,
22858) -> bool {
22859 let mut all_insertions = true;
22860 let mut all_deletions = true;
22861
22862 for (range, new_text) in edits.iter() {
22863 let range_is_empty = range.to_offset(&snapshot).is_empty();
22864 let text_is_empty = new_text.is_empty();
22865
22866 if range_is_empty != text_is_empty {
22867 if range_is_empty {
22868 all_deletions = false;
22869 } else {
22870 all_insertions = false;
22871 }
22872 } else {
22873 return false;
22874 }
22875
22876 if !all_insertions && !all_deletions {
22877 return false;
22878 }
22879 }
22880 all_insertions || all_deletions
22881}
22882
22883struct MissingEditPredictionKeybindingTooltip;
22884
22885impl Render for MissingEditPredictionKeybindingTooltip {
22886 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22887 ui::tooltip_container(window, cx, |container, _, cx| {
22888 container
22889 .flex_shrink_0()
22890 .max_w_80()
22891 .min_h(rems_from_px(124.))
22892 .justify_between()
22893 .child(
22894 v_flex()
22895 .flex_1()
22896 .text_ui_sm(cx)
22897 .child(Label::new("Conflict with Accept Keybinding"))
22898 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22899 )
22900 .child(
22901 h_flex()
22902 .pb_1()
22903 .gap_1()
22904 .items_end()
22905 .w_full()
22906 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22907 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22908 }))
22909 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22910 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22911 })),
22912 )
22913 })
22914 }
22915}
22916
22917#[derive(Debug, Clone, Copy, PartialEq)]
22918pub struct LineHighlight {
22919 pub background: Background,
22920 pub border: Option<gpui::Hsla>,
22921 pub include_gutter: bool,
22922 pub type_id: Option<TypeId>,
22923}
22924
22925fn render_diff_hunk_controls(
22926 row: u32,
22927 status: &DiffHunkStatus,
22928 hunk_range: Range<Anchor>,
22929 is_created_file: bool,
22930 line_height: Pixels,
22931 editor: &Entity<Editor>,
22932 _window: &mut Window,
22933 cx: &mut App,
22934) -> AnyElement {
22935 h_flex()
22936 .h(line_height)
22937 .mr_1()
22938 .gap_1()
22939 .px_0p5()
22940 .pb_1()
22941 .border_x_1()
22942 .border_b_1()
22943 .border_color(cx.theme().colors().border_variant)
22944 .rounded_b_lg()
22945 .bg(cx.theme().colors().editor_background)
22946 .gap_1()
22947 .block_mouse_except_scroll()
22948 .shadow_md()
22949 .child(if status.has_secondary_hunk() {
22950 Button::new(("stage", row as u64), "Stage")
22951 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22952 .tooltip({
22953 let focus_handle = editor.focus_handle(cx);
22954 move |window, cx| {
22955 Tooltip::for_action_in(
22956 "Stage Hunk",
22957 &::git::ToggleStaged,
22958 &focus_handle,
22959 window,
22960 cx,
22961 )
22962 }
22963 })
22964 .on_click({
22965 let editor = editor.clone();
22966 move |_event, _window, cx| {
22967 editor.update(cx, |editor, cx| {
22968 editor.stage_or_unstage_diff_hunks(
22969 true,
22970 vec![hunk_range.start..hunk_range.start],
22971 cx,
22972 );
22973 });
22974 }
22975 })
22976 } else {
22977 Button::new(("unstage", row as u64), "Unstage")
22978 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22979 .tooltip({
22980 let focus_handle = editor.focus_handle(cx);
22981 move |window, cx| {
22982 Tooltip::for_action_in(
22983 "Unstage Hunk",
22984 &::git::ToggleStaged,
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 editor.stage_or_unstage_diff_hunks(
22996 false,
22997 vec![hunk_range.start..hunk_range.start],
22998 cx,
22999 );
23000 });
23001 }
23002 })
23003 })
23004 .child(
23005 Button::new(("restore", row as u64), "Restore")
23006 .tooltip({
23007 let focus_handle = editor.focus_handle(cx);
23008 move |window, cx| {
23009 Tooltip::for_action_in(
23010 "Restore Hunk",
23011 &::git::Restore,
23012 &focus_handle,
23013 window,
23014 cx,
23015 )
23016 }
23017 })
23018 .on_click({
23019 let editor = editor.clone();
23020 move |_event, window, cx| {
23021 editor.update(cx, |editor, cx| {
23022 let snapshot = editor.snapshot(window, cx);
23023 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
23024 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
23025 });
23026 }
23027 })
23028 .disabled(is_created_file),
23029 )
23030 .when(
23031 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
23032 |el| {
23033 el.child(
23034 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
23035 .shape(IconButtonShape::Square)
23036 .icon_size(IconSize::Small)
23037 // .disabled(!has_multiple_hunks)
23038 .tooltip({
23039 let focus_handle = editor.focus_handle(cx);
23040 move |window, cx| {
23041 Tooltip::for_action_in(
23042 "Next Hunk",
23043 &GoToHunk,
23044 &focus_handle,
23045 window,
23046 cx,
23047 )
23048 }
23049 })
23050 .on_click({
23051 let editor = editor.clone();
23052 move |_event, window, cx| {
23053 editor.update(cx, |editor, cx| {
23054 let snapshot = editor.snapshot(window, cx);
23055 let position =
23056 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23057 editor.go_to_hunk_before_or_after_position(
23058 &snapshot,
23059 position,
23060 Direction::Next,
23061 window,
23062 cx,
23063 );
23064 editor.expand_selected_diff_hunks(cx);
23065 });
23066 }
23067 }),
23068 )
23069 .child(
23070 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23071 .shape(IconButtonShape::Square)
23072 .icon_size(IconSize::Small)
23073 // .disabled(!has_multiple_hunks)
23074 .tooltip({
23075 let focus_handle = editor.focus_handle(cx);
23076 move |window, cx| {
23077 Tooltip::for_action_in(
23078 "Previous Hunk",
23079 &GoToPreviousHunk,
23080 &focus_handle,
23081 window,
23082 cx,
23083 )
23084 }
23085 })
23086 .on_click({
23087 let editor = editor.clone();
23088 move |_event, window, cx| {
23089 editor.update(cx, |editor, cx| {
23090 let snapshot = editor.snapshot(window, cx);
23091 let point =
23092 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23093 editor.go_to_hunk_before_or_after_position(
23094 &snapshot,
23095 point,
23096 Direction::Prev,
23097 window,
23098 cx,
23099 );
23100 editor.expand_selected_diff_hunks(cx);
23101 });
23102 }
23103 }),
23104 )
23105 },
23106 )
23107 .into_any_element()
23108}