1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82
83use ::git::blame::BlameEntry;
84use ::git::{Restore, blame::ParsedCommitMessage};
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use git::blame::{GitBlame, GlobalBlameRenderer};
90use gpui::{
91 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
92 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
93 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
94 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
95 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
96 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
97 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
98 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
99};
100use highlight_matching_bracket::refresh_matching_bracket_highlights;
101use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
102pub use hover_popover::hover_markdown_style;
103use hover_popover::{HoverState, hide_hover};
104use indent_guides::ActiveIndentGuidesState;
105use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
106pub use inline_completion::Direction;
107use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
108pub use items::MAX_TAB_TITLE_LEN;
109use itertools::Itertools;
110use language::{
111 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
112 CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
113 EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
114 OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
115 WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 project_settings::DiagnosticSeverity,
137};
138
139pub use git::blame::BlameRenderer;
140pub use proposed_changes_editor::{
141 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
142};
143use std::{cell::OnceCell, iter::Peekable, ops::Not};
144use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
145
146pub use lsp::CompletionContext;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId, LanguageServerName,
150};
151
152use language::BufferSnapshot;
153pub use lsp_ext::lsp_tasks;
154use movement::TextLayoutDetails;
155pub use multi_buffer::{
156 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
157 RowInfo, ToOffset, ToPoint,
158};
159use multi_buffer::{
160 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
161 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
162};
163use parking_lot::Mutex;
164use project::{
165 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
166 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
167 TaskSourceKind,
168 debugger::breakpoint_store::Breakpoint,
169 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
170 project_settings::{GitGutterSetting, ProjectSettings},
171};
172use rand::prelude::*;
173use rpc::{ErrorExt, proto::*};
174use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
175use selections_collection::{
176 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
177};
178use serde::{Deserialize, Serialize};
179use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
180use smallvec::{SmallVec, smallvec};
181use snippet::Snippet;
182use std::sync::Arc;
183use std::{
184 any::TypeId,
185 borrow::Cow,
186 cell::RefCell,
187 cmp::{self, Ordering, Reverse},
188 mem,
189 num::NonZeroU32,
190 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
191 path::{Path, PathBuf},
192 rc::Rc,
193 time::{Duration, Instant},
194};
195pub use sum_tree::Bias;
196use sum_tree::TreeMap;
197use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
198use theme::{
199 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
200 observe_buffer_font_size_adjustment,
201};
202use ui::{
203 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
204 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
205};
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
209 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
210 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{ItemHandle, PreviewTabsSettings, SaveOptions},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::{
217 code_context_menus::CompletionsMenuSource,
218 hover_links::{find_url, find_url_from_range},
219};
220use crate::{
221 editor_settings::MultiCursorModifier,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293enum DocumentHighlightRead {}
294enum DocumentHighlightWrite {}
295enum InputComposition {}
296pub enum PendingInput {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 mode: ColumnarMode,
451 goal_column: u32,
452 },
453 Extend {
454 position: DisplayPoint,
455 click_count: usize,
456 },
457 Update {
458 position: DisplayPoint,
459 goal_column: u32,
460 scroll_delta: gpui::Point<f32>,
461 },
462 End,
463}
464
465#[derive(Clone, Debug, PartialEq)]
466pub enum ColumnarMode {
467 FromMouse,
468 FromSelection,
469}
470
471#[derive(Clone, Debug)]
472pub enum SelectMode {
473 Character,
474 Word(Range<Anchor>),
475 Line(Range<Anchor>),
476 All,
477}
478
479#[derive(Clone, PartialEq, Eq, Debug)]
480pub enum EditorMode {
481 SingleLine {
482 auto_width: bool,
483 },
484 AutoHeight {
485 min_lines: usize,
486 max_lines: usize,
487 },
488 Full {
489 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
490 scale_ui_elements_with_buffer_font_size: bool,
491 /// When set to `true`, the editor will render a background for the active line.
492 show_active_line_background: bool,
493 /// When set to `true`, the editor's height will be determined by its content.
494 sized_by_content: bool,
495 },
496 Minimap {
497 parent: WeakEntity<Editor>,
498 },
499}
500
501impl EditorMode {
502 pub fn full() -> Self {
503 Self::Full {
504 scale_ui_elements_with_buffer_font_size: true,
505 show_active_line_background: true,
506 sized_by_content: false,
507 }
508 }
509
510 pub fn is_full(&self) -> bool {
511 matches!(self, Self::Full { .. })
512 }
513
514 fn is_minimap(&self) -> bool {
515 matches!(self, Self::Minimap { .. })
516 }
517}
518
519#[derive(Copy, Clone, Debug)]
520pub enum SoftWrap {
521 /// Prefer not to wrap at all.
522 ///
523 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
524 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
525 GitDiff,
526 /// Prefer a single line generally, unless an overly long line is encountered.
527 None,
528 /// Soft wrap lines that exceed the editor width.
529 EditorWidth,
530 /// Soft wrap lines at the preferred line length.
531 Column(u32),
532 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
533 Bounded(u32),
534}
535
536#[derive(Clone)]
537pub struct EditorStyle {
538 pub background: Hsla,
539 pub local_player: PlayerColor,
540 pub text: TextStyle,
541 pub scrollbar_width: Pixels,
542 pub syntax: Arc<SyntaxTheme>,
543 pub status: StatusColors,
544 pub inlay_hints_style: HighlightStyle,
545 pub inline_completion_styles: InlineCompletionStyles,
546 pub unnecessary_code_fade: f32,
547 pub show_underlines: bool,
548}
549
550impl Default for EditorStyle {
551 fn default() -> Self {
552 Self {
553 background: Hsla::default(),
554 local_player: PlayerColor::default(),
555 text: TextStyle::default(),
556 scrollbar_width: Pixels::default(),
557 syntax: Default::default(),
558 // HACK: Status colors don't have a real default.
559 // We should look into removing the status colors from the editor
560 // style and retrieve them directly from the theme.
561 status: StatusColors::dark(),
562 inlay_hints_style: HighlightStyle::default(),
563 inline_completion_styles: InlineCompletionStyles {
564 insertion: HighlightStyle::default(),
565 whitespace: HighlightStyle::default(),
566 },
567 unnecessary_code_fade: Default::default(),
568 show_underlines: true,
569 }
570 }
571}
572
573pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
574 let show_background = language_settings::language_settings(None, None, cx)
575 .inlay_hints
576 .show_background;
577
578 HighlightStyle {
579 color: Some(cx.theme().status().hint),
580 background_color: show_background.then(|| cx.theme().status().hint_background),
581 ..HighlightStyle::default()
582 }
583}
584
585pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
586 InlineCompletionStyles {
587 insertion: HighlightStyle {
588 color: Some(cx.theme().status().predictive),
589 ..HighlightStyle::default()
590 },
591 whitespace: HighlightStyle {
592 background_color: Some(cx.theme().status().created_background),
593 ..HighlightStyle::default()
594 },
595 }
596}
597
598type CompletionId = usize;
599
600pub(crate) enum EditDisplayMode {
601 TabAccept,
602 DiffPopover,
603 Inline,
604}
605
606enum InlineCompletion {
607 Edit {
608 edits: Vec<(Range<Anchor>, String)>,
609 edit_preview: Option<EditPreview>,
610 display_mode: EditDisplayMode,
611 snapshot: BufferSnapshot,
612 },
613 Move {
614 target: Anchor,
615 snapshot: BufferSnapshot,
616 },
617}
618
619struct InlineCompletionState {
620 inlay_ids: Vec<InlayId>,
621 completion: InlineCompletion,
622 completion_id: Option<SharedString>,
623 invalidation_range: Range<Anchor>,
624}
625
626enum EditPredictionSettings {
627 Disabled,
628 Enabled {
629 show_in_menu: bool,
630 preview_requires_modifier: bool,
631 },
632}
633
634enum InlineCompletionHighlight {}
635
636#[derive(Debug, Clone)]
637struct InlineDiagnostic {
638 message: SharedString,
639 group_id: usize,
640 is_primary: bool,
641 start: Point,
642 severity: lsp::DiagnosticSeverity,
643}
644
645pub enum MenuInlineCompletionsPolicy {
646 Never,
647 ByProvider,
648}
649
650pub enum EditPredictionPreview {
651 /// Modifier is not pressed
652 Inactive { released_too_fast: bool },
653 /// Modifier pressed
654 Active {
655 since: Instant,
656 previous_scroll_position: Option<ScrollAnchor>,
657 },
658}
659
660impl EditPredictionPreview {
661 pub fn released_too_fast(&self) -> bool {
662 match self {
663 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
664 EditPredictionPreview::Active { .. } => false,
665 }
666 }
667
668 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
669 if let EditPredictionPreview::Active {
670 previous_scroll_position,
671 ..
672 } = self
673 {
674 *previous_scroll_position = scroll_position;
675 }
676 }
677}
678
679pub struct ContextMenuOptions {
680 pub min_entries_visible: usize,
681 pub max_entries_visible: usize,
682 pub placement: Option<ContextMenuPlacement>,
683}
684
685#[derive(Debug, Clone, PartialEq, Eq)]
686pub enum ContextMenuPlacement {
687 Above,
688 Below,
689}
690
691#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
692struct EditorActionId(usize);
693
694impl EditorActionId {
695 pub fn post_inc(&mut self) -> Self {
696 let answer = self.0;
697
698 *self = Self(answer + 1);
699
700 Self(answer)
701 }
702}
703
704// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
705// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
706
707#[derive(Clone)]
708pub struct BackgroundHighlight {
709 pub range: Range<Anchor>,
710 pub color_fetcher: fn(&Theme) -> Hsla,
711}
712
713type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
714
715#[derive(Default)]
716struct ScrollbarMarkerState {
717 scrollbar_size: Size<Pixels>,
718 dirty: bool,
719 markers: Arc<[PaintQuad]>,
720 pending_refresh: Option<Task<Result<()>>>,
721}
722
723impl ScrollbarMarkerState {
724 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
725 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
726 }
727}
728
729#[derive(Clone, Copy, PartialEq, Eq)]
730pub enum MinimapVisibility {
731 Disabled,
732 Enabled {
733 /// The configuration currently present in the users settings.
734 setting_configuration: bool,
735 /// Whether to override the currently set visibility from the users setting.
736 toggle_override: bool,
737 },
738}
739
740impl MinimapVisibility {
741 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
742 if mode.is_full() {
743 Self::Enabled {
744 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
745 toggle_override: false,
746 }
747 } else {
748 Self::Disabled
749 }
750 }
751
752 fn hidden(&self) -> Self {
753 match *self {
754 Self::Enabled {
755 setting_configuration,
756 ..
757 } => Self::Enabled {
758 setting_configuration,
759 toggle_override: setting_configuration,
760 },
761 Self::Disabled => Self::Disabled,
762 }
763 }
764
765 fn disabled(&self) -> bool {
766 match *self {
767 Self::Disabled => true,
768 _ => false,
769 }
770 }
771
772 fn settings_visibility(&self) -> bool {
773 match *self {
774 Self::Enabled {
775 setting_configuration,
776 ..
777 } => setting_configuration,
778 _ => false,
779 }
780 }
781
782 fn visible(&self) -> bool {
783 match *self {
784 Self::Enabled {
785 setting_configuration,
786 toggle_override,
787 } => setting_configuration ^ toggle_override,
788 _ => false,
789 }
790 }
791
792 fn toggle_visibility(&self) -> Self {
793 match *self {
794 Self::Enabled {
795 toggle_override,
796 setting_configuration,
797 } => Self::Enabled {
798 setting_configuration,
799 toggle_override: !toggle_override,
800 },
801 Self::Disabled => Self::Disabled,
802 }
803 }
804}
805
806#[derive(Clone, Debug)]
807struct RunnableTasks {
808 templates: Vec<(TaskSourceKind, TaskTemplate)>,
809 offset: multi_buffer::Anchor,
810 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
811 column: u32,
812 // Values of all named captures, including those starting with '_'
813 extra_variables: HashMap<String, String>,
814 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
815 context_range: Range<BufferOffset>,
816}
817
818impl RunnableTasks {
819 fn resolve<'a>(
820 &'a self,
821 cx: &'a task::TaskContext,
822 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
823 self.templates.iter().filter_map(|(kind, template)| {
824 template
825 .resolve_task(&kind.to_id_base(), cx)
826 .map(|task| (kind.clone(), task))
827 })
828 }
829}
830
831#[derive(Clone)]
832pub struct ResolvedTasks {
833 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
834 position: Anchor,
835}
836
837#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
838struct BufferOffset(usize);
839
840// Addons allow storing per-editor state in other crates (e.g. Vim)
841pub trait Addon: 'static {
842 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
843
844 fn render_buffer_header_controls(
845 &self,
846 _: &ExcerptInfo,
847 _: &Window,
848 _: &App,
849 ) -> Option<AnyElement> {
850 None
851 }
852
853 fn to_any(&self) -> &dyn std::any::Any;
854
855 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
856 None
857 }
858}
859
860/// A set of caret positions, registered when the editor was edited.
861pub struct ChangeList {
862 changes: Vec<Vec<Anchor>>,
863 /// Currently "selected" change.
864 position: Option<usize>,
865}
866
867impl ChangeList {
868 pub fn new() -> Self {
869 Self {
870 changes: Vec::new(),
871 position: None,
872 }
873 }
874
875 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
876 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
877 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
878 if self.changes.is_empty() {
879 return None;
880 }
881
882 let prev = self.position.unwrap_or(self.changes.len());
883 let next = if direction == Direction::Prev {
884 prev.saturating_sub(count)
885 } else {
886 (prev + count).min(self.changes.len() - 1)
887 };
888 self.position = Some(next);
889 self.changes.get(next).map(|anchors| anchors.as_slice())
890 }
891
892 /// Adds a new change to the list, resetting the change list position.
893 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
894 self.position.take();
895 if pop_state {
896 self.changes.pop();
897 }
898 self.changes.push(new_positions.clone());
899 }
900
901 pub fn last(&self) -> Option<&[Anchor]> {
902 self.changes.last().map(|anchors| anchors.as_slice())
903 }
904}
905
906#[derive(Clone)]
907struct InlineBlamePopoverState {
908 scroll_handle: ScrollHandle,
909 commit_message: Option<ParsedCommitMessage>,
910 markdown: Entity<Markdown>,
911}
912
913struct InlineBlamePopover {
914 position: gpui::Point<Pixels>,
915 hide_task: Option<Task<()>>,
916 popover_bounds: Option<Bounds<Pixels>>,
917 popover_state: InlineBlamePopoverState,
918}
919
920enum SelectionDragState {
921 /// State when no drag related activity is detected.
922 None,
923 /// State when the mouse is down on a selection that is about to be dragged.
924 ReadyToDrag {
925 selection: Selection<Anchor>,
926 click_position: gpui::Point<Pixels>,
927 mouse_down_time: Instant,
928 },
929 /// State when the mouse is dragging the selection in the editor.
930 Dragging {
931 selection: Selection<Anchor>,
932 drop_cursor: Selection<Anchor>,
933 hide_drop_cursor: bool,
934 },
935}
936
937enum ColumnarSelectionState {
938 FromMouse {
939 selection_tail: Anchor,
940 display_point: Option<DisplayPoint>,
941 },
942 FromSelection {
943 selection_tail: Anchor,
944 },
945}
946
947/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
948/// a breakpoint on them.
949#[derive(Clone, Copy, Debug, PartialEq, Eq)]
950struct PhantomBreakpointIndicator {
951 display_row: DisplayRow,
952 /// There's a small debounce between hovering over the line and showing the indicator.
953 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
954 is_active: bool,
955 collides_with_existing_breakpoint: bool,
956}
957
958/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
959///
960/// See the [module level documentation](self) for more information.
961pub struct Editor {
962 focus_handle: FocusHandle,
963 last_focused_descendant: Option<WeakFocusHandle>,
964 /// The text buffer being edited
965 buffer: Entity<MultiBuffer>,
966 /// Map of how text in the buffer should be displayed.
967 /// Handles soft wraps, folds, fake inlay text insertions, etc.
968 pub display_map: Entity<DisplayMap>,
969 pub selections: SelectionsCollection,
970 pub scroll_manager: ScrollManager,
971 /// When inline assist editors are linked, they all render cursors because
972 /// typing enters text into each of them, even the ones that aren't focused.
973 pub(crate) show_cursor_when_unfocused: bool,
974 columnar_selection_state: Option<ColumnarSelectionState>,
975 add_selections_state: Option<AddSelectionsState>,
976 select_next_state: Option<SelectNextState>,
977 select_prev_state: Option<SelectNextState>,
978 selection_history: SelectionHistory,
979 defer_selection_effects: bool,
980 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
981 autoclose_regions: Vec<AutocloseRegion>,
982 snippet_stack: InvalidationStack<SnippetState>,
983 select_syntax_node_history: SelectSyntaxNodeHistory,
984 ime_transaction: Option<TransactionId>,
985 pub diagnostics_max_severity: DiagnosticSeverity,
986 active_diagnostics: ActiveDiagnostic,
987 show_inline_diagnostics: bool,
988 inline_diagnostics_update: Task<()>,
989 inline_diagnostics_enabled: bool,
990 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
991 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
992 hard_wrap: Option<usize>,
993
994 // TODO: make this a access method
995 pub project: Option<Entity<Project>>,
996 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
997 completion_provider: Option<Rc<dyn CompletionProvider>>,
998 collaboration_hub: Option<Box<dyn CollaborationHub>>,
999 blink_manager: Entity<BlinkManager>,
1000 show_cursor_names: bool,
1001 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1002 pub show_local_selections: bool,
1003 mode: EditorMode,
1004 show_breadcrumbs: bool,
1005 show_gutter: bool,
1006 show_scrollbars: ScrollbarAxes,
1007 minimap_visibility: MinimapVisibility,
1008 offset_content: bool,
1009 disable_expand_excerpt_buttons: bool,
1010 show_line_numbers: Option<bool>,
1011 use_relative_line_numbers: Option<bool>,
1012 show_git_diff_gutter: Option<bool>,
1013 show_code_actions: Option<bool>,
1014 show_runnables: Option<bool>,
1015 show_breakpoints: Option<bool>,
1016 show_wrap_guides: Option<bool>,
1017 show_indent_guides: Option<bool>,
1018 placeholder_text: Option<Arc<str>>,
1019 highlight_order: usize,
1020 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1021 background_highlights: TreeMap<TypeId, Vec<BackgroundHighlight>>,
1022 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1023 scrollbar_marker_state: ScrollbarMarkerState,
1024 active_indent_guides_state: ActiveIndentGuidesState,
1025 nav_history: Option<ItemNavHistory>,
1026 context_menu: RefCell<Option<CodeContextMenu>>,
1027 context_menu_options: Option<ContextMenuOptions>,
1028 mouse_context_menu: Option<MouseContextMenu>,
1029 completion_tasks: Vec<(CompletionId, Task<()>)>,
1030 inline_blame_popover: Option<InlineBlamePopover>,
1031 inline_blame_popover_show_task: Option<Task<()>>,
1032 signature_help_state: SignatureHelpState,
1033 auto_signature_help: Option<bool>,
1034 find_all_references_task_sources: Vec<Anchor>,
1035 next_completion_id: CompletionId,
1036 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1037 code_actions_task: Option<Task<Result<()>>>,
1038 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1039 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1040 document_highlights_task: Option<Task<()>>,
1041 linked_editing_range_task: Option<Task<Option<()>>>,
1042 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1043 pending_rename: Option<RenameState>,
1044 searchable: bool,
1045 cursor_shape: CursorShape,
1046 current_line_highlight: Option<CurrentLineHighlight>,
1047 collapse_matches: bool,
1048 autoindent_mode: Option<AutoindentMode>,
1049 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1050 input_enabled: bool,
1051 use_modal_editing: bool,
1052 read_only: bool,
1053 leader_id: Option<CollaboratorId>,
1054 remote_id: Option<ViewId>,
1055 pub hover_state: HoverState,
1056 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1057 gutter_hovered: bool,
1058 hovered_link_state: Option<HoveredLinkState>,
1059 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1060 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1061 active_inline_completion: Option<InlineCompletionState>,
1062 /// Used to prevent flickering as the user types while the menu is open
1063 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1064 edit_prediction_settings: EditPredictionSettings,
1065 inline_completions_hidden_for_vim_mode: bool,
1066 show_inline_completions_override: Option<bool>,
1067 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1068 edit_prediction_preview: EditPredictionPreview,
1069 edit_prediction_indent_conflict: bool,
1070 edit_prediction_requires_modifier_in_indent_conflict: bool,
1071 inlay_hint_cache: InlayHintCache,
1072 next_inlay_id: usize,
1073 _subscriptions: Vec<Subscription>,
1074 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1075 gutter_dimensions: GutterDimensions,
1076 style: Option<EditorStyle>,
1077 text_style_refinement: Option<TextStyleRefinement>,
1078 next_editor_action_id: EditorActionId,
1079 editor_actions: Rc<
1080 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1081 >,
1082 use_autoclose: bool,
1083 use_auto_surround: bool,
1084 auto_replace_emoji_shortcode: bool,
1085 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1086 show_git_blame_gutter: bool,
1087 show_git_blame_inline: bool,
1088 show_git_blame_inline_delay_task: Option<Task<()>>,
1089 git_blame_inline_enabled: bool,
1090 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1091 serialize_dirty_buffers: bool,
1092 show_selection_menu: Option<bool>,
1093 blame: Option<Entity<GitBlame>>,
1094 blame_subscription: Option<Subscription>,
1095 custom_context_menu: Option<
1096 Box<
1097 dyn 'static
1098 + Fn(
1099 &mut Self,
1100 DisplayPoint,
1101 &mut Window,
1102 &mut Context<Self>,
1103 ) -> Option<Entity<ui::ContextMenu>>,
1104 >,
1105 >,
1106 last_bounds: Option<Bounds<Pixels>>,
1107 last_position_map: Option<Rc<PositionMap>>,
1108 expect_bounds_change: Option<Bounds<Pixels>>,
1109 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1110 tasks_update_task: Option<Task<()>>,
1111 breakpoint_store: Option<Entity<BreakpointStore>>,
1112 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1113 hovered_diff_hunk_row: Option<DisplayRow>,
1114 pull_diagnostics_task: Task<()>,
1115 in_project_search: bool,
1116 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1117 breadcrumb_header: Option<String>,
1118 focused_block: Option<FocusedBlock>,
1119 next_scroll_position: NextScrollCursorCenterTopBottom,
1120 addons: HashMap<TypeId, Box<dyn Addon>>,
1121 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1122 load_diff_task: Option<Shared<Task<()>>>,
1123 /// Whether we are temporarily displaying a diff other than git's
1124 temporary_diff_override: bool,
1125 selection_mark_mode: bool,
1126 toggle_fold_multiple_buffers: Task<()>,
1127 _scroll_cursor_center_top_bottom_task: Task<()>,
1128 serialize_selections: Task<()>,
1129 serialize_folds: Task<()>,
1130 mouse_cursor_hidden: bool,
1131 minimap: Option<Entity<Self>>,
1132 hide_mouse_mode: HideMouseMode,
1133 pub change_list: ChangeList,
1134 inline_value_cache: InlineValueCache,
1135 selection_drag_state: SelectionDragState,
1136 drag_and_drop_selection_enabled: bool,
1137}
1138
1139#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1140enum NextScrollCursorCenterTopBottom {
1141 #[default]
1142 Center,
1143 Top,
1144 Bottom,
1145}
1146
1147impl NextScrollCursorCenterTopBottom {
1148 fn next(&self) -> Self {
1149 match self {
1150 Self::Center => Self::Top,
1151 Self::Top => Self::Bottom,
1152 Self::Bottom => Self::Center,
1153 }
1154 }
1155}
1156
1157#[derive(Clone)]
1158pub struct EditorSnapshot {
1159 pub mode: EditorMode,
1160 show_gutter: bool,
1161 show_line_numbers: Option<bool>,
1162 show_git_diff_gutter: Option<bool>,
1163 show_code_actions: Option<bool>,
1164 show_runnables: Option<bool>,
1165 show_breakpoints: Option<bool>,
1166 git_blame_gutter_max_author_length: Option<usize>,
1167 pub display_snapshot: DisplaySnapshot,
1168 pub placeholder_text: Option<Arc<str>>,
1169 is_focused: bool,
1170 scroll_anchor: ScrollAnchor,
1171 ongoing_scroll: OngoingScroll,
1172 current_line_highlight: CurrentLineHighlight,
1173 gutter_hovered: bool,
1174}
1175
1176#[derive(Default, Debug, Clone, Copy)]
1177pub struct GutterDimensions {
1178 pub left_padding: Pixels,
1179 pub right_padding: Pixels,
1180 pub width: Pixels,
1181 pub margin: Pixels,
1182 pub git_blame_entries_width: Option<Pixels>,
1183}
1184
1185impl GutterDimensions {
1186 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1187 Self {
1188 margin: Self::default_gutter_margin(font_id, font_size, cx),
1189 ..Default::default()
1190 }
1191 }
1192
1193 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1194 -cx.text_system().descent(font_id, font_size)
1195 }
1196 /// The full width of the space taken up by the gutter.
1197 pub fn full_width(&self) -> Pixels {
1198 self.margin + self.width
1199 }
1200
1201 /// The width of the space reserved for the fold indicators,
1202 /// use alongside 'justify_end' and `gutter_width` to
1203 /// right align content with the line numbers
1204 pub fn fold_area_width(&self) -> Pixels {
1205 self.margin + self.right_padding
1206 }
1207}
1208
1209#[derive(Debug)]
1210pub struct RemoteSelection {
1211 pub replica_id: ReplicaId,
1212 pub selection: Selection<Anchor>,
1213 pub cursor_shape: CursorShape,
1214 pub collaborator_id: CollaboratorId,
1215 pub line_mode: bool,
1216 pub user_name: Option<SharedString>,
1217 pub color: PlayerColor,
1218}
1219
1220#[derive(Clone, Debug)]
1221struct SelectionHistoryEntry {
1222 selections: Arc<[Selection<Anchor>]>,
1223 select_next_state: Option<SelectNextState>,
1224 select_prev_state: Option<SelectNextState>,
1225 add_selections_state: Option<AddSelectionsState>,
1226}
1227
1228#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1229enum SelectionHistoryMode {
1230 Normal,
1231 Undoing,
1232 Redoing,
1233 Skipping,
1234}
1235
1236#[derive(Clone, PartialEq, Eq, Hash)]
1237struct HoveredCursor {
1238 replica_id: u16,
1239 selection_id: usize,
1240}
1241
1242impl Default for SelectionHistoryMode {
1243 fn default() -> Self {
1244 Self::Normal
1245 }
1246}
1247
1248#[derive(Debug)]
1249pub struct SelectionEffects {
1250 nav_history: bool,
1251 completions: bool,
1252 scroll: Option<Autoscroll>,
1253}
1254
1255impl Default for SelectionEffects {
1256 fn default() -> Self {
1257 Self {
1258 nav_history: true,
1259 completions: true,
1260 scroll: Some(Autoscroll::fit()),
1261 }
1262 }
1263}
1264impl SelectionEffects {
1265 pub fn scroll(scroll: Autoscroll) -> Self {
1266 Self {
1267 scroll: Some(scroll),
1268 ..Default::default()
1269 }
1270 }
1271
1272 pub fn no_scroll() -> Self {
1273 Self {
1274 scroll: None,
1275 ..Default::default()
1276 }
1277 }
1278
1279 pub fn completions(self, completions: bool) -> Self {
1280 Self {
1281 completions,
1282 ..self
1283 }
1284 }
1285
1286 pub fn nav_history(self, nav_history: bool) -> Self {
1287 Self {
1288 nav_history,
1289 ..self
1290 }
1291 }
1292}
1293
1294struct DeferredSelectionEffectsState {
1295 changed: bool,
1296 effects: SelectionEffects,
1297 old_cursor_position: Anchor,
1298 history_entry: SelectionHistoryEntry,
1299}
1300
1301#[derive(Default)]
1302struct SelectionHistory {
1303 #[allow(clippy::type_complexity)]
1304 selections_by_transaction:
1305 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1306 mode: SelectionHistoryMode,
1307 undo_stack: VecDeque<SelectionHistoryEntry>,
1308 redo_stack: VecDeque<SelectionHistoryEntry>,
1309}
1310
1311impl SelectionHistory {
1312 #[track_caller]
1313 fn insert_transaction(
1314 &mut self,
1315 transaction_id: TransactionId,
1316 selections: Arc<[Selection<Anchor>]>,
1317 ) {
1318 if selections.is_empty() {
1319 log::error!(
1320 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1321 std::panic::Location::caller()
1322 );
1323 return;
1324 }
1325 self.selections_by_transaction
1326 .insert(transaction_id, (selections, None));
1327 }
1328
1329 #[allow(clippy::type_complexity)]
1330 fn transaction(
1331 &self,
1332 transaction_id: TransactionId,
1333 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1334 self.selections_by_transaction.get(&transaction_id)
1335 }
1336
1337 #[allow(clippy::type_complexity)]
1338 fn transaction_mut(
1339 &mut self,
1340 transaction_id: TransactionId,
1341 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1342 self.selections_by_transaction.get_mut(&transaction_id)
1343 }
1344
1345 fn push(&mut self, entry: SelectionHistoryEntry) {
1346 if !entry.selections.is_empty() {
1347 match self.mode {
1348 SelectionHistoryMode::Normal => {
1349 self.push_undo(entry);
1350 self.redo_stack.clear();
1351 }
1352 SelectionHistoryMode::Undoing => self.push_redo(entry),
1353 SelectionHistoryMode::Redoing => self.push_undo(entry),
1354 SelectionHistoryMode::Skipping => {}
1355 }
1356 }
1357 }
1358
1359 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1360 if self
1361 .undo_stack
1362 .back()
1363 .map_or(true, |e| e.selections != entry.selections)
1364 {
1365 self.undo_stack.push_back(entry);
1366 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1367 self.undo_stack.pop_front();
1368 }
1369 }
1370 }
1371
1372 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1373 if self
1374 .redo_stack
1375 .back()
1376 .map_or(true, |e| e.selections != entry.selections)
1377 {
1378 self.redo_stack.push_back(entry);
1379 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1380 self.redo_stack.pop_front();
1381 }
1382 }
1383 }
1384}
1385
1386#[derive(Clone, Copy)]
1387pub struct RowHighlightOptions {
1388 pub autoscroll: bool,
1389 pub include_gutter: bool,
1390}
1391
1392impl Default for RowHighlightOptions {
1393 fn default() -> Self {
1394 Self {
1395 autoscroll: Default::default(),
1396 include_gutter: true,
1397 }
1398 }
1399}
1400
1401struct RowHighlight {
1402 index: usize,
1403 range: Range<Anchor>,
1404 color: Hsla,
1405 options: RowHighlightOptions,
1406 type_id: TypeId,
1407}
1408
1409#[derive(Clone, Debug)]
1410struct AddSelectionsState {
1411 groups: Vec<AddSelectionsGroup>,
1412}
1413
1414#[derive(Clone, Debug)]
1415struct AddSelectionsGroup {
1416 above: bool,
1417 stack: Vec<usize>,
1418}
1419
1420#[derive(Clone)]
1421struct SelectNextState {
1422 query: AhoCorasick,
1423 wordwise: bool,
1424 done: bool,
1425}
1426
1427impl std::fmt::Debug for SelectNextState {
1428 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1429 f.debug_struct(std::any::type_name::<Self>())
1430 .field("wordwise", &self.wordwise)
1431 .field("done", &self.done)
1432 .finish()
1433 }
1434}
1435
1436#[derive(Debug)]
1437struct AutocloseRegion {
1438 selection_id: usize,
1439 range: Range<Anchor>,
1440 pair: BracketPair,
1441}
1442
1443#[derive(Debug)]
1444struct SnippetState {
1445 ranges: Vec<Vec<Range<Anchor>>>,
1446 active_index: usize,
1447 choices: Vec<Option<Vec<String>>>,
1448}
1449
1450#[doc(hidden)]
1451pub struct RenameState {
1452 pub range: Range<Anchor>,
1453 pub old_name: Arc<str>,
1454 pub editor: Entity<Editor>,
1455 block_id: CustomBlockId,
1456}
1457
1458struct InvalidationStack<T>(Vec<T>);
1459
1460struct RegisteredInlineCompletionProvider {
1461 provider: Arc<dyn InlineCompletionProviderHandle>,
1462 _subscription: Subscription,
1463}
1464
1465#[derive(Debug, PartialEq, Eq)]
1466pub struct ActiveDiagnosticGroup {
1467 pub active_range: Range<Anchor>,
1468 pub active_message: String,
1469 pub group_id: usize,
1470 pub blocks: HashSet<CustomBlockId>,
1471}
1472
1473#[derive(Debug, PartialEq, Eq)]
1474
1475pub(crate) enum ActiveDiagnostic {
1476 None,
1477 All,
1478 Group(ActiveDiagnosticGroup),
1479}
1480
1481#[derive(Serialize, Deserialize, Clone, Debug)]
1482pub struct ClipboardSelection {
1483 /// The number of bytes in this selection.
1484 pub len: usize,
1485 /// Whether this was a full-line selection.
1486 pub is_entire_line: bool,
1487 /// The indentation of the first line when this content was originally copied.
1488 pub first_line_indent: u32,
1489}
1490
1491// selections, scroll behavior, was newest selection reversed
1492type SelectSyntaxNodeHistoryState = (
1493 Box<[Selection<usize>]>,
1494 SelectSyntaxNodeScrollBehavior,
1495 bool,
1496);
1497
1498#[derive(Default)]
1499struct SelectSyntaxNodeHistory {
1500 stack: Vec<SelectSyntaxNodeHistoryState>,
1501 // disable temporarily to allow changing selections without losing the stack
1502 pub disable_clearing: bool,
1503}
1504
1505impl SelectSyntaxNodeHistory {
1506 pub fn try_clear(&mut self) {
1507 if !self.disable_clearing {
1508 self.stack.clear();
1509 }
1510 }
1511
1512 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1513 self.stack.push(selection);
1514 }
1515
1516 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1517 self.stack.pop()
1518 }
1519}
1520
1521enum SelectSyntaxNodeScrollBehavior {
1522 CursorTop,
1523 FitSelection,
1524 CursorBottom,
1525}
1526
1527#[derive(Debug)]
1528pub(crate) struct NavigationData {
1529 cursor_anchor: Anchor,
1530 cursor_position: Point,
1531 scroll_anchor: ScrollAnchor,
1532 scroll_top_row: u32,
1533}
1534
1535#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1536pub enum GotoDefinitionKind {
1537 Symbol,
1538 Declaration,
1539 Type,
1540 Implementation,
1541}
1542
1543#[derive(Debug, Clone)]
1544enum InlayHintRefreshReason {
1545 ModifiersChanged(bool),
1546 Toggle(bool),
1547 SettingsChange(InlayHintSettings),
1548 NewLinesShown,
1549 BufferEdited(HashSet<Arc<Language>>),
1550 RefreshRequested,
1551 ExcerptsRemoved(Vec<ExcerptId>),
1552}
1553
1554impl InlayHintRefreshReason {
1555 fn description(&self) -> &'static str {
1556 match self {
1557 Self::ModifiersChanged(_) => "modifiers changed",
1558 Self::Toggle(_) => "toggle",
1559 Self::SettingsChange(_) => "settings change",
1560 Self::NewLinesShown => "new lines shown",
1561 Self::BufferEdited(_) => "buffer edited",
1562 Self::RefreshRequested => "refresh requested",
1563 Self::ExcerptsRemoved(_) => "excerpts removed",
1564 }
1565 }
1566}
1567
1568pub enum FormatTarget {
1569 Buffers(HashSet<Entity<Buffer>>),
1570 Ranges(Vec<Range<MultiBufferPoint>>),
1571}
1572
1573pub(crate) struct FocusedBlock {
1574 id: BlockId,
1575 focus_handle: WeakFocusHandle,
1576}
1577
1578#[derive(Clone)]
1579enum JumpData {
1580 MultiBufferRow {
1581 row: MultiBufferRow,
1582 line_offset_from_top: u32,
1583 },
1584 MultiBufferPoint {
1585 excerpt_id: ExcerptId,
1586 position: Point,
1587 anchor: text::Anchor,
1588 line_offset_from_top: u32,
1589 },
1590}
1591
1592pub enum MultibufferSelectionMode {
1593 First,
1594 All,
1595}
1596
1597#[derive(Clone, Copy, Debug, Default)]
1598pub struct RewrapOptions {
1599 pub override_language_settings: bool,
1600 pub preserve_existing_whitespace: bool,
1601}
1602
1603impl Editor {
1604 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1605 let buffer = cx.new(|cx| Buffer::local("", cx));
1606 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1607 Self::new(
1608 EditorMode::SingleLine { auto_width: false },
1609 buffer,
1610 None,
1611 window,
1612 cx,
1613 )
1614 }
1615
1616 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1617 let buffer = cx.new(|cx| Buffer::local("", cx));
1618 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1619 Self::new(EditorMode::full(), buffer, None, window, cx)
1620 }
1621
1622 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1623 let buffer = cx.new(|cx| Buffer::local("", cx));
1624 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1625 Self::new(
1626 EditorMode::SingleLine { auto_width: true },
1627 buffer,
1628 None,
1629 window,
1630 cx,
1631 )
1632 }
1633
1634 pub fn auto_height(
1635 min_lines: usize,
1636 max_lines: usize,
1637 window: &mut Window,
1638 cx: &mut Context<Self>,
1639 ) -> Self {
1640 let buffer = cx.new(|cx| Buffer::local("", cx));
1641 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1642 Self::new(
1643 EditorMode::AutoHeight {
1644 min_lines,
1645 max_lines,
1646 },
1647 buffer,
1648 None,
1649 window,
1650 cx,
1651 )
1652 }
1653
1654 pub fn for_buffer(
1655 buffer: Entity<Buffer>,
1656 project: Option<Entity<Project>>,
1657 window: &mut Window,
1658 cx: &mut Context<Self>,
1659 ) -> Self {
1660 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1661 Self::new(EditorMode::full(), buffer, project, window, cx)
1662 }
1663
1664 pub fn for_multibuffer(
1665 buffer: Entity<MultiBuffer>,
1666 project: Option<Entity<Project>>,
1667 window: &mut Window,
1668 cx: &mut Context<Self>,
1669 ) -> Self {
1670 Self::new(EditorMode::full(), buffer, project, window, cx)
1671 }
1672
1673 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let mut clone = Self::new(
1675 self.mode.clone(),
1676 self.buffer.clone(),
1677 self.project.clone(),
1678 window,
1679 cx,
1680 );
1681 self.display_map.update(cx, |display_map, cx| {
1682 let snapshot = display_map.snapshot(cx);
1683 clone.display_map.update(cx, |display_map, cx| {
1684 display_map.set_state(&snapshot, cx);
1685 });
1686 });
1687 clone.folds_did_change(cx);
1688 clone.selections.clone_state(&self.selections);
1689 clone.scroll_manager.clone_state(&self.scroll_manager);
1690 clone.searchable = self.searchable;
1691 clone.read_only = self.read_only;
1692 clone
1693 }
1694
1695 pub fn new(
1696 mode: EditorMode,
1697 buffer: Entity<MultiBuffer>,
1698 project: Option<Entity<Project>>,
1699 window: &mut Window,
1700 cx: &mut Context<Self>,
1701 ) -> Self {
1702 Editor::new_internal(mode, buffer, project, None, window, cx)
1703 }
1704
1705 fn new_internal(
1706 mode: EditorMode,
1707 buffer: Entity<MultiBuffer>,
1708 project: Option<Entity<Project>>,
1709 display_map: Option<Entity<DisplayMap>>,
1710 window: &mut Window,
1711 cx: &mut Context<Self>,
1712 ) -> Self {
1713 debug_assert!(
1714 display_map.is_none() || mode.is_minimap(),
1715 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1716 );
1717
1718 let full_mode = mode.is_full();
1719 let diagnostics_max_severity = if full_mode {
1720 EditorSettings::get_global(cx)
1721 .diagnostics_max_severity
1722 .unwrap_or(DiagnosticSeverity::Hint)
1723 } else {
1724 DiagnosticSeverity::Off
1725 };
1726 let style = window.text_style();
1727 let font_size = style.font_size.to_pixels(window.rem_size());
1728 let editor = cx.entity().downgrade();
1729 let fold_placeholder = FoldPlaceholder {
1730 constrain_width: true,
1731 render: Arc::new(move |fold_id, fold_range, cx| {
1732 let editor = editor.clone();
1733 div()
1734 .id(fold_id)
1735 .bg(cx.theme().colors().ghost_element_background)
1736 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1737 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1738 .rounded_xs()
1739 .size_full()
1740 .cursor_pointer()
1741 .child("⋯")
1742 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1743 .on_click(move |_, _window, cx| {
1744 editor
1745 .update(cx, |editor, cx| {
1746 editor.unfold_ranges(
1747 &[fold_range.start..fold_range.end],
1748 true,
1749 false,
1750 cx,
1751 );
1752 cx.stop_propagation();
1753 })
1754 .ok();
1755 })
1756 .into_any()
1757 }),
1758 merge_adjacent: true,
1759 ..FoldPlaceholder::default()
1760 };
1761 let display_map = display_map.unwrap_or_else(|| {
1762 cx.new(|cx| {
1763 DisplayMap::new(
1764 buffer.clone(),
1765 style.font(),
1766 font_size,
1767 None,
1768 FILE_HEADER_HEIGHT,
1769 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1770 fold_placeholder,
1771 diagnostics_max_severity,
1772 cx,
1773 )
1774 })
1775 });
1776
1777 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1778
1779 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1780
1781 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1782 .then(|| language_settings::SoftWrap::None);
1783
1784 let mut project_subscriptions = Vec::new();
1785 if mode.is_full() {
1786 if let Some(project) = project.as_ref() {
1787 project_subscriptions.push(cx.subscribe_in(
1788 project,
1789 window,
1790 |editor, _, event, window, cx| match event {
1791 project::Event::RefreshCodeLens => {
1792 // we always query lens with actions, without storing them, always refreshing them
1793 }
1794 project::Event::RefreshInlayHints => {
1795 editor
1796 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1797 }
1798 project::Event::LanguageServerAdded(..)
1799 | project::Event::LanguageServerRemoved(..) => {
1800 if editor.tasks_update_task.is_none() {
1801 editor.tasks_update_task =
1802 Some(editor.refresh_runnables(window, cx));
1803 }
1804 editor.pull_diagnostics(None, window, cx);
1805 }
1806 project::Event::SnippetEdit(id, snippet_edits) => {
1807 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1808 let focus_handle = editor.focus_handle(cx);
1809 if focus_handle.is_focused(window) {
1810 let snapshot = buffer.read(cx).snapshot();
1811 for (range, snippet) in snippet_edits {
1812 let editor_range =
1813 language::range_from_lsp(*range).to_offset(&snapshot);
1814 editor
1815 .insert_snippet(
1816 &[editor_range],
1817 snippet.clone(),
1818 window,
1819 cx,
1820 )
1821 .ok();
1822 }
1823 }
1824 }
1825 }
1826 _ => {}
1827 },
1828 ));
1829 if let Some(task_inventory) = project
1830 .read(cx)
1831 .task_store()
1832 .read(cx)
1833 .task_inventory()
1834 .cloned()
1835 {
1836 project_subscriptions.push(cx.observe_in(
1837 &task_inventory,
1838 window,
1839 |editor, _, window, cx| {
1840 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1841 },
1842 ));
1843 };
1844
1845 project_subscriptions.push(cx.subscribe_in(
1846 &project.read(cx).breakpoint_store(),
1847 window,
1848 |editor, _, event, window, cx| match event {
1849 BreakpointStoreEvent::ClearDebugLines => {
1850 editor.clear_row_highlights::<ActiveDebugLine>();
1851 editor.refresh_inline_values(cx);
1852 }
1853 BreakpointStoreEvent::SetDebugLine => {
1854 if editor.go_to_active_debug_line(window, cx) {
1855 cx.stop_propagation();
1856 }
1857
1858 editor.refresh_inline_values(cx);
1859 }
1860 _ => {}
1861 },
1862 ));
1863 }
1864 }
1865
1866 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1867
1868 let inlay_hint_settings =
1869 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1870 let focus_handle = cx.focus_handle();
1871 cx.on_focus(&focus_handle, window, Self::handle_focus)
1872 .detach();
1873 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1874 .detach();
1875 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1876 .detach();
1877 cx.on_blur(&focus_handle, window, Self::handle_blur)
1878 .detach();
1879 cx.observe_pending_input(window, Self::observe_pending_input)
1880 .detach();
1881
1882 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1883 Some(false)
1884 } else {
1885 None
1886 };
1887
1888 let breakpoint_store = match (&mode, project.as_ref()) {
1889 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1890 _ => None,
1891 };
1892
1893 let mut code_action_providers = Vec::new();
1894 let mut load_uncommitted_diff = None;
1895 if let Some(project) = project.clone() {
1896 load_uncommitted_diff = Some(
1897 update_uncommitted_diff_for_buffer(
1898 cx.entity(),
1899 &project,
1900 buffer.read(cx).all_buffers(),
1901 buffer.clone(),
1902 cx,
1903 )
1904 .shared(),
1905 );
1906 code_action_providers.push(Rc::new(project) as Rc<_>);
1907 }
1908
1909 let mut editor = Self {
1910 focus_handle,
1911 show_cursor_when_unfocused: false,
1912 last_focused_descendant: None,
1913 buffer: buffer.clone(),
1914 display_map: display_map.clone(),
1915 selections,
1916 scroll_manager: ScrollManager::new(cx),
1917 columnar_selection_state: None,
1918 add_selections_state: None,
1919 select_next_state: None,
1920 select_prev_state: None,
1921 selection_history: SelectionHistory::default(),
1922 defer_selection_effects: false,
1923 deferred_selection_effects_state: None,
1924 autoclose_regions: Vec::new(),
1925 snippet_stack: InvalidationStack::default(),
1926 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1927 ime_transaction: None,
1928 active_diagnostics: ActiveDiagnostic::None,
1929 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1930 inline_diagnostics_update: Task::ready(()),
1931 inline_diagnostics: Vec::new(),
1932 soft_wrap_mode_override,
1933 diagnostics_max_severity,
1934 hard_wrap: None,
1935 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1936 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1937 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1938 project,
1939 blink_manager: blink_manager.clone(),
1940 show_local_selections: true,
1941 show_scrollbars: ScrollbarAxes {
1942 horizontal: full_mode,
1943 vertical: full_mode,
1944 },
1945 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1946 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1947 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1948 show_gutter: mode.is_full(),
1949 show_line_numbers: None,
1950 use_relative_line_numbers: None,
1951 disable_expand_excerpt_buttons: false,
1952 show_git_diff_gutter: None,
1953 show_code_actions: None,
1954 show_runnables: None,
1955 show_breakpoints: None,
1956 show_wrap_guides: None,
1957 show_indent_guides,
1958 placeholder_text: None,
1959 highlight_order: 0,
1960 highlighted_rows: HashMap::default(),
1961 background_highlights: TreeMap::default(),
1962 gutter_highlights: TreeMap::default(),
1963 scrollbar_marker_state: ScrollbarMarkerState::default(),
1964 active_indent_guides_state: ActiveIndentGuidesState::default(),
1965 nav_history: None,
1966 context_menu: RefCell::new(None),
1967 context_menu_options: None,
1968 mouse_context_menu: None,
1969 completion_tasks: Vec::new(),
1970 inline_blame_popover: None,
1971 inline_blame_popover_show_task: None,
1972 signature_help_state: SignatureHelpState::default(),
1973 auto_signature_help: None,
1974 find_all_references_task_sources: Vec::new(),
1975 next_completion_id: 0,
1976 next_inlay_id: 0,
1977 code_action_providers,
1978 available_code_actions: None,
1979 code_actions_task: None,
1980 quick_selection_highlight_task: None,
1981 debounced_selection_highlight_task: None,
1982 document_highlights_task: None,
1983 linked_editing_range_task: None,
1984 pending_rename: None,
1985 searchable: true,
1986 cursor_shape: EditorSettings::get_global(cx)
1987 .cursor_shape
1988 .unwrap_or_default(),
1989 current_line_highlight: None,
1990 autoindent_mode: Some(AutoindentMode::EachLine),
1991 collapse_matches: false,
1992 workspace: None,
1993 input_enabled: true,
1994 use_modal_editing: mode.is_full(),
1995 read_only: mode.is_minimap(),
1996 use_autoclose: true,
1997 use_auto_surround: true,
1998 auto_replace_emoji_shortcode: false,
1999 jsx_tag_auto_close_enabled_in_any_buffer: false,
2000 leader_id: None,
2001 remote_id: None,
2002 hover_state: HoverState::default(),
2003 pending_mouse_down: None,
2004 hovered_link_state: None,
2005 edit_prediction_provider: None,
2006 active_inline_completion: None,
2007 stale_inline_completion_in_menu: None,
2008 edit_prediction_preview: EditPredictionPreview::Inactive {
2009 released_too_fast: false,
2010 },
2011 inline_diagnostics_enabled: mode.is_full(),
2012 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2013 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2014
2015 gutter_hovered: false,
2016 pixel_position_of_newest_cursor: None,
2017 last_bounds: None,
2018 last_position_map: None,
2019 expect_bounds_change: None,
2020 gutter_dimensions: GutterDimensions::default(),
2021 style: None,
2022 show_cursor_names: false,
2023 hovered_cursors: HashMap::default(),
2024 next_editor_action_id: EditorActionId::default(),
2025 editor_actions: Rc::default(),
2026 inline_completions_hidden_for_vim_mode: false,
2027 show_inline_completions_override: None,
2028 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
2029 edit_prediction_settings: EditPredictionSettings::Disabled,
2030 edit_prediction_indent_conflict: false,
2031 edit_prediction_requires_modifier_in_indent_conflict: true,
2032 custom_context_menu: None,
2033 show_git_blame_gutter: false,
2034 show_git_blame_inline: false,
2035 show_selection_menu: None,
2036 show_git_blame_inline_delay_task: None,
2037 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
2038 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2039 serialize_dirty_buffers: !mode.is_minimap()
2040 && ProjectSettings::get_global(cx)
2041 .session
2042 .restore_unsaved_buffers,
2043 blame: None,
2044 blame_subscription: None,
2045 tasks: BTreeMap::default(),
2046
2047 breakpoint_store,
2048 gutter_breakpoint_indicator: (None, None),
2049 hovered_diff_hunk_row: None,
2050 _subscriptions: vec![
2051 cx.observe(&buffer, Self::on_buffer_changed),
2052 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
2053 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2054 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2055 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2056 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2057 cx.observe_window_activation(window, |editor, window, cx| {
2058 let active = window.is_window_active();
2059 editor.blink_manager.update(cx, |blink_manager, cx| {
2060 if active {
2061 blink_manager.enable(cx);
2062 } else {
2063 blink_manager.disable(cx);
2064 }
2065 });
2066 if active {
2067 editor.show_mouse_cursor(cx);
2068 }
2069 }),
2070 ],
2071 tasks_update_task: None,
2072 pull_diagnostics_task: Task::ready(()),
2073 linked_edit_ranges: Default::default(),
2074 in_project_search: false,
2075 previous_search_ranges: None,
2076 breadcrumb_header: None,
2077 focused_block: None,
2078 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2079 addons: HashMap::default(),
2080 registered_buffers: HashMap::default(),
2081 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2082 selection_mark_mode: false,
2083 toggle_fold_multiple_buffers: Task::ready(()),
2084 serialize_selections: Task::ready(()),
2085 serialize_folds: Task::ready(()),
2086 text_style_refinement: None,
2087 load_diff_task: load_uncommitted_diff,
2088 temporary_diff_override: false,
2089 mouse_cursor_hidden: false,
2090 minimap: None,
2091 hide_mouse_mode: EditorSettings::get_global(cx)
2092 .hide_mouse
2093 .unwrap_or_default(),
2094 change_list: ChangeList::new(),
2095 mode,
2096 selection_drag_state: SelectionDragState::None,
2097 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2098 };
2099 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2100 editor
2101 ._subscriptions
2102 .push(cx.observe(breakpoints, |_, _, cx| {
2103 cx.notify();
2104 }));
2105 }
2106 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2107 editor._subscriptions.extend(project_subscriptions);
2108
2109 editor._subscriptions.push(cx.subscribe_in(
2110 &cx.entity(),
2111 window,
2112 |editor, _, e: &EditorEvent, window, cx| match e {
2113 EditorEvent::ScrollPositionChanged { local, .. } => {
2114 if *local {
2115 let new_anchor = editor.scroll_manager.anchor();
2116 let snapshot = editor.snapshot(window, cx);
2117 editor.update_restoration_data(cx, move |data| {
2118 data.scroll_position = (
2119 new_anchor.top_row(&snapshot.buffer_snapshot),
2120 new_anchor.offset,
2121 );
2122 });
2123 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2124 editor.inline_blame_popover.take();
2125 }
2126 }
2127 EditorEvent::Edited { .. } => {
2128 if !vim_enabled(cx) {
2129 let (map, selections) = editor.selections.all_adjusted_display(cx);
2130 let pop_state = editor
2131 .change_list
2132 .last()
2133 .map(|previous| {
2134 previous.len() == selections.len()
2135 && previous.iter().enumerate().all(|(ix, p)| {
2136 p.to_display_point(&map).row()
2137 == selections[ix].head().row()
2138 })
2139 })
2140 .unwrap_or(false);
2141 let new_positions = selections
2142 .into_iter()
2143 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2144 .collect();
2145 editor
2146 .change_list
2147 .push_to_change_list(pop_state, new_positions);
2148 }
2149 }
2150 _ => (),
2151 },
2152 ));
2153
2154 if let Some(dap_store) = editor
2155 .project
2156 .as_ref()
2157 .map(|project| project.read(cx).dap_store())
2158 {
2159 let weak_editor = cx.weak_entity();
2160
2161 editor
2162 ._subscriptions
2163 .push(
2164 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2165 let session_entity = cx.entity();
2166 weak_editor
2167 .update(cx, |editor, cx| {
2168 editor._subscriptions.push(
2169 cx.subscribe(&session_entity, Self::on_debug_session_event),
2170 );
2171 })
2172 .ok();
2173 }),
2174 );
2175
2176 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2177 editor
2178 ._subscriptions
2179 .push(cx.subscribe(&session, Self::on_debug_session_event));
2180 }
2181 }
2182
2183 // skip adding the initial selection to selection history
2184 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2185 editor.end_selection(window, cx);
2186 editor.selection_history.mode = SelectionHistoryMode::Normal;
2187
2188 editor.scroll_manager.show_scrollbars(window, cx);
2189 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2190
2191 if full_mode {
2192 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2193 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2194
2195 if editor.git_blame_inline_enabled {
2196 editor.start_git_blame_inline(false, window, cx);
2197 }
2198
2199 editor.go_to_active_debug_line(window, cx);
2200
2201 if let Some(buffer) = buffer.read(cx).as_singleton() {
2202 if let Some(project) = editor.project.as_ref() {
2203 let handle = project.update(cx, |project, cx| {
2204 project.register_buffer_with_language_servers(&buffer, cx)
2205 });
2206 editor
2207 .registered_buffers
2208 .insert(buffer.read(cx).remote_id(), handle);
2209 }
2210 }
2211
2212 editor.minimap =
2213 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2214 editor.pull_diagnostics(None, window, cx);
2215 }
2216
2217 editor.report_editor_event("Editor Opened", None, cx);
2218 editor
2219 }
2220
2221 pub fn deploy_mouse_context_menu(
2222 &mut self,
2223 position: gpui::Point<Pixels>,
2224 context_menu: Entity<ContextMenu>,
2225 window: &mut Window,
2226 cx: &mut Context<Self>,
2227 ) {
2228 self.mouse_context_menu = Some(MouseContextMenu::new(
2229 self,
2230 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2231 context_menu,
2232 window,
2233 cx,
2234 ));
2235 }
2236
2237 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2238 self.mouse_context_menu
2239 .as_ref()
2240 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2241 }
2242
2243 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2244 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2245 }
2246
2247 fn key_context_internal(
2248 &self,
2249 has_active_edit_prediction: bool,
2250 window: &Window,
2251 cx: &App,
2252 ) -> KeyContext {
2253 let mut key_context = KeyContext::new_with_defaults();
2254 key_context.add("Editor");
2255 let mode = match self.mode {
2256 EditorMode::SingleLine { .. } => "single_line",
2257 EditorMode::AutoHeight { .. } => "auto_height",
2258 EditorMode::Minimap { .. } => "minimap",
2259 EditorMode::Full { .. } => "full",
2260 };
2261
2262 if EditorSettings::jupyter_enabled(cx) {
2263 key_context.add("jupyter");
2264 }
2265
2266 key_context.set("mode", mode);
2267 if self.pending_rename.is_some() {
2268 key_context.add("renaming");
2269 }
2270
2271 match self.context_menu.borrow().as_ref() {
2272 Some(CodeContextMenu::Completions(_)) => {
2273 key_context.add("menu");
2274 key_context.add("showing_completions");
2275 }
2276 Some(CodeContextMenu::CodeActions(_)) => {
2277 key_context.add("menu");
2278 key_context.add("showing_code_actions")
2279 }
2280 None => {}
2281 }
2282
2283 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2284 if !self.focus_handle(cx).contains_focused(window, cx)
2285 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2286 {
2287 for addon in self.addons.values() {
2288 addon.extend_key_context(&mut key_context, cx)
2289 }
2290 }
2291
2292 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2293 if let Some(extension) = singleton_buffer
2294 .read(cx)
2295 .file()
2296 .and_then(|file| file.path().extension()?.to_str())
2297 {
2298 key_context.set("extension", extension.to_string());
2299 }
2300 } else {
2301 key_context.add("multibuffer");
2302 }
2303
2304 if has_active_edit_prediction {
2305 if self.edit_prediction_in_conflict() {
2306 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2307 } else {
2308 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2309 key_context.add("copilot_suggestion");
2310 }
2311 }
2312
2313 if self.selection_mark_mode {
2314 key_context.add("selection_mode");
2315 }
2316
2317 key_context
2318 }
2319
2320 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2321 if self.mouse_cursor_hidden {
2322 self.mouse_cursor_hidden = false;
2323 cx.notify();
2324 }
2325 }
2326
2327 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2328 let hide_mouse_cursor = match origin {
2329 HideMouseCursorOrigin::TypingAction => {
2330 matches!(
2331 self.hide_mouse_mode,
2332 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2333 )
2334 }
2335 HideMouseCursorOrigin::MovementAction => {
2336 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2337 }
2338 };
2339 if self.mouse_cursor_hidden != hide_mouse_cursor {
2340 self.mouse_cursor_hidden = hide_mouse_cursor;
2341 cx.notify();
2342 }
2343 }
2344
2345 pub fn edit_prediction_in_conflict(&self) -> bool {
2346 if !self.show_edit_predictions_in_menu() {
2347 return false;
2348 }
2349
2350 let showing_completions = self
2351 .context_menu
2352 .borrow()
2353 .as_ref()
2354 .map_or(false, |context| {
2355 matches!(context, CodeContextMenu::Completions(_))
2356 });
2357
2358 showing_completions
2359 || self.edit_prediction_requires_modifier()
2360 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2361 // bindings to insert tab characters.
2362 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2363 }
2364
2365 pub fn accept_edit_prediction_keybind(
2366 &self,
2367 accept_partial: bool,
2368 window: &Window,
2369 cx: &App,
2370 ) -> AcceptEditPredictionBinding {
2371 let key_context = self.key_context_internal(true, window, cx);
2372 let in_conflict = self.edit_prediction_in_conflict();
2373
2374 let bindings = if accept_partial {
2375 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2376 } else {
2377 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2378 };
2379
2380 // TODO: if the binding contains multiple keystrokes, display all of them, not
2381 // just the first one.
2382 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2383 !in_conflict
2384 || binding
2385 .keystrokes()
2386 .first()
2387 .map_or(false, |keystroke| keystroke.modifiers.modified())
2388 }))
2389 }
2390
2391 pub fn new_file(
2392 workspace: &mut Workspace,
2393 _: &workspace::NewFile,
2394 window: &mut Window,
2395 cx: &mut Context<Workspace>,
2396 ) {
2397 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2398 "Failed to create buffer",
2399 window,
2400 cx,
2401 |e, _, _| match e.error_code() {
2402 ErrorCode::RemoteUpgradeRequired => Some(format!(
2403 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2404 e.error_tag("required").unwrap_or("the latest version")
2405 )),
2406 _ => None,
2407 },
2408 );
2409 }
2410
2411 pub fn new_in_workspace(
2412 workspace: &mut Workspace,
2413 window: &mut Window,
2414 cx: &mut Context<Workspace>,
2415 ) -> Task<Result<Entity<Editor>>> {
2416 let project = workspace.project().clone();
2417 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2418
2419 cx.spawn_in(window, async move |workspace, cx| {
2420 let buffer = create.await?;
2421 workspace.update_in(cx, |workspace, window, cx| {
2422 let editor =
2423 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2424 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2425 editor
2426 })
2427 })
2428 }
2429
2430 fn new_file_vertical(
2431 workspace: &mut Workspace,
2432 _: &workspace::NewFileSplitVertical,
2433 window: &mut Window,
2434 cx: &mut Context<Workspace>,
2435 ) {
2436 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2437 }
2438
2439 fn new_file_horizontal(
2440 workspace: &mut Workspace,
2441 _: &workspace::NewFileSplitHorizontal,
2442 window: &mut Window,
2443 cx: &mut Context<Workspace>,
2444 ) {
2445 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2446 }
2447
2448 fn new_file_in_direction(
2449 workspace: &mut Workspace,
2450 direction: SplitDirection,
2451 window: &mut Window,
2452 cx: &mut Context<Workspace>,
2453 ) {
2454 let project = workspace.project().clone();
2455 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2456
2457 cx.spawn_in(window, async move |workspace, cx| {
2458 let buffer = create.await?;
2459 workspace.update_in(cx, move |workspace, window, cx| {
2460 workspace.split_item(
2461 direction,
2462 Box::new(
2463 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2464 ),
2465 window,
2466 cx,
2467 )
2468 })?;
2469 anyhow::Ok(())
2470 })
2471 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2472 match e.error_code() {
2473 ErrorCode::RemoteUpgradeRequired => Some(format!(
2474 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2475 e.error_tag("required").unwrap_or("the latest version")
2476 )),
2477 _ => None,
2478 }
2479 });
2480 }
2481
2482 pub fn leader_id(&self) -> Option<CollaboratorId> {
2483 self.leader_id
2484 }
2485
2486 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2487 &self.buffer
2488 }
2489
2490 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2491 self.workspace.as_ref()?.0.upgrade()
2492 }
2493
2494 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2495 self.buffer().read(cx).title(cx)
2496 }
2497
2498 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2499 let git_blame_gutter_max_author_length = self
2500 .render_git_blame_gutter(cx)
2501 .then(|| {
2502 if let Some(blame) = self.blame.as_ref() {
2503 let max_author_length =
2504 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2505 Some(max_author_length)
2506 } else {
2507 None
2508 }
2509 })
2510 .flatten();
2511
2512 EditorSnapshot {
2513 mode: self.mode.clone(),
2514 show_gutter: self.show_gutter,
2515 show_line_numbers: self.show_line_numbers,
2516 show_git_diff_gutter: self.show_git_diff_gutter,
2517 show_code_actions: self.show_code_actions,
2518 show_runnables: self.show_runnables,
2519 show_breakpoints: self.show_breakpoints,
2520 git_blame_gutter_max_author_length,
2521 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2522 scroll_anchor: self.scroll_manager.anchor(),
2523 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2524 placeholder_text: self.placeholder_text.clone(),
2525 is_focused: self.focus_handle.is_focused(window),
2526 current_line_highlight: self
2527 .current_line_highlight
2528 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2529 gutter_hovered: self.gutter_hovered,
2530 }
2531 }
2532
2533 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2534 self.buffer.read(cx).language_at(point, cx)
2535 }
2536
2537 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2538 self.buffer.read(cx).read(cx).file_at(point).cloned()
2539 }
2540
2541 pub fn active_excerpt(
2542 &self,
2543 cx: &App,
2544 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2545 self.buffer
2546 .read(cx)
2547 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2548 }
2549
2550 pub fn mode(&self) -> &EditorMode {
2551 &self.mode
2552 }
2553
2554 pub fn set_mode(&mut self, mode: EditorMode) {
2555 self.mode = mode;
2556 }
2557
2558 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2559 self.collaboration_hub.as_deref()
2560 }
2561
2562 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2563 self.collaboration_hub = Some(hub);
2564 }
2565
2566 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2567 self.in_project_search = in_project_search;
2568 }
2569
2570 pub fn set_custom_context_menu(
2571 &mut self,
2572 f: impl 'static
2573 + Fn(
2574 &mut Self,
2575 DisplayPoint,
2576 &mut Window,
2577 &mut Context<Self>,
2578 ) -> Option<Entity<ui::ContextMenu>>,
2579 ) {
2580 self.custom_context_menu = Some(Box::new(f))
2581 }
2582
2583 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2584 self.completion_provider = provider;
2585 }
2586
2587 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2588 self.semantics_provider.clone()
2589 }
2590
2591 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2592 self.semantics_provider = provider;
2593 }
2594
2595 pub fn set_edit_prediction_provider<T>(
2596 &mut self,
2597 provider: Option<Entity<T>>,
2598 window: &mut Window,
2599 cx: &mut Context<Self>,
2600 ) where
2601 T: EditPredictionProvider,
2602 {
2603 self.edit_prediction_provider =
2604 provider.map(|provider| RegisteredInlineCompletionProvider {
2605 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2606 if this.focus_handle.is_focused(window) {
2607 this.update_visible_inline_completion(window, cx);
2608 }
2609 }),
2610 provider: Arc::new(provider),
2611 });
2612 self.update_edit_prediction_settings(cx);
2613 self.refresh_inline_completion(false, false, window, cx);
2614 }
2615
2616 pub fn placeholder_text(&self) -> Option<&str> {
2617 self.placeholder_text.as_deref()
2618 }
2619
2620 pub fn set_placeholder_text(
2621 &mut self,
2622 placeholder_text: impl Into<Arc<str>>,
2623 cx: &mut Context<Self>,
2624 ) {
2625 let placeholder_text = Some(placeholder_text.into());
2626 if self.placeholder_text != placeholder_text {
2627 self.placeholder_text = placeholder_text;
2628 cx.notify();
2629 }
2630 }
2631
2632 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2633 self.cursor_shape = cursor_shape;
2634
2635 // Disrupt blink for immediate user feedback that the cursor shape has changed
2636 self.blink_manager.update(cx, BlinkManager::show_cursor);
2637
2638 cx.notify();
2639 }
2640
2641 pub fn set_current_line_highlight(
2642 &mut self,
2643 current_line_highlight: Option<CurrentLineHighlight>,
2644 ) {
2645 self.current_line_highlight = current_line_highlight;
2646 }
2647
2648 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2649 self.collapse_matches = collapse_matches;
2650 }
2651
2652 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2653 let buffers = self.buffer.read(cx).all_buffers();
2654 let Some(project) = self.project.as_ref() else {
2655 return;
2656 };
2657 project.update(cx, |project, cx| {
2658 for buffer in buffers {
2659 self.registered_buffers
2660 .entry(buffer.read(cx).remote_id())
2661 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2662 }
2663 })
2664 }
2665
2666 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2667 if self.collapse_matches {
2668 return range.start..range.start;
2669 }
2670 range.clone()
2671 }
2672
2673 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2674 if self.display_map.read(cx).clip_at_line_ends != clip {
2675 self.display_map
2676 .update(cx, |map, _| map.clip_at_line_ends = clip);
2677 }
2678 }
2679
2680 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2681 self.input_enabled = input_enabled;
2682 }
2683
2684 pub fn set_inline_completions_hidden_for_vim_mode(
2685 &mut self,
2686 hidden: bool,
2687 window: &mut Window,
2688 cx: &mut Context<Self>,
2689 ) {
2690 if hidden != self.inline_completions_hidden_for_vim_mode {
2691 self.inline_completions_hidden_for_vim_mode = hidden;
2692 if hidden {
2693 self.update_visible_inline_completion(window, cx);
2694 } else {
2695 self.refresh_inline_completion(true, false, window, cx);
2696 }
2697 }
2698 }
2699
2700 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2701 self.menu_inline_completions_policy = value;
2702 }
2703
2704 pub fn set_autoindent(&mut self, autoindent: bool) {
2705 if autoindent {
2706 self.autoindent_mode = Some(AutoindentMode::EachLine);
2707 } else {
2708 self.autoindent_mode = None;
2709 }
2710 }
2711
2712 pub fn read_only(&self, cx: &App) -> bool {
2713 self.read_only || self.buffer.read(cx).read_only()
2714 }
2715
2716 pub fn set_read_only(&mut self, read_only: bool) {
2717 self.read_only = read_only;
2718 }
2719
2720 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2721 self.use_autoclose = autoclose;
2722 }
2723
2724 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2725 self.use_auto_surround = auto_surround;
2726 }
2727
2728 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2729 self.auto_replace_emoji_shortcode = auto_replace;
2730 }
2731
2732 pub fn toggle_edit_predictions(
2733 &mut self,
2734 _: &ToggleEditPrediction,
2735 window: &mut Window,
2736 cx: &mut Context<Self>,
2737 ) {
2738 if self.show_inline_completions_override.is_some() {
2739 self.set_show_edit_predictions(None, window, cx);
2740 } else {
2741 let show_edit_predictions = !self.edit_predictions_enabled();
2742 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2743 }
2744 }
2745
2746 pub fn set_show_edit_predictions(
2747 &mut self,
2748 show_edit_predictions: Option<bool>,
2749 window: &mut Window,
2750 cx: &mut Context<Self>,
2751 ) {
2752 self.show_inline_completions_override = show_edit_predictions;
2753 self.update_edit_prediction_settings(cx);
2754
2755 if let Some(false) = show_edit_predictions {
2756 self.discard_inline_completion(false, cx);
2757 } else {
2758 self.refresh_inline_completion(false, true, window, cx);
2759 }
2760 }
2761
2762 fn inline_completions_disabled_in_scope(
2763 &self,
2764 buffer: &Entity<Buffer>,
2765 buffer_position: language::Anchor,
2766 cx: &App,
2767 ) -> bool {
2768 let snapshot = buffer.read(cx).snapshot();
2769 let settings = snapshot.settings_at(buffer_position, cx);
2770
2771 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2772 return false;
2773 };
2774
2775 scope.override_name().map_or(false, |scope_name| {
2776 settings
2777 .edit_predictions_disabled_in
2778 .iter()
2779 .any(|s| s == scope_name)
2780 })
2781 }
2782
2783 pub fn set_use_modal_editing(&mut self, to: bool) {
2784 self.use_modal_editing = to;
2785 }
2786
2787 pub fn use_modal_editing(&self) -> bool {
2788 self.use_modal_editing
2789 }
2790
2791 fn selections_did_change(
2792 &mut self,
2793 local: bool,
2794 old_cursor_position: &Anchor,
2795 effects: SelectionEffects,
2796 window: &mut Window,
2797 cx: &mut Context<Self>,
2798 ) {
2799 window.invalidate_character_coordinates();
2800
2801 // Copy selections to primary selection buffer
2802 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2803 if local {
2804 let selections = self.selections.all::<usize>(cx);
2805 let buffer_handle = self.buffer.read(cx).read(cx);
2806
2807 let mut text = String::new();
2808 for (index, selection) in selections.iter().enumerate() {
2809 let text_for_selection = buffer_handle
2810 .text_for_range(selection.start..selection.end)
2811 .collect::<String>();
2812
2813 text.push_str(&text_for_selection);
2814 if index != selections.len() - 1 {
2815 text.push('\n');
2816 }
2817 }
2818
2819 if !text.is_empty() {
2820 cx.write_to_primary(ClipboardItem::new_string(text));
2821 }
2822 }
2823
2824 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2825 self.buffer.update(cx, |buffer, cx| {
2826 buffer.set_active_selections(
2827 &self.selections.disjoint_anchors(),
2828 self.selections.line_mode,
2829 self.cursor_shape,
2830 cx,
2831 )
2832 });
2833 }
2834 let display_map = self
2835 .display_map
2836 .update(cx, |display_map, cx| display_map.snapshot(cx));
2837 let buffer = &display_map.buffer_snapshot;
2838 if self.selections.count() == 1 {
2839 self.add_selections_state = None;
2840 }
2841 self.select_next_state = None;
2842 self.select_prev_state = None;
2843 self.select_syntax_node_history.try_clear();
2844 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2845 self.snippet_stack
2846 .invalidate(&self.selections.disjoint_anchors(), buffer);
2847 self.take_rename(false, window, cx);
2848
2849 let newest_selection = self.selections.newest_anchor();
2850 let new_cursor_position = newest_selection.head();
2851 let selection_start = newest_selection.start;
2852
2853 if effects.nav_history {
2854 self.push_to_nav_history(
2855 *old_cursor_position,
2856 Some(new_cursor_position.to_point(buffer)),
2857 false,
2858 cx,
2859 );
2860 }
2861
2862 if local {
2863 if let Some(buffer_id) = new_cursor_position.buffer_id {
2864 if !self.registered_buffers.contains_key(&buffer_id) {
2865 if let Some(project) = self.project.as_ref() {
2866 project.update(cx, |project, cx| {
2867 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2868 return;
2869 };
2870 self.registered_buffers.insert(
2871 buffer_id,
2872 project.register_buffer_with_language_servers(&buffer, cx),
2873 );
2874 })
2875 }
2876 }
2877 }
2878
2879 let mut context_menu = self.context_menu.borrow_mut();
2880 let completion_menu = match context_menu.as_ref() {
2881 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2882 Some(CodeContextMenu::CodeActions(_)) => {
2883 *context_menu = None;
2884 None
2885 }
2886 None => None,
2887 };
2888 let completion_position = completion_menu.map(|menu| menu.initial_position);
2889 drop(context_menu);
2890
2891 if effects.completions {
2892 if let Some(completion_position) = completion_position {
2893 let start_offset = selection_start.to_offset(buffer);
2894 let position_matches = start_offset == completion_position.to_offset(buffer);
2895 let continue_showing = if position_matches {
2896 if self.snippet_stack.is_empty() {
2897 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2898 } else {
2899 // Snippet choices can be shown even when the cursor is in whitespace.
2900 // Dismissing the menu with actions like backspace is handled by
2901 // invalidation regions.
2902 true
2903 }
2904 } else {
2905 false
2906 };
2907
2908 if continue_showing {
2909 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2910 } else {
2911 self.hide_context_menu(window, cx);
2912 }
2913 }
2914 }
2915
2916 hide_hover(self, cx);
2917
2918 if old_cursor_position.to_display_point(&display_map).row()
2919 != new_cursor_position.to_display_point(&display_map).row()
2920 {
2921 self.available_code_actions.take();
2922 }
2923 self.refresh_code_actions(window, cx);
2924 self.refresh_document_highlights(cx);
2925 self.refresh_selected_text_highlights(false, window, cx);
2926 refresh_matching_bracket_highlights(self, window, cx);
2927 self.update_visible_inline_completion(window, cx);
2928 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2929 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2930 self.inline_blame_popover.take();
2931 if self.git_blame_inline_enabled {
2932 self.start_inline_blame_timer(window, cx);
2933 }
2934 }
2935
2936 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2937 cx.emit(EditorEvent::SelectionsChanged { local });
2938
2939 let selections = &self.selections.disjoint;
2940 if selections.len() == 1 {
2941 cx.emit(SearchEvent::ActiveMatchChanged)
2942 }
2943 if local {
2944 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2945 let inmemory_selections = selections
2946 .iter()
2947 .map(|s| {
2948 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2949 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2950 })
2951 .collect();
2952 self.update_restoration_data(cx, |data| {
2953 data.selections = inmemory_selections;
2954 });
2955
2956 if WorkspaceSettings::get(None, cx).restore_on_startup
2957 != RestoreOnStartupBehavior::None
2958 {
2959 if let Some(workspace_id) =
2960 self.workspace.as_ref().and_then(|workspace| workspace.1)
2961 {
2962 let snapshot = self.buffer().read(cx).snapshot(cx);
2963 let selections = selections.clone();
2964 let background_executor = cx.background_executor().clone();
2965 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2966 self.serialize_selections = cx.background_spawn(async move {
2967 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2968 let db_selections = selections
2969 .iter()
2970 .map(|selection| {
2971 (
2972 selection.start.to_offset(&snapshot),
2973 selection.end.to_offset(&snapshot),
2974 )
2975 })
2976 .collect();
2977
2978 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2979 .await
2980 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2981 .log_err();
2982 });
2983 }
2984 }
2985 }
2986 }
2987
2988 cx.notify();
2989 }
2990
2991 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2992 use text::ToOffset as _;
2993 use text::ToPoint as _;
2994
2995 if self.mode.is_minimap()
2996 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2997 {
2998 return;
2999 }
3000
3001 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
3002 return;
3003 };
3004
3005 let snapshot = singleton.read(cx).snapshot();
3006 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
3007 let display_snapshot = display_map.snapshot(cx);
3008
3009 display_snapshot
3010 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
3011 .map(|fold| {
3012 fold.range.start.text_anchor.to_point(&snapshot)
3013 ..fold.range.end.text_anchor.to_point(&snapshot)
3014 })
3015 .collect()
3016 });
3017 self.update_restoration_data(cx, |data| {
3018 data.folds = inmemory_folds;
3019 });
3020
3021 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3022 return;
3023 };
3024 let background_executor = cx.background_executor().clone();
3025 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3026 let db_folds = self.display_map.update(cx, |display_map, cx| {
3027 display_map
3028 .snapshot(cx)
3029 .folds_in_range(0..snapshot.len())
3030 .map(|fold| {
3031 (
3032 fold.range.start.text_anchor.to_offset(&snapshot),
3033 fold.range.end.text_anchor.to_offset(&snapshot),
3034 )
3035 })
3036 .collect()
3037 });
3038 self.serialize_folds = cx.background_spawn(async move {
3039 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3040 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3041 .await
3042 .with_context(|| {
3043 format!(
3044 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3045 )
3046 })
3047 .log_err();
3048 });
3049 }
3050
3051 pub fn sync_selections(
3052 &mut self,
3053 other: Entity<Editor>,
3054 cx: &mut Context<Self>,
3055 ) -> gpui::Subscription {
3056 let other_selections = other.read(cx).selections.disjoint.to_vec();
3057 self.selections.change_with(cx, |selections| {
3058 selections.select_anchors(other_selections);
3059 });
3060
3061 let other_subscription =
3062 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
3063 EditorEvent::SelectionsChanged { local: true } => {
3064 let other_selections = other.read(cx).selections.disjoint.to_vec();
3065 if other_selections.is_empty() {
3066 return;
3067 }
3068 this.selections.change_with(cx, |selections| {
3069 selections.select_anchors(other_selections);
3070 });
3071 }
3072 _ => {}
3073 });
3074
3075 let this_subscription =
3076 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
3077 EditorEvent::SelectionsChanged { local: true } => {
3078 let these_selections = this.selections.disjoint.to_vec();
3079 if these_selections.is_empty() {
3080 return;
3081 }
3082 other.update(cx, |other_editor, cx| {
3083 other_editor.selections.change_with(cx, |selections| {
3084 selections.select_anchors(these_selections);
3085 })
3086 });
3087 }
3088 _ => {}
3089 });
3090
3091 Subscription::join(other_subscription, this_subscription)
3092 }
3093
3094 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3095 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3096 /// effects of selection change occur at the end of the transaction.
3097 pub fn change_selections<R>(
3098 &mut self,
3099 effects: impl Into<SelectionEffects>,
3100 window: &mut Window,
3101 cx: &mut Context<Self>,
3102 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3103 ) -> R {
3104 let effects = effects.into();
3105 if let Some(state) = &mut self.deferred_selection_effects_state {
3106 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3107 state.effects.completions = effects.completions;
3108 state.effects.nav_history |= effects.nav_history;
3109 let (changed, result) = self.selections.change_with(cx, change);
3110 state.changed |= changed;
3111 return result;
3112 }
3113 let mut state = DeferredSelectionEffectsState {
3114 changed: false,
3115 effects,
3116 old_cursor_position: self.selections.newest_anchor().head(),
3117 history_entry: SelectionHistoryEntry {
3118 selections: self.selections.disjoint_anchors(),
3119 select_next_state: self.select_next_state.clone(),
3120 select_prev_state: self.select_prev_state.clone(),
3121 add_selections_state: self.add_selections_state.clone(),
3122 },
3123 };
3124 let (changed, result) = self.selections.change_with(cx, change);
3125 state.changed = state.changed || changed;
3126 if self.defer_selection_effects {
3127 self.deferred_selection_effects_state = Some(state);
3128 } else {
3129 self.apply_selection_effects(state, window, cx);
3130 }
3131 result
3132 }
3133
3134 /// Defers the effects of selection change, so that the effects of multiple calls to
3135 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3136 /// to selection history and the state of popovers based on selection position aren't
3137 /// erroneously updated.
3138 pub fn with_selection_effects_deferred<R>(
3139 &mut self,
3140 window: &mut Window,
3141 cx: &mut Context<Self>,
3142 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3143 ) -> R {
3144 let already_deferred = self.defer_selection_effects;
3145 self.defer_selection_effects = true;
3146 let result = update(self, window, cx);
3147 if !already_deferred {
3148 self.defer_selection_effects = false;
3149 if let Some(state) = self.deferred_selection_effects_state.take() {
3150 self.apply_selection_effects(state, window, cx);
3151 }
3152 }
3153 result
3154 }
3155
3156 fn apply_selection_effects(
3157 &mut self,
3158 state: DeferredSelectionEffectsState,
3159 window: &mut Window,
3160 cx: &mut Context<Self>,
3161 ) {
3162 if state.changed {
3163 self.selection_history.push(state.history_entry);
3164
3165 if let Some(autoscroll) = state.effects.scroll {
3166 self.request_autoscroll(autoscroll, cx);
3167 }
3168
3169 let old_cursor_position = &state.old_cursor_position;
3170
3171 self.selections_did_change(true, &old_cursor_position, state.effects, window, cx);
3172
3173 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3174 self.show_signature_help(&ShowSignatureHelp, window, cx);
3175 }
3176 }
3177 }
3178
3179 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3180 where
3181 I: IntoIterator<Item = (Range<S>, T)>,
3182 S: ToOffset,
3183 T: Into<Arc<str>>,
3184 {
3185 if self.read_only(cx) {
3186 return;
3187 }
3188
3189 self.buffer
3190 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3191 }
3192
3193 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3194 where
3195 I: IntoIterator<Item = (Range<S>, T)>,
3196 S: ToOffset,
3197 T: Into<Arc<str>>,
3198 {
3199 if self.read_only(cx) {
3200 return;
3201 }
3202
3203 self.buffer.update(cx, |buffer, cx| {
3204 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3205 });
3206 }
3207
3208 pub fn edit_with_block_indent<I, S, T>(
3209 &mut self,
3210 edits: I,
3211 original_indent_columns: Vec<Option<u32>>,
3212 cx: &mut Context<Self>,
3213 ) where
3214 I: IntoIterator<Item = (Range<S>, T)>,
3215 S: ToOffset,
3216 T: Into<Arc<str>>,
3217 {
3218 if self.read_only(cx) {
3219 return;
3220 }
3221
3222 self.buffer.update(cx, |buffer, cx| {
3223 buffer.edit(
3224 edits,
3225 Some(AutoindentMode::Block {
3226 original_indent_columns,
3227 }),
3228 cx,
3229 )
3230 });
3231 }
3232
3233 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3234 self.hide_context_menu(window, cx);
3235
3236 match phase {
3237 SelectPhase::Begin {
3238 position,
3239 add,
3240 click_count,
3241 } => self.begin_selection(position, add, click_count, window, cx),
3242 SelectPhase::BeginColumnar {
3243 position,
3244 goal_column,
3245 reset,
3246 mode,
3247 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3248 SelectPhase::Extend {
3249 position,
3250 click_count,
3251 } => self.extend_selection(position, click_count, window, cx),
3252 SelectPhase::Update {
3253 position,
3254 goal_column,
3255 scroll_delta,
3256 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3257 SelectPhase::End => self.end_selection(window, cx),
3258 }
3259 }
3260
3261 fn extend_selection(
3262 &mut self,
3263 position: DisplayPoint,
3264 click_count: usize,
3265 window: &mut Window,
3266 cx: &mut Context<Self>,
3267 ) {
3268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3269 let tail = self.selections.newest::<usize>(cx).tail();
3270 self.begin_selection(position, false, click_count, window, cx);
3271
3272 let position = position.to_offset(&display_map, Bias::Left);
3273 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3274
3275 let mut pending_selection = self
3276 .selections
3277 .pending_anchor()
3278 .expect("extend_selection not called with pending selection");
3279 if position >= tail {
3280 pending_selection.start = tail_anchor;
3281 } else {
3282 pending_selection.end = tail_anchor;
3283 pending_selection.reversed = true;
3284 }
3285
3286 let mut pending_mode = self.selections.pending_mode().unwrap();
3287 match &mut pending_mode {
3288 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3289 _ => {}
3290 }
3291
3292 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3293 SelectionEffects::scroll(Autoscroll::fit())
3294 } else {
3295 SelectionEffects::no_scroll()
3296 };
3297
3298 self.change_selections(effects, window, cx, |s| {
3299 s.set_pending(pending_selection, pending_mode)
3300 });
3301 }
3302
3303 fn begin_selection(
3304 &mut self,
3305 position: DisplayPoint,
3306 add: bool,
3307 click_count: usize,
3308 window: &mut Window,
3309 cx: &mut Context<Self>,
3310 ) {
3311 if !self.focus_handle.is_focused(window) {
3312 self.last_focused_descendant = None;
3313 window.focus(&self.focus_handle);
3314 }
3315
3316 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3317 let buffer = &display_map.buffer_snapshot;
3318 let position = display_map.clip_point(position, Bias::Left);
3319
3320 let start;
3321 let end;
3322 let mode;
3323 let mut auto_scroll;
3324 match click_count {
3325 1 => {
3326 start = buffer.anchor_before(position.to_point(&display_map));
3327 end = start;
3328 mode = SelectMode::Character;
3329 auto_scroll = true;
3330 }
3331 2 => {
3332 let range = movement::surrounding_word(&display_map, position);
3333 start = buffer.anchor_before(range.start.to_point(&display_map));
3334 end = buffer.anchor_before(range.end.to_point(&display_map));
3335 mode = SelectMode::Word(start..end);
3336 auto_scroll = true;
3337 }
3338 3 => {
3339 let position = display_map
3340 .clip_point(position, Bias::Left)
3341 .to_point(&display_map);
3342 let line_start = display_map.prev_line_boundary(position).0;
3343 let next_line_start = buffer.clip_point(
3344 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3345 Bias::Left,
3346 );
3347 start = buffer.anchor_before(line_start);
3348 end = buffer.anchor_before(next_line_start);
3349 mode = SelectMode::Line(start..end);
3350 auto_scroll = true;
3351 }
3352 _ => {
3353 start = buffer.anchor_before(0);
3354 end = buffer.anchor_before(buffer.len());
3355 mode = SelectMode::All;
3356 auto_scroll = false;
3357 }
3358 }
3359 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3360
3361 let point_to_delete: Option<usize> = {
3362 let selected_points: Vec<Selection<Point>> =
3363 self.selections.disjoint_in_range(start..end, cx);
3364
3365 if !add || click_count > 1 {
3366 None
3367 } else if !selected_points.is_empty() {
3368 Some(selected_points[0].id)
3369 } else {
3370 let clicked_point_already_selected =
3371 self.selections.disjoint.iter().find(|selection| {
3372 selection.start.to_point(buffer) == start.to_point(buffer)
3373 || selection.end.to_point(buffer) == end.to_point(buffer)
3374 });
3375
3376 clicked_point_already_selected.map(|selection| selection.id)
3377 }
3378 };
3379
3380 let selections_count = self.selections.count();
3381
3382 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3383 if let Some(point_to_delete) = point_to_delete {
3384 s.delete(point_to_delete);
3385
3386 if selections_count == 1 {
3387 s.set_pending_anchor_range(start..end, mode);
3388 }
3389 } else {
3390 if !add {
3391 s.clear_disjoint();
3392 }
3393
3394 s.set_pending_anchor_range(start..end, mode);
3395 }
3396 });
3397 }
3398
3399 fn begin_columnar_selection(
3400 &mut self,
3401 position: DisplayPoint,
3402 goal_column: u32,
3403 reset: bool,
3404 mode: ColumnarMode,
3405 window: &mut Window,
3406 cx: &mut Context<Self>,
3407 ) {
3408 if !self.focus_handle.is_focused(window) {
3409 self.last_focused_descendant = None;
3410 window.focus(&self.focus_handle);
3411 }
3412
3413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3414
3415 if reset {
3416 let pointer_position = display_map
3417 .buffer_snapshot
3418 .anchor_before(position.to_point(&display_map));
3419
3420 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3421 s.clear_disjoint();
3422 s.set_pending_anchor_range(
3423 pointer_position..pointer_position,
3424 SelectMode::Character,
3425 );
3426 });
3427 };
3428
3429 let tail = self.selections.newest::<Point>(cx).tail();
3430 let selection_anchor = display_map.buffer_snapshot.anchor_before(tail);
3431 self.columnar_selection_state = match mode {
3432 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3433 selection_tail: selection_anchor,
3434 display_point: if reset {
3435 if position.column() != goal_column {
3436 Some(DisplayPoint::new(position.row(), goal_column))
3437 } else {
3438 None
3439 }
3440 } else {
3441 None
3442 },
3443 }),
3444 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3445 selection_tail: selection_anchor,
3446 }),
3447 };
3448
3449 if !reset {
3450 self.select_columns(position, goal_column, &display_map, window, cx);
3451 }
3452 }
3453
3454 fn update_selection(
3455 &mut self,
3456 position: DisplayPoint,
3457 goal_column: u32,
3458 scroll_delta: gpui::Point<f32>,
3459 window: &mut Window,
3460 cx: &mut Context<Self>,
3461 ) {
3462 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3463
3464 if self.columnar_selection_state.is_some() {
3465 self.select_columns(position, goal_column, &display_map, window, cx);
3466 } else if let Some(mut pending) = self.selections.pending_anchor() {
3467 let buffer = self.buffer.read(cx).snapshot(cx);
3468 let head;
3469 let tail;
3470 let mode = self.selections.pending_mode().unwrap();
3471 match &mode {
3472 SelectMode::Character => {
3473 head = position.to_point(&display_map);
3474 tail = pending.tail().to_point(&buffer);
3475 }
3476 SelectMode::Word(original_range) => {
3477 let original_display_range = original_range.start.to_display_point(&display_map)
3478 ..original_range.end.to_display_point(&display_map);
3479 let original_buffer_range = original_display_range.start.to_point(&display_map)
3480 ..original_display_range.end.to_point(&display_map);
3481 if movement::is_inside_word(&display_map, position)
3482 || original_display_range.contains(&position)
3483 {
3484 let word_range = movement::surrounding_word(&display_map, position);
3485 if word_range.start < original_display_range.start {
3486 head = word_range.start.to_point(&display_map);
3487 } else {
3488 head = word_range.end.to_point(&display_map);
3489 }
3490 } else {
3491 head = position.to_point(&display_map);
3492 }
3493
3494 if head <= original_buffer_range.start {
3495 tail = original_buffer_range.end;
3496 } else {
3497 tail = original_buffer_range.start;
3498 }
3499 }
3500 SelectMode::Line(original_range) => {
3501 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3502
3503 let position = display_map
3504 .clip_point(position, Bias::Left)
3505 .to_point(&display_map);
3506 let line_start = display_map.prev_line_boundary(position).0;
3507 let next_line_start = buffer.clip_point(
3508 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3509 Bias::Left,
3510 );
3511
3512 if line_start < original_range.start {
3513 head = line_start
3514 } else {
3515 head = next_line_start
3516 }
3517
3518 if head <= original_range.start {
3519 tail = original_range.end;
3520 } else {
3521 tail = original_range.start;
3522 }
3523 }
3524 SelectMode::All => {
3525 return;
3526 }
3527 };
3528
3529 if head < tail {
3530 pending.start = buffer.anchor_before(head);
3531 pending.end = buffer.anchor_before(tail);
3532 pending.reversed = true;
3533 } else {
3534 pending.start = buffer.anchor_before(tail);
3535 pending.end = buffer.anchor_before(head);
3536 pending.reversed = false;
3537 }
3538
3539 self.change_selections(None, window, cx, |s| {
3540 s.set_pending(pending, mode);
3541 });
3542 } else {
3543 log::error!("update_selection dispatched with no pending selection");
3544 return;
3545 }
3546
3547 self.apply_scroll_delta(scroll_delta, window, cx);
3548 cx.notify();
3549 }
3550
3551 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3552 self.columnar_selection_state.take();
3553 if self.selections.pending_anchor().is_some() {
3554 let selections = self.selections.all::<usize>(cx);
3555 self.change_selections(None, window, cx, |s| {
3556 s.select(selections);
3557 s.clear_pending();
3558 });
3559 }
3560 }
3561
3562 fn select_columns(
3563 &mut self,
3564 head: DisplayPoint,
3565 goal_column: u32,
3566 display_map: &DisplaySnapshot,
3567 window: &mut Window,
3568 cx: &mut Context<Self>,
3569 ) {
3570 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3571 return;
3572 };
3573
3574 let tail = match columnar_state {
3575 ColumnarSelectionState::FromMouse {
3576 selection_tail,
3577 display_point,
3578 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(&display_map)),
3579 ColumnarSelectionState::FromSelection { selection_tail } => {
3580 selection_tail.to_display_point(&display_map)
3581 }
3582 };
3583
3584 let start_row = cmp::min(tail.row(), head.row());
3585 let end_row = cmp::max(tail.row(), head.row());
3586 let start_column = cmp::min(tail.column(), goal_column);
3587 let end_column = cmp::max(tail.column(), goal_column);
3588 let reversed = start_column < tail.column();
3589
3590 let selection_ranges = (start_row.0..=end_row.0)
3591 .map(DisplayRow)
3592 .filter_map(|row| {
3593 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3594 || start_column <= display_map.line_len(row))
3595 && !display_map.is_block_line(row)
3596 {
3597 let start = display_map
3598 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3599 .to_point(display_map);
3600 let end = display_map
3601 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3602 .to_point(display_map);
3603 if reversed {
3604 Some(end..start)
3605 } else {
3606 Some(start..end)
3607 }
3608 } else {
3609 None
3610 }
3611 })
3612 .collect::<Vec<_>>();
3613
3614 let ranges = match columnar_state {
3615 ColumnarSelectionState::FromMouse { .. } => {
3616 let mut non_empty_ranges = selection_ranges
3617 .iter()
3618 .filter(|selection_range| selection_range.start != selection_range.end)
3619 .peekable();
3620 if non_empty_ranges.peek().is_some() {
3621 non_empty_ranges.cloned().collect()
3622 } else {
3623 selection_ranges
3624 }
3625 }
3626 _ => selection_ranges,
3627 };
3628
3629 self.change_selections(None, window, cx, |s| {
3630 s.select_ranges(ranges);
3631 });
3632 cx.notify();
3633 }
3634
3635 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3636 self.selections
3637 .all_adjusted(cx)
3638 .iter()
3639 .any(|selection| !selection.is_empty())
3640 }
3641
3642 pub fn has_pending_nonempty_selection(&self) -> bool {
3643 let pending_nonempty_selection = match self.selections.pending_anchor() {
3644 Some(Selection { start, end, .. }) => start != end,
3645 None => false,
3646 };
3647
3648 pending_nonempty_selection
3649 || (self.columnar_selection_state.is_some() && self.selections.disjoint.len() > 1)
3650 }
3651
3652 pub fn has_pending_selection(&self) -> bool {
3653 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3654 }
3655
3656 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3657 self.selection_mark_mode = false;
3658 self.selection_drag_state = SelectionDragState::None;
3659
3660 if self.clear_expanded_diff_hunks(cx) {
3661 cx.notify();
3662 return;
3663 }
3664 if self.dismiss_menus_and_popups(true, window, cx) {
3665 return;
3666 }
3667
3668 if self.mode.is_full()
3669 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3670 {
3671 return;
3672 }
3673
3674 cx.propagate();
3675 }
3676
3677 pub fn dismiss_menus_and_popups(
3678 &mut self,
3679 is_user_requested: bool,
3680 window: &mut Window,
3681 cx: &mut Context<Self>,
3682 ) -> bool {
3683 if self.take_rename(false, window, cx).is_some() {
3684 return true;
3685 }
3686
3687 if hide_hover(self, cx) {
3688 return true;
3689 }
3690
3691 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3692 return true;
3693 }
3694
3695 if self.hide_context_menu(window, cx).is_some() {
3696 return true;
3697 }
3698
3699 if self.mouse_context_menu.take().is_some() {
3700 return true;
3701 }
3702
3703 if is_user_requested && self.discard_inline_completion(true, cx) {
3704 return true;
3705 }
3706
3707 if self.snippet_stack.pop().is_some() {
3708 return true;
3709 }
3710
3711 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3712 self.dismiss_diagnostics(cx);
3713 return true;
3714 }
3715
3716 false
3717 }
3718
3719 fn linked_editing_ranges_for(
3720 &self,
3721 selection: Range<text::Anchor>,
3722 cx: &App,
3723 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3724 if self.linked_edit_ranges.is_empty() {
3725 return None;
3726 }
3727 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3728 selection.end.buffer_id.and_then(|end_buffer_id| {
3729 if selection.start.buffer_id != Some(end_buffer_id) {
3730 return None;
3731 }
3732 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3733 let snapshot = buffer.read(cx).snapshot();
3734 self.linked_edit_ranges
3735 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3736 .map(|ranges| (ranges, snapshot, buffer))
3737 })?;
3738 use text::ToOffset as TO;
3739 // find offset from the start of current range to current cursor position
3740 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3741
3742 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3743 let start_difference = start_offset - start_byte_offset;
3744 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3745 let end_difference = end_offset - start_byte_offset;
3746 // Current range has associated linked ranges.
3747 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3748 for range in linked_ranges.iter() {
3749 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3750 let end_offset = start_offset + end_difference;
3751 let start_offset = start_offset + start_difference;
3752 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3753 continue;
3754 }
3755 if self.selections.disjoint_anchor_ranges().any(|s| {
3756 if s.start.buffer_id != selection.start.buffer_id
3757 || s.end.buffer_id != selection.end.buffer_id
3758 {
3759 return false;
3760 }
3761 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3762 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3763 }) {
3764 continue;
3765 }
3766 let start = buffer_snapshot.anchor_after(start_offset);
3767 let end = buffer_snapshot.anchor_after(end_offset);
3768 linked_edits
3769 .entry(buffer.clone())
3770 .or_default()
3771 .push(start..end);
3772 }
3773 Some(linked_edits)
3774 }
3775
3776 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3777 let text: Arc<str> = text.into();
3778
3779 if self.read_only(cx) {
3780 return;
3781 }
3782
3783 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
3784
3785 let selections = self.selections.all_adjusted(cx);
3786 let mut bracket_inserted = false;
3787 let mut edits = Vec::new();
3788 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3789 let mut new_selections = Vec::with_capacity(selections.len());
3790 let mut new_autoclose_regions = Vec::new();
3791 let snapshot = self.buffer.read(cx).read(cx);
3792 let mut clear_linked_edit_ranges = false;
3793
3794 for (selection, autoclose_region) in
3795 self.selections_with_autoclose_regions(selections, &snapshot)
3796 {
3797 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3798 // Determine if the inserted text matches the opening or closing
3799 // bracket of any of this language's bracket pairs.
3800 let mut bracket_pair = None;
3801 let mut is_bracket_pair_start = false;
3802 let mut is_bracket_pair_end = false;
3803 if !text.is_empty() {
3804 let mut bracket_pair_matching_end = None;
3805 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3806 // and they are removing the character that triggered IME popup.
3807 for (pair, enabled) in scope.brackets() {
3808 if !pair.close && !pair.surround {
3809 continue;
3810 }
3811
3812 if enabled && pair.start.ends_with(text.as_ref()) {
3813 let prefix_len = pair.start.len() - text.len();
3814 let preceding_text_matches_prefix = prefix_len == 0
3815 || (selection.start.column >= (prefix_len as u32)
3816 && snapshot.contains_str_at(
3817 Point::new(
3818 selection.start.row,
3819 selection.start.column - (prefix_len as u32),
3820 ),
3821 &pair.start[..prefix_len],
3822 ));
3823 if preceding_text_matches_prefix {
3824 bracket_pair = Some(pair.clone());
3825 is_bracket_pair_start = true;
3826 break;
3827 }
3828 }
3829 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3830 {
3831 // take first bracket pair matching end, but don't break in case a later bracket
3832 // pair matches start
3833 bracket_pair_matching_end = Some(pair.clone());
3834 }
3835 }
3836 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3837 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3838 is_bracket_pair_end = true;
3839 }
3840 }
3841
3842 if let Some(bracket_pair) = bracket_pair {
3843 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3844 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3845 let auto_surround =
3846 self.use_auto_surround && snapshot_settings.use_auto_surround;
3847 if selection.is_empty() {
3848 if is_bracket_pair_start {
3849 // If the inserted text is a suffix of an opening bracket and the
3850 // selection is preceded by the rest of the opening bracket, then
3851 // insert the closing bracket.
3852 let following_text_allows_autoclose = snapshot
3853 .chars_at(selection.start)
3854 .next()
3855 .map_or(true, |c| scope.should_autoclose_before(c));
3856
3857 let preceding_text_allows_autoclose = selection.start.column == 0
3858 || snapshot.reversed_chars_at(selection.start).next().map_or(
3859 true,
3860 |c| {
3861 bracket_pair.start != bracket_pair.end
3862 || !snapshot
3863 .char_classifier_at(selection.start)
3864 .is_word(c)
3865 },
3866 );
3867
3868 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3869 && bracket_pair.start.len() == 1
3870 {
3871 let target = bracket_pair.start.chars().next().unwrap();
3872 let current_line_count = snapshot
3873 .reversed_chars_at(selection.start)
3874 .take_while(|&c| c != '\n')
3875 .filter(|&c| c == target)
3876 .count();
3877 current_line_count % 2 == 1
3878 } else {
3879 false
3880 };
3881
3882 if autoclose
3883 && bracket_pair.close
3884 && following_text_allows_autoclose
3885 && preceding_text_allows_autoclose
3886 && !is_closing_quote
3887 {
3888 let anchor = snapshot.anchor_before(selection.end);
3889 new_selections.push((selection.map(|_| anchor), text.len()));
3890 new_autoclose_regions.push((
3891 anchor,
3892 text.len(),
3893 selection.id,
3894 bracket_pair.clone(),
3895 ));
3896 edits.push((
3897 selection.range(),
3898 format!("{}{}", text, bracket_pair.end).into(),
3899 ));
3900 bracket_inserted = true;
3901 continue;
3902 }
3903 }
3904
3905 if let Some(region) = autoclose_region {
3906 // If the selection is followed by an auto-inserted closing bracket,
3907 // then don't insert that closing bracket again; just move the selection
3908 // past the closing bracket.
3909 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3910 && text.as_ref() == region.pair.end.as_str();
3911 if should_skip {
3912 let anchor = snapshot.anchor_after(selection.end);
3913 new_selections
3914 .push((selection.map(|_| anchor), region.pair.end.len()));
3915 continue;
3916 }
3917 }
3918
3919 let always_treat_brackets_as_autoclosed = snapshot
3920 .language_settings_at(selection.start, cx)
3921 .always_treat_brackets_as_autoclosed;
3922 if always_treat_brackets_as_autoclosed
3923 && is_bracket_pair_end
3924 && snapshot.contains_str_at(selection.end, text.as_ref())
3925 {
3926 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3927 // and the inserted text is a closing bracket and the selection is followed
3928 // by the closing bracket then move the selection past the closing bracket.
3929 let anchor = snapshot.anchor_after(selection.end);
3930 new_selections.push((selection.map(|_| anchor), text.len()));
3931 continue;
3932 }
3933 }
3934 // If an opening bracket is 1 character long and is typed while
3935 // text is selected, then surround that text with the bracket pair.
3936 else if auto_surround
3937 && bracket_pair.surround
3938 && is_bracket_pair_start
3939 && bracket_pair.start.chars().count() == 1
3940 {
3941 edits.push((selection.start..selection.start, text.clone()));
3942 edits.push((
3943 selection.end..selection.end,
3944 bracket_pair.end.as_str().into(),
3945 ));
3946 bracket_inserted = true;
3947 new_selections.push((
3948 Selection {
3949 id: selection.id,
3950 start: snapshot.anchor_after(selection.start),
3951 end: snapshot.anchor_before(selection.end),
3952 reversed: selection.reversed,
3953 goal: selection.goal,
3954 },
3955 0,
3956 ));
3957 continue;
3958 }
3959 }
3960 }
3961
3962 if self.auto_replace_emoji_shortcode
3963 && selection.is_empty()
3964 && text.as_ref().ends_with(':')
3965 {
3966 if let Some(possible_emoji_short_code) =
3967 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3968 {
3969 if !possible_emoji_short_code.is_empty() {
3970 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3971 let emoji_shortcode_start = Point::new(
3972 selection.start.row,
3973 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3974 );
3975
3976 // Remove shortcode from buffer
3977 edits.push((
3978 emoji_shortcode_start..selection.start,
3979 "".to_string().into(),
3980 ));
3981 new_selections.push((
3982 Selection {
3983 id: selection.id,
3984 start: snapshot.anchor_after(emoji_shortcode_start),
3985 end: snapshot.anchor_before(selection.start),
3986 reversed: selection.reversed,
3987 goal: selection.goal,
3988 },
3989 0,
3990 ));
3991
3992 // Insert emoji
3993 let selection_start_anchor = snapshot.anchor_after(selection.start);
3994 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3995 edits.push((selection.start..selection.end, emoji.to_string().into()));
3996
3997 continue;
3998 }
3999 }
4000 }
4001 }
4002
4003 // If not handling any auto-close operation, then just replace the selected
4004 // text with the given input and move the selection to the end of the
4005 // newly inserted text.
4006 let anchor = snapshot.anchor_after(selection.end);
4007 if !self.linked_edit_ranges.is_empty() {
4008 let start_anchor = snapshot.anchor_before(selection.start);
4009
4010 let is_word_char = text.chars().next().map_or(true, |char| {
4011 let classifier = snapshot
4012 .char_classifier_at(start_anchor.to_offset(&snapshot))
4013 .ignore_punctuation(true);
4014 classifier.is_word(char)
4015 });
4016
4017 if is_word_char {
4018 if let Some(ranges) = self
4019 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4020 {
4021 for (buffer, edits) in ranges {
4022 linked_edits
4023 .entry(buffer.clone())
4024 .or_default()
4025 .extend(edits.into_iter().map(|range| (range, text.clone())));
4026 }
4027 }
4028 } else {
4029 clear_linked_edit_ranges = true;
4030 }
4031 }
4032
4033 new_selections.push((selection.map(|_| anchor), 0));
4034 edits.push((selection.start..selection.end, text.clone()));
4035 }
4036
4037 drop(snapshot);
4038
4039 self.transact(window, cx, |this, window, cx| {
4040 if clear_linked_edit_ranges {
4041 this.linked_edit_ranges.clear();
4042 }
4043 let initial_buffer_versions =
4044 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4045
4046 this.buffer.update(cx, |buffer, cx| {
4047 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4048 });
4049 for (buffer, edits) in linked_edits {
4050 buffer.update(cx, |buffer, cx| {
4051 let snapshot = buffer.snapshot();
4052 let edits = edits
4053 .into_iter()
4054 .map(|(range, text)| {
4055 use text::ToPoint as TP;
4056 let end_point = TP::to_point(&range.end, &snapshot);
4057 let start_point = TP::to_point(&range.start, &snapshot);
4058 (start_point..end_point, text)
4059 })
4060 .sorted_by_key(|(range, _)| range.start);
4061 buffer.edit(edits, None, cx);
4062 })
4063 }
4064 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4065 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4066 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4067 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
4068 .zip(new_selection_deltas)
4069 .map(|(selection, delta)| Selection {
4070 id: selection.id,
4071 start: selection.start + delta,
4072 end: selection.end + delta,
4073 reversed: selection.reversed,
4074 goal: SelectionGoal::None,
4075 })
4076 .collect::<Vec<_>>();
4077
4078 let mut i = 0;
4079 for (position, delta, selection_id, pair) in new_autoclose_regions {
4080 let position = position.to_offset(&map.buffer_snapshot) + delta;
4081 let start = map.buffer_snapshot.anchor_before(position);
4082 let end = map.buffer_snapshot.anchor_after(position);
4083 while let Some(existing_state) = this.autoclose_regions.get(i) {
4084 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
4085 Ordering::Less => i += 1,
4086 Ordering::Greater => break,
4087 Ordering::Equal => {
4088 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4089 Ordering::Less => i += 1,
4090 Ordering::Equal => break,
4091 Ordering::Greater => break,
4092 }
4093 }
4094 }
4095 }
4096 this.autoclose_regions.insert(
4097 i,
4098 AutocloseRegion {
4099 selection_id,
4100 range: start..end,
4101 pair,
4102 },
4103 );
4104 }
4105
4106 let had_active_inline_completion = this.has_active_inline_completion();
4107 this.change_selections(
4108 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4109 window,
4110 cx,
4111 |s| s.select(new_selections),
4112 );
4113
4114 if !bracket_inserted {
4115 if let Some(on_type_format_task) =
4116 this.trigger_on_type_formatting(text.to_string(), window, cx)
4117 {
4118 on_type_format_task.detach_and_log_err(cx);
4119 }
4120 }
4121
4122 let editor_settings = EditorSettings::get_global(cx);
4123 if bracket_inserted
4124 && (editor_settings.auto_signature_help
4125 || editor_settings.show_signature_help_after_edits)
4126 {
4127 this.show_signature_help(&ShowSignatureHelp, window, cx);
4128 }
4129
4130 let trigger_in_words =
4131 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4132 if this.hard_wrap.is_some() {
4133 let latest: Range<Point> = this.selections.newest(cx).range();
4134 if latest.is_empty()
4135 && this
4136 .buffer()
4137 .read(cx)
4138 .snapshot(cx)
4139 .line_len(MultiBufferRow(latest.start.row))
4140 == latest.start.column
4141 {
4142 this.rewrap_impl(
4143 RewrapOptions {
4144 override_language_settings: true,
4145 preserve_existing_whitespace: true,
4146 },
4147 cx,
4148 )
4149 }
4150 }
4151 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4152 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4153 this.refresh_inline_completion(true, false, window, cx);
4154 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4155 });
4156 }
4157
4158 fn find_possible_emoji_shortcode_at_position(
4159 snapshot: &MultiBufferSnapshot,
4160 position: Point,
4161 ) -> Option<String> {
4162 let mut chars = Vec::new();
4163 let mut found_colon = false;
4164 for char in snapshot.reversed_chars_at(position).take(100) {
4165 // Found a possible emoji shortcode in the middle of the buffer
4166 if found_colon {
4167 if char.is_whitespace() {
4168 chars.reverse();
4169 return Some(chars.iter().collect());
4170 }
4171 // If the previous character is not a whitespace, we are in the middle of a word
4172 // and we only want to complete the shortcode if the word is made up of other emojis
4173 let mut containing_word = String::new();
4174 for ch in snapshot
4175 .reversed_chars_at(position)
4176 .skip(chars.len() + 1)
4177 .take(100)
4178 {
4179 if ch.is_whitespace() {
4180 break;
4181 }
4182 containing_word.push(ch);
4183 }
4184 let containing_word = containing_word.chars().rev().collect::<String>();
4185 if util::word_consists_of_emojis(containing_word.as_str()) {
4186 chars.reverse();
4187 return Some(chars.iter().collect());
4188 }
4189 }
4190
4191 if char.is_whitespace() || !char.is_ascii() {
4192 return None;
4193 }
4194 if char == ':' {
4195 found_colon = true;
4196 } else {
4197 chars.push(char);
4198 }
4199 }
4200 // Found a possible emoji shortcode at the beginning of the buffer
4201 chars.reverse();
4202 Some(chars.iter().collect())
4203 }
4204
4205 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4207 self.transact(window, cx, |this, window, cx| {
4208 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4209 let selections = this.selections.all::<usize>(cx);
4210 let multi_buffer = this.buffer.read(cx);
4211 let buffer = multi_buffer.snapshot(cx);
4212 selections
4213 .iter()
4214 .map(|selection| {
4215 let start_point = selection.start.to_point(&buffer);
4216 let mut existing_indent =
4217 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4218 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4219 let start = selection.start;
4220 let end = selection.end;
4221 let selection_is_empty = start == end;
4222 let language_scope = buffer.language_scope_at(start);
4223 let (
4224 comment_delimiter,
4225 doc_delimiter,
4226 insert_extra_newline,
4227 indent_on_newline,
4228 indent_on_extra_newline,
4229 ) = if let Some(language) = &language_scope {
4230 let mut insert_extra_newline =
4231 insert_extra_newline_brackets(&buffer, start..end, language)
4232 || insert_extra_newline_tree_sitter(&buffer, start..end);
4233
4234 // Comment extension on newline is allowed only for cursor selections
4235 let comment_delimiter = maybe!({
4236 if !selection_is_empty {
4237 return None;
4238 }
4239
4240 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4241 return None;
4242 }
4243
4244 let delimiters = language.line_comment_prefixes();
4245 let max_len_of_delimiter =
4246 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4247 let (snapshot, range) =
4248 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4249
4250 let num_of_whitespaces = snapshot
4251 .chars_for_range(range.clone())
4252 .take_while(|c| c.is_whitespace())
4253 .count();
4254 let comment_candidate = snapshot
4255 .chars_for_range(range)
4256 .skip(num_of_whitespaces)
4257 .take(max_len_of_delimiter)
4258 .collect::<String>();
4259 let (delimiter, trimmed_len) = delimiters
4260 .iter()
4261 .filter_map(|delimiter| {
4262 let prefix = delimiter.trim_end();
4263 if comment_candidate.starts_with(prefix) {
4264 Some((delimiter, prefix.len()))
4265 } else {
4266 None
4267 }
4268 })
4269 .max_by_key(|(_, len)| *len)?;
4270
4271 let cursor_is_placed_after_comment_marker =
4272 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4273 if cursor_is_placed_after_comment_marker {
4274 Some(delimiter.clone())
4275 } else {
4276 None
4277 }
4278 });
4279
4280 let mut indent_on_newline = IndentSize::spaces(0);
4281 let mut indent_on_extra_newline = IndentSize::spaces(0);
4282
4283 let doc_delimiter = maybe!({
4284 if !selection_is_empty {
4285 return None;
4286 }
4287
4288 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4289 return None;
4290 }
4291
4292 let DocumentationConfig {
4293 start: start_tag,
4294 end: end_tag,
4295 prefix: delimiter,
4296 tab_size: len,
4297 } = language.documentation()?;
4298
4299 let is_within_block_comment = buffer
4300 .language_scope_at(start_point)
4301 .is_some_and(|scope| scope.override_name() == Some("comment"));
4302 if !is_within_block_comment {
4303 return None;
4304 }
4305
4306 let (snapshot, range) =
4307 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4308
4309 let num_of_whitespaces = snapshot
4310 .chars_for_range(range.clone())
4311 .take_while(|c| c.is_whitespace())
4312 .count();
4313
4314 // It is safe to use a column from MultiBufferPoint in context of a single buffer ranges, because we're only ever looking at a single line at a time.
4315 let column = start_point.column;
4316 let cursor_is_after_start_tag = {
4317 let start_tag_len = start_tag.len();
4318 let start_tag_line = snapshot
4319 .chars_for_range(range.clone())
4320 .skip(num_of_whitespaces)
4321 .take(start_tag_len)
4322 .collect::<String>();
4323 if start_tag_line.starts_with(start_tag.as_ref()) {
4324 num_of_whitespaces + start_tag_len <= column as usize
4325 } else {
4326 false
4327 }
4328 };
4329
4330 let cursor_is_after_delimiter = {
4331 let delimiter_trim = delimiter.trim_end();
4332 let delimiter_line = snapshot
4333 .chars_for_range(range.clone())
4334 .skip(num_of_whitespaces)
4335 .take(delimiter_trim.len())
4336 .collect::<String>();
4337 if delimiter_line.starts_with(delimiter_trim) {
4338 num_of_whitespaces + delimiter_trim.len() <= column as usize
4339 } else {
4340 false
4341 }
4342 };
4343
4344 let cursor_is_before_end_tag_if_exists = {
4345 let mut char_position = 0u32;
4346 let mut end_tag_offset = None;
4347
4348 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4349 if let Some(byte_pos) = chunk.find(&**end_tag) {
4350 let chars_before_match =
4351 chunk[..byte_pos].chars().count() as u32;
4352 end_tag_offset =
4353 Some(char_position + chars_before_match);
4354 break 'outer;
4355 }
4356 char_position += chunk.chars().count() as u32;
4357 }
4358
4359 if let Some(end_tag_offset) = end_tag_offset {
4360 let cursor_is_before_end_tag = column <= end_tag_offset;
4361 if cursor_is_after_start_tag {
4362 if cursor_is_before_end_tag {
4363 insert_extra_newline = true;
4364 }
4365 let cursor_is_at_start_of_end_tag =
4366 column == end_tag_offset;
4367 if cursor_is_at_start_of_end_tag {
4368 indent_on_extra_newline.len = (*len).into();
4369 }
4370 }
4371 cursor_is_before_end_tag
4372 } else {
4373 true
4374 }
4375 };
4376
4377 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4378 && cursor_is_before_end_tag_if_exists
4379 {
4380 if cursor_is_after_start_tag {
4381 indent_on_newline.len = (*len).into();
4382 }
4383 Some(delimiter.clone())
4384 } else {
4385 None
4386 }
4387 });
4388
4389 (
4390 comment_delimiter,
4391 doc_delimiter,
4392 insert_extra_newline,
4393 indent_on_newline,
4394 indent_on_extra_newline,
4395 )
4396 } else {
4397 (
4398 None,
4399 None,
4400 false,
4401 IndentSize::default(),
4402 IndentSize::default(),
4403 )
4404 };
4405
4406 let prevent_auto_indent = doc_delimiter.is_some();
4407 let delimiter = comment_delimiter.or(doc_delimiter);
4408
4409 let capacity_for_delimiter =
4410 delimiter.as_deref().map(str::len).unwrap_or_default();
4411 let mut new_text = String::with_capacity(
4412 1 + capacity_for_delimiter
4413 + existing_indent.len as usize
4414 + indent_on_newline.len as usize
4415 + indent_on_extra_newline.len as usize,
4416 );
4417 new_text.push('\n');
4418 new_text.extend(existing_indent.chars());
4419 new_text.extend(indent_on_newline.chars());
4420
4421 if let Some(delimiter) = &delimiter {
4422 new_text.push_str(delimiter);
4423 }
4424
4425 if insert_extra_newline {
4426 new_text.push('\n');
4427 new_text.extend(existing_indent.chars());
4428 new_text.extend(indent_on_extra_newline.chars());
4429 }
4430
4431 let anchor = buffer.anchor_after(end);
4432 let new_selection = selection.map(|_| anchor);
4433 (
4434 ((start..end, new_text), prevent_auto_indent),
4435 (insert_extra_newline, new_selection),
4436 )
4437 })
4438 .unzip()
4439 };
4440
4441 let mut auto_indent_edits = Vec::new();
4442 let mut edits = Vec::new();
4443 for (edit, prevent_auto_indent) in edits_with_flags {
4444 if prevent_auto_indent {
4445 edits.push(edit);
4446 } else {
4447 auto_indent_edits.push(edit);
4448 }
4449 }
4450 if !edits.is_empty() {
4451 this.edit(edits, cx);
4452 }
4453 if !auto_indent_edits.is_empty() {
4454 this.edit_with_autoindent(auto_indent_edits, cx);
4455 }
4456
4457 let buffer = this.buffer.read(cx).snapshot(cx);
4458 let new_selections = selection_info
4459 .into_iter()
4460 .map(|(extra_newline_inserted, new_selection)| {
4461 let mut cursor = new_selection.end.to_point(&buffer);
4462 if extra_newline_inserted {
4463 cursor.row -= 1;
4464 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4465 }
4466 new_selection.map(|_| cursor)
4467 })
4468 .collect();
4469
4470 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4471 s.select(new_selections)
4472 });
4473 this.refresh_inline_completion(true, false, window, cx);
4474 });
4475 }
4476
4477 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4478 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4479
4480 let buffer = self.buffer.read(cx);
4481 let snapshot = buffer.snapshot(cx);
4482
4483 let mut edits = Vec::new();
4484 let mut rows = Vec::new();
4485
4486 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4487 let cursor = selection.head();
4488 let row = cursor.row;
4489
4490 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4491
4492 let newline = "\n".to_string();
4493 edits.push((start_of_line..start_of_line, newline));
4494
4495 rows.push(row + rows_inserted as u32);
4496 }
4497
4498 self.transact(window, cx, |editor, window, cx| {
4499 editor.edit(edits, cx);
4500
4501 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4502 let mut index = 0;
4503 s.move_cursors_with(|map, _, _| {
4504 let row = rows[index];
4505 index += 1;
4506
4507 let point = Point::new(row, 0);
4508 let boundary = map.next_line_boundary(point).1;
4509 let clipped = map.clip_point(boundary, Bias::Left);
4510
4511 (clipped, SelectionGoal::None)
4512 });
4513 });
4514
4515 let mut indent_edits = Vec::new();
4516 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4517 for row in rows {
4518 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4519 for (row, indent) in indents {
4520 if indent.len == 0 {
4521 continue;
4522 }
4523
4524 let text = match indent.kind {
4525 IndentKind::Space => " ".repeat(indent.len as usize),
4526 IndentKind::Tab => "\t".repeat(indent.len as usize),
4527 };
4528 let point = Point::new(row.0, 0);
4529 indent_edits.push((point..point, text));
4530 }
4531 }
4532 editor.edit(indent_edits, cx);
4533 });
4534 }
4535
4536 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4538
4539 let buffer = self.buffer.read(cx);
4540 let snapshot = buffer.snapshot(cx);
4541
4542 let mut edits = Vec::new();
4543 let mut rows = Vec::new();
4544 let mut rows_inserted = 0;
4545
4546 for selection in self.selections.all_adjusted(cx) {
4547 let cursor = selection.head();
4548 let row = cursor.row;
4549
4550 let point = Point::new(row + 1, 0);
4551 let start_of_line = snapshot.clip_point(point, Bias::Left);
4552
4553 let newline = "\n".to_string();
4554 edits.push((start_of_line..start_of_line, newline));
4555
4556 rows_inserted += 1;
4557 rows.push(row + rows_inserted);
4558 }
4559
4560 self.transact(window, cx, |editor, window, cx| {
4561 editor.edit(edits, cx);
4562
4563 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4564 let mut index = 0;
4565 s.move_cursors_with(|map, _, _| {
4566 let row = rows[index];
4567 index += 1;
4568
4569 let point = Point::new(row, 0);
4570 let boundary = map.next_line_boundary(point).1;
4571 let clipped = map.clip_point(boundary, Bias::Left);
4572
4573 (clipped, SelectionGoal::None)
4574 });
4575 });
4576
4577 let mut indent_edits = Vec::new();
4578 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4579 for row in rows {
4580 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4581 for (row, indent) in indents {
4582 if indent.len == 0 {
4583 continue;
4584 }
4585
4586 let text = match indent.kind {
4587 IndentKind::Space => " ".repeat(indent.len as usize),
4588 IndentKind::Tab => "\t".repeat(indent.len as usize),
4589 };
4590 let point = Point::new(row.0, 0);
4591 indent_edits.push((point..point, text));
4592 }
4593 }
4594 editor.edit(indent_edits, cx);
4595 });
4596 }
4597
4598 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4599 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4600 original_indent_columns: Vec::new(),
4601 });
4602 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4603 }
4604
4605 fn insert_with_autoindent_mode(
4606 &mut self,
4607 text: &str,
4608 autoindent_mode: Option<AutoindentMode>,
4609 window: &mut Window,
4610 cx: &mut Context<Self>,
4611 ) {
4612 if self.read_only(cx) {
4613 return;
4614 }
4615
4616 let text: Arc<str> = text.into();
4617 self.transact(window, cx, |this, window, cx| {
4618 let old_selections = this.selections.all_adjusted(cx);
4619 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4620 let anchors = {
4621 let snapshot = buffer.read(cx);
4622 old_selections
4623 .iter()
4624 .map(|s| {
4625 let anchor = snapshot.anchor_after(s.head());
4626 s.map(|_| anchor)
4627 })
4628 .collect::<Vec<_>>()
4629 };
4630 buffer.edit(
4631 old_selections
4632 .iter()
4633 .map(|s| (s.start..s.end, text.clone())),
4634 autoindent_mode,
4635 cx,
4636 );
4637 anchors
4638 });
4639
4640 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4641 s.select_anchors(selection_anchors);
4642 });
4643
4644 cx.notify();
4645 });
4646 }
4647
4648 fn trigger_completion_on_input(
4649 &mut self,
4650 text: &str,
4651 trigger_in_words: bool,
4652 window: &mut Window,
4653 cx: &mut Context<Self>,
4654 ) {
4655 let completions_source = self
4656 .context_menu
4657 .borrow()
4658 .as_ref()
4659 .and_then(|menu| match menu {
4660 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4661 CodeContextMenu::CodeActions(_) => None,
4662 });
4663
4664 match completions_source {
4665 Some(CompletionsMenuSource::Words) => {
4666 self.show_word_completions(&ShowWordCompletions, window, cx)
4667 }
4668 Some(CompletionsMenuSource::Normal)
4669 | Some(CompletionsMenuSource::SnippetChoices)
4670 | None
4671 if self.is_completion_trigger(
4672 text,
4673 trigger_in_words,
4674 completions_source.is_some(),
4675 cx,
4676 ) =>
4677 {
4678 self.show_completions(
4679 &ShowCompletions {
4680 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4681 },
4682 window,
4683 cx,
4684 )
4685 }
4686 _ => {
4687 self.hide_context_menu(window, cx);
4688 }
4689 }
4690 }
4691
4692 fn is_completion_trigger(
4693 &self,
4694 text: &str,
4695 trigger_in_words: bool,
4696 menu_is_open: bool,
4697 cx: &mut Context<Self>,
4698 ) -> bool {
4699 let position = self.selections.newest_anchor().head();
4700 let multibuffer = self.buffer.read(cx);
4701 let Some(buffer) = position
4702 .buffer_id
4703 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4704 else {
4705 return false;
4706 };
4707
4708 if let Some(completion_provider) = &self.completion_provider {
4709 completion_provider.is_completion_trigger(
4710 &buffer,
4711 position.text_anchor,
4712 text,
4713 trigger_in_words,
4714 menu_is_open,
4715 cx,
4716 )
4717 } else {
4718 false
4719 }
4720 }
4721
4722 /// If any empty selections is touching the start of its innermost containing autoclose
4723 /// region, expand it to select the brackets.
4724 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4725 let selections = self.selections.all::<usize>(cx);
4726 let buffer = self.buffer.read(cx).read(cx);
4727 let new_selections = self
4728 .selections_with_autoclose_regions(selections, &buffer)
4729 .map(|(mut selection, region)| {
4730 if !selection.is_empty() {
4731 return selection;
4732 }
4733
4734 if let Some(region) = region {
4735 let mut range = region.range.to_offset(&buffer);
4736 if selection.start == range.start && range.start >= region.pair.start.len() {
4737 range.start -= region.pair.start.len();
4738 if buffer.contains_str_at(range.start, ®ion.pair.start)
4739 && buffer.contains_str_at(range.end, ®ion.pair.end)
4740 {
4741 range.end += region.pair.end.len();
4742 selection.start = range.start;
4743 selection.end = range.end;
4744
4745 return selection;
4746 }
4747 }
4748 }
4749
4750 let always_treat_brackets_as_autoclosed = buffer
4751 .language_settings_at(selection.start, cx)
4752 .always_treat_brackets_as_autoclosed;
4753
4754 if !always_treat_brackets_as_autoclosed {
4755 return selection;
4756 }
4757
4758 if let Some(scope) = buffer.language_scope_at(selection.start) {
4759 for (pair, enabled) in scope.brackets() {
4760 if !enabled || !pair.close {
4761 continue;
4762 }
4763
4764 if buffer.contains_str_at(selection.start, &pair.end) {
4765 let pair_start_len = pair.start.len();
4766 if buffer.contains_str_at(
4767 selection.start.saturating_sub(pair_start_len),
4768 &pair.start,
4769 ) {
4770 selection.start -= pair_start_len;
4771 selection.end += pair.end.len();
4772
4773 return selection;
4774 }
4775 }
4776 }
4777 }
4778
4779 selection
4780 })
4781 .collect();
4782
4783 drop(buffer);
4784 self.change_selections(None, window, cx, |selections| {
4785 selections.select(new_selections)
4786 });
4787 }
4788
4789 /// Iterate the given selections, and for each one, find the smallest surrounding
4790 /// autoclose region. This uses the ordering of the selections and the autoclose
4791 /// regions to avoid repeated comparisons.
4792 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4793 &'a self,
4794 selections: impl IntoIterator<Item = Selection<D>>,
4795 buffer: &'a MultiBufferSnapshot,
4796 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4797 let mut i = 0;
4798 let mut regions = self.autoclose_regions.as_slice();
4799 selections.into_iter().map(move |selection| {
4800 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4801
4802 let mut enclosing = None;
4803 while let Some(pair_state) = regions.get(i) {
4804 if pair_state.range.end.to_offset(buffer) < range.start {
4805 regions = ®ions[i + 1..];
4806 i = 0;
4807 } else if pair_state.range.start.to_offset(buffer) > range.end {
4808 break;
4809 } else {
4810 if pair_state.selection_id == selection.id {
4811 enclosing = Some(pair_state);
4812 }
4813 i += 1;
4814 }
4815 }
4816
4817 (selection, enclosing)
4818 })
4819 }
4820
4821 /// Remove any autoclose regions that no longer contain their selection.
4822 fn invalidate_autoclose_regions(
4823 &mut self,
4824 mut selections: &[Selection<Anchor>],
4825 buffer: &MultiBufferSnapshot,
4826 ) {
4827 self.autoclose_regions.retain(|state| {
4828 let mut i = 0;
4829 while let Some(selection) = selections.get(i) {
4830 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4831 selections = &selections[1..];
4832 continue;
4833 }
4834 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4835 break;
4836 }
4837 if selection.id == state.selection_id {
4838 return true;
4839 } else {
4840 i += 1;
4841 }
4842 }
4843 false
4844 });
4845 }
4846
4847 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4848 let offset = position.to_offset(buffer);
4849 let (word_range, kind) = buffer.surrounding_word(offset, true);
4850 if offset > word_range.start && kind == Some(CharKind::Word) {
4851 Some(
4852 buffer
4853 .text_for_range(word_range.start..offset)
4854 .collect::<String>(),
4855 )
4856 } else {
4857 None
4858 }
4859 }
4860
4861 pub fn toggle_inline_values(
4862 &mut self,
4863 _: &ToggleInlineValues,
4864 _: &mut Window,
4865 cx: &mut Context<Self>,
4866 ) {
4867 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4868
4869 self.refresh_inline_values(cx);
4870 }
4871
4872 pub fn toggle_inlay_hints(
4873 &mut self,
4874 _: &ToggleInlayHints,
4875 _: &mut Window,
4876 cx: &mut Context<Self>,
4877 ) {
4878 self.refresh_inlay_hints(
4879 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4880 cx,
4881 );
4882 }
4883
4884 pub fn inlay_hints_enabled(&self) -> bool {
4885 self.inlay_hint_cache.enabled
4886 }
4887
4888 pub fn inline_values_enabled(&self) -> bool {
4889 self.inline_value_cache.enabled
4890 }
4891
4892 #[cfg(any(test, feature = "test-support"))]
4893 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4894 self.display_map
4895 .read(cx)
4896 .current_inlays()
4897 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4898 .cloned()
4899 .collect()
4900 }
4901
4902 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4903 if self.semantics_provider.is_none() || !self.mode.is_full() {
4904 return;
4905 }
4906
4907 let reason_description = reason.description();
4908 let ignore_debounce = matches!(
4909 reason,
4910 InlayHintRefreshReason::SettingsChange(_)
4911 | InlayHintRefreshReason::Toggle(_)
4912 | InlayHintRefreshReason::ExcerptsRemoved(_)
4913 | InlayHintRefreshReason::ModifiersChanged(_)
4914 );
4915 let (invalidate_cache, required_languages) = match reason {
4916 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4917 match self.inlay_hint_cache.modifiers_override(enabled) {
4918 Some(enabled) => {
4919 if enabled {
4920 (InvalidationStrategy::RefreshRequested, None)
4921 } else {
4922 self.splice_inlays(
4923 &self
4924 .visible_inlay_hints(cx)
4925 .iter()
4926 .map(|inlay| inlay.id)
4927 .collect::<Vec<InlayId>>(),
4928 Vec::new(),
4929 cx,
4930 );
4931 return;
4932 }
4933 }
4934 None => return,
4935 }
4936 }
4937 InlayHintRefreshReason::Toggle(enabled) => {
4938 if self.inlay_hint_cache.toggle(enabled) {
4939 if enabled {
4940 (InvalidationStrategy::RefreshRequested, None)
4941 } else {
4942 self.splice_inlays(
4943 &self
4944 .visible_inlay_hints(cx)
4945 .iter()
4946 .map(|inlay| inlay.id)
4947 .collect::<Vec<InlayId>>(),
4948 Vec::new(),
4949 cx,
4950 );
4951 return;
4952 }
4953 } else {
4954 return;
4955 }
4956 }
4957 InlayHintRefreshReason::SettingsChange(new_settings) => {
4958 match self.inlay_hint_cache.update_settings(
4959 &self.buffer,
4960 new_settings,
4961 self.visible_inlay_hints(cx),
4962 cx,
4963 ) {
4964 ControlFlow::Break(Some(InlaySplice {
4965 to_remove,
4966 to_insert,
4967 })) => {
4968 self.splice_inlays(&to_remove, to_insert, cx);
4969 return;
4970 }
4971 ControlFlow::Break(None) => return,
4972 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4973 }
4974 }
4975 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4976 if let Some(InlaySplice {
4977 to_remove,
4978 to_insert,
4979 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4980 {
4981 self.splice_inlays(&to_remove, to_insert, cx);
4982 }
4983 self.display_map.update(cx, |display_map, _| {
4984 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4985 });
4986 return;
4987 }
4988 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4989 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4990 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4991 }
4992 InlayHintRefreshReason::RefreshRequested => {
4993 (InvalidationStrategy::RefreshRequested, None)
4994 }
4995 };
4996
4997 if let Some(InlaySplice {
4998 to_remove,
4999 to_insert,
5000 }) = self.inlay_hint_cache.spawn_hint_refresh(
5001 reason_description,
5002 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
5003 invalidate_cache,
5004 ignore_debounce,
5005 cx,
5006 ) {
5007 self.splice_inlays(&to_remove, to_insert, cx);
5008 }
5009 }
5010
5011 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
5012 self.display_map
5013 .read(cx)
5014 .current_inlays()
5015 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5016 .cloned()
5017 .collect()
5018 }
5019
5020 pub fn excerpts_for_inlay_hints_query(
5021 &self,
5022 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5023 cx: &mut Context<Editor>,
5024 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5025 let Some(project) = self.project.as_ref() else {
5026 return HashMap::default();
5027 };
5028 let project = project.read(cx);
5029 let multi_buffer = self.buffer().read(cx);
5030 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5031 let multi_buffer_visible_start = self
5032 .scroll_manager
5033 .anchor()
5034 .anchor
5035 .to_point(&multi_buffer_snapshot);
5036 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5037 multi_buffer_visible_start
5038 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5039 Bias::Left,
5040 );
5041 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5042 multi_buffer_snapshot
5043 .range_to_buffer_ranges(multi_buffer_visible_range)
5044 .into_iter()
5045 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5046 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5047 let buffer_file = project::File::from_dyn(buffer.file())?;
5048 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5049 let worktree_entry = buffer_worktree
5050 .read(cx)
5051 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
5052 if worktree_entry.is_ignored {
5053 return None;
5054 }
5055
5056 let language = buffer.language()?;
5057 if let Some(restrict_to_languages) = restrict_to_languages {
5058 if !restrict_to_languages.contains(language) {
5059 return None;
5060 }
5061 }
5062 Some((
5063 excerpt_id,
5064 (
5065 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5066 buffer.version().clone(),
5067 excerpt_visible_range,
5068 ),
5069 ))
5070 })
5071 .collect()
5072 }
5073
5074 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5075 TextLayoutDetails {
5076 text_system: window.text_system().clone(),
5077 editor_style: self.style.clone().unwrap(),
5078 rem_size: window.rem_size(),
5079 scroll_anchor: self.scroll_manager.anchor(),
5080 visible_rows: self.visible_line_count(),
5081 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5082 }
5083 }
5084
5085 pub fn splice_inlays(
5086 &self,
5087 to_remove: &[InlayId],
5088 to_insert: Vec<Inlay>,
5089 cx: &mut Context<Self>,
5090 ) {
5091 self.display_map.update(cx, |display_map, cx| {
5092 display_map.splice_inlays(to_remove, to_insert, cx)
5093 });
5094 cx.notify();
5095 }
5096
5097 fn trigger_on_type_formatting(
5098 &self,
5099 input: String,
5100 window: &mut Window,
5101 cx: &mut Context<Self>,
5102 ) -> Option<Task<Result<()>>> {
5103 if input.len() != 1 {
5104 return None;
5105 }
5106
5107 let project = self.project.as_ref()?;
5108 let position = self.selections.newest_anchor().head();
5109 let (buffer, buffer_position) = self
5110 .buffer
5111 .read(cx)
5112 .text_anchor_for_position(position, cx)?;
5113
5114 let settings = language_settings::language_settings(
5115 buffer
5116 .read(cx)
5117 .language_at(buffer_position)
5118 .map(|l| l.name()),
5119 buffer.read(cx).file(),
5120 cx,
5121 );
5122 if !settings.use_on_type_format {
5123 return None;
5124 }
5125
5126 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5127 // hence we do LSP request & edit on host side only — add formats to host's history.
5128 let push_to_lsp_host_history = true;
5129 // If this is not the host, append its history with new edits.
5130 let push_to_client_history = project.read(cx).is_via_collab();
5131
5132 let on_type_formatting = project.update(cx, |project, cx| {
5133 project.on_type_format(
5134 buffer.clone(),
5135 buffer_position,
5136 input,
5137 push_to_lsp_host_history,
5138 cx,
5139 )
5140 });
5141 Some(cx.spawn_in(window, async move |editor, cx| {
5142 if let Some(transaction) = on_type_formatting.await? {
5143 if push_to_client_history {
5144 buffer
5145 .update(cx, |buffer, _| {
5146 buffer.push_transaction(transaction, Instant::now());
5147 buffer.finalize_last_transaction();
5148 })
5149 .ok();
5150 }
5151 editor.update(cx, |editor, cx| {
5152 editor.refresh_document_highlights(cx);
5153 })?;
5154 }
5155 Ok(())
5156 }))
5157 }
5158
5159 pub fn show_word_completions(
5160 &mut self,
5161 _: &ShowWordCompletions,
5162 window: &mut Window,
5163 cx: &mut Context<Self>,
5164 ) {
5165 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5166 }
5167
5168 pub fn show_completions(
5169 &mut self,
5170 options: &ShowCompletions,
5171 window: &mut Window,
5172 cx: &mut Context<Self>,
5173 ) {
5174 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5175 }
5176
5177 fn open_or_update_completions_menu(
5178 &mut self,
5179 requested_source: Option<CompletionsMenuSource>,
5180 trigger: Option<&str>,
5181 window: &mut Window,
5182 cx: &mut Context<Self>,
5183 ) {
5184 if self.pending_rename.is_some() {
5185 return;
5186 }
5187
5188 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5189
5190 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5191 // inserted and selected. To handle that case, the start of the selection is used so that
5192 // the menu starts with all choices.
5193 let position = self
5194 .selections
5195 .newest_anchor()
5196 .start
5197 .bias_right(&multibuffer_snapshot);
5198 if position.diff_base_anchor.is_some() {
5199 return;
5200 }
5201 let (buffer, buffer_position) =
5202 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5203 output
5204 } else {
5205 return;
5206 };
5207 let buffer_snapshot = buffer.read(cx).snapshot();
5208
5209 let query: Option<Arc<String>> =
5210 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5211
5212 drop(multibuffer_snapshot);
5213
5214 let provider = match requested_source {
5215 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5216 Some(CompletionsMenuSource::Words) => None,
5217 Some(CompletionsMenuSource::SnippetChoices) => {
5218 log::error!("bug: SnippetChoices requested_source is not handled");
5219 None
5220 }
5221 };
5222
5223 let sort_completions = provider
5224 .as_ref()
5225 .map_or(false, |provider| provider.sort_completions());
5226
5227 let filter_completions = provider
5228 .as_ref()
5229 .map_or(true, |provider| provider.filter_completions());
5230
5231 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5232 if filter_completions {
5233 menu.filter(query.clone(), provider.clone(), window, cx);
5234 }
5235 // When `is_incomplete` is false, no need to re-query completions when the current query
5236 // is a suffix of the initial query.
5237 if !menu.is_incomplete {
5238 // If the new query is a suffix of the old query (typing more characters) and
5239 // the previous result was complete, the existing completions can be filtered.
5240 //
5241 // Note that this is always true for snippet completions.
5242 let query_matches = match (&menu.initial_query, &query) {
5243 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5244 (None, _) => true,
5245 _ => false,
5246 };
5247 if query_matches {
5248 let position_matches = if menu.initial_position == position {
5249 true
5250 } else {
5251 let snapshot = self.buffer.read(cx).read(cx);
5252 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5253 };
5254 if position_matches {
5255 return;
5256 }
5257 }
5258 }
5259 };
5260
5261 let trigger_kind = match trigger {
5262 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5263 CompletionTriggerKind::TRIGGER_CHARACTER
5264 }
5265 _ => CompletionTriggerKind::INVOKED,
5266 };
5267 let completion_context = CompletionContext {
5268 trigger_character: trigger.and_then(|trigger| {
5269 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5270 Some(String::from(trigger))
5271 } else {
5272 None
5273 }
5274 }),
5275 trigger_kind,
5276 };
5277
5278 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5279 buffer_snapshot.surrounding_word(buffer_position)
5280 {
5281 let word_to_exclude = buffer_snapshot
5282 .text_for_range(word_range.clone())
5283 .collect::<String>();
5284 (
5285 buffer_snapshot.anchor_before(word_range.start)
5286 ..buffer_snapshot.anchor_after(buffer_position),
5287 Some(word_to_exclude),
5288 )
5289 } else {
5290 (buffer_position..buffer_position, None)
5291 };
5292
5293 let language = buffer_snapshot
5294 .language_at(buffer_position)
5295 .map(|language| language.name());
5296
5297 let completion_settings =
5298 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5299
5300 let show_completion_documentation = buffer_snapshot
5301 .settings_at(buffer_position, cx)
5302 .show_completion_documentation;
5303
5304 // The document can be large, so stay in reasonable bounds when searching for words,
5305 // otherwise completion pop-up might be slow to appear.
5306 const WORD_LOOKUP_ROWS: u32 = 5_000;
5307 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5308 let min_word_search = buffer_snapshot.clip_point(
5309 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5310 Bias::Left,
5311 );
5312 let max_word_search = buffer_snapshot.clip_point(
5313 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5314 Bias::Right,
5315 );
5316 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5317 ..buffer_snapshot.point_to_offset(max_word_search);
5318
5319 let skip_digits = query
5320 .as_ref()
5321 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5322
5323 let (mut words, provider_responses) = match &provider {
5324 Some(provider) => {
5325 let provider_responses = provider.completions(
5326 position.excerpt_id,
5327 &buffer,
5328 buffer_position,
5329 completion_context,
5330 window,
5331 cx,
5332 );
5333
5334 let words = match completion_settings.words {
5335 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5336 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5337 .background_spawn(async move {
5338 buffer_snapshot.words_in_range(WordsQuery {
5339 fuzzy_contents: None,
5340 range: word_search_range,
5341 skip_digits,
5342 })
5343 }),
5344 };
5345
5346 (words, provider_responses)
5347 }
5348 None => (
5349 cx.background_spawn(async move {
5350 buffer_snapshot.words_in_range(WordsQuery {
5351 fuzzy_contents: None,
5352 range: word_search_range,
5353 skip_digits,
5354 })
5355 }),
5356 Task::ready(Ok(Vec::new())),
5357 ),
5358 };
5359
5360 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5361
5362 let id = post_inc(&mut self.next_completion_id);
5363 let task = cx.spawn_in(window, async move |editor, cx| {
5364 let Ok(()) = editor.update(cx, |this, _| {
5365 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5366 }) else {
5367 return;
5368 };
5369
5370 // TODO: Ideally completions from different sources would be selectively re-queried, so
5371 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5372 let mut completions = Vec::new();
5373 let mut is_incomplete = false;
5374 if let Some(provider_responses) = provider_responses.await.log_err() {
5375 if !provider_responses.is_empty() {
5376 for response in provider_responses {
5377 completions.extend(response.completions);
5378 is_incomplete = is_incomplete || response.is_incomplete;
5379 }
5380 if completion_settings.words == WordsCompletionMode::Fallback {
5381 words = Task::ready(BTreeMap::default());
5382 }
5383 }
5384 }
5385
5386 let mut words = words.await;
5387 if let Some(word_to_exclude) = &word_to_exclude {
5388 words.remove(word_to_exclude);
5389 }
5390 for lsp_completion in &completions {
5391 words.remove(&lsp_completion.new_text);
5392 }
5393 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5394 replace_range: word_replace_range.clone(),
5395 new_text: word.clone(),
5396 label: CodeLabel::plain(word, None),
5397 icon_path: None,
5398 documentation: None,
5399 source: CompletionSource::BufferWord {
5400 word_range,
5401 resolved: false,
5402 },
5403 insert_text_mode: Some(InsertTextMode::AS_IS),
5404 confirm: None,
5405 }));
5406
5407 let menu = if completions.is_empty() {
5408 None
5409 } else {
5410 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5411 let languages = editor
5412 .workspace
5413 .as_ref()
5414 .and_then(|(workspace, _)| workspace.upgrade())
5415 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5416 let menu = CompletionsMenu::new(
5417 id,
5418 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5419 sort_completions,
5420 show_completion_documentation,
5421 position,
5422 query.clone(),
5423 is_incomplete,
5424 buffer.clone(),
5425 completions.into(),
5426 snippet_sort_order,
5427 languages,
5428 language,
5429 cx,
5430 );
5431
5432 let query = if filter_completions { query } else { None };
5433 let matches_task = if let Some(query) = query {
5434 menu.do_async_filtering(query, cx)
5435 } else {
5436 Task::ready(menu.unfiltered_matches())
5437 };
5438 (menu, matches_task)
5439 }) else {
5440 return;
5441 };
5442
5443 let matches = matches_task.await;
5444
5445 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5446 // Newer menu already set, so exit.
5447 match editor.context_menu.borrow().as_ref() {
5448 Some(CodeContextMenu::Completions(prev_menu)) => {
5449 if prev_menu.id > id {
5450 return;
5451 }
5452 }
5453 _ => {}
5454 };
5455
5456 // Only valid to take prev_menu because it the new menu is immediately set
5457 // below, or the menu is hidden.
5458 match editor.context_menu.borrow_mut().take() {
5459 Some(CodeContextMenu::Completions(prev_menu)) => {
5460 let position_matches =
5461 if prev_menu.initial_position == menu.initial_position {
5462 true
5463 } else {
5464 let snapshot = editor.buffer.read(cx).read(cx);
5465 prev_menu.initial_position.to_offset(&snapshot)
5466 == menu.initial_position.to_offset(&snapshot)
5467 };
5468 if position_matches {
5469 // Preserve markdown cache before `set_filter_results` because it will
5470 // try to populate the documentation cache.
5471 menu.preserve_markdown_cache(prev_menu);
5472 }
5473 }
5474 _ => {}
5475 };
5476
5477 menu.set_filter_results(matches, provider, window, cx);
5478 }) else {
5479 return;
5480 };
5481
5482 menu.visible().then_some(menu)
5483 };
5484
5485 editor
5486 .update_in(cx, |editor, window, cx| {
5487 if editor.focus_handle.is_focused(window) {
5488 if let Some(menu) = menu {
5489 *editor.context_menu.borrow_mut() =
5490 Some(CodeContextMenu::Completions(menu));
5491
5492 crate::hover_popover::hide_hover(editor, cx);
5493 if editor.show_edit_predictions_in_menu() {
5494 editor.update_visible_inline_completion(window, cx);
5495 } else {
5496 editor.discard_inline_completion(false, cx);
5497 }
5498
5499 cx.notify();
5500 return;
5501 }
5502 }
5503
5504 if editor.completion_tasks.len() <= 1 {
5505 // If there are no more completion tasks and the last menu was empty, we should hide it.
5506 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5507 // If it was already hidden and we don't show inline completions in the menu, we should
5508 // also show the inline-completion when available.
5509 if was_hidden && editor.show_edit_predictions_in_menu() {
5510 editor.update_visible_inline_completion(window, cx);
5511 }
5512 }
5513 })
5514 .ok();
5515 });
5516
5517 self.completion_tasks.push((id, task));
5518 }
5519
5520 #[cfg(feature = "test-support")]
5521 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5522 let menu = self.context_menu.borrow();
5523 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5524 let completions = menu.completions.borrow();
5525 Some(completions.to_vec())
5526 } else {
5527 None
5528 }
5529 }
5530
5531 pub fn with_completions_menu_matching_id<R>(
5532 &self,
5533 id: CompletionId,
5534 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5535 ) -> R {
5536 let mut context_menu = self.context_menu.borrow_mut();
5537 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5538 return f(None);
5539 };
5540 if completions_menu.id != id {
5541 return f(None);
5542 }
5543 f(Some(completions_menu))
5544 }
5545
5546 pub fn confirm_completion(
5547 &mut self,
5548 action: &ConfirmCompletion,
5549 window: &mut Window,
5550 cx: &mut Context<Self>,
5551 ) -> Option<Task<Result<()>>> {
5552 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5553 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5554 }
5555
5556 pub fn confirm_completion_insert(
5557 &mut self,
5558 _: &ConfirmCompletionInsert,
5559 window: &mut Window,
5560 cx: &mut Context<Self>,
5561 ) -> Option<Task<Result<()>>> {
5562 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5563 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5564 }
5565
5566 pub fn confirm_completion_replace(
5567 &mut self,
5568 _: &ConfirmCompletionReplace,
5569 window: &mut Window,
5570 cx: &mut Context<Self>,
5571 ) -> Option<Task<Result<()>>> {
5572 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5573 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5574 }
5575
5576 pub fn compose_completion(
5577 &mut self,
5578 action: &ComposeCompletion,
5579 window: &mut Window,
5580 cx: &mut Context<Self>,
5581 ) -> Option<Task<Result<()>>> {
5582 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5583 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5584 }
5585
5586 fn do_completion(
5587 &mut self,
5588 item_ix: Option<usize>,
5589 intent: CompletionIntent,
5590 window: &mut Window,
5591 cx: &mut Context<Editor>,
5592 ) -> Option<Task<Result<()>>> {
5593 use language::ToOffset as _;
5594
5595 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5596 else {
5597 return None;
5598 };
5599
5600 let candidate_id = {
5601 let entries = completions_menu.entries.borrow();
5602 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5603 if self.show_edit_predictions_in_menu() {
5604 self.discard_inline_completion(true, cx);
5605 }
5606 mat.candidate_id
5607 };
5608
5609 let completion = completions_menu
5610 .completions
5611 .borrow()
5612 .get(candidate_id)?
5613 .clone();
5614 cx.stop_propagation();
5615
5616 let buffer_handle = completions_menu.buffer.clone();
5617
5618 let CompletionEdit {
5619 new_text,
5620 snippet,
5621 replace_range,
5622 } = process_completion_for_edit(
5623 &completion,
5624 intent,
5625 &buffer_handle,
5626 &completions_menu.initial_position.text_anchor,
5627 cx,
5628 );
5629
5630 let buffer = buffer_handle.read(cx);
5631 let snapshot = self.buffer.read(cx).snapshot(cx);
5632 let newest_anchor = self.selections.newest_anchor();
5633 let replace_range_multibuffer = {
5634 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5635 let multibuffer_anchor = snapshot
5636 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5637 .unwrap()
5638 ..snapshot
5639 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5640 .unwrap();
5641 multibuffer_anchor.start.to_offset(&snapshot)
5642 ..multibuffer_anchor.end.to_offset(&snapshot)
5643 };
5644 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5645 return None;
5646 }
5647
5648 let old_text = buffer
5649 .text_for_range(replace_range.clone())
5650 .collect::<String>();
5651 let lookbehind = newest_anchor
5652 .start
5653 .text_anchor
5654 .to_offset(buffer)
5655 .saturating_sub(replace_range.start);
5656 let lookahead = replace_range
5657 .end
5658 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5659 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5660 let suffix = &old_text[lookbehind.min(old_text.len())..];
5661
5662 let selections = self.selections.all::<usize>(cx);
5663 let mut ranges = Vec::new();
5664 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5665
5666 for selection in &selections {
5667 let range = if selection.id == newest_anchor.id {
5668 replace_range_multibuffer.clone()
5669 } else {
5670 let mut range = selection.range();
5671
5672 // if prefix is present, don't duplicate it
5673 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5674 range.start = range.start.saturating_sub(lookbehind);
5675
5676 // if suffix is also present, mimic the newest cursor and replace it
5677 if selection.id != newest_anchor.id
5678 && snapshot.contains_str_at(range.end, suffix)
5679 {
5680 range.end += lookahead;
5681 }
5682 }
5683 range
5684 };
5685
5686 ranges.push(range.clone());
5687
5688 if !self.linked_edit_ranges.is_empty() {
5689 let start_anchor = snapshot.anchor_before(range.start);
5690 let end_anchor = snapshot.anchor_after(range.end);
5691 if let Some(ranges) = self
5692 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5693 {
5694 for (buffer, edits) in ranges {
5695 linked_edits
5696 .entry(buffer.clone())
5697 .or_default()
5698 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5699 }
5700 }
5701 }
5702 }
5703
5704 let common_prefix_len = old_text
5705 .chars()
5706 .zip(new_text.chars())
5707 .take_while(|(a, b)| a == b)
5708 .map(|(a, _)| a.len_utf8())
5709 .sum::<usize>();
5710
5711 cx.emit(EditorEvent::InputHandled {
5712 utf16_range_to_replace: None,
5713 text: new_text[common_prefix_len..].into(),
5714 });
5715
5716 self.transact(window, cx, |this, window, cx| {
5717 if let Some(mut snippet) = snippet {
5718 snippet.text = new_text.to_string();
5719 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5720 } else {
5721 this.buffer.update(cx, |buffer, cx| {
5722 let auto_indent = match completion.insert_text_mode {
5723 Some(InsertTextMode::AS_IS) => None,
5724 _ => this.autoindent_mode.clone(),
5725 };
5726 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5727 buffer.edit(edits, auto_indent, cx);
5728 });
5729 }
5730 for (buffer, edits) in linked_edits {
5731 buffer.update(cx, |buffer, cx| {
5732 let snapshot = buffer.snapshot();
5733 let edits = edits
5734 .into_iter()
5735 .map(|(range, text)| {
5736 use text::ToPoint as TP;
5737 let end_point = TP::to_point(&range.end, &snapshot);
5738 let start_point = TP::to_point(&range.start, &snapshot);
5739 (start_point..end_point, text)
5740 })
5741 .sorted_by_key(|(range, _)| range.start);
5742 buffer.edit(edits, None, cx);
5743 })
5744 }
5745
5746 this.refresh_inline_completion(true, false, window, cx);
5747 });
5748
5749 let show_new_completions_on_confirm = completion
5750 .confirm
5751 .as_ref()
5752 .map_or(false, |confirm| confirm(intent, window, cx));
5753 if show_new_completions_on_confirm {
5754 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5755 }
5756
5757 let provider = self.completion_provider.as_ref()?;
5758 drop(completion);
5759 let apply_edits = provider.apply_additional_edits_for_completion(
5760 buffer_handle,
5761 completions_menu.completions.clone(),
5762 candidate_id,
5763 true,
5764 cx,
5765 );
5766
5767 let editor_settings = EditorSettings::get_global(cx);
5768 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5769 // After the code completion is finished, users often want to know what signatures are needed.
5770 // so we should automatically call signature_help
5771 self.show_signature_help(&ShowSignatureHelp, window, cx);
5772 }
5773
5774 Some(cx.foreground_executor().spawn(async move {
5775 apply_edits.await?;
5776 Ok(())
5777 }))
5778 }
5779
5780 pub fn toggle_code_actions(
5781 &mut self,
5782 action: &ToggleCodeActions,
5783 window: &mut Window,
5784 cx: &mut Context<Self>,
5785 ) {
5786 let quick_launch = action.quick_launch;
5787 let mut context_menu = self.context_menu.borrow_mut();
5788 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5789 if code_actions.deployed_from == action.deployed_from {
5790 // Toggle if we're selecting the same one
5791 *context_menu = None;
5792 cx.notify();
5793 return;
5794 } else {
5795 // Otherwise, clear it and start a new one
5796 *context_menu = None;
5797 cx.notify();
5798 }
5799 }
5800 drop(context_menu);
5801 let snapshot = self.snapshot(window, cx);
5802 let deployed_from = action.deployed_from.clone();
5803 let action = action.clone();
5804 self.completion_tasks.clear();
5805 self.discard_inline_completion(false, cx);
5806
5807 let multibuffer_point = match &action.deployed_from {
5808 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5809 DisplayPoint::new(*row, 0).to_point(&snapshot)
5810 }
5811 _ => self.selections.newest::<Point>(cx).head(),
5812 };
5813 let Some((buffer, buffer_row)) = snapshot
5814 .buffer_snapshot
5815 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5816 .and_then(|(buffer_snapshot, range)| {
5817 self.buffer()
5818 .read(cx)
5819 .buffer(buffer_snapshot.remote_id())
5820 .map(|buffer| (buffer, range.start.row))
5821 })
5822 else {
5823 return;
5824 };
5825 let buffer_id = buffer.read(cx).remote_id();
5826 let tasks = self
5827 .tasks
5828 .get(&(buffer_id, buffer_row))
5829 .map(|t| Arc::new(t.to_owned()));
5830
5831 if !self.focus_handle.is_focused(window) {
5832 return;
5833 }
5834 let project = self.project.clone();
5835
5836 let code_actions_task = match deployed_from {
5837 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5838 _ => self.code_actions(buffer_row, window, cx),
5839 };
5840
5841 let runnable_task = match deployed_from {
5842 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5843 _ => {
5844 let mut task_context_task = Task::ready(None);
5845 if let Some(tasks) = &tasks {
5846 if let Some(project) = project {
5847 task_context_task =
5848 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5849 }
5850 }
5851
5852 cx.spawn_in(window, {
5853 let buffer = buffer.clone();
5854 async move |editor, cx| {
5855 let task_context = task_context_task.await;
5856
5857 let resolved_tasks =
5858 tasks
5859 .zip(task_context.clone())
5860 .map(|(tasks, task_context)| ResolvedTasks {
5861 templates: tasks.resolve(&task_context).collect(),
5862 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5863 multibuffer_point.row,
5864 tasks.column,
5865 )),
5866 });
5867 let debug_scenarios = editor
5868 .update(cx, |editor, cx| {
5869 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5870 })?
5871 .await;
5872 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5873 }
5874 })
5875 }
5876 };
5877
5878 cx.spawn_in(window, async move |editor, cx| {
5879 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5880 let code_actions = code_actions_task.await;
5881 let spawn_straight_away = quick_launch
5882 && resolved_tasks
5883 .as_ref()
5884 .map_or(false, |tasks| tasks.templates.len() == 1)
5885 && code_actions
5886 .as_ref()
5887 .map_or(true, |actions| actions.is_empty())
5888 && debug_scenarios.is_empty();
5889
5890 editor.update_in(cx, |editor, window, cx| {
5891 crate::hover_popover::hide_hover(editor, cx);
5892 *editor.context_menu.borrow_mut() =
5893 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5894 buffer,
5895 actions: CodeActionContents::new(
5896 resolved_tasks,
5897 code_actions,
5898 debug_scenarios,
5899 task_context.unwrap_or_default(),
5900 ),
5901 selected_item: Default::default(),
5902 scroll_handle: UniformListScrollHandle::default(),
5903 deployed_from,
5904 }));
5905 cx.notify();
5906 if spawn_straight_away {
5907 if let Some(task) = editor.confirm_code_action(
5908 &ConfirmCodeAction { item_ix: Some(0) },
5909 window,
5910 cx,
5911 ) {
5912 return task;
5913 }
5914 }
5915
5916 Task::ready(Ok(()))
5917 })
5918 })
5919 .detach_and_log_err(cx);
5920 }
5921
5922 fn debug_scenarios(
5923 &mut self,
5924 resolved_tasks: &Option<ResolvedTasks>,
5925 buffer: &Entity<Buffer>,
5926 cx: &mut App,
5927 ) -> Task<Vec<task::DebugScenario>> {
5928 if cx.has_flag::<DebuggerFeatureFlag>() {
5929 maybe!({
5930 let project = self.project.as_ref()?;
5931 let dap_store = project.read(cx).dap_store();
5932 let mut scenarios = vec![];
5933 let resolved_tasks = resolved_tasks.as_ref()?;
5934 let buffer = buffer.read(cx);
5935 let language = buffer.language()?;
5936 let file = buffer.file();
5937 let debug_adapter = language_settings(language.name().into(), file, cx)
5938 .debuggers
5939 .first()
5940 .map(SharedString::from)
5941 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5942
5943 dap_store.update(cx, |dap_store, cx| {
5944 for (_, task) in &resolved_tasks.templates {
5945 let maybe_scenario = dap_store.debug_scenario_for_build_task(
5946 task.original_task().clone(),
5947 debug_adapter.clone().into(),
5948 task.display_label().to_owned().into(),
5949 cx,
5950 );
5951 scenarios.push(maybe_scenario);
5952 }
5953 });
5954 Some(cx.background_spawn(async move {
5955 let scenarios = futures::future::join_all(scenarios)
5956 .await
5957 .into_iter()
5958 .flatten()
5959 .collect::<Vec<_>>();
5960 scenarios
5961 }))
5962 })
5963 .unwrap_or_else(|| Task::ready(vec![]))
5964 } else {
5965 Task::ready(vec![])
5966 }
5967 }
5968
5969 fn code_actions(
5970 &mut self,
5971 buffer_row: u32,
5972 window: &mut Window,
5973 cx: &mut Context<Self>,
5974 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5975 let mut task = self.code_actions_task.take();
5976 cx.spawn_in(window, async move |editor, cx| {
5977 while let Some(prev_task) = task {
5978 prev_task.await.log_err();
5979 task = editor
5980 .update(cx, |this, _| this.code_actions_task.take())
5981 .ok()?;
5982 }
5983
5984 editor
5985 .update(cx, |editor, cx| {
5986 editor
5987 .available_code_actions
5988 .clone()
5989 .and_then(|(location, code_actions)| {
5990 let snapshot = location.buffer.read(cx).snapshot();
5991 let point_range = location.range.to_point(&snapshot);
5992 let point_range = point_range.start.row..=point_range.end.row;
5993 if point_range.contains(&buffer_row) {
5994 Some(code_actions)
5995 } else {
5996 None
5997 }
5998 })
5999 })
6000 .ok()
6001 .flatten()
6002 })
6003 }
6004
6005 pub fn confirm_code_action(
6006 &mut self,
6007 action: &ConfirmCodeAction,
6008 window: &mut Window,
6009 cx: &mut Context<Self>,
6010 ) -> Option<Task<Result<()>>> {
6011 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6012
6013 let actions_menu =
6014 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6015 menu
6016 } else {
6017 return None;
6018 };
6019
6020 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6021 let action = actions_menu.actions.get(action_ix)?;
6022 let title = action.label();
6023 let buffer = actions_menu.buffer;
6024 let workspace = self.workspace()?;
6025
6026 match action {
6027 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6028 workspace.update(cx, |workspace, cx| {
6029 workspace.schedule_resolved_task(
6030 task_source_kind,
6031 resolved_task,
6032 false,
6033 window,
6034 cx,
6035 );
6036
6037 Some(Task::ready(Ok(())))
6038 })
6039 }
6040 CodeActionsItem::CodeAction {
6041 excerpt_id,
6042 action,
6043 provider,
6044 } => {
6045 let apply_code_action =
6046 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6047 let workspace = workspace.downgrade();
6048 Some(cx.spawn_in(window, async move |editor, cx| {
6049 let project_transaction = apply_code_action.await?;
6050 Self::open_project_transaction(
6051 &editor,
6052 workspace,
6053 project_transaction,
6054 title,
6055 cx,
6056 )
6057 .await
6058 }))
6059 }
6060 CodeActionsItem::DebugScenario(scenario) => {
6061 let context = actions_menu.actions.context.clone();
6062
6063 workspace.update(cx, |workspace, cx| {
6064 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6065 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
6066 });
6067 Some(Task::ready(Ok(())))
6068 }
6069 }
6070 }
6071
6072 pub async fn open_project_transaction(
6073 this: &WeakEntity<Editor>,
6074 workspace: WeakEntity<Workspace>,
6075 transaction: ProjectTransaction,
6076 title: String,
6077 cx: &mut AsyncWindowContext,
6078 ) -> Result<()> {
6079 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6080 cx.update(|_, cx| {
6081 entries.sort_unstable_by_key(|(buffer, _)| {
6082 buffer.read(cx).file().map(|f| f.path().clone())
6083 });
6084 })?;
6085
6086 // If the project transaction's edits are all contained within this editor, then
6087 // avoid opening a new editor to display them.
6088
6089 if let Some((buffer, transaction)) = entries.first() {
6090 if entries.len() == 1 {
6091 let excerpt = this.update(cx, |editor, cx| {
6092 editor
6093 .buffer()
6094 .read(cx)
6095 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6096 })?;
6097 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6098 if excerpted_buffer == *buffer {
6099 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6100 let excerpt_range = excerpt_range.to_offset(buffer);
6101 buffer
6102 .edited_ranges_for_transaction::<usize>(transaction)
6103 .all(|range| {
6104 excerpt_range.start <= range.start
6105 && excerpt_range.end >= range.end
6106 })
6107 })?;
6108
6109 if all_edits_within_excerpt {
6110 return Ok(());
6111 }
6112 }
6113 }
6114 }
6115 } else {
6116 return Ok(());
6117 }
6118
6119 let mut ranges_to_highlight = Vec::new();
6120 let excerpt_buffer = cx.new(|cx| {
6121 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6122 for (buffer_handle, transaction) in &entries {
6123 let edited_ranges = buffer_handle
6124 .read(cx)
6125 .edited_ranges_for_transaction::<Point>(transaction)
6126 .collect::<Vec<_>>();
6127 let (ranges, _) = multibuffer.set_excerpts_for_path(
6128 PathKey::for_buffer(buffer_handle, cx),
6129 buffer_handle.clone(),
6130 edited_ranges,
6131 DEFAULT_MULTIBUFFER_CONTEXT,
6132 cx,
6133 );
6134
6135 ranges_to_highlight.extend(ranges);
6136 }
6137 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6138 multibuffer
6139 })?;
6140
6141 workspace.update_in(cx, |workspace, window, cx| {
6142 let project = workspace.project().clone();
6143 let editor =
6144 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6145 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6146 editor.update(cx, |editor, cx| {
6147 editor.highlight_background::<Self>(
6148 &ranges_to_highlight,
6149 |theme| theme.colors().editor_highlighted_line_background,
6150 cx,
6151 );
6152 });
6153 })?;
6154
6155 Ok(())
6156 }
6157
6158 pub fn clear_code_action_providers(&mut self) {
6159 self.code_action_providers.clear();
6160 self.available_code_actions.take();
6161 }
6162
6163 pub fn add_code_action_provider(
6164 &mut self,
6165 provider: Rc<dyn CodeActionProvider>,
6166 window: &mut Window,
6167 cx: &mut Context<Self>,
6168 ) {
6169 if self
6170 .code_action_providers
6171 .iter()
6172 .any(|existing_provider| existing_provider.id() == provider.id())
6173 {
6174 return;
6175 }
6176
6177 self.code_action_providers.push(provider);
6178 self.refresh_code_actions(window, cx);
6179 }
6180
6181 pub fn remove_code_action_provider(
6182 &mut self,
6183 id: Arc<str>,
6184 window: &mut Window,
6185 cx: &mut Context<Self>,
6186 ) {
6187 self.code_action_providers
6188 .retain(|provider| provider.id() != id);
6189 self.refresh_code_actions(window, cx);
6190 }
6191
6192 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6193 !self.code_action_providers.is_empty()
6194 && EditorSettings::get_global(cx).toolbar.code_actions
6195 }
6196
6197 pub fn has_available_code_actions(&self) -> bool {
6198 self.available_code_actions
6199 .as_ref()
6200 .is_some_and(|(_, actions)| !actions.is_empty())
6201 }
6202
6203 fn render_inline_code_actions(
6204 &self,
6205 icon_size: ui::IconSize,
6206 display_row: DisplayRow,
6207 is_active: bool,
6208 cx: &mut Context<Self>,
6209 ) -> AnyElement {
6210 let show_tooltip = !self.context_menu_visible();
6211 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6212 .icon_size(icon_size)
6213 .shape(ui::IconButtonShape::Square)
6214 .style(ButtonStyle::Transparent)
6215 .icon_color(ui::Color::Hidden)
6216 .toggle_state(is_active)
6217 .when(show_tooltip, |this| {
6218 this.tooltip({
6219 let focus_handle = self.focus_handle.clone();
6220 move |window, cx| {
6221 Tooltip::for_action_in(
6222 "Toggle Code Actions",
6223 &ToggleCodeActions {
6224 deployed_from: None,
6225 quick_launch: false,
6226 },
6227 &focus_handle,
6228 window,
6229 cx,
6230 )
6231 }
6232 })
6233 })
6234 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6235 window.focus(&editor.focus_handle(cx));
6236 editor.toggle_code_actions(
6237 &crate::actions::ToggleCodeActions {
6238 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6239 display_row,
6240 )),
6241 quick_launch: false,
6242 },
6243 window,
6244 cx,
6245 );
6246 }))
6247 .into_any_element()
6248 }
6249
6250 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6251 &self.context_menu
6252 }
6253
6254 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6255 let newest_selection = self.selections.newest_anchor().clone();
6256 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6257 let buffer = self.buffer.read(cx);
6258 if newest_selection.head().diff_base_anchor.is_some() {
6259 return None;
6260 }
6261 let (start_buffer, start) =
6262 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6263 let (end_buffer, end) =
6264 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6265 if start_buffer != end_buffer {
6266 return None;
6267 }
6268
6269 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6270 cx.background_executor()
6271 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6272 .await;
6273
6274 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6275 let providers = this.code_action_providers.clone();
6276 let tasks = this
6277 .code_action_providers
6278 .iter()
6279 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6280 .collect::<Vec<_>>();
6281 (providers, tasks)
6282 })?;
6283
6284 let mut actions = Vec::new();
6285 for (provider, provider_actions) in
6286 providers.into_iter().zip(future::join_all(tasks).await)
6287 {
6288 if let Some(provider_actions) = provider_actions.log_err() {
6289 actions.extend(provider_actions.into_iter().map(|action| {
6290 AvailableCodeAction {
6291 excerpt_id: newest_selection.start.excerpt_id,
6292 action,
6293 provider: provider.clone(),
6294 }
6295 }));
6296 }
6297 }
6298
6299 this.update(cx, |this, cx| {
6300 this.available_code_actions = if actions.is_empty() {
6301 None
6302 } else {
6303 Some((
6304 Location {
6305 buffer: start_buffer,
6306 range: start..end,
6307 },
6308 actions.into(),
6309 ))
6310 };
6311 cx.notify();
6312 })
6313 }));
6314 None
6315 }
6316
6317 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6318 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6319 self.show_git_blame_inline = false;
6320
6321 self.show_git_blame_inline_delay_task =
6322 Some(cx.spawn_in(window, async move |this, cx| {
6323 cx.background_executor().timer(delay).await;
6324
6325 this.update(cx, |this, cx| {
6326 this.show_git_blame_inline = true;
6327 cx.notify();
6328 })
6329 .log_err();
6330 }));
6331 }
6332 }
6333
6334 fn show_blame_popover(
6335 &mut self,
6336 blame_entry: &BlameEntry,
6337 position: gpui::Point<Pixels>,
6338 cx: &mut Context<Self>,
6339 ) {
6340 if let Some(state) = &mut self.inline_blame_popover {
6341 state.hide_task.take();
6342 } else {
6343 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6344 let blame_entry = blame_entry.clone();
6345 let show_task = cx.spawn(async move |editor, cx| {
6346 cx.background_executor()
6347 .timer(std::time::Duration::from_millis(delay))
6348 .await;
6349 editor
6350 .update(cx, |editor, cx| {
6351 editor.inline_blame_popover_show_task.take();
6352 let Some(blame) = editor.blame.as_ref() else {
6353 return;
6354 };
6355 let blame = blame.read(cx);
6356 let details = blame.details_for_entry(&blame_entry);
6357 let markdown = cx.new(|cx| {
6358 Markdown::new(
6359 details
6360 .as_ref()
6361 .map(|message| message.message.clone())
6362 .unwrap_or_default(),
6363 None,
6364 None,
6365 cx,
6366 )
6367 });
6368 editor.inline_blame_popover = Some(InlineBlamePopover {
6369 position,
6370 hide_task: None,
6371 popover_bounds: None,
6372 popover_state: InlineBlamePopoverState {
6373 scroll_handle: ScrollHandle::new(),
6374 commit_message: details,
6375 markdown,
6376 },
6377 });
6378 cx.notify();
6379 })
6380 .ok();
6381 });
6382 self.inline_blame_popover_show_task = Some(show_task);
6383 }
6384 }
6385
6386 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6387 self.inline_blame_popover_show_task.take();
6388 if let Some(state) = &mut self.inline_blame_popover {
6389 let hide_task = cx.spawn(async move |editor, cx| {
6390 cx.background_executor()
6391 .timer(std::time::Duration::from_millis(100))
6392 .await;
6393 editor
6394 .update(cx, |editor, cx| {
6395 editor.inline_blame_popover.take();
6396 cx.notify();
6397 })
6398 .ok();
6399 });
6400 state.hide_task = Some(hide_task);
6401 }
6402 }
6403
6404 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6405 if self.pending_rename.is_some() {
6406 return None;
6407 }
6408
6409 let provider = self.semantics_provider.clone()?;
6410 let buffer = self.buffer.read(cx);
6411 let newest_selection = self.selections.newest_anchor().clone();
6412 let cursor_position = newest_selection.head();
6413 let (cursor_buffer, cursor_buffer_position) =
6414 buffer.text_anchor_for_position(cursor_position, cx)?;
6415 let (tail_buffer, tail_buffer_position) =
6416 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6417 if cursor_buffer != tail_buffer {
6418 return None;
6419 }
6420
6421 let snapshot = cursor_buffer.read(cx).snapshot();
6422 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6423 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6424 if start_word_range != end_word_range {
6425 self.document_highlights_task.take();
6426 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6427 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6428 return None;
6429 }
6430
6431 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6432 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6433 cx.background_executor()
6434 .timer(Duration::from_millis(debounce))
6435 .await;
6436
6437 let highlights = if let Some(highlights) = cx
6438 .update(|cx| {
6439 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6440 })
6441 .ok()
6442 .flatten()
6443 {
6444 highlights.await.log_err()
6445 } else {
6446 None
6447 };
6448
6449 if let Some(highlights) = highlights {
6450 this.update(cx, |this, cx| {
6451 if this.pending_rename.is_some() {
6452 return;
6453 }
6454
6455 let buffer_id = cursor_position.buffer_id;
6456 let buffer = this.buffer.read(cx);
6457 if !buffer
6458 .text_anchor_for_position(cursor_position, cx)
6459 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6460 {
6461 return;
6462 }
6463
6464 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6465 let mut write_ranges = Vec::new();
6466 let mut read_ranges = Vec::new();
6467 for highlight in highlights {
6468 for (excerpt_id, excerpt_range) in
6469 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6470 {
6471 let start = highlight
6472 .range
6473 .start
6474 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6475 let end = highlight
6476 .range
6477 .end
6478 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6479 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6480 continue;
6481 }
6482
6483 let range = Anchor {
6484 buffer_id,
6485 excerpt_id,
6486 text_anchor: start,
6487 diff_base_anchor: None,
6488 }..Anchor {
6489 buffer_id,
6490 excerpt_id,
6491 text_anchor: end,
6492 diff_base_anchor: None,
6493 };
6494 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6495 write_ranges.push(range);
6496 } else {
6497 read_ranges.push(range);
6498 }
6499 }
6500 }
6501
6502 this.highlight_background::<DocumentHighlightRead>(
6503 &read_ranges,
6504 |theme| theme.colors().editor_document_highlight_read_background,
6505 cx,
6506 );
6507 this.highlight_background::<DocumentHighlightWrite>(
6508 &write_ranges,
6509 |theme| theme.colors().editor_document_highlight_write_background,
6510 cx,
6511 );
6512 cx.notify();
6513 })
6514 .log_err();
6515 }
6516 }));
6517 None
6518 }
6519
6520 fn prepare_highlight_query_from_selection(
6521 &mut self,
6522 cx: &mut Context<Editor>,
6523 ) -> Option<(String, Range<Anchor>)> {
6524 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6525 return None;
6526 }
6527 if !EditorSettings::get_global(cx).selection_highlight {
6528 return None;
6529 }
6530 if self.selections.count() != 1 || self.selections.line_mode {
6531 return None;
6532 }
6533 let selection = self.selections.newest::<Point>(cx);
6534 if selection.is_empty() || selection.start.row != selection.end.row {
6535 return None;
6536 }
6537 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6538 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6539 let query = multi_buffer_snapshot
6540 .text_for_range(selection_anchor_range.clone())
6541 .collect::<String>();
6542 if query.trim().is_empty() {
6543 return None;
6544 }
6545 Some((query, selection_anchor_range))
6546 }
6547
6548 fn update_selection_occurrence_highlights(
6549 &mut self,
6550 query_text: String,
6551 query_range: Range<Anchor>,
6552 multi_buffer_range_to_query: Range<Point>,
6553 use_debounce: bool,
6554 window: &mut Window,
6555 cx: &mut Context<Editor>,
6556 ) -> Task<()> {
6557 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6558 cx.spawn_in(window, async move |editor, cx| {
6559 if use_debounce {
6560 cx.background_executor()
6561 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6562 .await;
6563 }
6564 let match_task = cx.background_spawn(async move {
6565 let buffer_ranges = multi_buffer_snapshot
6566 .range_to_buffer_ranges(multi_buffer_range_to_query)
6567 .into_iter()
6568 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6569 let mut match_ranges = Vec::new();
6570 let Ok(regex) = project::search::SearchQuery::text(
6571 query_text.clone(),
6572 false,
6573 false,
6574 false,
6575 Default::default(),
6576 Default::default(),
6577 false,
6578 None,
6579 ) else {
6580 return Vec::default();
6581 };
6582 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6583 match_ranges.extend(
6584 regex
6585 .search(&buffer_snapshot, Some(search_range.clone()))
6586 .await
6587 .into_iter()
6588 .filter_map(|match_range| {
6589 let match_start = buffer_snapshot
6590 .anchor_after(search_range.start + match_range.start);
6591 let match_end = buffer_snapshot
6592 .anchor_before(search_range.start + match_range.end);
6593 let match_anchor_range = Anchor::range_in_buffer(
6594 excerpt_id,
6595 buffer_snapshot.remote_id(),
6596 match_start..match_end,
6597 );
6598 (match_anchor_range != query_range).then_some(match_anchor_range)
6599 }),
6600 );
6601 }
6602 match_ranges
6603 });
6604 let match_ranges = match_task.await;
6605 editor
6606 .update_in(cx, |editor, _, cx| {
6607 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6608 if !match_ranges.is_empty() {
6609 editor.highlight_background::<SelectedTextHighlight>(
6610 &match_ranges,
6611 |theme| theme.colors().editor_document_highlight_bracket_background,
6612 cx,
6613 )
6614 }
6615 })
6616 .log_err();
6617 })
6618 }
6619
6620 fn refresh_selected_text_highlights(
6621 &mut self,
6622 on_buffer_edit: bool,
6623 window: &mut Window,
6624 cx: &mut Context<Editor>,
6625 ) {
6626 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6627 else {
6628 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6629 self.quick_selection_highlight_task.take();
6630 self.debounced_selection_highlight_task.take();
6631 return;
6632 };
6633 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6634 if on_buffer_edit
6635 || self
6636 .quick_selection_highlight_task
6637 .as_ref()
6638 .map_or(true, |(prev_anchor_range, _)| {
6639 prev_anchor_range != &query_range
6640 })
6641 {
6642 let multi_buffer_visible_start = self
6643 .scroll_manager
6644 .anchor()
6645 .anchor
6646 .to_point(&multi_buffer_snapshot);
6647 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6648 multi_buffer_visible_start
6649 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6650 Bias::Left,
6651 );
6652 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6653 self.quick_selection_highlight_task = Some((
6654 query_range.clone(),
6655 self.update_selection_occurrence_highlights(
6656 query_text.clone(),
6657 query_range.clone(),
6658 multi_buffer_visible_range,
6659 false,
6660 window,
6661 cx,
6662 ),
6663 ));
6664 }
6665 if on_buffer_edit
6666 || self
6667 .debounced_selection_highlight_task
6668 .as_ref()
6669 .map_or(true, |(prev_anchor_range, _)| {
6670 prev_anchor_range != &query_range
6671 })
6672 {
6673 let multi_buffer_start = multi_buffer_snapshot
6674 .anchor_before(0)
6675 .to_point(&multi_buffer_snapshot);
6676 let multi_buffer_end = multi_buffer_snapshot
6677 .anchor_after(multi_buffer_snapshot.len())
6678 .to_point(&multi_buffer_snapshot);
6679 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6680 self.debounced_selection_highlight_task = Some((
6681 query_range.clone(),
6682 self.update_selection_occurrence_highlights(
6683 query_text,
6684 query_range,
6685 multi_buffer_full_range,
6686 true,
6687 window,
6688 cx,
6689 ),
6690 ));
6691 }
6692 }
6693
6694 pub fn refresh_inline_completion(
6695 &mut self,
6696 debounce: bool,
6697 user_requested: bool,
6698 window: &mut Window,
6699 cx: &mut Context<Self>,
6700 ) -> Option<()> {
6701 let provider = self.edit_prediction_provider()?;
6702 let cursor = self.selections.newest_anchor().head();
6703 let (buffer, cursor_buffer_position) =
6704 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6705
6706 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6707 self.discard_inline_completion(false, cx);
6708 return None;
6709 }
6710
6711 if !user_requested
6712 && (!self.should_show_edit_predictions()
6713 || !self.is_focused(window)
6714 || buffer.read(cx).is_empty())
6715 {
6716 self.discard_inline_completion(false, cx);
6717 return None;
6718 }
6719
6720 self.update_visible_inline_completion(window, cx);
6721 provider.refresh(
6722 self.project.clone(),
6723 buffer,
6724 cursor_buffer_position,
6725 debounce,
6726 cx,
6727 );
6728 Some(())
6729 }
6730
6731 fn show_edit_predictions_in_menu(&self) -> bool {
6732 match self.edit_prediction_settings {
6733 EditPredictionSettings::Disabled => false,
6734 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6735 }
6736 }
6737
6738 pub fn edit_predictions_enabled(&self) -> bool {
6739 match self.edit_prediction_settings {
6740 EditPredictionSettings::Disabled => false,
6741 EditPredictionSettings::Enabled { .. } => true,
6742 }
6743 }
6744
6745 fn edit_prediction_requires_modifier(&self) -> bool {
6746 match self.edit_prediction_settings {
6747 EditPredictionSettings::Disabled => false,
6748 EditPredictionSettings::Enabled {
6749 preview_requires_modifier,
6750 ..
6751 } => preview_requires_modifier,
6752 }
6753 }
6754
6755 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6756 if self.edit_prediction_provider.is_none() {
6757 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6758 } else {
6759 let selection = self.selections.newest_anchor();
6760 let cursor = selection.head();
6761
6762 if let Some((buffer, cursor_buffer_position)) =
6763 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6764 {
6765 self.edit_prediction_settings =
6766 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6767 }
6768 }
6769 }
6770
6771 fn edit_prediction_settings_at_position(
6772 &self,
6773 buffer: &Entity<Buffer>,
6774 buffer_position: language::Anchor,
6775 cx: &App,
6776 ) -> EditPredictionSettings {
6777 if !self.mode.is_full()
6778 || !self.show_inline_completions_override.unwrap_or(true)
6779 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6780 {
6781 return EditPredictionSettings::Disabled;
6782 }
6783
6784 let buffer = buffer.read(cx);
6785
6786 let file = buffer.file();
6787
6788 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6789 return EditPredictionSettings::Disabled;
6790 };
6791
6792 let by_provider = matches!(
6793 self.menu_inline_completions_policy,
6794 MenuInlineCompletionsPolicy::ByProvider
6795 );
6796
6797 let show_in_menu = by_provider
6798 && self
6799 .edit_prediction_provider
6800 .as_ref()
6801 .map_or(false, |provider| {
6802 provider.provider.show_completions_in_menu()
6803 });
6804
6805 let preview_requires_modifier =
6806 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6807
6808 EditPredictionSettings::Enabled {
6809 show_in_menu,
6810 preview_requires_modifier,
6811 }
6812 }
6813
6814 fn should_show_edit_predictions(&self) -> bool {
6815 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6816 }
6817
6818 pub fn edit_prediction_preview_is_active(&self) -> bool {
6819 matches!(
6820 self.edit_prediction_preview,
6821 EditPredictionPreview::Active { .. }
6822 )
6823 }
6824
6825 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6826 let cursor = self.selections.newest_anchor().head();
6827 if let Some((buffer, cursor_position)) =
6828 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6829 {
6830 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6831 } else {
6832 false
6833 }
6834 }
6835
6836 pub fn supports_minimap(&self, cx: &App) -> bool {
6837 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6838 }
6839
6840 fn edit_predictions_enabled_in_buffer(
6841 &self,
6842 buffer: &Entity<Buffer>,
6843 buffer_position: language::Anchor,
6844 cx: &App,
6845 ) -> bool {
6846 maybe!({
6847 if self.read_only(cx) {
6848 return Some(false);
6849 }
6850 let provider = self.edit_prediction_provider()?;
6851 if !provider.is_enabled(&buffer, buffer_position, cx) {
6852 return Some(false);
6853 }
6854 let buffer = buffer.read(cx);
6855 let Some(file) = buffer.file() else {
6856 return Some(true);
6857 };
6858 let settings = all_language_settings(Some(file), cx);
6859 Some(settings.edit_predictions_enabled_for_file(file, cx))
6860 })
6861 .unwrap_or(false)
6862 }
6863
6864 fn cycle_inline_completion(
6865 &mut self,
6866 direction: Direction,
6867 window: &mut Window,
6868 cx: &mut Context<Self>,
6869 ) -> Option<()> {
6870 let provider = self.edit_prediction_provider()?;
6871 let cursor = self.selections.newest_anchor().head();
6872 let (buffer, cursor_buffer_position) =
6873 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6874 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6875 return None;
6876 }
6877
6878 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6879 self.update_visible_inline_completion(window, cx);
6880
6881 Some(())
6882 }
6883
6884 pub fn show_inline_completion(
6885 &mut self,
6886 _: &ShowEditPrediction,
6887 window: &mut Window,
6888 cx: &mut Context<Self>,
6889 ) {
6890 if !self.has_active_inline_completion() {
6891 self.refresh_inline_completion(false, true, window, cx);
6892 return;
6893 }
6894
6895 self.update_visible_inline_completion(window, cx);
6896 }
6897
6898 pub fn display_cursor_names(
6899 &mut self,
6900 _: &DisplayCursorNames,
6901 window: &mut Window,
6902 cx: &mut Context<Self>,
6903 ) {
6904 self.show_cursor_names(window, cx);
6905 }
6906
6907 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6908 self.show_cursor_names = true;
6909 cx.notify();
6910 cx.spawn_in(window, async move |this, cx| {
6911 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6912 this.update(cx, |this, cx| {
6913 this.show_cursor_names = false;
6914 cx.notify()
6915 })
6916 .ok()
6917 })
6918 .detach();
6919 }
6920
6921 pub fn next_edit_prediction(
6922 &mut self,
6923 _: &NextEditPrediction,
6924 window: &mut Window,
6925 cx: &mut Context<Self>,
6926 ) {
6927 if self.has_active_inline_completion() {
6928 self.cycle_inline_completion(Direction::Next, window, cx);
6929 } else {
6930 let is_copilot_disabled = self
6931 .refresh_inline_completion(false, true, window, cx)
6932 .is_none();
6933 if is_copilot_disabled {
6934 cx.propagate();
6935 }
6936 }
6937 }
6938
6939 pub fn previous_edit_prediction(
6940 &mut self,
6941 _: &PreviousEditPrediction,
6942 window: &mut Window,
6943 cx: &mut Context<Self>,
6944 ) {
6945 if self.has_active_inline_completion() {
6946 self.cycle_inline_completion(Direction::Prev, window, cx);
6947 } else {
6948 let is_copilot_disabled = self
6949 .refresh_inline_completion(false, true, window, cx)
6950 .is_none();
6951 if is_copilot_disabled {
6952 cx.propagate();
6953 }
6954 }
6955 }
6956
6957 pub fn accept_edit_prediction(
6958 &mut self,
6959 _: &AcceptEditPrediction,
6960 window: &mut Window,
6961 cx: &mut Context<Self>,
6962 ) {
6963 if self.show_edit_predictions_in_menu() {
6964 self.hide_context_menu(window, cx);
6965 }
6966
6967 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6968 return;
6969 };
6970
6971 self.report_inline_completion_event(
6972 active_inline_completion.completion_id.clone(),
6973 true,
6974 cx,
6975 );
6976
6977 match &active_inline_completion.completion {
6978 InlineCompletion::Move { target, .. } => {
6979 let target = *target;
6980
6981 if let Some(position_map) = &self.last_position_map {
6982 if position_map
6983 .visible_row_range
6984 .contains(&target.to_display_point(&position_map.snapshot).row())
6985 || !self.edit_prediction_requires_modifier()
6986 {
6987 self.unfold_ranges(&[target..target], true, false, cx);
6988 // Note that this is also done in vim's handler of the Tab action.
6989 self.change_selections(
6990 Some(Autoscroll::newest()),
6991 window,
6992 cx,
6993 |selections| {
6994 selections.select_anchor_ranges([target..target]);
6995 },
6996 );
6997 self.clear_row_highlights::<EditPredictionPreview>();
6998
6999 self.edit_prediction_preview
7000 .set_previous_scroll_position(None);
7001 } else {
7002 self.edit_prediction_preview
7003 .set_previous_scroll_position(Some(
7004 position_map.snapshot.scroll_anchor,
7005 ));
7006
7007 self.highlight_rows::<EditPredictionPreview>(
7008 target..target,
7009 cx.theme().colors().editor_highlighted_line_background,
7010 RowHighlightOptions {
7011 autoscroll: true,
7012 ..Default::default()
7013 },
7014 cx,
7015 );
7016 self.request_autoscroll(Autoscroll::fit(), cx);
7017 }
7018 }
7019 }
7020 InlineCompletion::Edit { edits, .. } => {
7021 if let Some(provider) = self.edit_prediction_provider() {
7022 provider.accept(cx);
7023 }
7024
7025 // Store the transaction ID and selections before applying the edit
7026 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7027
7028 let snapshot = self.buffer.read(cx).snapshot(cx);
7029 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7030
7031 self.buffer.update(cx, |buffer, cx| {
7032 buffer.edit(edits.iter().cloned(), None, cx)
7033 });
7034
7035 self.change_selections(None, window, cx, |s| {
7036 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7037 });
7038
7039 let selections = self.selections.disjoint_anchors();
7040 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7041 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7042 if has_new_transaction {
7043 self.selection_history
7044 .insert_transaction(transaction_id_now, selections);
7045 }
7046 }
7047
7048 self.update_visible_inline_completion(window, cx);
7049 if self.active_inline_completion.is_none() {
7050 self.refresh_inline_completion(true, true, window, cx);
7051 }
7052
7053 cx.notify();
7054 }
7055 }
7056
7057 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7058 }
7059
7060 pub fn accept_partial_inline_completion(
7061 &mut self,
7062 _: &AcceptPartialEditPrediction,
7063 window: &mut Window,
7064 cx: &mut Context<Self>,
7065 ) {
7066 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
7067 return;
7068 };
7069 if self.selections.count() != 1 {
7070 return;
7071 }
7072
7073 self.report_inline_completion_event(
7074 active_inline_completion.completion_id.clone(),
7075 true,
7076 cx,
7077 );
7078
7079 match &active_inline_completion.completion {
7080 InlineCompletion::Move { target, .. } => {
7081 let target = *target;
7082 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
7083 selections.select_anchor_ranges([target..target]);
7084 });
7085 }
7086 InlineCompletion::Edit { edits, .. } => {
7087 // Find an insertion that starts at the cursor position.
7088 let snapshot = self.buffer.read(cx).snapshot(cx);
7089 let cursor_offset = self.selections.newest::<usize>(cx).head();
7090 let insertion = edits.iter().find_map(|(range, text)| {
7091 let range = range.to_offset(&snapshot);
7092 if range.is_empty() && range.start == cursor_offset {
7093 Some(text)
7094 } else {
7095 None
7096 }
7097 });
7098
7099 if let Some(text) = insertion {
7100 let mut partial_completion = text
7101 .chars()
7102 .by_ref()
7103 .take_while(|c| c.is_alphabetic())
7104 .collect::<String>();
7105 if partial_completion.is_empty() {
7106 partial_completion = text
7107 .chars()
7108 .by_ref()
7109 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7110 .collect::<String>();
7111 }
7112
7113 cx.emit(EditorEvent::InputHandled {
7114 utf16_range_to_replace: None,
7115 text: partial_completion.clone().into(),
7116 });
7117
7118 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7119
7120 self.refresh_inline_completion(true, true, window, cx);
7121 cx.notify();
7122 } else {
7123 self.accept_edit_prediction(&Default::default(), window, cx);
7124 }
7125 }
7126 }
7127 }
7128
7129 fn discard_inline_completion(
7130 &mut self,
7131 should_report_inline_completion_event: bool,
7132 cx: &mut Context<Self>,
7133 ) -> bool {
7134 if should_report_inline_completion_event {
7135 let completion_id = self
7136 .active_inline_completion
7137 .as_ref()
7138 .and_then(|active_completion| active_completion.completion_id.clone());
7139
7140 self.report_inline_completion_event(completion_id, false, cx);
7141 }
7142
7143 if let Some(provider) = self.edit_prediction_provider() {
7144 provider.discard(cx);
7145 }
7146
7147 self.take_active_inline_completion(cx)
7148 }
7149
7150 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7151 let Some(provider) = self.edit_prediction_provider() else {
7152 return;
7153 };
7154
7155 let Some((_, buffer, _)) = self
7156 .buffer
7157 .read(cx)
7158 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7159 else {
7160 return;
7161 };
7162
7163 let extension = buffer
7164 .read(cx)
7165 .file()
7166 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7167
7168 let event_type = match accepted {
7169 true => "Edit Prediction Accepted",
7170 false => "Edit Prediction Discarded",
7171 };
7172 telemetry::event!(
7173 event_type,
7174 provider = provider.name(),
7175 prediction_id = id,
7176 suggestion_accepted = accepted,
7177 file_extension = extension,
7178 );
7179 }
7180
7181 pub fn has_active_inline_completion(&self) -> bool {
7182 self.active_inline_completion.is_some()
7183 }
7184
7185 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7186 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7187 return false;
7188 };
7189
7190 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7191 self.clear_highlights::<InlineCompletionHighlight>(cx);
7192 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7193 true
7194 }
7195
7196 /// Returns true when we're displaying the edit prediction popover below the cursor
7197 /// like we are not previewing and the LSP autocomplete menu is visible
7198 /// or we are in `when_holding_modifier` mode.
7199 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7200 if self.edit_prediction_preview_is_active()
7201 || !self.show_edit_predictions_in_menu()
7202 || !self.edit_predictions_enabled()
7203 {
7204 return false;
7205 }
7206
7207 if self.has_visible_completions_menu() {
7208 return true;
7209 }
7210
7211 has_completion && self.edit_prediction_requires_modifier()
7212 }
7213
7214 fn handle_modifiers_changed(
7215 &mut self,
7216 modifiers: Modifiers,
7217 position_map: &PositionMap,
7218 window: &mut Window,
7219 cx: &mut Context<Self>,
7220 ) {
7221 if self.show_edit_predictions_in_menu() {
7222 self.update_edit_prediction_preview(&modifiers, window, cx);
7223 }
7224
7225 self.update_selection_mode(&modifiers, position_map, window, cx);
7226
7227 let mouse_position = window.mouse_position();
7228 if !position_map.text_hitbox.is_hovered(window) {
7229 return;
7230 }
7231
7232 self.update_hovered_link(
7233 position_map.point_for_position(mouse_position),
7234 &position_map.snapshot,
7235 modifiers,
7236 window,
7237 cx,
7238 )
7239 }
7240
7241 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7242 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7243 if invert {
7244 match multi_cursor_setting {
7245 MultiCursorModifier::Alt => modifiers.alt,
7246 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7247 }
7248 } else {
7249 match multi_cursor_setting {
7250 MultiCursorModifier::Alt => modifiers.secondary(),
7251 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7252 }
7253 }
7254 }
7255
7256 fn columnar_selection_mode(
7257 modifiers: &Modifiers,
7258 cx: &mut Context<Self>,
7259 ) -> Option<ColumnarMode> {
7260 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7261 if Self::multi_cursor_modifier(false, modifiers, cx) {
7262 Some(ColumnarMode::FromMouse)
7263 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7264 Some(ColumnarMode::FromSelection)
7265 } else {
7266 None
7267 }
7268 } else {
7269 None
7270 }
7271 }
7272
7273 fn update_selection_mode(
7274 &mut self,
7275 modifiers: &Modifiers,
7276 position_map: &PositionMap,
7277 window: &mut Window,
7278 cx: &mut Context<Self>,
7279 ) {
7280 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7281 return;
7282 };
7283 if self.selections.pending.is_none() {
7284 return;
7285 }
7286
7287 let mouse_position = window.mouse_position();
7288 let point_for_position = position_map.point_for_position(mouse_position);
7289 let position = point_for_position.previous_valid;
7290
7291 self.select(
7292 SelectPhase::BeginColumnar {
7293 position,
7294 reset: false,
7295 mode,
7296 goal_column: point_for_position.exact_unclipped.column(),
7297 },
7298 window,
7299 cx,
7300 );
7301 }
7302
7303 fn update_edit_prediction_preview(
7304 &mut self,
7305 modifiers: &Modifiers,
7306 window: &mut Window,
7307 cx: &mut Context<Self>,
7308 ) {
7309 let mut modifiers_held = false;
7310 if let Some(accept_keystroke) = self
7311 .accept_edit_prediction_keybind(false, window, cx)
7312 .keystroke()
7313 {
7314 modifiers_held = modifiers_held
7315 || (&accept_keystroke.modifiers == modifiers
7316 && accept_keystroke.modifiers.modified());
7317 };
7318 if let Some(accept_partial_keystroke) = self
7319 .accept_edit_prediction_keybind(true, window, cx)
7320 .keystroke()
7321 {
7322 modifiers_held = modifiers_held
7323 || (&accept_partial_keystroke.modifiers == modifiers
7324 && accept_partial_keystroke.modifiers.modified());
7325 }
7326
7327 if modifiers_held {
7328 if matches!(
7329 self.edit_prediction_preview,
7330 EditPredictionPreview::Inactive { .. }
7331 ) {
7332 self.edit_prediction_preview = EditPredictionPreview::Active {
7333 previous_scroll_position: None,
7334 since: Instant::now(),
7335 };
7336
7337 self.update_visible_inline_completion(window, cx);
7338 cx.notify();
7339 }
7340 } else if let EditPredictionPreview::Active {
7341 previous_scroll_position,
7342 since,
7343 } = self.edit_prediction_preview
7344 {
7345 if let (Some(previous_scroll_position), Some(position_map)) =
7346 (previous_scroll_position, self.last_position_map.as_ref())
7347 {
7348 self.set_scroll_position(
7349 previous_scroll_position
7350 .scroll_position(&position_map.snapshot.display_snapshot),
7351 window,
7352 cx,
7353 );
7354 }
7355
7356 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7357 released_too_fast: since.elapsed() < Duration::from_millis(200),
7358 };
7359 self.clear_row_highlights::<EditPredictionPreview>();
7360 self.update_visible_inline_completion(window, cx);
7361 cx.notify();
7362 }
7363 }
7364
7365 fn update_visible_inline_completion(
7366 &mut self,
7367 _window: &mut Window,
7368 cx: &mut Context<Self>,
7369 ) -> Option<()> {
7370 let selection = self.selections.newest_anchor();
7371 let cursor = selection.head();
7372 let multibuffer = self.buffer.read(cx).snapshot(cx);
7373 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7374 let excerpt_id = cursor.excerpt_id;
7375
7376 let show_in_menu = self.show_edit_predictions_in_menu();
7377 let completions_menu_has_precedence = !show_in_menu
7378 && (self.context_menu.borrow().is_some()
7379 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7380
7381 if completions_menu_has_precedence
7382 || !offset_selection.is_empty()
7383 || self
7384 .active_inline_completion
7385 .as_ref()
7386 .map_or(false, |completion| {
7387 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7388 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7389 !invalidation_range.contains(&offset_selection.head())
7390 })
7391 {
7392 self.discard_inline_completion(false, cx);
7393 return None;
7394 }
7395
7396 self.take_active_inline_completion(cx);
7397 let Some(provider) = self.edit_prediction_provider() else {
7398 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7399 return None;
7400 };
7401
7402 let (buffer, cursor_buffer_position) =
7403 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7404
7405 self.edit_prediction_settings =
7406 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7407
7408 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7409
7410 if self.edit_prediction_indent_conflict {
7411 let cursor_point = cursor.to_point(&multibuffer);
7412
7413 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7414
7415 if let Some((_, indent)) = indents.iter().next() {
7416 if indent.len == cursor_point.column {
7417 self.edit_prediction_indent_conflict = false;
7418 }
7419 }
7420 }
7421
7422 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7423 let edits = inline_completion
7424 .edits
7425 .into_iter()
7426 .flat_map(|(range, new_text)| {
7427 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7428 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7429 Some((start..end, new_text))
7430 })
7431 .collect::<Vec<_>>();
7432 if edits.is_empty() {
7433 return None;
7434 }
7435
7436 let first_edit_start = edits.first().unwrap().0.start;
7437 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7438 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7439
7440 let last_edit_end = edits.last().unwrap().0.end;
7441 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7442 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7443
7444 let cursor_row = cursor.to_point(&multibuffer).row;
7445
7446 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7447
7448 let mut inlay_ids = Vec::new();
7449 let invalidation_row_range;
7450 let move_invalidation_row_range = if cursor_row < edit_start_row {
7451 Some(cursor_row..edit_end_row)
7452 } else if cursor_row > edit_end_row {
7453 Some(edit_start_row..cursor_row)
7454 } else {
7455 None
7456 };
7457 let is_move =
7458 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7459 let completion = if is_move {
7460 invalidation_row_range =
7461 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7462 let target = first_edit_start;
7463 InlineCompletion::Move { target, snapshot }
7464 } else {
7465 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7466 && !self.inline_completions_hidden_for_vim_mode;
7467
7468 if show_completions_in_buffer {
7469 if edits
7470 .iter()
7471 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7472 {
7473 let mut inlays = Vec::new();
7474 for (range, new_text) in &edits {
7475 let inlay = Inlay::inline_completion(
7476 post_inc(&mut self.next_inlay_id),
7477 range.start,
7478 new_text.as_str(),
7479 );
7480 inlay_ids.push(inlay.id);
7481 inlays.push(inlay);
7482 }
7483
7484 self.splice_inlays(&[], inlays, cx);
7485 } else {
7486 let background_color = cx.theme().status().deleted_background;
7487 let style = HighlightStyle {
7488 background_color: Some(background_color),
7489 ..Default::default()
7490 };
7491 self.highlight_text::<InlineCompletionHighlight>(
7492 edits
7493 .iter()
7494 .map(|(range, _)| (range.clone(), style))
7495 .collect(),
7496 cx,
7497 );
7498 }
7499 }
7500
7501 invalidation_row_range = edit_start_row..edit_end_row;
7502
7503 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7504 if provider.show_tab_accept_marker() {
7505 EditDisplayMode::TabAccept
7506 } else {
7507 EditDisplayMode::Inline
7508 }
7509 } else {
7510 EditDisplayMode::DiffPopover
7511 };
7512
7513 InlineCompletion::Edit {
7514 edits,
7515 edit_preview: inline_completion.edit_preview,
7516 display_mode,
7517 snapshot,
7518 }
7519 };
7520
7521 let invalidation_range = multibuffer
7522 .anchor_before(Point::new(invalidation_row_range.start, 0))
7523 ..multibuffer.anchor_after(Point::new(
7524 invalidation_row_range.end,
7525 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7526 ));
7527
7528 self.stale_inline_completion_in_menu = None;
7529 self.active_inline_completion = Some(InlineCompletionState {
7530 inlay_ids,
7531 completion,
7532 completion_id: inline_completion.id,
7533 invalidation_range,
7534 });
7535
7536 cx.notify();
7537
7538 Some(())
7539 }
7540
7541 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7542 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7543 }
7544
7545 fn clear_tasks(&mut self) {
7546 self.tasks.clear()
7547 }
7548
7549 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7550 if self.tasks.insert(key, value).is_some() {
7551 // This case should hopefully be rare, but just in case...
7552 log::error!(
7553 "multiple different run targets found on a single line, only the last target will be rendered"
7554 )
7555 }
7556 }
7557
7558 /// Get all display points of breakpoints that will be rendered within editor
7559 ///
7560 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7561 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7562 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7563 fn active_breakpoints(
7564 &self,
7565 range: Range<DisplayRow>,
7566 window: &mut Window,
7567 cx: &mut Context<Self>,
7568 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7569 let mut breakpoint_display_points = HashMap::default();
7570
7571 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7572 return breakpoint_display_points;
7573 };
7574
7575 let snapshot = self.snapshot(window, cx);
7576
7577 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7578 let Some(project) = self.project.as_ref() else {
7579 return breakpoint_display_points;
7580 };
7581
7582 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7583 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7584
7585 for (buffer_snapshot, range, excerpt_id) in
7586 multi_buffer_snapshot.range_to_buffer_ranges(range)
7587 {
7588 let Some(buffer) = project
7589 .read(cx)
7590 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7591 else {
7592 continue;
7593 };
7594 let breakpoints = breakpoint_store.read(cx).breakpoints(
7595 &buffer,
7596 Some(
7597 buffer_snapshot.anchor_before(range.start)
7598 ..buffer_snapshot.anchor_after(range.end),
7599 ),
7600 buffer_snapshot,
7601 cx,
7602 );
7603 for (breakpoint, state) in breakpoints {
7604 let multi_buffer_anchor =
7605 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7606 let position = multi_buffer_anchor
7607 .to_point(&multi_buffer_snapshot)
7608 .to_display_point(&snapshot);
7609
7610 breakpoint_display_points.insert(
7611 position.row(),
7612 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7613 );
7614 }
7615 }
7616
7617 breakpoint_display_points
7618 }
7619
7620 fn breakpoint_context_menu(
7621 &self,
7622 anchor: Anchor,
7623 window: &mut Window,
7624 cx: &mut Context<Self>,
7625 ) -> Entity<ui::ContextMenu> {
7626 let weak_editor = cx.weak_entity();
7627 let focus_handle = self.focus_handle(cx);
7628
7629 let row = self
7630 .buffer
7631 .read(cx)
7632 .snapshot(cx)
7633 .summary_for_anchor::<Point>(&anchor)
7634 .row;
7635
7636 let breakpoint = self
7637 .breakpoint_at_row(row, window, cx)
7638 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7639
7640 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7641 "Edit Log Breakpoint"
7642 } else {
7643 "Set Log Breakpoint"
7644 };
7645
7646 let condition_breakpoint_msg = if breakpoint
7647 .as_ref()
7648 .is_some_and(|bp| bp.1.condition.is_some())
7649 {
7650 "Edit Condition Breakpoint"
7651 } else {
7652 "Set Condition Breakpoint"
7653 };
7654
7655 let hit_condition_breakpoint_msg = if breakpoint
7656 .as_ref()
7657 .is_some_and(|bp| bp.1.hit_condition.is_some())
7658 {
7659 "Edit Hit Condition Breakpoint"
7660 } else {
7661 "Set Hit Condition Breakpoint"
7662 };
7663
7664 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7665 "Unset Breakpoint"
7666 } else {
7667 "Set Breakpoint"
7668 };
7669
7670 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7671
7672 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7673 BreakpointState::Enabled => Some("Disable"),
7674 BreakpointState::Disabled => Some("Enable"),
7675 });
7676
7677 let (anchor, breakpoint) =
7678 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7679
7680 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7681 menu.on_blur_subscription(Subscription::new(|| {}))
7682 .context(focus_handle)
7683 .when(run_to_cursor, |this| {
7684 let weak_editor = weak_editor.clone();
7685 this.entry("Run to cursor", None, move |window, cx| {
7686 weak_editor
7687 .update(cx, |editor, cx| {
7688 editor.change_selections(None, window, cx, |s| {
7689 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7690 });
7691 })
7692 .ok();
7693
7694 window.dispatch_action(Box::new(RunToCursor), cx);
7695 })
7696 .separator()
7697 })
7698 .when_some(toggle_state_msg, |this, msg| {
7699 this.entry(msg, None, {
7700 let weak_editor = weak_editor.clone();
7701 let breakpoint = breakpoint.clone();
7702 move |_window, cx| {
7703 weak_editor
7704 .update(cx, |this, cx| {
7705 this.edit_breakpoint_at_anchor(
7706 anchor,
7707 breakpoint.as_ref().clone(),
7708 BreakpointEditAction::InvertState,
7709 cx,
7710 );
7711 })
7712 .log_err();
7713 }
7714 })
7715 })
7716 .entry(set_breakpoint_msg, None, {
7717 let weak_editor = weak_editor.clone();
7718 let breakpoint = breakpoint.clone();
7719 move |_window, cx| {
7720 weak_editor
7721 .update(cx, |this, cx| {
7722 this.edit_breakpoint_at_anchor(
7723 anchor,
7724 breakpoint.as_ref().clone(),
7725 BreakpointEditAction::Toggle,
7726 cx,
7727 );
7728 })
7729 .log_err();
7730 }
7731 })
7732 .entry(log_breakpoint_msg, None, {
7733 let breakpoint = breakpoint.clone();
7734 let weak_editor = weak_editor.clone();
7735 move |window, cx| {
7736 weak_editor
7737 .update(cx, |this, cx| {
7738 this.add_edit_breakpoint_block(
7739 anchor,
7740 breakpoint.as_ref(),
7741 BreakpointPromptEditAction::Log,
7742 window,
7743 cx,
7744 );
7745 })
7746 .log_err();
7747 }
7748 })
7749 .entry(condition_breakpoint_msg, None, {
7750 let breakpoint = breakpoint.clone();
7751 let weak_editor = weak_editor.clone();
7752 move |window, cx| {
7753 weak_editor
7754 .update(cx, |this, cx| {
7755 this.add_edit_breakpoint_block(
7756 anchor,
7757 breakpoint.as_ref(),
7758 BreakpointPromptEditAction::Condition,
7759 window,
7760 cx,
7761 );
7762 })
7763 .log_err();
7764 }
7765 })
7766 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7767 weak_editor
7768 .update(cx, |this, cx| {
7769 this.add_edit_breakpoint_block(
7770 anchor,
7771 breakpoint.as_ref(),
7772 BreakpointPromptEditAction::HitCondition,
7773 window,
7774 cx,
7775 );
7776 })
7777 .log_err();
7778 })
7779 })
7780 }
7781
7782 fn render_breakpoint(
7783 &self,
7784 position: Anchor,
7785 row: DisplayRow,
7786 breakpoint: &Breakpoint,
7787 state: Option<BreakpointSessionState>,
7788 cx: &mut Context<Self>,
7789 ) -> IconButton {
7790 let is_rejected = state.is_some_and(|s| !s.verified);
7791 // Is it a breakpoint that shows up when hovering over gutter?
7792 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7793 (false, false),
7794 |PhantomBreakpointIndicator {
7795 is_active,
7796 display_row,
7797 collides_with_existing_breakpoint,
7798 }| {
7799 (
7800 is_active && display_row == row,
7801 collides_with_existing_breakpoint,
7802 )
7803 },
7804 );
7805
7806 let (color, icon) = {
7807 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7808 (false, false) => ui::IconName::DebugBreakpoint,
7809 (true, false) => ui::IconName::DebugLogBreakpoint,
7810 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7811 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7812 };
7813
7814 let color = if is_phantom {
7815 Color::Hint
7816 } else if is_rejected {
7817 Color::Disabled
7818 } else {
7819 Color::Debugger
7820 };
7821
7822 (color, icon)
7823 };
7824
7825 let breakpoint = Arc::from(breakpoint.clone());
7826
7827 let alt_as_text = gpui::Keystroke {
7828 modifiers: Modifiers::secondary_key(),
7829 ..Default::default()
7830 };
7831 let primary_action_text = if breakpoint.is_disabled() {
7832 "Enable breakpoint"
7833 } else if is_phantom && !collides_with_existing {
7834 "Set breakpoint"
7835 } else {
7836 "Unset breakpoint"
7837 };
7838 let focus_handle = self.focus_handle.clone();
7839
7840 let meta = if is_rejected {
7841 SharedString::from("No executable code is associated with this line.")
7842 } else if collides_with_existing && !breakpoint.is_disabled() {
7843 SharedString::from(format!(
7844 "{alt_as_text}-click to disable,\nright-click for more options."
7845 ))
7846 } else {
7847 SharedString::from("Right-click for more options.")
7848 };
7849 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7850 .icon_size(IconSize::XSmall)
7851 .size(ui::ButtonSize::None)
7852 .when(is_rejected, |this| {
7853 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7854 })
7855 .icon_color(color)
7856 .style(ButtonStyle::Transparent)
7857 .on_click(cx.listener({
7858 let breakpoint = breakpoint.clone();
7859
7860 move |editor, event: &ClickEvent, window, cx| {
7861 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7862 BreakpointEditAction::InvertState
7863 } else {
7864 BreakpointEditAction::Toggle
7865 };
7866
7867 window.focus(&editor.focus_handle(cx));
7868 editor.edit_breakpoint_at_anchor(
7869 position,
7870 breakpoint.as_ref().clone(),
7871 edit_action,
7872 cx,
7873 );
7874 }
7875 }))
7876 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7877 editor.set_breakpoint_context_menu(
7878 row,
7879 Some(position),
7880 event.down.position,
7881 window,
7882 cx,
7883 );
7884 }))
7885 .tooltip(move |window, cx| {
7886 Tooltip::with_meta_in(
7887 primary_action_text,
7888 Some(&ToggleBreakpoint),
7889 meta.clone(),
7890 &focus_handle,
7891 window,
7892 cx,
7893 )
7894 })
7895 }
7896
7897 fn build_tasks_context(
7898 project: &Entity<Project>,
7899 buffer: &Entity<Buffer>,
7900 buffer_row: u32,
7901 tasks: &Arc<RunnableTasks>,
7902 cx: &mut Context<Self>,
7903 ) -> Task<Option<task::TaskContext>> {
7904 let position = Point::new(buffer_row, tasks.column);
7905 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7906 let location = Location {
7907 buffer: buffer.clone(),
7908 range: range_start..range_start,
7909 };
7910 // Fill in the environmental variables from the tree-sitter captures
7911 let mut captured_task_variables = TaskVariables::default();
7912 for (capture_name, value) in tasks.extra_variables.clone() {
7913 captured_task_variables.insert(
7914 task::VariableName::Custom(capture_name.into()),
7915 value.clone(),
7916 );
7917 }
7918 project.update(cx, |project, cx| {
7919 project.task_store().update(cx, |task_store, cx| {
7920 task_store.task_context_for_location(captured_task_variables, location, cx)
7921 })
7922 })
7923 }
7924
7925 pub fn spawn_nearest_task(
7926 &mut self,
7927 action: &SpawnNearestTask,
7928 window: &mut Window,
7929 cx: &mut Context<Self>,
7930 ) {
7931 let Some((workspace, _)) = self.workspace.clone() else {
7932 return;
7933 };
7934 let Some(project) = self.project.clone() else {
7935 return;
7936 };
7937
7938 // Try to find a closest, enclosing node using tree-sitter that has a
7939 // task
7940 let Some((buffer, buffer_row, tasks)) = self
7941 .find_enclosing_node_task(cx)
7942 // Or find the task that's closest in row-distance.
7943 .or_else(|| self.find_closest_task(cx))
7944 else {
7945 return;
7946 };
7947
7948 let reveal_strategy = action.reveal;
7949 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7950 cx.spawn_in(window, async move |_, cx| {
7951 let context = task_context.await?;
7952 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7953
7954 let resolved = &mut resolved_task.resolved;
7955 resolved.reveal = reveal_strategy;
7956
7957 workspace
7958 .update_in(cx, |workspace, window, cx| {
7959 workspace.schedule_resolved_task(
7960 task_source_kind,
7961 resolved_task,
7962 false,
7963 window,
7964 cx,
7965 );
7966 })
7967 .ok()
7968 })
7969 .detach();
7970 }
7971
7972 fn find_closest_task(
7973 &mut self,
7974 cx: &mut Context<Self>,
7975 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7976 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7977
7978 let ((buffer_id, row), tasks) = self
7979 .tasks
7980 .iter()
7981 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7982
7983 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7984 let tasks = Arc::new(tasks.to_owned());
7985 Some((buffer, *row, tasks))
7986 }
7987
7988 fn find_enclosing_node_task(
7989 &mut self,
7990 cx: &mut Context<Self>,
7991 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7992 let snapshot = self.buffer.read(cx).snapshot(cx);
7993 let offset = self.selections.newest::<usize>(cx).head();
7994 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7995 let buffer_id = excerpt.buffer().remote_id();
7996
7997 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7998 let mut cursor = layer.node().walk();
7999
8000 while cursor.goto_first_child_for_byte(offset).is_some() {
8001 if cursor.node().end_byte() == offset {
8002 cursor.goto_next_sibling();
8003 }
8004 }
8005
8006 // Ascend to the smallest ancestor that contains the range and has a task.
8007 loop {
8008 let node = cursor.node();
8009 let node_range = node.byte_range();
8010 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8011
8012 // Check if this node contains our offset
8013 if node_range.start <= offset && node_range.end >= offset {
8014 // If it contains offset, check for task
8015 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8016 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8017 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8018 }
8019 }
8020
8021 if !cursor.goto_parent() {
8022 break;
8023 }
8024 }
8025 None
8026 }
8027
8028 fn render_run_indicator(
8029 &self,
8030 _style: &EditorStyle,
8031 is_active: bool,
8032 row: DisplayRow,
8033 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8034 cx: &mut Context<Self>,
8035 ) -> IconButton {
8036 let color = Color::Muted;
8037 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8038
8039 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
8040 .shape(ui::IconButtonShape::Square)
8041 .icon_size(IconSize::XSmall)
8042 .icon_color(color)
8043 .toggle_state(is_active)
8044 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8045 let quick_launch = e.down.button == MouseButton::Left;
8046 window.focus(&editor.focus_handle(cx));
8047 editor.toggle_code_actions(
8048 &ToggleCodeActions {
8049 deployed_from: Some(CodeActionSource::RunMenu(row)),
8050 quick_launch,
8051 },
8052 window,
8053 cx,
8054 );
8055 }))
8056 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8057 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
8058 }))
8059 }
8060
8061 pub fn context_menu_visible(&self) -> bool {
8062 !self.edit_prediction_preview_is_active()
8063 && self
8064 .context_menu
8065 .borrow()
8066 .as_ref()
8067 .map_or(false, |menu| menu.visible())
8068 }
8069
8070 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8071 self.context_menu
8072 .borrow()
8073 .as_ref()
8074 .map(|menu| menu.origin())
8075 }
8076
8077 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8078 self.context_menu_options = Some(options);
8079 }
8080
8081 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
8082 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
8083
8084 fn render_edit_prediction_popover(
8085 &mut self,
8086 text_bounds: &Bounds<Pixels>,
8087 content_origin: gpui::Point<Pixels>,
8088 right_margin: Pixels,
8089 editor_snapshot: &EditorSnapshot,
8090 visible_row_range: Range<DisplayRow>,
8091 scroll_top: f32,
8092 scroll_bottom: f32,
8093 line_layouts: &[LineWithInvisibles],
8094 line_height: Pixels,
8095 scroll_pixel_position: gpui::Point<Pixels>,
8096 newest_selection_head: Option<DisplayPoint>,
8097 editor_width: Pixels,
8098 style: &EditorStyle,
8099 window: &mut Window,
8100 cx: &mut App,
8101 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8102 if self.mode().is_minimap() {
8103 return None;
8104 }
8105 let active_inline_completion = self.active_inline_completion.as_ref()?;
8106
8107 if self.edit_prediction_visible_in_cursor_popover(true) {
8108 return None;
8109 }
8110
8111 match &active_inline_completion.completion {
8112 InlineCompletion::Move { target, .. } => {
8113 let target_display_point = target.to_display_point(editor_snapshot);
8114
8115 if self.edit_prediction_requires_modifier() {
8116 if !self.edit_prediction_preview_is_active() {
8117 return None;
8118 }
8119
8120 self.render_edit_prediction_modifier_jump_popover(
8121 text_bounds,
8122 content_origin,
8123 visible_row_range,
8124 line_layouts,
8125 line_height,
8126 scroll_pixel_position,
8127 newest_selection_head,
8128 target_display_point,
8129 window,
8130 cx,
8131 )
8132 } else {
8133 self.render_edit_prediction_eager_jump_popover(
8134 text_bounds,
8135 content_origin,
8136 editor_snapshot,
8137 visible_row_range,
8138 scroll_top,
8139 scroll_bottom,
8140 line_height,
8141 scroll_pixel_position,
8142 target_display_point,
8143 editor_width,
8144 window,
8145 cx,
8146 )
8147 }
8148 }
8149 InlineCompletion::Edit {
8150 display_mode: EditDisplayMode::Inline,
8151 ..
8152 } => None,
8153 InlineCompletion::Edit {
8154 display_mode: EditDisplayMode::TabAccept,
8155 edits,
8156 ..
8157 } => {
8158 let range = &edits.first()?.0;
8159 let target_display_point = range.end.to_display_point(editor_snapshot);
8160
8161 self.render_edit_prediction_end_of_line_popover(
8162 "Accept",
8163 editor_snapshot,
8164 visible_row_range,
8165 target_display_point,
8166 line_height,
8167 scroll_pixel_position,
8168 content_origin,
8169 editor_width,
8170 window,
8171 cx,
8172 )
8173 }
8174 InlineCompletion::Edit {
8175 edits,
8176 edit_preview,
8177 display_mode: EditDisplayMode::DiffPopover,
8178 snapshot,
8179 } => self.render_edit_prediction_diff_popover(
8180 text_bounds,
8181 content_origin,
8182 right_margin,
8183 editor_snapshot,
8184 visible_row_range,
8185 line_layouts,
8186 line_height,
8187 scroll_pixel_position,
8188 newest_selection_head,
8189 editor_width,
8190 style,
8191 edits,
8192 edit_preview,
8193 snapshot,
8194 window,
8195 cx,
8196 ),
8197 }
8198 }
8199
8200 fn render_edit_prediction_modifier_jump_popover(
8201 &mut self,
8202 text_bounds: &Bounds<Pixels>,
8203 content_origin: gpui::Point<Pixels>,
8204 visible_row_range: Range<DisplayRow>,
8205 line_layouts: &[LineWithInvisibles],
8206 line_height: Pixels,
8207 scroll_pixel_position: gpui::Point<Pixels>,
8208 newest_selection_head: Option<DisplayPoint>,
8209 target_display_point: DisplayPoint,
8210 window: &mut Window,
8211 cx: &mut App,
8212 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8213 let scrolled_content_origin =
8214 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8215
8216 const SCROLL_PADDING_Y: Pixels = px(12.);
8217
8218 if target_display_point.row() < visible_row_range.start {
8219 return self.render_edit_prediction_scroll_popover(
8220 |_| SCROLL_PADDING_Y,
8221 IconName::ArrowUp,
8222 visible_row_range,
8223 line_layouts,
8224 newest_selection_head,
8225 scrolled_content_origin,
8226 window,
8227 cx,
8228 );
8229 } else if target_display_point.row() >= visible_row_range.end {
8230 return self.render_edit_prediction_scroll_popover(
8231 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8232 IconName::ArrowDown,
8233 visible_row_range,
8234 line_layouts,
8235 newest_selection_head,
8236 scrolled_content_origin,
8237 window,
8238 cx,
8239 );
8240 }
8241
8242 const POLE_WIDTH: Pixels = px(2.);
8243
8244 let line_layout =
8245 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8246 let target_column = target_display_point.column() as usize;
8247
8248 let target_x = line_layout.x_for_index(target_column);
8249 let target_y =
8250 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8251
8252 let flag_on_right = target_x < text_bounds.size.width / 2.;
8253
8254 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8255 border_color.l += 0.001;
8256
8257 let mut element = v_flex()
8258 .items_end()
8259 .when(flag_on_right, |el| el.items_start())
8260 .child(if flag_on_right {
8261 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8262 .rounded_bl(px(0.))
8263 .rounded_tl(px(0.))
8264 .border_l_2()
8265 .border_color(border_color)
8266 } else {
8267 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8268 .rounded_br(px(0.))
8269 .rounded_tr(px(0.))
8270 .border_r_2()
8271 .border_color(border_color)
8272 })
8273 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8274 .into_any();
8275
8276 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8277
8278 let mut origin = scrolled_content_origin + point(target_x, target_y)
8279 - point(
8280 if flag_on_right {
8281 POLE_WIDTH
8282 } else {
8283 size.width - POLE_WIDTH
8284 },
8285 size.height - line_height,
8286 );
8287
8288 origin.x = origin.x.max(content_origin.x);
8289
8290 element.prepaint_at(origin, window, cx);
8291
8292 Some((element, origin))
8293 }
8294
8295 fn render_edit_prediction_scroll_popover(
8296 &mut self,
8297 to_y: impl Fn(Size<Pixels>) -> Pixels,
8298 scroll_icon: IconName,
8299 visible_row_range: Range<DisplayRow>,
8300 line_layouts: &[LineWithInvisibles],
8301 newest_selection_head: Option<DisplayPoint>,
8302 scrolled_content_origin: gpui::Point<Pixels>,
8303 window: &mut Window,
8304 cx: &mut App,
8305 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8306 let mut element = self
8307 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8308 .into_any();
8309
8310 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8311
8312 let cursor = newest_selection_head?;
8313 let cursor_row_layout =
8314 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8315 let cursor_column = cursor.column() as usize;
8316
8317 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8318
8319 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8320
8321 element.prepaint_at(origin, window, cx);
8322 Some((element, origin))
8323 }
8324
8325 fn render_edit_prediction_eager_jump_popover(
8326 &mut self,
8327 text_bounds: &Bounds<Pixels>,
8328 content_origin: gpui::Point<Pixels>,
8329 editor_snapshot: &EditorSnapshot,
8330 visible_row_range: Range<DisplayRow>,
8331 scroll_top: f32,
8332 scroll_bottom: f32,
8333 line_height: Pixels,
8334 scroll_pixel_position: gpui::Point<Pixels>,
8335 target_display_point: DisplayPoint,
8336 editor_width: Pixels,
8337 window: &mut Window,
8338 cx: &mut App,
8339 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8340 if target_display_point.row().as_f32() < scroll_top {
8341 let mut element = self
8342 .render_edit_prediction_line_popover(
8343 "Jump to Edit",
8344 Some(IconName::ArrowUp),
8345 window,
8346 cx,
8347 )?
8348 .into_any();
8349
8350 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8351 let offset = point(
8352 (text_bounds.size.width - size.width) / 2.,
8353 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8354 );
8355
8356 let origin = text_bounds.origin + offset;
8357 element.prepaint_at(origin, window, cx);
8358 Some((element, origin))
8359 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8360 let mut element = self
8361 .render_edit_prediction_line_popover(
8362 "Jump to Edit",
8363 Some(IconName::ArrowDown),
8364 window,
8365 cx,
8366 )?
8367 .into_any();
8368
8369 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8370 let offset = point(
8371 (text_bounds.size.width - size.width) / 2.,
8372 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8373 );
8374
8375 let origin = text_bounds.origin + offset;
8376 element.prepaint_at(origin, window, cx);
8377 Some((element, origin))
8378 } else {
8379 self.render_edit_prediction_end_of_line_popover(
8380 "Jump to Edit",
8381 editor_snapshot,
8382 visible_row_range,
8383 target_display_point,
8384 line_height,
8385 scroll_pixel_position,
8386 content_origin,
8387 editor_width,
8388 window,
8389 cx,
8390 )
8391 }
8392 }
8393
8394 fn render_edit_prediction_end_of_line_popover(
8395 self: &mut Editor,
8396 label: &'static str,
8397 editor_snapshot: &EditorSnapshot,
8398 visible_row_range: Range<DisplayRow>,
8399 target_display_point: DisplayPoint,
8400 line_height: Pixels,
8401 scroll_pixel_position: gpui::Point<Pixels>,
8402 content_origin: gpui::Point<Pixels>,
8403 editor_width: Pixels,
8404 window: &mut Window,
8405 cx: &mut App,
8406 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8407 let target_line_end = DisplayPoint::new(
8408 target_display_point.row(),
8409 editor_snapshot.line_len(target_display_point.row()),
8410 );
8411
8412 let mut element = self
8413 .render_edit_prediction_line_popover(label, None, window, cx)?
8414 .into_any();
8415
8416 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8417
8418 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8419
8420 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8421 let mut origin = start_point
8422 + line_origin
8423 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8424 origin.x = origin.x.max(content_origin.x);
8425
8426 let max_x = content_origin.x + editor_width - size.width;
8427
8428 if origin.x > max_x {
8429 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8430
8431 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8432 origin.y += offset;
8433 IconName::ArrowUp
8434 } else {
8435 origin.y -= offset;
8436 IconName::ArrowDown
8437 };
8438
8439 element = self
8440 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8441 .into_any();
8442
8443 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8444
8445 origin.x = content_origin.x + editor_width - size.width - px(2.);
8446 }
8447
8448 element.prepaint_at(origin, window, cx);
8449 Some((element, origin))
8450 }
8451
8452 fn render_edit_prediction_diff_popover(
8453 self: &Editor,
8454 text_bounds: &Bounds<Pixels>,
8455 content_origin: gpui::Point<Pixels>,
8456 right_margin: Pixels,
8457 editor_snapshot: &EditorSnapshot,
8458 visible_row_range: Range<DisplayRow>,
8459 line_layouts: &[LineWithInvisibles],
8460 line_height: Pixels,
8461 scroll_pixel_position: gpui::Point<Pixels>,
8462 newest_selection_head: Option<DisplayPoint>,
8463 editor_width: Pixels,
8464 style: &EditorStyle,
8465 edits: &Vec<(Range<Anchor>, String)>,
8466 edit_preview: &Option<language::EditPreview>,
8467 snapshot: &language::BufferSnapshot,
8468 window: &mut Window,
8469 cx: &mut App,
8470 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8471 let edit_start = edits
8472 .first()
8473 .unwrap()
8474 .0
8475 .start
8476 .to_display_point(editor_snapshot);
8477 let edit_end = edits
8478 .last()
8479 .unwrap()
8480 .0
8481 .end
8482 .to_display_point(editor_snapshot);
8483
8484 let is_visible = visible_row_range.contains(&edit_start.row())
8485 || visible_row_range.contains(&edit_end.row());
8486 if !is_visible {
8487 return None;
8488 }
8489
8490 let highlighted_edits =
8491 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8492
8493 let styled_text = highlighted_edits.to_styled_text(&style.text);
8494 let line_count = highlighted_edits.text.lines().count();
8495
8496 const BORDER_WIDTH: Pixels = px(1.);
8497
8498 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8499 let has_keybind = keybind.is_some();
8500
8501 let mut element = h_flex()
8502 .items_start()
8503 .child(
8504 h_flex()
8505 .bg(cx.theme().colors().editor_background)
8506 .border(BORDER_WIDTH)
8507 .shadow_sm()
8508 .border_color(cx.theme().colors().border)
8509 .rounded_l_lg()
8510 .when(line_count > 1, |el| el.rounded_br_lg())
8511 .pr_1()
8512 .child(styled_text),
8513 )
8514 .child(
8515 h_flex()
8516 .h(line_height + BORDER_WIDTH * 2.)
8517 .px_1p5()
8518 .gap_1()
8519 // Workaround: For some reason, there's a gap if we don't do this
8520 .ml(-BORDER_WIDTH)
8521 .shadow(vec![gpui::BoxShadow {
8522 color: gpui::black().opacity(0.05),
8523 offset: point(px(1.), px(1.)),
8524 blur_radius: px(2.),
8525 spread_radius: px(0.),
8526 }])
8527 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8528 .border(BORDER_WIDTH)
8529 .border_color(cx.theme().colors().border)
8530 .rounded_r_lg()
8531 .id("edit_prediction_diff_popover_keybind")
8532 .when(!has_keybind, |el| {
8533 let status_colors = cx.theme().status();
8534
8535 el.bg(status_colors.error_background)
8536 .border_color(status_colors.error.opacity(0.6))
8537 .child(Icon::new(IconName::Info).color(Color::Error))
8538 .cursor_default()
8539 .hoverable_tooltip(move |_window, cx| {
8540 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8541 })
8542 })
8543 .children(keybind),
8544 )
8545 .into_any();
8546
8547 let longest_row =
8548 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8549 let longest_line_width = if visible_row_range.contains(&longest_row) {
8550 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8551 } else {
8552 layout_line(
8553 longest_row,
8554 editor_snapshot,
8555 style,
8556 editor_width,
8557 |_| false,
8558 window,
8559 cx,
8560 )
8561 .width
8562 };
8563
8564 let viewport_bounds =
8565 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8566 right: -right_margin,
8567 ..Default::default()
8568 });
8569
8570 let x_after_longest =
8571 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8572 - scroll_pixel_position.x;
8573
8574 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8575
8576 // Fully visible if it can be displayed within the window (allow overlapping other
8577 // panes). However, this is only allowed if the popover starts within text_bounds.
8578 let can_position_to_the_right = x_after_longest < text_bounds.right()
8579 && x_after_longest + element_bounds.width < viewport_bounds.right();
8580
8581 let mut origin = if can_position_to_the_right {
8582 point(
8583 x_after_longest,
8584 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8585 - scroll_pixel_position.y,
8586 )
8587 } else {
8588 let cursor_row = newest_selection_head.map(|head| head.row());
8589 let above_edit = edit_start
8590 .row()
8591 .0
8592 .checked_sub(line_count as u32)
8593 .map(DisplayRow);
8594 let below_edit = Some(edit_end.row() + 1);
8595 let above_cursor =
8596 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8597 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8598
8599 // Place the edit popover adjacent to the edit if there is a location
8600 // available that is onscreen and does not obscure the cursor. Otherwise,
8601 // place it adjacent to the cursor.
8602 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8603 .into_iter()
8604 .flatten()
8605 .find(|&start_row| {
8606 let end_row = start_row + line_count as u32;
8607 visible_row_range.contains(&start_row)
8608 && visible_row_range.contains(&end_row)
8609 && cursor_row.map_or(true, |cursor_row| {
8610 !((start_row..end_row).contains(&cursor_row))
8611 })
8612 })?;
8613
8614 content_origin
8615 + point(
8616 -scroll_pixel_position.x,
8617 row_target.as_f32() * line_height - scroll_pixel_position.y,
8618 )
8619 };
8620
8621 origin.x -= BORDER_WIDTH;
8622
8623 window.defer_draw(element, origin, 1);
8624
8625 // Do not return an element, since it will already be drawn due to defer_draw.
8626 None
8627 }
8628
8629 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8630 px(30.)
8631 }
8632
8633 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8634 if self.read_only(cx) {
8635 cx.theme().players().read_only()
8636 } else {
8637 self.style.as_ref().unwrap().local_player
8638 }
8639 }
8640
8641 fn render_edit_prediction_accept_keybind(
8642 &self,
8643 window: &mut Window,
8644 cx: &App,
8645 ) -> Option<AnyElement> {
8646 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8647 let accept_keystroke = accept_binding.keystroke()?;
8648
8649 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8650
8651 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8652 Color::Accent
8653 } else {
8654 Color::Muted
8655 };
8656
8657 h_flex()
8658 .px_0p5()
8659 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8660 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8661 .text_size(TextSize::XSmall.rems(cx))
8662 .child(h_flex().children(ui::render_modifiers(
8663 &accept_keystroke.modifiers,
8664 PlatformStyle::platform(),
8665 Some(modifiers_color),
8666 Some(IconSize::XSmall.rems().into()),
8667 true,
8668 )))
8669 .when(is_platform_style_mac, |parent| {
8670 parent.child(accept_keystroke.key.clone())
8671 })
8672 .when(!is_platform_style_mac, |parent| {
8673 parent.child(
8674 Key::new(
8675 util::capitalize(&accept_keystroke.key),
8676 Some(Color::Default),
8677 )
8678 .size(Some(IconSize::XSmall.rems().into())),
8679 )
8680 })
8681 .into_any()
8682 .into()
8683 }
8684
8685 fn render_edit_prediction_line_popover(
8686 &self,
8687 label: impl Into<SharedString>,
8688 icon: Option<IconName>,
8689 window: &mut Window,
8690 cx: &App,
8691 ) -> Option<Stateful<Div>> {
8692 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8693
8694 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8695 let has_keybind = keybind.is_some();
8696
8697 let result = h_flex()
8698 .id("ep-line-popover")
8699 .py_0p5()
8700 .pl_1()
8701 .pr(padding_right)
8702 .gap_1()
8703 .rounded_md()
8704 .border_1()
8705 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8706 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8707 .shadow_sm()
8708 .when(!has_keybind, |el| {
8709 let status_colors = cx.theme().status();
8710
8711 el.bg(status_colors.error_background)
8712 .border_color(status_colors.error.opacity(0.6))
8713 .pl_2()
8714 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8715 .cursor_default()
8716 .hoverable_tooltip(move |_window, cx| {
8717 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8718 })
8719 })
8720 .children(keybind)
8721 .child(
8722 Label::new(label)
8723 .size(LabelSize::Small)
8724 .when(!has_keybind, |el| {
8725 el.color(cx.theme().status().error.into()).strikethrough()
8726 }),
8727 )
8728 .when(!has_keybind, |el| {
8729 el.child(
8730 h_flex().ml_1().child(
8731 Icon::new(IconName::Info)
8732 .size(IconSize::Small)
8733 .color(cx.theme().status().error.into()),
8734 ),
8735 )
8736 })
8737 .when_some(icon, |element, icon| {
8738 element.child(
8739 div()
8740 .mt(px(1.5))
8741 .child(Icon::new(icon).size(IconSize::Small)),
8742 )
8743 });
8744
8745 Some(result)
8746 }
8747
8748 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8749 let accent_color = cx.theme().colors().text_accent;
8750 let editor_bg_color = cx.theme().colors().editor_background;
8751 editor_bg_color.blend(accent_color.opacity(0.1))
8752 }
8753
8754 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8755 let accent_color = cx.theme().colors().text_accent;
8756 let editor_bg_color = cx.theme().colors().editor_background;
8757 editor_bg_color.blend(accent_color.opacity(0.6))
8758 }
8759
8760 fn render_edit_prediction_cursor_popover(
8761 &self,
8762 min_width: Pixels,
8763 max_width: Pixels,
8764 cursor_point: Point,
8765 style: &EditorStyle,
8766 accept_keystroke: Option<&gpui::Keystroke>,
8767 _window: &Window,
8768 cx: &mut Context<Editor>,
8769 ) -> Option<AnyElement> {
8770 let provider = self.edit_prediction_provider.as_ref()?;
8771
8772 if provider.provider.needs_terms_acceptance(cx) {
8773 return Some(
8774 h_flex()
8775 .min_w(min_width)
8776 .flex_1()
8777 .px_2()
8778 .py_1()
8779 .gap_3()
8780 .elevation_2(cx)
8781 .hover(|style| style.bg(cx.theme().colors().element_hover))
8782 .id("accept-terms")
8783 .cursor_pointer()
8784 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8785 .on_click(cx.listener(|this, _event, window, cx| {
8786 cx.stop_propagation();
8787 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8788 window.dispatch_action(
8789 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8790 cx,
8791 );
8792 }))
8793 .child(
8794 h_flex()
8795 .flex_1()
8796 .gap_2()
8797 .child(Icon::new(IconName::ZedPredict))
8798 .child(Label::new("Accept Terms of Service"))
8799 .child(div().w_full())
8800 .child(
8801 Icon::new(IconName::ArrowUpRight)
8802 .color(Color::Muted)
8803 .size(IconSize::Small),
8804 )
8805 .into_any_element(),
8806 )
8807 .into_any(),
8808 );
8809 }
8810
8811 let is_refreshing = provider.provider.is_refreshing(cx);
8812
8813 fn pending_completion_container() -> Div {
8814 h_flex()
8815 .h_full()
8816 .flex_1()
8817 .gap_2()
8818 .child(Icon::new(IconName::ZedPredict))
8819 }
8820
8821 let completion = match &self.active_inline_completion {
8822 Some(prediction) => {
8823 if !self.has_visible_completions_menu() {
8824 const RADIUS: Pixels = px(6.);
8825 const BORDER_WIDTH: Pixels = px(1.);
8826
8827 return Some(
8828 h_flex()
8829 .elevation_2(cx)
8830 .border(BORDER_WIDTH)
8831 .border_color(cx.theme().colors().border)
8832 .when(accept_keystroke.is_none(), |el| {
8833 el.border_color(cx.theme().status().error)
8834 })
8835 .rounded(RADIUS)
8836 .rounded_tl(px(0.))
8837 .overflow_hidden()
8838 .child(div().px_1p5().child(match &prediction.completion {
8839 InlineCompletion::Move { target, snapshot } => {
8840 use text::ToPoint as _;
8841 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8842 {
8843 Icon::new(IconName::ZedPredictDown)
8844 } else {
8845 Icon::new(IconName::ZedPredictUp)
8846 }
8847 }
8848 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8849 }))
8850 .child(
8851 h_flex()
8852 .gap_1()
8853 .py_1()
8854 .px_2()
8855 .rounded_r(RADIUS - BORDER_WIDTH)
8856 .border_l_1()
8857 .border_color(cx.theme().colors().border)
8858 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8859 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8860 el.child(
8861 Label::new("Hold")
8862 .size(LabelSize::Small)
8863 .when(accept_keystroke.is_none(), |el| {
8864 el.strikethrough()
8865 })
8866 .line_height_style(LineHeightStyle::UiLabel),
8867 )
8868 })
8869 .id("edit_prediction_cursor_popover_keybind")
8870 .when(accept_keystroke.is_none(), |el| {
8871 let status_colors = cx.theme().status();
8872
8873 el.bg(status_colors.error_background)
8874 .border_color(status_colors.error.opacity(0.6))
8875 .child(Icon::new(IconName::Info).color(Color::Error))
8876 .cursor_default()
8877 .hoverable_tooltip(move |_window, cx| {
8878 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8879 .into()
8880 })
8881 })
8882 .when_some(
8883 accept_keystroke.as_ref(),
8884 |el, accept_keystroke| {
8885 el.child(h_flex().children(ui::render_modifiers(
8886 &accept_keystroke.modifiers,
8887 PlatformStyle::platform(),
8888 Some(Color::Default),
8889 Some(IconSize::XSmall.rems().into()),
8890 false,
8891 )))
8892 },
8893 ),
8894 )
8895 .into_any(),
8896 );
8897 }
8898
8899 self.render_edit_prediction_cursor_popover_preview(
8900 prediction,
8901 cursor_point,
8902 style,
8903 cx,
8904 )?
8905 }
8906
8907 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8908 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8909 stale_completion,
8910 cursor_point,
8911 style,
8912 cx,
8913 )?,
8914
8915 None => {
8916 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8917 }
8918 },
8919
8920 None => pending_completion_container().child(Label::new("No Prediction")),
8921 };
8922
8923 let completion = if is_refreshing {
8924 completion
8925 .with_animation(
8926 "loading-completion",
8927 Animation::new(Duration::from_secs(2))
8928 .repeat()
8929 .with_easing(pulsating_between(0.4, 0.8)),
8930 |label, delta| label.opacity(delta),
8931 )
8932 .into_any_element()
8933 } else {
8934 completion.into_any_element()
8935 };
8936
8937 let has_completion = self.active_inline_completion.is_some();
8938
8939 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8940 Some(
8941 h_flex()
8942 .min_w(min_width)
8943 .max_w(max_width)
8944 .flex_1()
8945 .elevation_2(cx)
8946 .border_color(cx.theme().colors().border)
8947 .child(
8948 div()
8949 .flex_1()
8950 .py_1()
8951 .px_2()
8952 .overflow_hidden()
8953 .child(completion),
8954 )
8955 .when_some(accept_keystroke, |el, accept_keystroke| {
8956 if !accept_keystroke.modifiers.modified() {
8957 return el;
8958 }
8959
8960 el.child(
8961 h_flex()
8962 .h_full()
8963 .border_l_1()
8964 .rounded_r_lg()
8965 .border_color(cx.theme().colors().border)
8966 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8967 .gap_1()
8968 .py_1()
8969 .px_2()
8970 .child(
8971 h_flex()
8972 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8973 .when(is_platform_style_mac, |parent| parent.gap_1())
8974 .child(h_flex().children(ui::render_modifiers(
8975 &accept_keystroke.modifiers,
8976 PlatformStyle::platform(),
8977 Some(if !has_completion {
8978 Color::Muted
8979 } else {
8980 Color::Default
8981 }),
8982 None,
8983 false,
8984 ))),
8985 )
8986 .child(Label::new("Preview").into_any_element())
8987 .opacity(if has_completion { 1.0 } else { 0.4 }),
8988 )
8989 })
8990 .into_any(),
8991 )
8992 }
8993
8994 fn render_edit_prediction_cursor_popover_preview(
8995 &self,
8996 completion: &InlineCompletionState,
8997 cursor_point: Point,
8998 style: &EditorStyle,
8999 cx: &mut Context<Editor>,
9000 ) -> Option<Div> {
9001 use text::ToPoint as _;
9002
9003 fn render_relative_row_jump(
9004 prefix: impl Into<String>,
9005 current_row: u32,
9006 target_row: u32,
9007 ) -> Div {
9008 let (row_diff, arrow) = if target_row < current_row {
9009 (current_row - target_row, IconName::ArrowUp)
9010 } else {
9011 (target_row - current_row, IconName::ArrowDown)
9012 };
9013
9014 h_flex()
9015 .child(
9016 Label::new(format!("{}{}", prefix.into(), row_diff))
9017 .color(Color::Muted)
9018 .size(LabelSize::Small),
9019 )
9020 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9021 }
9022
9023 match &completion.completion {
9024 InlineCompletion::Move {
9025 target, snapshot, ..
9026 } => Some(
9027 h_flex()
9028 .px_2()
9029 .gap_2()
9030 .flex_1()
9031 .child(
9032 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
9033 Icon::new(IconName::ZedPredictDown)
9034 } else {
9035 Icon::new(IconName::ZedPredictUp)
9036 },
9037 )
9038 .child(Label::new("Jump to Edit")),
9039 ),
9040
9041 InlineCompletion::Edit {
9042 edits,
9043 edit_preview,
9044 snapshot,
9045 display_mode: _,
9046 } => {
9047 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
9048
9049 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
9050 &snapshot,
9051 &edits,
9052 edit_preview.as_ref()?,
9053 true,
9054 cx,
9055 )
9056 .first_line_preview();
9057
9058 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9059 .with_default_highlights(&style.text, highlighted_edits.highlights);
9060
9061 let preview = h_flex()
9062 .gap_1()
9063 .min_w_16()
9064 .child(styled_text)
9065 .when(has_more_lines, |parent| parent.child("…"));
9066
9067 let left = if first_edit_row != cursor_point.row {
9068 render_relative_row_jump("", cursor_point.row, first_edit_row)
9069 .into_any_element()
9070 } else {
9071 Icon::new(IconName::ZedPredict).into_any_element()
9072 };
9073
9074 Some(
9075 h_flex()
9076 .h_full()
9077 .flex_1()
9078 .gap_2()
9079 .pr_1()
9080 .overflow_x_hidden()
9081 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9082 .child(left)
9083 .child(preview),
9084 )
9085 }
9086 }
9087 }
9088
9089 pub fn render_context_menu(
9090 &self,
9091 style: &EditorStyle,
9092 max_height_in_lines: u32,
9093 window: &mut Window,
9094 cx: &mut Context<Editor>,
9095 ) -> Option<AnyElement> {
9096 let menu = self.context_menu.borrow();
9097 let menu = menu.as_ref()?;
9098 if !menu.visible() {
9099 return None;
9100 };
9101 Some(menu.render(style, max_height_in_lines, window, cx))
9102 }
9103
9104 fn render_context_menu_aside(
9105 &mut self,
9106 max_size: Size<Pixels>,
9107 window: &mut Window,
9108 cx: &mut Context<Editor>,
9109 ) -> Option<AnyElement> {
9110 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9111 if menu.visible() {
9112 menu.render_aside(max_size, window, cx)
9113 } else {
9114 None
9115 }
9116 })
9117 }
9118
9119 fn hide_context_menu(
9120 &mut self,
9121 window: &mut Window,
9122 cx: &mut Context<Self>,
9123 ) -> Option<CodeContextMenu> {
9124 cx.notify();
9125 self.completion_tasks.clear();
9126 let context_menu = self.context_menu.borrow_mut().take();
9127 self.stale_inline_completion_in_menu.take();
9128 self.update_visible_inline_completion(window, cx);
9129 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9130 if let Some(completion_provider) = &self.completion_provider {
9131 completion_provider.selection_changed(None, window, cx);
9132 }
9133 }
9134 context_menu
9135 }
9136
9137 fn show_snippet_choices(
9138 &mut self,
9139 choices: &Vec<String>,
9140 selection: Range<Anchor>,
9141 cx: &mut Context<Self>,
9142 ) {
9143 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9144 (Some(a), Some(b)) if a == b => a,
9145 _ => {
9146 log::error!("expected anchor range to have matching buffer IDs");
9147 return;
9148 }
9149 };
9150 let multi_buffer = self.buffer().read(cx);
9151 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9152 return;
9153 };
9154
9155 let id = post_inc(&mut self.next_completion_id);
9156 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9157 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9158 CompletionsMenu::new_snippet_choices(
9159 id,
9160 true,
9161 choices,
9162 selection,
9163 buffer,
9164 snippet_sort_order,
9165 ),
9166 ));
9167 }
9168
9169 pub fn insert_snippet(
9170 &mut self,
9171 insertion_ranges: &[Range<usize>],
9172 snippet: Snippet,
9173 window: &mut Window,
9174 cx: &mut Context<Self>,
9175 ) -> Result<()> {
9176 struct Tabstop<T> {
9177 is_end_tabstop: bool,
9178 ranges: Vec<Range<T>>,
9179 choices: Option<Vec<String>>,
9180 }
9181
9182 let tabstops = self.buffer.update(cx, |buffer, cx| {
9183 let snippet_text: Arc<str> = snippet.text.clone().into();
9184 let edits = insertion_ranges
9185 .iter()
9186 .cloned()
9187 .map(|range| (range, snippet_text.clone()));
9188 let autoindent_mode = AutoindentMode::Block {
9189 original_indent_columns: Vec::new(),
9190 };
9191 buffer.edit(edits, Some(autoindent_mode), cx);
9192
9193 let snapshot = &*buffer.read(cx);
9194 let snippet = &snippet;
9195 snippet
9196 .tabstops
9197 .iter()
9198 .map(|tabstop| {
9199 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9200 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9201 });
9202 let mut tabstop_ranges = tabstop
9203 .ranges
9204 .iter()
9205 .flat_map(|tabstop_range| {
9206 let mut delta = 0_isize;
9207 insertion_ranges.iter().map(move |insertion_range| {
9208 let insertion_start = insertion_range.start as isize + delta;
9209 delta +=
9210 snippet.text.len() as isize - insertion_range.len() as isize;
9211
9212 let start = ((insertion_start + tabstop_range.start) as usize)
9213 .min(snapshot.len());
9214 let end = ((insertion_start + tabstop_range.end) as usize)
9215 .min(snapshot.len());
9216 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9217 })
9218 })
9219 .collect::<Vec<_>>();
9220 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9221
9222 Tabstop {
9223 is_end_tabstop,
9224 ranges: tabstop_ranges,
9225 choices: tabstop.choices.clone(),
9226 }
9227 })
9228 .collect::<Vec<_>>()
9229 });
9230 if let Some(tabstop) = tabstops.first() {
9231 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9232 // Reverse order so that the first range is the newest created selection.
9233 // Completions will use it and autoscroll will prioritize it.
9234 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9235 });
9236
9237 if let Some(choices) = &tabstop.choices {
9238 if let Some(selection) = tabstop.ranges.first() {
9239 self.show_snippet_choices(choices, selection.clone(), cx)
9240 }
9241 }
9242
9243 // If we're already at the last tabstop and it's at the end of the snippet,
9244 // we're done, we don't need to keep the state around.
9245 if !tabstop.is_end_tabstop {
9246 let choices = tabstops
9247 .iter()
9248 .map(|tabstop| tabstop.choices.clone())
9249 .collect();
9250
9251 let ranges = tabstops
9252 .into_iter()
9253 .map(|tabstop| tabstop.ranges)
9254 .collect::<Vec<_>>();
9255
9256 self.snippet_stack.push(SnippetState {
9257 active_index: 0,
9258 ranges,
9259 choices,
9260 });
9261 }
9262
9263 // Check whether the just-entered snippet ends with an auto-closable bracket.
9264 if self.autoclose_regions.is_empty() {
9265 let snapshot = self.buffer.read(cx).snapshot(cx);
9266 for selection in &mut self.selections.all::<Point>(cx) {
9267 let selection_head = selection.head();
9268 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9269 continue;
9270 };
9271
9272 let mut bracket_pair = None;
9273 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9274 let prev_chars = snapshot
9275 .reversed_chars_at(selection_head)
9276 .collect::<String>();
9277 for (pair, enabled) in scope.brackets() {
9278 if enabled
9279 && pair.close
9280 && prev_chars.starts_with(pair.start.as_str())
9281 && next_chars.starts_with(pair.end.as_str())
9282 {
9283 bracket_pair = Some(pair.clone());
9284 break;
9285 }
9286 }
9287 if let Some(pair) = bracket_pair {
9288 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9289 let autoclose_enabled =
9290 self.use_autoclose && snapshot_settings.use_autoclose;
9291 if autoclose_enabled {
9292 let start = snapshot.anchor_after(selection_head);
9293 let end = snapshot.anchor_after(selection_head);
9294 self.autoclose_regions.push(AutocloseRegion {
9295 selection_id: selection.id,
9296 range: start..end,
9297 pair,
9298 });
9299 }
9300 }
9301 }
9302 }
9303 }
9304 Ok(())
9305 }
9306
9307 pub fn move_to_next_snippet_tabstop(
9308 &mut self,
9309 window: &mut Window,
9310 cx: &mut Context<Self>,
9311 ) -> bool {
9312 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9313 }
9314
9315 pub fn move_to_prev_snippet_tabstop(
9316 &mut self,
9317 window: &mut Window,
9318 cx: &mut Context<Self>,
9319 ) -> bool {
9320 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9321 }
9322
9323 pub fn move_to_snippet_tabstop(
9324 &mut self,
9325 bias: Bias,
9326 window: &mut Window,
9327 cx: &mut Context<Self>,
9328 ) -> bool {
9329 if let Some(mut snippet) = self.snippet_stack.pop() {
9330 match bias {
9331 Bias::Left => {
9332 if snippet.active_index > 0 {
9333 snippet.active_index -= 1;
9334 } else {
9335 self.snippet_stack.push(snippet);
9336 return false;
9337 }
9338 }
9339 Bias::Right => {
9340 if snippet.active_index + 1 < snippet.ranges.len() {
9341 snippet.active_index += 1;
9342 } else {
9343 self.snippet_stack.push(snippet);
9344 return false;
9345 }
9346 }
9347 }
9348 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9349 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9350 // Reverse order so that the first range is the newest created selection.
9351 // Completions will use it and autoscroll will prioritize it.
9352 s.select_ranges(current_ranges.iter().rev().cloned())
9353 });
9354
9355 if let Some(choices) = &snippet.choices[snippet.active_index] {
9356 if let Some(selection) = current_ranges.first() {
9357 self.show_snippet_choices(&choices, selection.clone(), cx);
9358 }
9359 }
9360
9361 // If snippet state is not at the last tabstop, push it back on the stack
9362 if snippet.active_index + 1 < snippet.ranges.len() {
9363 self.snippet_stack.push(snippet);
9364 }
9365 return true;
9366 }
9367 }
9368
9369 false
9370 }
9371
9372 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9373 self.transact(window, cx, |this, window, cx| {
9374 this.select_all(&SelectAll, window, cx);
9375 this.insert("", window, cx);
9376 });
9377 }
9378
9379 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9380 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9381 self.transact(window, cx, |this, window, cx| {
9382 this.select_autoclose_pair(window, cx);
9383 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9384 if !this.linked_edit_ranges.is_empty() {
9385 let selections = this.selections.all::<MultiBufferPoint>(cx);
9386 let snapshot = this.buffer.read(cx).snapshot(cx);
9387
9388 for selection in selections.iter() {
9389 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9390 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9391 if selection_start.buffer_id != selection_end.buffer_id {
9392 continue;
9393 }
9394 if let Some(ranges) =
9395 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9396 {
9397 for (buffer, entries) in ranges {
9398 linked_ranges.entry(buffer).or_default().extend(entries);
9399 }
9400 }
9401 }
9402 }
9403
9404 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9405 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9406 for selection in &mut selections {
9407 if selection.is_empty() {
9408 let old_head = selection.head();
9409 let mut new_head =
9410 movement::left(&display_map, old_head.to_display_point(&display_map))
9411 .to_point(&display_map);
9412 if let Some((buffer, line_buffer_range)) = display_map
9413 .buffer_snapshot
9414 .buffer_line_for_row(MultiBufferRow(old_head.row))
9415 {
9416 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9417 let indent_len = match indent_size.kind {
9418 IndentKind::Space => {
9419 buffer.settings_at(line_buffer_range.start, cx).tab_size
9420 }
9421 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9422 };
9423 if old_head.column <= indent_size.len && old_head.column > 0 {
9424 let indent_len = indent_len.get();
9425 new_head = cmp::min(
9426 new_head,
9427 MultiBufferPoint::new(
9428 old_head.row,
9429 ((old_head.column - 1) / indent_len) * indent_len,
9430 ),
9431 );
9432 }
9433 }
9434
9435 selection.set_head(new_head, SelectionGoal::None);
9436 }
9437 }
9438
9439 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9440 s.select(selections)
9441 });
9442 this.insert("", window, cx);
9443 let empty_str: Arc<str> = Arc::from("");
9444 for (buffer, edits) in linked_ranges {
9445 let snapshot = buffer.read(cx).snapshot();
9446 use text::ToPoint as TP;
9447
9448 let edits = edits
9449 .into_iter()
9450 .map(|range| {
9451 let end_point = TP::to_point(&range.end, &snapshot);
9452 let mut start_point = TP::to_point(&range.start, &snapshot);
9453
9454 if end_point == start_point {
9455 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9456 .saturating_sub(1);
9457 start_point =
9458 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9459 };
9460
9461 (start_point..end_point, empty_str.clone())
9462 })
9463 .sorted_by_key(|(range, _)| range.start)
9464 .collect::<Vec<_>>();
9465 buffer.update(cx, |this, cx| {
9466 this.edit(edits, None, cx);
9467 })
9468 }
9469 this.refresh_inline_completion(true, false, window, cx);
9470 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9471 });
9472 }
9473
9474 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9475 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9476 self.transact(window, cx, |this, window, cx| {
9477 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9478 s.move_with(|map, selection| {
9479 if selection.is_empty() {
9480 let cursor = movement::right(map, selection.head());
9481 selection.end = cursor;
9482 selection.reversed = true;
9483 selection.goal = SelectionGoal::None;
9484 }
9485 })
9486 });
9487 this.insert("", window, cx);
9488 this.refresh_inline_completion(true, false, window, cx);
9489 });
9490 }
9491
9492 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9493 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9494 if self.move_to_prev_snippet_tabstop(window, cx) {
9495 return;
9496 }
9497 self.outdent(&Outdent, window, cx);
9498 }
9499
9500 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9501 if self.move_to_next_snippet_tabstop(window, cx) {
9502 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9503 return;
9504 }
9505 if self.read_only(cx) {
9506 return;
9507 }
9508 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9509 let mut selections = self.selections.all_adjusted(cx);
9510 let buffer = self.buffer.read(cx);
9511 let snapshot = buffer.snapshot(cx);
9512 let rows_iter = selections.iter().map(|s| s.head().row);
9513 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9514
9515 let has_some_cursor_in_whitespace = selections
9516 .iter()
9517 .filter(|selection| selection.is_empty())
9518 .any(|selection| {
9519 let cursor = selection.head();
9520 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9521 cursor.column < current_indent.len
9522 });
9523
9524 let mut edits = Vec::new();
9525 let mut prev_edited_row = 0;
9526 let mut row_delta = 0;
9527 for selection in &mut selections {
9528 if selection.start.row != prev_edited_row {
9529 row_delta = 0;
9530 }
9531 prev_edited_row = selection.end.row;
9532
9533 // If the selection is non-empty, then increase the indentation of the selected lines.
9534 if !selection.is_empty() {
9535 row_delta =
9536 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9537 continue;
9538 }
9539
9540 let cursor = selection.head();
9541 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9542 if let Some(suggested_indent) =
9543 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9544 {
9545 // Don't do anything if already at suggested indent
9546 // and there is any other cursor which is not
9547 if has_some_cursor_in_whitespace
9548 && cursor.column == current_indent.len
9549 && current_indent.len == suggested_indent.len
9550 {
9551 continue;
9552 }
9553
9554 // Adjust line and move cursor to suggested indent
9555 // if cursor is not at suggested indent
9556 if cursor.column < suggested_indent.len
9557 && cursor.column <= current_indent.len
9558 && current_indent.len <= suggested_indent.len
9559 {
9560 selection.start = Point::new(cursor.row, suggested_indent.len);
9561 selection.end = selection.start;
9562 if row_delta == 0 {
9563 edits.extend(Buffer::edit_for_indent_size_adjustment(
9564 cursor.row,
9565 current_indent,
9566 suggested_indent,
9567 ));
9568 row_delta = suggested_indent.len - current_indent.len;
9569 }
9570 continue;
9571 }
9572
9573 // If current indent is more than suggested indent
9574 // only move cursor to current indent and skip indent
9575 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9576 selection.start = Point::new(cursor.row, current_indent.len);
9577 selection.end = selection.start;
9578 continue;
9579 }
9580 }
9581
9582 // Otherwise, insert a hard or soft tab.
9583 let settings = buffer.language_settings_at(cursor, cx);
9584 let tab_size = if settings.hard_tabs {
9585 IndentSize::tab()
9586 } else {
9587 let tab_size = settings.tab_size.get();
9588 let indent_remainder = snapshot
9589 .text_for_range(Point::new(cursor.row, 0)..cursor)
9590 .flat_map(str::chars)
9591 .fold(row_delta % tab_size, |counter: u32, c| {
9592 if c == '\t' {
9593 0
9594 } else {
9595 (counter + 1) % tab_size
9596 }
9597 });
9598
9599 let chars_to_next_tab_stop = tab_size - indent_remainder;
9600 IndentSize::spaces(chars_to_next_tab_stop)
9601 };
9602 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9603 selection.end = selection.start;
9604 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9605 row_delta += tab_size.len;
9606 }
9607
9608 self.transact(window, cx, |this, window, cx| {
9609 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9610 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9611 s.select(selections)
9612 });
9613 this.refresh_inline_completion(true, false, window, cx);
9614 });
9615 }
9616
9617 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9618 if self.read_only(cx) {
9619 return;
9620 }
9621 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9622 let mut selections = self.selections.all::<Point>(cx);
9623 let mut prev_edited_row = 0;
9624 let mut row_delta = 0;
9625 let mut edits = Vec::new();
9626 let buffer = self.buffer.read(cx);
9627 let snapshot = buffer.snapshot(cx);
9628 for selection in &mut selections {
9629 if selection.start.row != prev_edited_row {
9630 row_delta = 0;
9631 }
9632 prev_edited_row = selection.end.row;
9633
9634 row_delta =
9635 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9636 }
9637
9638 self.transact(window, cx, |this, window, cx| {
9639 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9640 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9641 s.select(selections)
9642 });
9643 });
9644 }
9645
9646 fn indent_selection(
9647 buffer: &MultiBuffer,
9648 snapshot: &MultiBufferSnapshot,
9649 selection: &mut Selection<Point>,
9650 edits: &mut Vec<(Range<Point>, String)>,
9651 delta_for_start_row: u32,
9652 cx: &App,
9653 ) -> u32 {
9654 let settings = buffer.language_settings_at(selection.start, cx);
9655 let tab_size = settings.tab_size.get();
9656 let indent_kind = if settings.hard_tabs {
9657 IndentKind::Tab
9658 } else {
9659 IndentKind::Space
9660 };
9661 let mut start_row = selection.start.row;
9662 let mut end_row = selection.end.row + 1;
9663
9664 // If a selection ends at the beginning of a line, don't indent
9665 // that last line.
9666 if selection.end.column == 0 && selection.end.row > selection.start.row {
9667 end_row -= 1;
9668 }
9669
9670 // Avoid re-indenting a row that has already been indented by a
9671 // previous selection, but still update this selection's column
9672 // to reflect that indentation.
9673 if delta_for_start_row > 0 {
9674 start_row += 1;
9675 selection.start.column += delta_for_start_row;
9676 if selection.end.row == selection.start.row {
9677 selection.end.column += delta_for_start_row;
9678 }
9679 }
9680
9681 let mut delta_for_end_row = 0;
9682 let has_multiple_rows = start_row + 1 != end_row;
9683 for row in start_row..end_row {
9684 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9685 let indent_delta = match (current_indent.kind, indent_kind) {
9686 (IndentKind::Space, IndentKind::Space) => {
9687 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9688 IndentSize::spaces(columns_to_next_tab_stop)
9689 }
9690 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9691 (_, IndentKind::Tab) => IndentSize::tab(),
9692 };
9693
9694 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9695 0
9696 } else {
9697 selection.start.column
9698 };
9699 let row_start = Point::new(row, start);
9700 edits.push((
9701 row_start..row_start,
9702 indent_delta.chars().collect::<String>(),
9703 ));
9704
9705 // Update this selection's endpoints to reflect the indentation.
9706 if row == selection.start.row {
9707 selection.start.column += indent_delta.len;
9708 }
9709 if row == selection.end.row {
9710 selection.end.column += indent_delta.len;
9711 delta_for_end_row = indent_delta.len;
9712 }
9713 }
9714
9715 if selection.start.row == selection.end.row {
9716 delta_for_start_row + delta_for_end_row
9717 } else {
9718 delta_for_end_row
9719 }
9720 }
9721
9722 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9723 if self.read_only(cx) {
9724 return;
9725 }
9726 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9727 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9728 let selections = self.selections.all::<Point>(cx);
9729 let mut deletion_ranges = Vec::new();
9730 let mut last_outdent = None;
9731 {
9732 let buffer = self.buffer.read(cx);
9733 let snapshot = buffer.snapshot(cx);
9734 for selection in &selections {
9735 let settings = buffer.language_settings_at(selection.start, cx);
9736 let tab_size = settings.tab_size.get();
9737 let mut rows = selection.spanned_rows(false, &display_map);
9738
9739 // Avoid re-outdenting a row that has already been outdented by a
9740 // previous selection.
9741 if let Some(last_row) = last_outdent {
9742 if last_row == rows.start {
9743 rows.start = rows.start.next_row();
9744 }
9745 }
9746 let has_multiple_rows = rows.len() > 1;
9747 for row in rows.iter_rows() {
9748 let indent_size = snapshot.indent_size_for_line(row);
9749 if indent_size.len > 0 {
9750 let deletion_len = match indent_size.kind {
9751 IndentKind::Space => {
9752 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9753 if columns_to_prev_tab_stop == 0 {
9754 tab_size
9755 } else {
9756 columns_to_prev_tab_stop
9757 }
9758 }
9759 IndentKind::Tab => 1,
9760 };
9761 let start = if has_multiple_rows
9762 || deletion_len > selection.start.column
9763 || indent_size.len < selection.start.column
9764 {
9765 0
9766 } else {
9767 selection.start.column - deletion_len
9768 };
9769 deletion_ranges.push(
9770 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9771 );
9772 last_outdent = Some(row);
9773 }
9774 }
9775 }
9776 }
9777
9778 self.transact(window, cx, |this, window, cx| {
9779 this.buffer.update(cx, |buffer, cx| {
9780 let empty_str: Arc<str> = Arc::default();
9781 buffer.edit(
9782 deletion_ranges
9783 .into_iter()
9784 .map(|range| (range, empty_str.clone())),
9785 None,
9786 cx,
9787 );
9788 });
9789 let selections = this.selections.all::<usize>(cx);
9790 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9791 s.select(selections)
9792 });
9793 });
9794 }
9795
9796 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9797 if self.read_only(cx) {
9798 return;
9799 }
9800 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9801 let selections = self
9802 .selections
9803 .all::<usize>(cx)
9804 .into_iter()
9805 .map(|s| s.range());
9806
9807 self.transact(window, cx, |this, window, cx| {
9808 this.buffer.update(cx, |buffer, cx| {
9809 buffer.autoindent_ranges(selections, cx);
9810 });
9811 let selections = this.selections.all::<usize>(cx);
9812 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9813 s.select(selections)
9814 });
9815 });
9816 }
9817
9818 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9819 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9821 let selections = self.selections.all::<Point>(cx);
9822
9823 let mut new_cursors = Vec::new();
9824 let mut edit_ranges = Vec::new();
9825 let mut selections = selections.iter().peekable();
9826 while let Some(selection) = selections.next() {
9827 let mut rows = selection.spanned_rows(false, &display_map);
9828 let goal_display_column = selection.head().to_display_point(&display_map).column();
9829
9830 // Accumulate contiguous regions of rows that we want to delete.
9831 while let Some(next_selection) = selections.peek() {
9832 let next_rows = next_selection.spanned_rows(false, &display_map);
9833 if next_rows.start <= rows.end {
9834 rows.end = next_rows.end;
9835 selections.next().unwrap();
9836 } else {
9837 break;
9838 }
9839 }
9840
9841 let buffer = &display_map.buffer_snapshot;
9842 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9843 let edit_end;
9844 let cursor_buffer_row;
9845 if buffer.max_point().row >= rows.end.0 {
9846 // If there's a line after the range, delete the \n from the end of the row range
9847 // and position the cursor on the next line.
9848 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9849 cursor_buffer_row = rows.end;
9850 } else {
9851 // If there isn't a line after the range, delete the \n from the line before the
9852 // start of the row range and position the cursor there.
9853 edit_start = edit_start.saturating_sub(1);
9854 edit_end = buffer.len();
9855 cursor_buffer_row = rows.start.previous_row();
9856 }
9857
9858 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9859 *cursor.column_mut() =
9860 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9861
9862 new_cursors.push((
9863 selection.id,
9864 buffer.anchor_after(cursor.to_point(&display_map)),
9865 ));
9866 edit_ranges.push(edit_start..edit_end);
9867 }
9868
9869 self.transact(window, cx, |this, window, cx| {
9870 let buffer = this.buffer.update(cx, |buffer, cx| {
9871 let empty_str: Arc<str> = Arc::default();
9872 buffer.edit(
9873 edit_ranges
9874 .into_iter()
9875 .map(|range| (range, empty_str.clone())),
9876 None,
9877 cx,
9878 );
9879 buffer.snapshot(cx)
9880 });
9881 let new_selections = new_cursors
9882 .into_iter()
9883 .map(|(id, cursor)| {
9884 let cursor = cursor.to_point(&buffer);
9885 Selection {
9886 id,
9887 start: cursor,
9888 end: cursor,
9889 reversed: false,
9890 goal: SelectionGoal::None,
9891 }
9892 })
9893 .collect();
9894
9895 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9896 s.select(new_selections);
9897 });
9898 });
9899 }
9900
9901 pub fn join_lines_impl(
9902 &mut self,
9903 insert_whitespace: bool,
9904 window: &mut Window,
9905 cx: &mut Context<Self>,
9906 ) {
9907 if self.read_only(cx) {
9908 return;
9909 }
9910 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9911 for selection in self.selections.all::<Point>(cx) {
9912 let start = MultiBufferRow(selection.start.row);
9913 // Treat single line selections as if they include the next line. Otherwise this action
9914 // would do nothing for single line selections individual cursors.
9915 let end = if selection.start.row == selection.end.row {
9916 MultiBufferRow(selection.start.row + 1)
9917 } else {
9918 MultiBufferRow(selection.end.row)
9919 };
9920
9921 if let Some(last_row_range) = row_ranges.last_mut() {
9922 if start <= last_row_range.end {
9923 last_row_range.end = end;
9924 continue;
9925 }
9926 }
9927 row_ranges.push(start..end);
9928 }
9929
9930 let snapshot = self.buffer.read(cx).snapshot(cx);
9931 let mut cursor_positions = Vec::new();
9932 for row_range in &row_ranges {
9933 let anchor = snapshot.anchor_before(Point::new(
9934 row_range.end.previous_row().0,
9935 snapshot.line_len(row_range.end.previous_row()),
9936 ));
9937 cursor_positions.push(anchor..anchor);
9938 }
9939
9940 self.transact(window, cx, |this, window, cx| {
9941 for row_range in row_ranges.into_iter().rev() {
9942 for row in row_range.iter_rows().rev() {
9943 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9944 let next_line_row = row.next_row();
9945 let indent = snapshot.indent_size_for_line(next_line_row);
9946 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9947
9948 let replace =
9949 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9950 " "
9951 } else {
9952 ""
9953 };
9954
9955 this.buffer.update(cx, |buffer, cx| {
9956 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9957 });
9958 }
9959 }
9960
9961 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9962 s.select_anchor_ranges(cursor_positions)
9963 });
9964 });
9965 }
9966
9967 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9968 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9969 self.join_lines_impl(true, window, cx);
9970 }
9971
9972 pub fn sort_lines_case_sensitive(
9973 &mut self,
9974 _: &SortLinesCaseSensitive,
9975 window: &mut Window,
9976 cx: &mut Context<Self>,
9977 ) {
9978 self.manipulate_lines(window, cx, |lines| lines.sort())
9979 }
9980
9981 pub fn sort_lines_case_insensitive(
9982 &mut self,
9983 _: &SortLinesCaseInsensitive,
9984 window: &mut Window,
9985 cx: &mut Context<Self>,
9986 ) {
9987 self.manipulate_lines(window, cx, |lines| {
9988 lines.sort_by_key(|line| line.to_lowercase())
9989 })
9990 }
9991
9992 pub fn unique_lines_case_insensitive(
9993 &mut self,
9994 _: &UniqueLinesCaseInsensitive,
9995 window: &mut Window,
9996 cx: &mut Context<Self>,
9997 ) {
9998 self.manipulate_lines(window, cx, |lines| {
9999 let mut seen = HashSet::default();
10000 lines.retain(|line| seen.insert(line.to_lowercase()));
10001 })
10002 }
10003
10004 pub fn unique_lines_case_sensitive(
10005 &mut self,
10006 _: &UniqueLinesCaseSensitive,
10007 window: &mut Window,
10008 cx: &mut Context<Self>,
10009 ) {
10010 self.manipulate_lines(window, cx, |lines| {
10011 let mut seen = HashSet::default();
10012 lines.retain(|line| seen.insert(*line));
10013 })
10014 }
10015
10016 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10017 let Some(project) = self.project.clone() else {
10018 return;
10019 };
10020 self.reload(project, window, cx)
10021 .detach_and_notify_err(window, cx);
10022 }
10023
10024 pub fn restore_file(
10025 &mut self,
10026 _: &::git::RestoreFile,
10027 window: &mut Window,
10028 cx: &mut Context<Self>,
10029 ) {
10030 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10031 let mut buffer_ids = HashSet::default();
10032 let snapshot = self.buffer().read(cx).snapshot(cx);
10033 for selection in self.selections.all::<usize>(cx) {
10034 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10035 }
10036
10037 let buffer = self.buffer().read(cx);
10038 let ranges = buffer_ids
10039 .into_iter()
10040 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10041 .collect::<Vec<_>>();
10042
10043 self.restore_hunks_in_ranges(ranges, window, cx);
10044 }
10045
10046 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10047 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10048 let selections = self
10049 .selections
10050 .all(cx)
10051 .into_iter()
10052 .map(|s| s.range())
10053 .collect();
10054 self.restore_hunks_in_ranges(selections, window, cx);
10055 }
10056
10057 pub fn restore_hunks_in_ranges(
10058 &mut self,
10059 ranges: Vec<Range<Point>>,
10060 window: &mut Window,
10061 cx: &mut Context<Editor>,
10062 ) {
10063 let mut revert_changes = HashMap::default();
10064 let chunk_by = self
10065 .snapshot(window, cx)
10066 .hunks_for_ranges(ranges)
10067 .into_iter()
10068 .chunk_by(|hunk| hunk.buffer_id);
10069 for (buffer_id, hunks) in &chunk_by {
10070 let hunks = hunks.collect::<Vec<_>>();
10071 for hunk in &hunks {
10072 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10073 }
10074 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10075 }
10076 drop(chunk_by);
10077 if !revert_changes.is_empty() {
10078 self.transact(window, cx, |editor, window, cx| {
10079 editor.restore(revert_changes, window, cx);
10080 });
10081 }
10082 }
10083
10084 pub fn open_active_item_in_terminal(
10085 &mut self,
10086 _: &OpenInTerminal,
10087 window: &mut Window,
10088 cx: &mut Context<Self>,
10089 ) {
10090 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10091 let project_path = buffer.read(cx).project_path(cx)?;
10092 let project = self.project.as_ref()?.read(cx);
10093 let entry = project.entry_for_path(&project_path, cx)?;
10094 let parent = match &entry.canonical_path {
10095 Some(canonical_path) => canonical_path.to_path_buf(),
10096 None => project.absolute_path(&project_path, cx)?,
10097 }
10098 .parent()?
10099 .to_path_buf();
10100 Some(parent)
10101 }) {
10102 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10103 }
10104 }
10105
10106 fn set_breakpoint_context_menu(
10107 &mut self,
10108 display_row: DisplayRow,
10109 position: Option<Anchor>,
10110 clicked_point: gpui::Point<Pixels>,
10111 window: &mut Window,
10112 cx: &mut Context<Self>,
10113 ) {
10114 if !cx.has_flag::<DebuggerFeatureFlag>() {
10115 return;
10116 }
10117 let source = self
10118 .buffer
10119 .read(cx)
10120 .snapshot(cx)
10121 .anchor_before(Point::new(display_row.0, 0u32));
10122
10123 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10124
10125 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10126 self,
10127 source,
10128 clicked_point,
10129 context_menu,
10130 window,
10131 cx,
10132 );
10133 }
10134
10135 fn add_edit_breakpoint_block(
10136 &mut self,
10137 anchor: Anchor,
10138 breakpoint: &Breakpoint,
10139 edit_action: BreakpointPromptEditAction,
10140 window: &mut Window,
10141 cx: &mut Context<Self>,
10142 ) {
10143 let weak_editor = cx.weak_entity();
10144 let bp_prompt = cx.new(|cx| {
10145 BreakpointPromptEditor::new(
10146 weak_editor,
10147 anchor,
10148 breakpoint.clone(),
10149 edit_action,
10150 window,
10151 cx,
10152 )
10153 });
10154
10155 let height = bp_prompt.update(cx, |this, cx| {
10156 this.prompt
10157 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10158 });
10159 let cloned_prompt = bp_prompt.clone();
10160 let blocks = vec![BlockProperties {
10161 style: BlockStyle::Sticky,
10162 placement: BlockPlacement::Above(anchor),
10163 height: Some(height),
10164 render: Arc::new(move |cx| {
10165 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10166 cloned_prompt.clone().into_any_element()
10167 }),
10168 priority: 0,
10169 render_in_minimap: true,
10170 }];
10171
10172 let focus_handle = bp_prompt.focus_handle(cx);
10173 window.focus(&focus_handle);
10174
10175 let block_ids = self.insert_blocks(blocks, None, cx);
10176 bp_prompt.update(cx, |prompt, _| {
10177 prompt.add_block_ids(block_ids);
10178 });
10179 }
10180
10181 pub(crate) fn breakpoint_at_row(
10182 &self,
10183 row: u32,
10184 window: &mut Window,
10185 cx: &mut Context<Self>,
10186 ) -> Option<(Anchor, Breakpoint)> {
10187 let snapshot = self.snapshot(window, cx);
10188 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10189
10190 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10191 }
10192
10193 pub(crate) fn breakpoint_at_anchor(
10194 &self,
10195 breakpoint_position: Anchor,
10196 snapshot: &EditorSnapshot,
10197 cx: &mut Context<Self>,
10198 ) -> Option<(Anchor, Breakpoint)> {
10199 let project = self.project.clone()?;
10200
10201 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10202 snapshot
10203 .buffer_snapshot
10204 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10205 })?;
10206
10207 let enclosing_excerpt = breakpoint_position.excerpt_id;
10208 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10209 let buffer_snapshot = buffer.read(cx).snapshot();
10210
10211 let row = buffer_snapshot
10212 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10213 .row;
10214
10215 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10216 let anchor_end = snapshot
10217 .buffer_snapshot
10218 .anchor_after(Point::new(row, line_len));
10219
10220 let bp = self
10221 .breakpoint_store
10222 .as_ref()?
10223 .read_with(cx, |breakpoint_store, cx| {
10224 breakpoint_store
10225 .breakpoints(
10226 &buffer,
10227 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10228 &buffer_snapshot,
10229 cx,
10230 )
10231 .next()
10232 .and_then(|(bp, _)| {
10233 let breakpoint_row = buffer_snapshot
10234 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10235 .row;
10236
10237 if breakpoint_row == row {
10238 snapshot
10239 .buffer_snapshot
10240 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10241 .map(|position| (position, bp.bp.clone()))
10242 } else {
10243 None
10244 }
10245 })
10246 });
10247 bp
10248 }
10249
10250 pub fn edit_log_breakpoint(
10251 &mut self,
10252 _: &EditLogBreakpoint,
10253 window: &mut Window,
10254 cx: &mut Context<Self>,
10255 ) {
10256 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10257 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10258 message: None,
10259 state: BreakpointState::Enabled,
10260 condition: None,
10261 hit_condition: None,
10262 });
10263
10264 self.add_edit_breakpoint_block(
10265 anchor,
10266 &breakpoint,
10267 BreakpointPromptEditAction::Log,
10268 window,
10269 cx,
10270 );
10271 }
10272 }
10273
10274 fn breakpoints_at_cursors(
10275 &self,
10276 window: &mut Window,
10277 cx: &mut Context<Self>,
10278 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10279 let snapshot = self.snapshot(window, cx);
10280 let cursors = self
10281 .selections
10282 .disjoint_anchors()
10283 .into_iter()
10284 .map(|selection| {
10285 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10286
10287 let breakpoint_position = self
10288 .breakpoint_at_row(cursor_position.row, window, cx)
10289 .map(|bp| bp.0)
10290 .unwrap_or_else(|| {
10291 snapshot
10292 .display_snapshot
10293 .buffer_snapshot
10294 .anchor_after(Point::new(cursor_position.row, 0))
10295 });
10296
10297 let breakpoint = self
10298 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10299 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10300
10301 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10302 })
10303 // There might be multiple cursors on the same line; all of them should have the same anchors though as their breakpoints positions, which makes it possible to sort and dedup the list.
10304 .collect::<HashMap<Anchor, _>>();
10305
10306 cursors.into_iter().collect()
10307 }
10308
10309 pub fn enable_breakpoint(
10310 &mut self,
10311 _: &crate::actions::EnableBreakpoint,
10312 window: &mut Window,
10313 cx: &mut Context<Self>,
10314 ) {
10315 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10316 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10317 continue;
10318 };
10319 self.edit_breakpoint_at_anchor(
10320 anchor,
10321 breakpoint,
10322 BreakpointEditAction::InvertState,
10323 cx,
10324 );
10325 }
10326 }
10327
10328 pub fn disable_breakpoint(
10329 &mut self,
10330 _: &crate::actions::DisableBreakpoint,
10331 window: &mut Window,
10332 cx: &mut Context<Self>,
10333 ) {
10334 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10335 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10336 continue;
10337 };
10338 self.edit_breakpoint_at_anchor(
10339 anchor,
10340 breakpoint,
10341 BreakpointEditAction::InvertState,
10342 cx,
10343 );
10344 }
10345 }
10346
10347 pub fn toggle_breakpoint(
10348 &mut self,
10349 _: &crate::actions::ToggleBreakpoint,
10350 window: &mut Window,
10351 cx: &mut Context<Self>,
10352 ) {
10353 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10354 if let Some(breakpoint) = breakpoint {
10355 self.edit_breakpoint_at_anchor(
10356 anchor,
10357 breakpoint,
10358 BreakpointEditAction::Toggle,
10359 cx,
10360 );
10361 } else {
10362 self.edit_breakpoint_at_anchor(
10363 anchor,
10364 Breakpoint::new_standard(),
10365 BreakpointEditAction::Toggle,
10366 cx,
10367 );
10368 }
10369 }
10370 }
10371
10372 pub fn edit_breakpoint_at_anchor(
10373 &mut self,
10374 breakpoint_position: Anchor,
10375 breakpoint: Breakpoint,
10376 edit_action: BreakpointEditAction,
10377 cx: &mut Context<Self>,
10378 ) {
10379 let Some(breakpoint_store) = &self.breakpoint_store else {
10380 return;
10381 };
10382
10383 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10384 if breakpoint_position == Anchor::min() {
10385 self.buffer()
10386 .read(cx)
10387 .excerpt_buffer_ids()
10388 .into_iter()
10389 .next()
10390 } else {
10391 None
10392 }
10393 }) else {
10394 return;
10395 };
10396
10397 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10398 return;
10399 };
10400
10401 breakpoint_store.update(cx, |breakpoint_store, cx| {
10402 breakpoint_store.toggle_breakpoint(
10403 buffer,
10404 BreakpointWithPosition {
10405 position: breakpoint_position.text_anchor,
10406 bp: breakpoint,
10407 },
10408 edit_action,
10409 cx,
10410 );
10411 });
10412
10413 cx.notify();
10414 }
10415
10416 #[cfg(any(test, feature = "test-support"))]
10417 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10418 self.breakpoint_store.clone()
10419 }
10420
10421 pub fn prepare_restore_change(
10422 &self,
10423 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10424 hunk: &MultiBufferDiffHunk,
10425 cx: &mut App,
10426 ) -> Option<()> {
10427 if hunk.is_created_file() {
10428 return None;
10429 }
10430 let buffer = self.buffer.read(cx);
10431 let diff = buffer.diff_for(hunk.buffer_id)?;
10432 let buffer = buffer.buffer(hunk.buffer_id)?;
10433 let buffer = buffer.read(cx);
10434 let original_text = diff
10435 .read(cx)
10436 .base_text()
10437 .as_rope()
10438 .slice(hunk.diff_base_byte_range.clone());
10439 let buffer_snapshot = buffer.snapshot();
10440 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10441 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10442 probe
10443 .0
10444 .start
10445 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10446 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10447 }) {
10448 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10449 Some(())
10450 } else {
10451 None
10452 }
10453 }
10454
10455 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10456 self.manipulate_lines(window, cx, |lines| lines.reverse())
10457 }
10458
10459 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10460 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10461 }
10462
10463 fn manipulate_lines<Fn>(
10464 &mut self,
10465 window: &mut Window,
10466 cx: &mut Context<Self>,
10467 mut callback: Fn,
10468 ) where
10469 Fn: FnMut(&mut Vec<&str>),
10470 {
10471 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10472
10473 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10474 let buffer = self.buffer.read(cx).snapshot(cx);
10475
10476 let mut edits = Vec::new();
10477
10478 let selections = self.selections.all::<Point>(cx);
10479 let mut selections = selections.iter().peekable();
10480 let mut contiguous_row_selections = Vec::new();
10481 let mut new_selections = Vec::new();
10482 let mut added_lines = 0;
10483 let mut removed_lines = 0;
10484
10485 while let Some(selection) = selections.next() {
10486 let (start_row, end_row) = consume_contiguous_rows(
10487 &mut contiguous_row_selections,
10488 selection,
10489 &display_map,
10490 &mut selections,
10491 );
10492
10493 let start_point = Point::new(start_row.0, 0);
10494 let end_point = Point::new(
10495 end_row.previous_row().0,
10496 buffer.line_len(end_row.previous_row()),
10497 );
10498 let text = buffer
10499 .text_for_range(start_point..end_point)
10500 .collect::<String>();
10501
10502 let mut lines = text.split('\n').collect_vec();
10503
10504 let lines_before = lines.len();
10505 callback(&mut lines);
10506 let lines_after = lines.len();
10507
10508 edits.push((start_point..end_point, lines.join("\n")));
10509
10510 // Selections must change based on added and removed line count
10511 let start_row =
10512 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10513 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10514 new_selections.push(Selection {
10515 id: selection.id,
10516 start: start_row,
10517 end: end_row,
10518 goal: SelectionGoal::None,
10519 reversed: selection.reversed,
10520 });
10521
10522 if lines_after > lines_before {
10523 added_lines += lines_after - lines_before;
10524 } else if lines_before > lines_after {
10525 removed_lines += lines_before - lines_after;
10526 }
10527 }
10528
10529 self.transact(window, cx, |this, window, cx| {
10530 let buffer = this.buffer.update(cx, |buffer, cx| {
10531 buffer.edit(edits, None, cx);
10532 buffer.snapshot(cx)
10533 });
10534
10535 // Recalculate offsets on newly edited buffer
10536 let new_selections = new_selections
10537 .iter()
10538 .map(|s| {
10539 let start_point = Point::new(s.start.0, 0);
10540 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10541 Selection {
10542 id: s.id,
10543 start: buffer.point_to_offset(start_point),
10544 end: buffer.point_to_offset(end_point),
10545 goal: s.goal,
10546 reversed: s.reversed,
10547 }
10548 })
10549 .collect();
10550
10551 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10552 s.select(new_selections);
10553 });
10554
10555 this.request_autoscroll(Autoscroll::fit(), cx);
10556 });
10557 }
10558
10559 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10560 self.manipulate_text(window, cx, |text| {
10561 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10562 if has_upper_case_characters {
10563 text.to_lowercase()
10564 } else {
10565 text.to_uppercase()
10566 }
10567 })
10568 }
10569
10570 pub fn convert_to_upper_case(
10571 &mut self,
10572 _: &ConvertToUpperCase,
10573 window: &mut Window,
10574 cx: &mut Context<Self>,
10575 ) {
10576 self.manipulate_text(window, cx, |text| text.to_uppercase())
10577 }
10578
10579 pub fn convert_to_lower_case(
10580 &mut self,
10581 _: &ConvertToLowerCase,
10582 window: &mut Window,
10583 cx: &mut Context<Self>,
10584 ) {
10585 self.manipulate_text(window, cx, |text| text.to_lowercase())
10586 }
10587
10588 pub fn convert_to_title_case(
10589 &mut self,
10590 _: &ConvertToTitleCase,
10591 window: &mut Window,
10592 cx: &mut Context<Self>,
10593 ) {
10594 self.manipulate_text(window, cx, |text| {
10595 text.split('\n')
10596 .map(|line| line.to_case(Case::Title))
10597 .join("\n")
10598 })
10599 }
10600
10601 pub fn convert_to_snake_case(
10602 &mut self,
10603 _: &ConvertToSnakeCase,
10604 window: &mut Window,
10605 cx: &mut Context<Self>,
10606 ) {
10607 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10608 }
10609
10610 pub fn convert_to_kebab_case(
10611 &mut self,
10612 _: &ConvertToKebabCase,
10613 window: &mut Window,
10614 cx: &mut Context<Self>,
10615 ) {
10616 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10617 }
10618
10619 pub fn convert_to_upper_camel_case(
10620 &mut self,
10621 _: &ConvertToUpperCamelCase,
10622 window: &mut Window,
10623 cx: &mut Context<Self>,
10624 ) {
10625 self.manipulate_text(window, cx, |text| {
10626 text.split('\n')
10627 .map(|line| line.to_case(Case::UpperCamel))
10628 .join("\n")
10629 })
10630 }
10631
10632 pub fn convert_to_lower_camel_case(
10633 &mut self,
10634 _: &ConvertToLowerCamelCase,
10635 window: &mut Window,
10636 cx: &mut Context<Self>,
10637 ) {
10638 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10639 }
10640
10641 pub fn convert_to_opposite_case(
10642 &mut self,
10643 _: &ConvertToOppositeCase,
10644 window: &mut Window,
10645 cx: &mut Context<Self>,
10646 ) {
10647 self.manipulate_text(window, cx, |text| {
10648 text.chars()
10649 .fold(String::with_capacity(text.len()), |mut t, c| {
10650 if c.is_uppercase() {
10651 t.extend(c.to_lowercase());
10652 } else {
10653 t.extend(c.to_uppercase());
10654 }
10655 t
10656 })
10657 })
10658 }
10659
10660 pub fn convert_to_rot13(
10661 &mut self,
10662 _: &ConvertToRot13,
10663 window: &mut Window,
10664 cx: &mut Context<Self>,
10665 ) {
10666 self.manipulate_text(window, cx, |text| {
10667 text.chars()
10668 .map(|c| match c {
10669 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10670 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10671 _ => c,
10672 })
10673 .collect()
10674 })
10675 }
10676
10677 pub fn convert_to_rot47(
10678 &mut self,
10679 _: &ConvertToRot47,
10680 window: &mut Window,
10681 cx: &mut Context<Self>,
10682 ) {
10683 self.manipulate_text(window, cx, |text| {
10684 text.chars()
10685 .map(|c| {
10686 let code_point = c as u32;
10687 if code_point >= 33 && code_point <= 126 {
10688 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10689 }
10690 c
10691 })
10692 .collect()
10693 })
10694 }
10695
10696 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10697 where
10698 Fn: FnMut(&str) -> String,
10699 {
10700 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10701 let buffer = self.buffer.read(cx).snapshot(cx);
10702
10703 let mut new_selections = Vec::new();
10704 let mut edits = Vec::new();
10705 let mut selection_adjustment = 0i32;
10706
10707 for selection in self.selections.all::<usize>(cx) {
10708 let selection_is_empty = selection.is_empty();
10709
10710 let (start, end) = if selection_is_empty {
10711 let word_range = movement::surrounding_word(
10712 &display_map,
10713 selection.start.to_display_point(&display_map),
10714 );
10715 let start = word_range.start.to_offset(&display_map, Bias::Left);
10716 let end = word_range.end.to_offset(&display_map, Bias::Left);
10717 (start, end)
10718 } else {
10719 (selection.start, selection.end)
10720 };
10721
10722 let text = buffer.text_for_range(start..end).collect::<String>();
10723 let old_length = text.len() as i32;
10724 let text = callback(&text);
10725
10726 new_selections.push(Selection {
10727 start: (start as i32 - selection_adjustment) as usize,
10728 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10729 goal: SelectionGoal::None,
10730 ..selection
10731 });
10732
10733 selection_adjustment += old_length - text.len() as i32;
10734
10735 edits.push((start..end, text));
10736 }
10737
10738 self.transact(window, cx, |this, window, cx| {
10739 this.buffer.update(cx, |buffer, cx| {
10740 buffer.edit(edits, None, cx);
10741 });
10742
10743 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10744 s.select(new_selections);
10745 });
10746
10747 this.request_autoscroll(Autoscroll::fit(), cx);
10748 });
10749 }
10750
10751 pub fn move_selection_on_drop(
10752 &mut self,
10753 selection: &Selection<Anchor>,
10754 target: DisplayPoint,
10755 is_cut: bool,
10756 window: &mut Window,
10757 cx: &mut Context<Self>,
10758 ) {
10759 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10760 let buffer = &display_map.buffer_snapshot;
10761 let mut edits = Vec::new();
10762 let insert_point = display_map
10763 .clip_point(target, Bias::Left)
10764 .to_point(&display_map);
10765 let text = buffer
10766 .text_for_range(selection.start..selection.end)
10767 .collect::<String>();
10768 if is_cut {
10769 edits.push(((selection.start..selection.end), String::new()));
10770 }
10771 let insert_anchor = buffer.anchor_before(insert_point);
10772 edits.push(((insert_anchor..insert_anchor), text));
10773 let last_edit_start = insert_anchor.bias_left(buffer);
10774 let last_edit_end = insert_anchor.bias_right(buffer);
10775 self.transact(window, cx, |this, window, cx| {
10776 this.buffer.update(cx, |buffer, cx| {
10777 buffer.edit(edits, None, cx);
10778 });
10779 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10780 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10781 });
10782 });
10783 }
10784
10785 pub fn clear_selection_drag_state(&mut self) {
10786 self.selection_drag_state = SelectionDragState::None;
10787 }
10788
10789 pub fn duplicate(
10790 &mut self,
10791 upwards: bool,
10792 whole_lines: bool,
10793 window: &mut Window,
10794 cx: &mut Context<Self>,
10795 ) {
10796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10797
10798 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10799 let buffer = &display_map.buffer_snapshot;
10800 let selections = self.selections.all::<Point>(cx);
10801
10802 let mut edits = Vec::new();
10803 let mut selections_iter = selections.iter().peekable();
10804 while let Some(selection) = selections_iter.next() {
10805 let mut rows = selection.spanned_rows(false, &display_map);
10806 // duplicate line-wise
10807 if whole_lines || selection.start == selection.end {
10808 // Avoid duplicating the same lines twice.
10809 while let Some(next_selection) = selections_iter.peek() {
10810 let next_rows = next_selection.spanned_rows(false, &display_map);
10811 if next_rows.start < rows.end {
10812 rows.end = next_rows.end;
10813 selections_iter.next().unwrap();
10814 } else {
10815 break;
10816 }
10817 }
10818
10819 // Copy the text from the selected row region and splice it either at the start
10820 // or end of the region.
10821 let start = Point::new(rows.start.0, 0);
10822 let end = Point::new(
10823 rows.end.previous_row().0,
10824 buffer.line_len(rows.end.previous_row()),
10825 );
10826 let text = buffer
10827 .text_for_range(start..end)
10828 .chain(Some("\n"))
10829 .collect::<String>();
10830 let insert_location = if upwards {
10831 Point::new(rows.end.0, 0)
10832 } else {
10833 start
10834 };
10835 edits.push((insert_location..insert_location, text));
10836 } else {
10837 // duplicate character-wise
10838 let start = selection.start;
10839 let end = selection.end;
10840 let text = buffer.text_for_range(start..end).collect::<String>();
10841 edits.push((selection.end..selection.end, text));
10842 }
10843 }
10844
10845 self.transact(window, cx, |this, _, cx| {
10846 this.buffer.update(cx, |buffer, cx| {
10847 buffer.edit(edits, None, cx);
10848 });
10849
10850 this.request_autoscroll(Autoscroll::fit(), cx);
10851 });
10852 }
10853
10854 pub fn duplicate_line_up(
10855 &mut self,
10856 _: &DuplicateLineUp,
10857 window: &mut Window,
10858 cx: &mut Context<Self>,
10859 ) {
10860 self.duplicate(true, true, window, cx);
10861 }
10862
10863 pub fn duplicate_line_down(
10864 &mut self,
10865 _: &DuplicateLineDown,
10866 window: &mut Window,
10867 cx: &mut Context<Self>,
10868 ) {
10869 self.duplicate(false, true, window, cx);
10870 }
10871
10872 pub fn duplicate_selection(
10873 &mut self,
10874 _: &DuplicateSelection,
10875 window: &mut Window,
10876 cx: &mut Context<Self>,
10877 ) {
10878 self.duplicate(false, false, window, cx);
10879 }
10880
10881 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10882 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10883
10884 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10885 let buffer = self.buffer.read(cx).snapshot(cx);
10886
10887 let mut edits = Vec::new();
10888 let mut unfold_ranges = Vec::new();
10889 let mut refold_creases = Vec::new();
10890
10891 let selections = self.selections.all::<Point>(cx);
10892 let mut selections = selections.iter().peekable();
10893 let mut contiguous_row_selections = Vec::new();
10894 let mut new_selections = Vec::new();
10895
10896 while let Some(selection) = selections.next() {
10897 // Find all the selections that span a contiguous row range
10898 let (start_row, end_row) = consume_contiguous_rows(
10899 &mut contiguous_row_selections,
10900 selection,
10901 &display_map,
10902 &mut selections,
10903 );
10904
10905 // Move the text spanned by the row range to be before the line preceding the row range
10906 if start_row.0 > 0 {
10907 let range_to_move = Point::new(
10908 start_row.previous_row().0,
10909 buffer.line_len(start_row.previous_row()),
10910 )
10911 ..Point::new(
10912 end_row.previous_row().0,
10913 buffer.line_len(end_row.previous_row()),
10914 );
10915 let insertion_point = display_map
10916 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10917 .0;
10918
10919 // Don't move lines across excerpts
10920 if buffer
10921 .excerpt_containing(insertion_point..range_to_move.end)
10922 .is_some()
10923 {
10924 let text = buffer
10925 .text_for_range(range_to_move.clone())
10926 .flat_map(|s| s.chars())
10927 .skip(1)
10928 .chain(['\n'])
10929 .collect::<String>();
10930
10931 edits.push((
10932 buffer.anchor_after(range_to_move.start)
10933 ..buffer.anchor_before(range_to_move.end),
10934 String::new(),
10935 ));
10936 let insertion_anchor = buffer.anchor_after(insertion_point);
10937 edits.push((insertion_anchor..insertion_anchor, text));
10938
10939 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10940
10941 // Move selections up
10942 new_selections.extend(contiguous_row_selections.drain(..).map(
10943 |mut selection| {
10944 selection.start.row -= row_delta;
10945 selection.end.row -= row_delta;
10946 selection
10947 },
10948 ));
10949
10950 // Move folds up
10951 unfold_ranges.push(range_to_move.clone());
10952 for fold in display_map.folds_in_range(
10953 buffer.anchor_before(range_to_move.start)
10954 ..buffer.anchor_after(range_to_move.end),
10955 ) {
10956 let mut start = fold.range.start.to_point(&buffer);
10957 let mut end = fold.range.end.to_point(&buffer);
10958 start.row -= row_delta;
10959 end.row -= row_delta;
10960 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10961 }
10962 }
10963 }
10964
10965 // If we didn't move line(s), preserve the existing selections
10966 new_selections.append(&mut contiguous_row_selections);
10967 }
10968
10969 self.transact(window, cx, |this, window, cx| {
10970 this.unfold_ranges(&unfold_ranges, true, true, cx);
10971 this.buffer.update(cx, |buffer, cx| {
10972 for (range, text) in edits {
10973 buffer.edit([(range, text)], None, cx);
10974 }
10975 });
10976 this.fold_creases(refold_creases, true, window, cx);
10977 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10978 s.select(new_selections);
10979 })
10980 });
10981 }
10982
10983 pub fn move_line_down(
10984 &mut self,
10985 _: &MoveLineDown,
10986 window: &mut Window,
10987 cx: &mut Context<Self>,
10988 ) {
10989 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10990
10991 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10992 let buffer = self.buffer.read(cx).snapshot(cx);
10993
10994 let mut edits = Vec::new();
10995 let mut unfold_ranges = Vec::new();
10996 let mut refold_creases = Vec::new();
10997
10998 let selections = self.selections.all::<Point>(cx);
10999 let mut selections = selections.iter().peekable();
11000 let mut contiguous_row_selections = Vec::new();
11001 let mut new_selections = Vec::new();
11002
11003 while let Some(selection) = selections.next() {
11004 // Find all the selections that span a contiguous row range
11005 let (start_row, end_row) = consume_contiguous_rows(
11006 &mut contiguous_row_selections,
11007 selection,
11008 &display_map,
11009 &mut selections,
11010 );
11011
11012 // Move the text spanned by the row range to be after the last line of the row range
11013 if end_row.0 <= buffer.max_point().row {
11014 let range_to_move =
11015 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11016 let insertion_point = display_map
11017 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11018 .0;
11019
11020 // Don't move lines across excerpt boundaries
11021 if buffer
11022 .excerpt_containing(range_to_move.start..insertion_point)
11023 .is_some()
11024 {
11025 let mut text = String::from("\n");
11026 text.extend(buffer.text_for_range(range_to_move.clone()));
11027 text.pop(); // Drop trailing newline
11028 edits.push((
11029 buffer.anchor_after(range_to_move.start)
11030 ..buffer.anchor_before(range_to_move.end),
11031 String::new(),
11032 ));
11033 let insertion_anchor = buffer.anchor_after(insertion_point);
11034 edits.push((insertion_anchor..insertion_anchor, text));
11035
11036 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11037
11038 // Move selections down
11039 new_selections.extend(contiguous_row_selections.drain(..).map(
11040 |mut selection| {
11041 selection.start.row += row_delta;
11042 selection.end.row += row_delta;
11043 selection
11044 },
11045 ));
11046
11047 // Move folds down
11048 unfold_ranges.push(range_to_move.clone());
11049 for fold in display_map.folds_in_range(
11050 buffer.anchor_before(range_to_move.start)
11051 ..buffer.anchor_after(range_to_move.end),
11052 ) {
11053 let mut start = fold.range.start.to_point(&buffer);
11054 let mut end = fold.range.end.to_point(&buffer);
11055 start.row += row_delta;
11056 end.row += row_delta;
11057 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11058 }
11059 }
11060 }
11061
11062 // If we didn't move line(s), preserve the existing selections
11063 new_selections.append(&mut contiguous_row_selections);
11064 }
11065
11066 self.transact(window, cx, |this, window, cx| {
11067 this.unfold_ranges(&unfold_ranges, true, true, cx);
11068 this.buffer.update(cx, |buffer, cx| {
11069 for (range, text) in edits {
11070 buffer.edit([(range, text)], None, cx);
11071 }
11072 });
11073 this.fold_creases(refold_creases, true, window, cx);
11074 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11075 s.select(new_selections)
11076 });
11077 });
11078 }
11079
11080 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11082 let text_layout_details = &self.text_layout_details(window);
11083 self.transact(window, cx, |this, window, cx| {
11084 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11085 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11086 s.move_with(|display_map, selection| {
11087 if !selection.is_empty() {
11088 return;
11089 }
11090
11091 let mut head = selection.head();
11092 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11093 if head.column() == display_map.line_len(head.row()) {
11094 transpose_offset = display_map
11095 .buffer_snapshot
11096 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11097 }
11098
11099 if transpose_offset == 0 {
11100 return;
11101 }
11102
11103 *head.column_mut() += 1;
11104 head = display_map.clip_point(head, Bias::Right);
11105 let goal = SelectionGoal::HorizontalPosition(
11106 display_map
11107 .x_for_display_point(head, text_layout_details)
11108 .into(),
11109 );
11110 selection.collapse_to(head, goal);
11111
11112 let transpose_start = display_map
11113 .buffer_snapshot
11114 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11115 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11116 let transpose_end = display_map
11117 .buffer_snapshot
11118 .clip_offset(transpose_offset + 1, Bias::Right);
11119 if let Some(ch) =
11120 display_map.buffer_snapshot.chars_at(transpose_start).next()
11121 {
11122 edits.push((transpose_start..transpose_offset, String::new()));
11123 edits.push((transpose_end..transpose_end, ch.to_string()));
11124 }
11125 }
11126 });
11127 edits
11128 });
11129 this.buffer
11130 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11131 let selections = this.selections.all::<usize>(cx);
11132 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11133 s.select(selections);
11134 });
11135 });
11136 }
11137
11138 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11139 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11140 self.rewrap_impl(RewrapOptions::default(), cx)
11141 }
11142
11143 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11144 let buffer = self.buffer.read(cx).snapshot(cx);
11145 let selections = self.selections.all::<Point>(cx);
11146
11147 // Shrink and split selections to respect paragraph boundaries.
11148 let ranges = selections.into_iter().flat_map(|selection| {
11149 let language_settings = buffer.language_settings_at(selection.head(), cx);
11150 let language_scope = buffer.language_scope_at(selection.head());
11151
11152 let Some(start_row) = (selection.start.row..=selection.end.row)
11153 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11154 else {
11155 return vec![];
11156 };
11157 let Some(end_row) = (selection.start.row..=selection.end.row)
11158 .rev()
11159 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11160 else {
11161 return vec![];
11162 };
11163
11164 let mut row = start_row;
11165 let mut ranges = Vec::new();
11166 while let Some(blank_row) =
11167 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11168 {
11169 let next_paragraph_start = (blank_row + 1..=end_row)
11170 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11171 .unwrap();
11172 ranges.push((
11173 language_settings.clone(),
11174 language_scope.clone(),
11175 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11176 ));
11177 row = next_paragraph_start;
11178 }
11179 ranges.push((
11180 language_settings.clone(),
11181 language_scope.clone(),
11182 Point::new(row, 0)..Point::new(end_row, 0),
11183 ));
11184
11185 ranges
11186 });
11187
11188 let mut edits = Vec::new();
11189 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11190
11191 for (language_settings, language_scope, range) in ranges {
11192 let mut start_row = range.start.row;
11193 let mut end_row = range.end.row;
11194
11195 // Skip selections that overlap with a range that has already been rewrapped.
11196 let selection_range = start_row..end_row;
11197 if rewrapped_row_ranges
11198 .iter()
11199 .any(|range| range.overlaps(&selection_range))
11200 {
11201 continue;
11202 }
11203
11204 let tab_size = language_settings.tab_size;
11205
11206 // Since not all lines in the selection may be at the same indent
11207 // level, choose the indent size that is the most common between all
11208 // of the lines.
11209 //
11210 // If there is a tie, we use the deepest indent.
11211 let (indent_size, indent_end) = {
11212 let mut indent_size_occurrences = HashMap::default();
11213 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11214
11215 for row in start_row..=end_row {
11216 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11217 rows_by_indent_size.entry(indent).or_default().push(row);
11218 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11219 }
11220
11221 let indent_size = indent_size_occurrences
11222 .into_iter()
11223 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11224 .map(|(indent, _)| indent)
11225 .unwrap_or_default();
11226 let row = rows_by_indent_size[&indent_size][0];
11227 let indent_end = Point::new(row, indent_size.len);
11228
11229 (indent_size, indent_end)
11230 };
11231
11232 let mut line_prefix = indent_size.chars().collect::<String>();
11233
11234 let mut inside_comment = false;
11235 if let Some(comment_prefix) = language_scope.and_then(|language| {
11236 language
11237 .line_comment_prefixes()
11238 .iter()
11239 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11240 .cloned()
11241 }) {
11242 line_prefix.push_str(&comment_prefix);
11243 inside_comment = true;
11244 }
11245
11246 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11247 RewrapBehavior::InComments => inside_comment,
11248 RewrapBehavior::InSelections => !range.is_empty(),
11249 RewrapBehavior::Anywhere => true,
11250 };
11251
11252 let should_rewrap = options.override_language_settings
11253 || allow_rewrap_based_on_language
11254 || self.hard_wrap.is_some();
11255 if !should_rewrap {
11256 continue;
11257 }
11258
11259 if range.is_empty() {
11260 'expand_upwards: while start_row > 0 {
11261 let prev_row = start_row - 1;
11262 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11263 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11264 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11265 {
11266 start_row = prev_row;
11267 } else {
11268 break 'expand_upwards;
11269 }
11270 }
11271
11272 'expand_downwards: while end_row < buffer.max_point().row {
11273 let next_row = end_row + 1;
11274 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11275 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11276 && !buffer.is_line_blank(MultiBufferRow(next_row))
11277 {
11278 end_row = next_row;
11279 } else {
11280 break 'expand_downwards;
11281 }
11282 }
11283 }
11284
11285 let start = Point::new(start_row, 0);
11286 let start_offset = start.to_offset(&buffer);
11287 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11288 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11289 let Some(lines_without_prefixes) = selection_text
11290 .lines()
11291 .map(|line| {
11292 line.strip_prefix(&line_prefix)
11293 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11294 .with_context(|| {
11295 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11296 })
11297 })
11298 .collect::<Result<Vec<_>, _>>()
11299 .log_err()
11300 else {
11301 continue;
11302 };
11303
11304 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11305 buffer
11306 .language_settings_at(Point::new(start_row, 0), cx)
11307 .preferred_line_length as usize
11308 });
11309 let wrapped_text = wrap_with_prefix(
11310 line_prefix,
11311 lines_without_prefixes.join("\n"),
11312 wrap_column,
11313 tab_size,
11314 options.preserve_existing_whitespace,
11315 );
11316
11317 // TODO: should always use char-based diff while still supporting cursor behavior that
11318 // matches vim.
11319 let mut diff_options = DiffOptions::default();
11320 if options.override_language_settings {
11321 diff_options.max_word_diff_len = 0;
11322 diff_options.max_word_diff_line_count = 0;
11323 } else {
11324 diff_options.max_word_diff_len = usize::MAX;
11325 diff_options.max_word_diff_line_count = usize::MAX;
11326 }
11327
11328 for (old_range, new_text) in
11329 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11330 {
11331 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11332 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11333 edits.push((edit_start..edit_end, new_text));
11334 }
11335
11336 rewrapped_row_ranges.push(start_row..=end_row);
11337 }
11338
11339 self.buffer
11340 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11341 }
11342
11343 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11344 let mut text = String::new();
11345 let buffer = self.buffer.read(cx).snapshot(cx);
11346 let mut selections = self.selections.all::<Point>(cx);
11347 let mut clipboard_selections = Vec::with_capacity(selections.len());
11348 {
11349 let max_point = buffer.max_point();
11350 let mut is_first = true;
11351 for selection in &mut selections {
11352 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11353 if is_entire_line {
11354 selection.start = Point::new(selection.start.row, 0);
11355 if !selection.is_empty() && selection.end.column == 0 {
11356 selection.end = cmp::min(max_point, selection.end);
11357 } else {
11358 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11359 }
11360 selection.goal = SelectionGoal::None;
11361 }
11362 if is_first {
11363 is_first = false;
11364 } else {
11365 text += "\n";
11366 }
11367 let mut len = 0;
11368 for chunk in buffer.text_for_range(selection.start..selection.end) {
11369 text.push_str(chunk);
11370 len += chunk.len();
11371 }
11372 clipboard_selections.push(ClipboardSelection {
11373 len,
11374 is_entire_line,
11375 first_line_indent: buffer
11376 .indent_size_for_line(MultiBufferRow(selection.start.row))
11377 .len,
11378 });
11379 }
11380 }
11381
11382 self.transact(window, cx, |this, window, cx| {
11383 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11384 s.select(selections);
11385 });
11386 this.insert("", window, cx);
11387 });
11388 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11389 }
11390
11391 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11392 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11393 let item = self.cut_common(window, cx);
11394 cx.write_to_clipboard(item);
11395 }
11396
11397 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11399 self.change_selections(None, window, cx, |s| {
11400 s.move_with(|snapshot, sel| {
11401 if sel.is_empty() {
11402 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11403 }
11404 });
11405 });
11406 let item = self.cut_common(window, cx);
11407 cx.set_global(KillRing(item))
11408 }
11409
11410 pub fn kill_ring_yank(
11411 &mut self,
11412 _: &KillRingYank,
11413 window: &mut Window,
11414 cx: &mut Context<Self>,
11415 ) {
11416 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11417 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11418 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11419 (kill_ring.text().to_string(), kill_ring.metadata_json())
11420 } else {
11421 return;
11422 }
11423 } else {
11424 return;
11425 };
11426 self.do_paste(&text, metadata, false, window, cx);
11427 }
11428
11429 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11430 self.do_copy(true, cx);
11431 }
11432
11433 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11434 self.do_copy(false, cx);
11435 }
11436
11437 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11438 let selections = self.selections.all::<Point>(cx);
11439 let buffer = self.buffer.read(cx).read(cx);
11440 let mut text = String::new();
11441
11442 let mut clipboard_selections = Vec::with_capacity(selections.len());
11443 {
11444 let max_point = buffer.max_point();
11445 let mut is_first = true;
11446 for selection in &selections {
11447 let mut start = selection.start;
11448 let mut end = selection.end;
11449 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11450 if is_entire_line {
11451 start = Point::new(start.row, 0);
11452 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11453 }
11454
11455 let mut trimmed_selections = Vec::new();
11456 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11457 let row = MultiBufferRow(start.row);
11458 let first_indent = buffer.indent_size_for_line(row);
11459 if first_indent.len == 0 || start.column > first_indent.len {
11460 trimmed_selections.push(start..end);
11461 } else {
11462 trimmed_selections.push(
11463 Point::new(row.0, first_indent.len)
11464 ..Point::new(row.0, buffer.line_len(row)),
11465 );
11466 for row in start.row + 1..=end.row {
11467 let mut line_len = buffer.line_len(MultiBufferRow(row));
11468 if row == end.row {
11469 line_len = end.column;
11470 }
11471 if line_len == 0 {
11472 trimmed_selections
11473 .push(Point::new(row, 0)..Point::new(row, line_len));
11474 continue;
11475 }
11476 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11477 if row_indent_size.len >= first_indent.len {
11478 trimmed_selections.push(
11479 Point::new(row, first_indent.len)..Point::new(row, line_len),
11480 );
11481 } else {
11482 trimmed_selections.clear();
11483 trimmed_selections.push(start..end);
11484 break;
11485 }
11486 }
11487 }
11488 } else {
11489 trimmed_selections.push(start..end);
11490 }
11491
11492 for trimmed_range in trimmed_selections {
11493 if is_first {
11494 is_first = false;
11495 } else {
11496 text += "\n";
11497 }
11498 let mut len = 0;
11499 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11500 text.push_str(chunk);
11501 len += chunk.len();
11502 }
11503 clipboard_selections.push(ClipboardSelection {
11504 len,
11505 is_entire_line,
11506 first_line_indent: buffer
11507 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11508 .len,
11509 });
11510 }
11511 }
11512 }
11513
11514 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11515 text,
11516 clipboard_selections,
11517 ));
11518 }
11519
11520 pub fn do_paste(
11521 &mut self,
11522 text: &String,
11523 clipboard_selections: Option<Vec<ClipboardSelection>>,
11524 handle_entire_lines: bool,
11525 window: &mut Window,
11526 cx: &mut Context<Self>,
11527 ) {
11528 if self.read_only(cx) {
11529 return;
11530 }
11531
11532 let clipboard_text = Cow::Borrowed(text);
11533
11534 self.transact(window, cx, |this, window, cx| {
11535 if let Some(mut clipboard_selections) = clipboard_selections {
11536 let old_selections = this.selections.all::<usize>(cx);
11537 let all_selections_were_entire_line =
11538 clipboard_selections.iter().all(|s| s.is_entire_line);
11539 let first_selection_indent_column =
11540 clipboard_selections.first().map(|s| s.first_line_indent);
11541 if clipboard_selections.len() != old_selections.len() {
11542 clipboard_selections.drain(..);
11543 }
11544 let cursor_offset = this.selections.last::<usize>(cx).head();
11545 let mut auto_indent_on_paste = true;
11546
11547 this.buffer.update(cx, |buffer, cx| {
11548 let snapshot = buffer.read(cx);
11549 auto_indent_on_paste = snapshot
11550 .language_settings_at(cursor_offset, cx)
11551 .auto_indent_on_paste;
11552
11553 let mut start_offset = 0;
11554 let mut edits = Vec::new();
11555 let mut original_indent_columns = Vec::new();
11556 for (ix, selection) in old_selections.iter().enumerate() {
11557 let to_insert;
11558 let entire_line;
11559 let original_indent_column;
11560 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11561 let end_offset = start_offset + clipboard_selection.len;
11562 to_insert = &clipboard_text[start_offset..end_offset];
11563 entire_line = clipboard_selection.is_entire_line;
11564 start_offset = end_offset + 1;
11565 original_indent_column = Some(clipboard_selection.first_line_indent);
11566 } else {
11567 to_insert = clipboard_text.as_str();
11568 entire_line = all_selections_were_entire_line;
11569 original_indent_column = first_selection_indent_column
11570 }
11571
11572 // If the corresponding selection was empty when this slice of the
11573 // clipboard text was written, then the entire line containing the
11574 // selection was copied. If this selection is also currently empty,
11575 // then paste the line before the current line of the buffer.
11576 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11577 let column = selection.start.to_point(&snapshot).column as usize;
11578 let line_start = selection.start - column;
11579 line_start..line_start
11580 } else {
11581 selection.range()
11582 };
11583
11584 edits.push((range, to_insert));
11585 original_indent_columns.push(original_indent_column);
11586 }
11587 drop(snapshot);
11588
11589 buffer.edit(
11590 edits,
11591 if auto_indent_on_paste {
11592 Some(AutoindentMode::Block {
11593 original_indent_columns,
11594 })
11595 } else {
11596 None
11597 },
11598 cx,
11599 );
11600 });
11601
11602 let selections = this.selections.all::<usize>(cx);
11603 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11604 s.select(selections)
11605 });
11606 } else {
11607 this.insert(&clipboard_text, window, cx);
11608 }
11609 });
11610 }
11611
11612 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11613 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11614 if let Some(item) = cx.read_from_clipboard() {
11615 let entries = item.entries();
11616
11617 match entries.first() {
11618 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11619 // of all the pasted entries.
11620 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11621 .do_paste(
11622 clipboard_string.text(),
11623 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11624 true,
11625 window,
11626 cx,
11627 ),
11628 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11629 }
11630 }
11631 }
11632
11633 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11634 if self.read_only(cx) {
11635 return;
11636 }
11637
11638 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11639
11640 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11641 if let Some((selections, _)) =
11642 self.selection_history.transaction(transaction_id).cloned()
11643 {
11644 self.change_selections(None, window, cx, |s| {
11645 s.select_anchors(selections.to_vec());
11646 });
11647 } else {
11648 log::error!(
11649 "No entry in selection_history found for undo. \
11650 This may correspond to a bug where undo does not update the selection. \
11651 If this is occurring, please add details to \
11652 https://github.com/zed-industries/zed/issues/22692"
11653 );
11654 }
11655 self.request_autoscroll(Autoscroll::fit(), cx);
11656 self.unmark_text(window, cx);
11657 self.refresh_inline_completion(true, false, window, cx);
11658 cx.emit(EditorEvent::Edited { transaction_id });
11659 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11660 }
11661 }
11662
11663 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11664 if self.read_only(cx) {
11665 return;
11666 }
11667
11668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11669
11670 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11671 if let Some((_, Some(selections))) =
11672 self.selection_history.transaction(transaction_id).cloned()
11673 {
11674 self.change_selections(None, window, cx, |s| {
11675 s.select_anchors(selections.to_vec());
11676 });
11677 } else {
11678 log::error!(
11679 "No entry in selection_history found for redo. \
11680 This may correspond to a bug where undo does not update the selection. \
11681 If this is occurring, please add details to \
11682 https://github.com/zed-industries/zed/issues/22692"
11683 );
11684 }
11685 self.request_autoscroll(Autoscroll::fit(), cx);
11686 self.unmark_text(window, cx);
11687 self.refresh_inline_completion(true, false, window, cx);
11688 cx.emit(EditorEvent::Edited { transaction_id });
11689 }
11690 }
11691
11692 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11693 self.buffer
11694 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11695 }
11696
11697 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11698 self.buffer
11699 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11700 }
11701
11702 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11703 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11704 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11705 s.move_with(|map, selection| {
11706 let cursor = if selection.is_empty() {
11707 movement::left(map, selection.start)
11708 } else {
11709 selection.start
11710 };
11711 selection.collapse_to(cursor, SelectionGoal::None);
11712 });
11713 })
11714 }
11715
11716 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11717 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11718 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11719 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11720 })
11721 }
11722
11723 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11724 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11725 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11726 s.move_with(|map, selection| {
11727 let cursor = if selection.is_empty() {
11728 movement::right(map, selection.end)
11729 } else {
11730 selection.end
11731 };
11732 selection.collapse_to(cursor, SelectionGoal::None)
11733 });
11734 })
11735 }
11736
11737 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11738 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11739 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11740 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11741 })
11742 }
11743
11744 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11745 if self.take_rename(true, window, cx).is_some() {
11746 return;
11747 }
11748
11749 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11750 cx.propagate();
11751 return;
11752 }
11753
11754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11755
11756 let text_layout_details = &self.text_layout_details(window);
11757 let selection_count = self.selections.count();
11758 let first_selection = self.selections.first_anchor();
11759
11760 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11761 s.move_with(|map, selection| {
11762 if !selection.is_empty() {
11763 selection.goal = SelectionGoal::None;
11764 }
11765 let (cursor, goal) = movement::up(
11766 map,
11767 selection.start,
11768 selection.goal,
11769 false,
11770 text_layout_details,
11771 );
11772 selection.collapse_to(cursor, goal);
11773 });
11774 });
11775
11776 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11777 {
11778 cx.propagate();
11779 }
11780 }
11781
11782 pub fn move_up_by_lines(
11783 &mut self,
11784 action: &MoveUpByLines,
11785 window: &mut Window,
11786 cx: &mut Context<Self>,
11787 ) {
11788 if self.take_rename(true, window, cx).is_some() {
11789 return;
11790 }
11791
11792 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11793 cx.propagate();
11794 return;
11795 }
11796
11797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11798
11799 let text_layout_details = &self.text_layout_details(window);
11800
11801 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11802 s.move_with(|map, selection| {
11803 if !selection.is_empty() {
11804 selection.goal = SelectionGoal::None;
11805 }
11806 let (cursor, goal) = movement::up_by_rows(
11807 map,
11808 selection.start,
11809 action.lines,
11810 selection.goal,
11811 false,
11812 text_layout_details,
11813 );
11814 selection.collapse_to(cursor, goal);
11815 });
11816 })
11817 }
11818
11819 pub fn move_down_by_lines(
11820 &mut self,
11821 action: &MoveDownByLines,
11822 window: &mut Window,
11823 cx: &mut Context<Self>,
11824 ) {
11825 if self.take_rename(true, window, cx).is_some() {
11826 return;
11827 }
11828
11829 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11830 cx.propagate();
11831 return;
11832 }
11833
11834 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11835
11836 let text_layout_details = &self.text_layout_details(window);
11837
11838 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11839 s.move_with(|map, selection| {
11840 if !selection.is_empty() {
11841 selection.goal = SelectionGoal::None;
11842 }
11843 let (cursor, goal) = movement::down_by_rows(
11844 map,
11845 selection.start,
11846 action.lines,
11847 selection.goal,
11848 false,
11849 text_layout_details,
11850 );
11851 selection.collapse_to(cursor, goal);
11852 });
11853 })
11854 }
11855
11856 pub fn select_down_by_lines(
11857 &mut self,
11858 action: &SelectDownByLines,
11859 window: &mut Window,
11860 cx: &mut Context<Self>,
11861 ) {
11862 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11863 let text_layout_details = &self.text_layout_details(window);
11864 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11865 s.move_heads_with(|map, head, goal| {
11866 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11867 })
11868 })
11869 }
11870
11871 pub fn select_up_by_lines(
11872 &mut self,
11873 action: &SelectUpByLines,
11874 window: &mut Window,
11875 cx: &mut Context<Self>,
11876 ) {
11877 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11878 let text_layout_details = &self.text_layout_details(window);
11879 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11880 s.move_heads_with(|map, head, goal| {
11881 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11882 })
11883 })
11884 }
11885
11886 pub fn select_page_up(
11887 &mut self,
11888 _: &SelectPageUp,
11889 window: &mut Window,
11890 cx: &mut Context<Self>,
11891 ) {
11892 let Some(row_count) = self.visible_row_count() else {
11893 return;
11894 };
11895
11896 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11897
11898 let text_layout_details = &self.text_layout_details(window);
11899
11900 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11901 s.move_heads_with(|map, head, goal| {
11902 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11903 })
11904 })
11905 }
11906
11907 pub fn move_page_up(
11908 &mut self,
11909 action: &MovePageUp,
11910 window: &mut Window,
11911 cx: &mut Context<Self>,
11912 ) {
11913 if self.take_rename(true, window, cx).is_some() {
11914 return;
11915 }
11916
11917 if self
11918 .context_menu
11919 .borrow_mut()
11920 .as_mut()
11921 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11922 .unwrap_or(false)
11923 {
11924 return;
11925 }
11926
11927 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11928 cx.propagate();
11929 return;
11930 }
11931
11932 let Some(row_count) = self.visible_row_count() else {
11933 return;
11934 };
11935
11936 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11937
11938 let autoscroll = if action.center_cursor {
11939 Autoscroll::center()
11940 } else {
11941 Autoscroll::fit()
11942 };
11943
11944 let text_layout_details = &self.text_layout_details(window);
11945
11946 self.change_selections(Some(autoscroll), window, cx, |s| {
11947 s.move_with(|map, selection| {
11948 if !selection.is_empty() {
11949 selection.goal = SelectionGoal::None;
11950 }
11951 let (cursor, goal) = movement::up_by_rows(
11952 map,
11953 selection.end,
11954 row_count,
11955 selection.goal,
11956 false,
11957 text_layout_details,
11958 );
11959 selection.collapse_to(cursor, goal);
11960 });
11961 });
11962 }
11963
11964 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11965 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11966 let text_layout_details = &self.text_layout_details(window);
11967 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11968 s.move_heads_with(|map, head, goal| {
11969 movement::up(map, head, goal, false, text_layout_details)
11970 })
11971 })
11972 }
11973
11974 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11975 self.take_rename(true, window, cx);
11976
11977 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11978 cx.propagate();
11979 return;
11980 }
11981
11982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
11983
11984 let text_layout_details = &self.text_layout_details(window);
11985 let selection_count = self.selections.count();
11986 let first_selection = self.selections.first_anchor();
11987
11988 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11989 s.move_with(|map, selection| {
11990 if !selection.is_empty() {
11991 selection.goal = SelectionGoal::None;
11992 }
11993 let (cursor, goal) = movement::down(
11994 map,
11995 selection.end,
11996 selection.goal,
11997 false,
11998 text_layout_details,
11999 );
12000 selection.collapse_to(cursor, goal);
12001 });
12002 });
12003
12004 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12005 {
12006 cx.propagate();
12007 }
12008 }
12009
12010 pub fn select_page_down(
12011 &mut self,
12012 _: &SelectPageDown,
12013 window: &mut Window,
12014 cx: &mut Context<Self>,
12015 ) {
12016 let Some(row_count) = self.visible_row_count() else {
12017 return;
12018 };
12019
12020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12021
12022 let text_layout_details = &self.text_layout_details(window);
12023
12024 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12025 s.move_heads_with(|map, head, goal| {
12026 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
12027 })
12028 })
12029 }
12030
12031 pub fn move_page_down(
12032 &mut self,
12033 action: &MovePageDown,
12034 window: &mut Window,
12035 cx: &mut Context<Self>,
12036 ) {
12037 if self.take_rename(true, window, cx).is_some() {
12038 return;
12039 }
12040
12041 if self
12042 .context_menu
12043 .borrow_mut()
12044 .as_mut()
12045 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
12046 .unwrap_or(false)
12047 {
12048 return;
12049 }
12050
12051 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12052 cx.propagate();
12053 return;
12054 }
12055
12056 let Some(row_count) = self.visible_row_count() else {
12057 return;
12058 };
12059
12060 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12061
12062 let autoscroll = if action.center_cursor {
12063 Autoscroll::center()
12064 } else {
12065 Autoscroll::fit()
12066 };
12067
12068 let text_layout_details = &self.text_layout_details(window);
12069 self.change_selections(Some(autoscroll), window, cx, |s| {
12070 s.move_with(|map, selection| {
12071 if !selection.is_empty() {
12072 selection.goal = SelectionGoal::None;
12073 }
12074 let (cursor, goal) = movement::down_by_rows(
12075 map,
12076 selection.end,
12077 row_count,
12078 selection.goal,
12079 false,
12080 text_layout_details,
12081 );
12082 selection.collapse_to(cursor, goal);
12083 });
12084 });
12085 }
12086
12087 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
12088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12089 let text_layout_details = &self.text_layout_details(window);
12090 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12091 s.move_heads_with(|map, head, goal| {
12092 movement::down(map, head, goal, false, text_layout_details)
12093 })
12094 });
12095 }
12096
12097 pub fn context_menu_first(
12098 &mut self,
12099 _: &ContextMenuFirst,
12100 window: &mut Window,
12101 cx: &mut Context<Self>,
12102 ) {
12103 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12104 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12105 }
12106 }
12107
12108 pub fn context_menu_prev(
12109 &mut self,
12110 _: &ContextMenuPrevious,
12111 window: &mut Window,
12112 cx: &mut Context<Self>,
12113 ) {
12114 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12115 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12116 }
12117 }
12118
12119 pub fn context_menu_next(
12120 &mut self,
12121 _: &ContextMenuNext,
12122 window: &mut Window,
12123 cx: &mut Context<Self>,
12124 ) {
12125 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12126 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12127 }
12128 }
12129
12130 pub fn context_menu_last(
12131 &mut self,
12132 _: &ContextMenuLast,
12133 window: &mut Window,
12134 cx: &mut Context<Self>,
12135 ) {
12136 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12137 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12138 }
12139 }
12140
12141 pub fn move_to_previous_word_start(
12142 &mut self,
12143 _: &MoveToPreviousWordStart,
12144 window: &mut Window,
12145 cx: &mut Context<Self>,
12146 ) {
12147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12148 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12149 s.move_cursors_with(|map, head, _| {
12150 (
12151 movement::previous_word_start(map, head),
12152 SelectionGoal::None,
12153 )
12154 });
12155 })
12156 }
12157
12158 pub fn move_to_previous_subword_start(
12159 &mut self,
12160 _: &MoveToPreviousSubwordStart,
12161 window: &mut Window,
12162 cx: &mut Context<Self>,
12163 ) {
12164 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12165 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12166 s.move_cursors_with(|map, head, _| {
12167 (
12168 movement::previous_subword_start(map, head),
12169 SelectionGoal::None,
12170 )
12171 });
12172 })
12173 }
12174
12175 pub fn select_to_previous_word_start(
12176 &mut self,
12177 _: &SelectToPreviousWordStart,
12178 window: &mut Window,
12179 cx: &mut Context<Self>,
12180 ) {
12181 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12182 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12183 s.move_heads_with(|map, head, _| {
12184 (
12185 movement::previous_word_start(map, head),
12186 SelectionGoal::None,
12187 )
12188 });
12189 })
12190 }
12191
12192 pub fn select_to_previous_subword_start(
12193 &mut self,
12194 _: &SelectToPreviousSubwordStart,
12195 window: &mut Window,
12196 cx: &mut Context<Self>,
12197 ) {
12198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12199 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12200 s.move_heads_with(|map, head, _| {
12201 (
12202 movement::previous_subword_start(map, head),
12203 SelectionGoal::None,
12204 )
12205 });
12206 })
12207 }
12208
12209 pub fn delete_to_previous_word_start(
12210 &mut self,
12211 action: &DeleteToPreviousWordStart,
12212 window: &mut Window,
12213 cx: &mut Context<Self>,
12214 ) {
12215 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12216 self.transact(window, cx, |this, window, cx| {
12217 this.select_autoclose_pair(window, cx);
12218 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12219 s.move_with(|map, selection| {
12220 if selection.is_empty() {
12221 let cursor = if action.ignore_newlines {
12222 movement::previous_word_start(map, selection.head())
12223 } else {
12224 movement::previous_word_start_or_newline(map, selection.head())
12225 };
12226 selection.set_head(cursor, SelectionGoal::None);
12227 }
12228 });
12229 });
12230 this.insert("", window, cx);
12231 });
12232 }
12233
12234 pub fn delete_to_previous_subword_start(
12235 &mut self,
12236 _: &DeleteToPreviousSubwordStart,
12237 window: &mut Window,
12238 cx: &mut Context<Self>,
12239 ) {
12240 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12241 self.transact(window, cx, |this, window, cx| {
12242 this.select_autoclose_pair(window, cx);
12243 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12244 s.move_with(|map, selection| {
12245 if selection.is_empty() {
12246 let cursor = movement::previous_subword_start(map, selection.head());
12247 selection.set_head(cursor, SelectionGoal::None);
12248 }
12249 });
12250 });
12251 this.insert("", window, cx);
12252 });
12253 }
12254
12255 pub fn move_to_next_word_end(
12256 &mut self,
12257 _: &MoveToNextWordEnd,
12258 window: &mut Window,
12259 cx: &mut Context<Self>,
12260 ) {
12261 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12262 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12263 s.move_cursors_with(|map, head, _| {
12264 (movement::next_word_end(map, head), SelectionGoal::None)
12265 });
12266 })
12267 }
12268
12269 pub fn move_to_next_subword_end(
12270 &mut self,
12271 _: &MoveToNextSubwordEnd,
12272 window: &mut Window,
12273 cx: &mut Context<Self>,
12274 ) {
12275 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12276 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12277 s.move_cursors_with(|map, head, _| {
12278 (movement::next_subword_end(map, head), SelectionGoal::None)
12279 });
12280 })
12281 }
12282
12283 pub fn select_to_next_word_end(
12284 &mut self,
12285 _: &SelectToNextWordEnd,
12286 window: &mut Window,
12287 cx: &mut Context<Self>,
12288 ) {
12289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12290 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12291 s.move_heads_with(|map, head, _| {
12292 (movement::next_word_end(map, head), SelectionGoal::None)
12293 });
12294 })
12295 }
12296
12297 pub fn select_to_next_subword_end(
12298 &mut self,
12299 _: &SelectToNextSubwordEnd,
12300 window: &mut Window,
12301 cx: &mut Context<Self>,
12302 ) {
12303 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12304 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12305 s.move_heads_with(|map, head, _| {
12306 (movement::next_subword_end(map, head), SelectionGoal::None)
12307 });
12308 })
12309 }
12310
12311 pub fn delete_to_next_word_end(
12312 &mut self,
12313 action: &DeleteToNextWordEnd,
12314 window: &mut Window,
12315 cx: &mut Context<Self>,
12316 ) {
12317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12318 self.transact(window, cx, |this, window, cx| {
12319 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12320 s.move_with(|map, selection| {
12321 if selection.is_empty() {
12322 let cursor = if action.ignore_newlines {
12323 movement::next_word_end(map, selection.head())
12324 } else {
12325 movement::next_word_end_or_newline(map, selection.head())
12326 };
12327 selection.set_head(cursor, SelectionGoal::None);
12328 }
12329 });
12330 });
12331 this.insert("", window, cx);
12332 });
12333 }
12334
12335 pub fn delete_to_next_subword_end(
12336 &mut self,
12337 _: &DeleteToNextSubwordEnd,
12338 window: &mut Window,
12339 cx: &mut Context<Self>,
12340 ) {
12341 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12342 self.transact(window, cx, |this, window, cx| {
12343 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12344 s.move_with(|map, selection| {
12345 if selection.is_empty() {
12346 let cursor = movement::next_subword_end(map, selection.head());
12347 selection.set_head(cursor, SelectionGoal::None);
12348 }
12349 });
12350 });
12351 this.insert("", window, cx);
12352 });
12353 }
12354
12355 pub fn move_to_beginning_of_line(
12356 &mut self,
12357 action: &MoveToBeginningOfLine,
12358 window: &mut Window,
12359 cx: &mut Context<Self>,
12360 ) {
12361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12362 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12363 s.move_cursors_with(|map, head, _| {
12364 (
12365 movement::indented_line_beginning(
12366 map,
12367 head,
12368 action.stop_at_soft_wraps,
12369 action.stop_at_indent,
12370 ),
12371 SelectionGoal::None,
12372 )
12373 });
12374 })
12375 }
12376
12377 pub fn select_to_beginning_of_line(
12378 &mut self,
12379 action: &SelectToBeginningOfLine,
12380 window: &mut Window,
12381 cx: &mut Context<Self>,
12382 ) {
12383 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12384 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12385 s.move_heads_with(|map, head, _| {
12386 (
12387 movement::indented_line_beginning(
12388 map,
12389 head,
12390 action.stop_at_soft_wraps,
12391 action.stop_at_indent,
12392 ),
12393 SelectionGoal::None,
12394 )
12395 });
12396 });
12397 }
12398
12399 pub fn delete_to_beginning_of_line(
12400 &mut self,
12401 action: &DeleteToBeginningOfLine,
12402 window: &mut Window,
12403 cx: &mut Context<Self>,
12404 ) {
12405 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12406 self.transact(window, cx, |this, window, cx| {
12407 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12408 s.move_with(|_, selection| {
12409 selection.reversed = true;
12410 });
12411 });
12412
12413 this.select_to_beginning_of_line(
12414 &SelectToBeginningOfLine {
12415 stop_at_soft_wraps: false,
12416 stop_at_indent: action.stop_at_indent,
12417 },
12418 window,
12419 cx,
12420 );
12421 this.backspace(&Backspace, window, cx);
12422 });
12423 }
12424
12425 pub fn move_to_end_of_line(
12426 &mut self,
12427 action: &MoveToEndOfLine,
12428 window: &mut Window,
12429 cx: &mut Context<Self>,
12430 ) {
12431 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12432 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12433 s.move_cursors_with(|map, head, _| {
12434 (
12435 movement::line_end(map, head, action.stop_at_soft_wraps),
12436 SelectionGoal::None,
12437 )
12438 });
12439 })
12440 }
12441
12442 pub fn select_to_end_of_line(
12443 &mut self,
12444 action: &SelectToEndOfLine,
12445 window: &mut Window,
12446 cx: &mut Context<Self>,
12447 ) {
12448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12449 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12450 s.move_heads_with(|map, head, _| {
12451 (
12452 movement::line_end(map, head, action.stop_at_soft_wraps),
12453 SelectionGoal::None,
12454 )
12455 });
12456 })
12457 }
12458
12459 pub fn delete_to_end_of_line(
12460 &mut self,
12461 _: &DeleteToEndOfLine,
12462 window: &mut Window,
12463 cx: &mut Context<Self>,
12464 ) {
12465 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12466 self.transact(window, cx, |this, window, cx| {
12467 this.select_to_end_of_line(
12468 &SelectToEndOfLine {
12469 stop_at_soft_wraps: false,
12470 },
12471 window,
12472 cx,
12473 );
12474 this.delete(&Delete, window, cx);
12475 });
12476 }
12477
12478 pub fn cut_to_end_of_line(
12479 &mut self,
12480 _: &CutToEndOfLine,
12481 window: &mut Window,
12482 cx: &mut Context<Self>,
12483 ) {
12484 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12485 self.transact(window, cx, |this, window, cx| {
12486 this.select_to_end_of_line(
12487 &SelectToEndOfLine {
12488 stop_at_soft_wraps: false,
12489 },
12490 window,
12491 cx,
12492 );
12493 this.cut(&Cut, window, cx);
12494 });
12495 }
12496
12497 pub fn move_to_start_of_paragraph(
12498 &mut self,
12499 _: &MoveToStartOfParagraph,
12500 window: &mut Window,
12501 cx: &mut Context<Self>,
12502 ) {
12503 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12504 cx.propagate();
12505 return;
12506 }
12507 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12508 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12509 s.move_with(|map, selection| {
12510 selection.collapse_to(
12511 movement::start_of_paragraph(map, selection.head(), 1),
12512 SelectionGoal::None,
12513 )
12514 });
12515 })
12516 }
12517
12518 pub fn move_to_end_of_paragraph(
12519 &mut self,
12520 _: &MoveToEndOfParagraph,
12521 window: &mut Window,
12522 cx: &mut Context<Self>,
12523 ) {
12524 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12525 cx.propagate();
12526 return;
12527 }
12528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12529 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12530 s.move_with(|map, selection| {
12531 selection.collapse_to(
12532 movement::end_of_paragraph(map, selection.head(), 1),
12533 SelectionGoal::None,
12534 )
12535 });
12536 })
12537 }
12538
12539 pub fn select_to_start_of_paragraph(
12540 &mut self,
12541 _: &SelectToStartOfParagraph,
12542 window: &mut Window,
12543 cx: &mut Context<Self>,
12544 ) {
12545 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12546 cx.propagate();
12547 return;
12548 }
12549 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12550 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12551 s.move_heads_with(|map, head, _| {
12552 (
12553 movement::start_of_paragraph(map, head, 1),
12554 SelectionGoal::None,
12555 )
12556 });
12557 })
12558 }
12559
12560 pub fn select_to_end_of_paragraph(
12561 &mut self,
12562 _: &SelectToEndOfParagraph,
12563 window: &mut Window,
12564 cx: &mut Context<Self>,
12565 ) {
12566 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12567 cx.propagate();
12568 return;
12569 }
12570 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12571 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12572 s.move_heads_with(|map, head, _| {
12573 (
12574 movement::end_of_paragraph(map, head, 1),
12575 SelectionGoal::None,
12576 )
12577 });
12578 })
12579 }
12580
12581 pub fn move_to_start_of_excerpt(
12582 &mut self,
12583 _: &MoveToStartOfExcerpt,
12584 window: &mut Window,
12585 cx: &mut Context<Self>,
12586 ) {
12587 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12588 cx.propagate();
12589 return;
12590 }
12591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12592 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12593 s.move_with(|map, selection| {
12594 selection.collapse_to(
12595 movement::start_of_excerpt(
12596 map,
12597 selection.head(),
12598 workspace::searchable::Direction::Prev,
12599 ),
12600 SelectionGoal::None,
12601 )
12602 });
12603 })
12604 }
12605
12606 pub fn move_to_start_of_next_excerpt(
12607 &mut self,
12608 _: &MoveToStartOfNextExcerpt,
12609 window: &mut Window,
12610 cx: &mut Context<Self>,
12611 ) {
12612 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12613 cx.propagate();
12614 return;
12615 }
12616
12617 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12618 s.move_with(|map, selection| {
12619 selection.collapse_to(
12620 movement::start_of_excerpt(
12621 map,
12622 selection.head(),
12623 workspace::searchable::Direction::Next,
12624 ),
12625 SelectionGoal::None,
12626 )
12627 });
12628 })
12629 }
12630
12631 pub fn move_to_end_of_excerpt(
12632 &mut self,
12633 _: &MoveToEndOfExcerpt,
12634 window: &mut Window,
12635 cx: &mut Context<Self>,
12636 ) {
12637 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12638 cx.propagate();
12639 return;
12640 }
12641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12642 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12643 s.move_with(|map, selection| {
12644 selection.collapse_to(
12645 movement::end_of_excerpt(
12646 map,
12647 selection.head(),
12648 workspace::searchable::Direction::Next,
12649 ),
12650 SelectionGoal::None,
12651 )
12652 });
12653 })
12654 }
12655
12656 pub fn move_to_end_of_previous_excerpt(
12657 &mut self,
12658 _: &MoveToEndOfPreviousExcerpt,
12659 window: &mut Window,
12660 cx: &mut Context<Self>,
12661 ) {
12662 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12663 cx.propagate();
12664 return;
12665 }
12666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12667 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12668 s.move_with(|map, selection| {
12669 selection.collapse_to(
12670 movement::end_of_excerpt(
12671 map,
12672 selection.head(),
12673 workspace::searchable::Direction::Prev,
12674 ),
12675 SelectionGoal::None,
12676 )
12677 });
12678 })
12679 }
12680
12681 pub fn select_to_start_of_excerpt(
12682 &mut self,
12683 _: &SelectToStartOfExcerpt,
12684 window: &mut Window,
12685 cx: &mut Context<Self>,
12686 ) {
12687 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12688 cx.propagate();
12689 return;
12690 }
12691 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12692 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12693 s.move_heads_with(|map, head, _| {
12694 (
12695 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12696 SelectionGoal::None,
12697 )
12698 });
12699 })
12700 }
12701
12702 pub fn select_to_start_of_next_excerpt(
12703 &mut self,
12704 _: &SelectToStartOfNextExcerpt,
12705 window: &mut Window,
12706 cx: &mut Context<Self>,
12707 ) {
12708 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12709 cx.propagate();
12710 return;
12711 }
12712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12713 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12714 s.move_heads_with(|map, head, _| {
12715 (
12716 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12717 SelectionGoal::None,
12718 )
12719 });
12720 })
12721 }
12722
12723 pub fn select_to_end_of_excerpt(
12724 &mut self,
12725 _: &SelectToEndOfExcerpt,
12726 window: &mut Window,
12727 cx: &mut Context<Self>,
12728 ) {
12729 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12730 cx.propagate();
12731 return;
12732 }
12733 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12734 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12735 s.move_heads_with(|map, head, _| {
12736 (
12737 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12738 SelectionGoal::None,
12739 )
12740 });
12741 })
12742 }
12743
12744 pub fn select_to_end_of_previous_excerpt(
12745 &mut self,
12746 _: &SelectToEndOfPreviousExcerpt,
12747 window: &mut Window,
12748 cx: &mut Context<Self>,
12749 ) {
12750 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12751 cx.propagate();
12752 return;
12753 }
12754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12755 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12756 s.move_heads_with(|map, head, _| {
12757 (
12758 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12759 SelectionGoal::None,
12760 )
12761 });
12762 })
12763 }
12764
12765 pub fn move_to_beginning(
12766 &mut self,
12767 _: &MoveToBeginning,
12768 window: &mut Window,
12769 cx: &mut Context<Self>,
12770 ) {
12771 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12772 cx.propagate();
12773 return;
12774 }
12775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12776 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12777 s.select_ranges(vec![0..0]);
12778 });
12779 }
12780
12781 pub fn select_to_beginning(
12782 &mut self,
12783 _: &SelectToBeginning,
12784 window: &mut Window,
12785 cx: &mut Context<Self>,
12786 ) {
12787 let mut selection = self.selections.last::<Point>(cx);
12788 selection.set_head(Point::zero(), SelectionGoal::None);
12789 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12790 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12791 s.select(vec![selection]);
12792 });
12793 }
12794
12795 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12796 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12797 cx.propagate();
12798 return;
12799 }
12800 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12801 let cursor = self.buffer.read(cx).read(cx).len();
12802 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12803 s.select_ranges(vec![cursor..cursor])
12804 });
12805 }
12806
12807 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12808 self.nav_history = nav_history;
12809 }
12810
12811 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12812 self.nav_history.as_ref()
12813 }
12814
12815 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12816 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12817 }
12818
12819 fn push_to_nav_history(
12820 &mut self,
12821 cursor_anchor: Anchor,
12822 new_position: Option<Point>,
12823 is_deactivate: bool,
12824 cx: &mut Context<Self>,
12825 ) {
12826 if let Some(nav_history) = self.nav_history.as_mut() {
12827 let buffer = self.buffer.read(cx).read(cx);
12828 let cursor_position = cursor_anchor.to_point(&buffer);
12829 let scroll_state = self.scroll_manager.anchor();
12830 let scroll_top_row = scroll_state.top_row(&buffer);
12831 drop(buffer);
12832
12833 if let Some(new_position) = new_position {
12834 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12835 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12836 return;
12837 }
12838 }
12839
12840 nav_history.push(
12841 Some(NavigationData {
12842 cursor_anchor,
12843 cursor_position,
12844 scroll_anchor: scroll_state,
12845 scroll_top_row,
12846 }),
12847 cx,
12848 );
12849 cx.emit(EditorEvent::PushedToNavHistory {
12850 anchor: cursor_anchor,
12851 is_deactivate,
12852 })
12853 }
12854 }
12855
12856 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12858 let buffer = self.buffer.read(cx).snapshot(cx);
12859 let mut selection = self.selections.first::<usize>(cx);
12860 selection.set_head(buffer.len(), SelectionGoal::None);
12861 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12862 s.select(vec![selection]);
12863 });
12864 }
12865
12866 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12867 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12868 let end = self.buffer.read(cx).read(cx).len();
12869 self.change_selections(None, window, cx, |s| {
12870 s.select_ranges(vec![0..end]);
12871 });
12872 }
12873
12874 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12876 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12877 let mut selections = self.selections.all::<Point>(cx);
12878 let max_point = display_map.buffer_snapshot.max_point();
12879 for selection in &mut selections {
12880 let rows = selection.spanned_rows(true, &display_map);
12881 selection.start = Point::new(rows.start.0, 0);
12882 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12883 selection.reversed = false;
12884 }
12885 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12886 s.select(selections);
12887 });
12888 }
12889
12890 pub fn split_selection_into_lines(
12891 &mut self,
12892 _: &SplitSelectionIntoLines,
12893 window: &mut Window,
12894 cx: &mut Context<Self>,
12895 ) {
12896 let selections = self
12897 .selections
12898 .all::<Point>(cx)
12899 .into_iter()
12900 .map(|selection| selection.start..selection.end)
12901 .collect::<Vec<_>>();
12902 self.unfold_ranges(&selections, true, true, cx);
12903
12904 let mut new_selection_ranges = Vec::new();
12905 {
12906 let buffer = self.buffer.read(cx).read(cx);
12907 for selection in selections {
12908 for row in selection.start.row..selection.end.row {
12909 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12910 new_selection_ranges.push(cursor..cursor);
12911 }
12912
12913 let is_multiline_selection = selection.start.row != selection.end.row;
12914 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12915 // so this action feels more ergonomic when paired with other selection operations
12916 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12917 if !should_skip_last {
12918 new_selection_ranges.push(selection.end..selection.end);
12919 }
12920 }
12921 }
12922 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12923 s.select_ranges(new_selection_ranges);
12924 });
12925 }
12926
12927 pub fn add_selection_above(
12928 &mut self,
12929 _: &AddSelectionAbove,
12930 window: &mut Window,
12931 cx: &mut Context<Self>,
12932 ) {
12933 self.add_selection(true, window, cx);
12934 }
12935
12936 pub fn add_selection_below(
12937 &mut self,
12938 _: &AddSelectionBelow,
12939 window: &mut Window,
12940 cx: &mut Context<Self>,
12941 ) {
12942 self.add_selection(false, window, cx);
12943 }
12944
12945 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12947
12948 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12949 let all_selections = self.selections.all::<Point>(cx);
12950 let text_layout_details = self.text_layout_details(window);
12951
12952 let (mut columnar_selections, new_selections_to_columnarize) = {
12953 if let Some(state) = self.add_selections_state.as_ref() {
12954 let columnar_selection_ids: HashSet<_> = state
12955 .groups
12956 .iter()
12957 .flat_map(|group| group.stack.iter())
12958 .copied()
12959 .collect();
12960
12961 all_selections
12962 .into_iter()
12963 .partition(|s| columnar_selection_ids.contains(&s.id))
12964 } else {
12965 (Vec::new(), all_selections)
12966 }
12967 };
12968
12969 let mut state = self
12970 .add_selections_state
12971 .take()
12972 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12973
12974 for selection in new_selections_to_columnarize {
12975 let range = selection.display_range(&display_map).sorted();
12976 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12977 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12978 let positions = start_x.min(end_x)..start_x.max(end_x);
12979 let mut stack = Vec::new();
12980 for row in range.start.row().0..=range.end.row().0 {
12981 if let Some(selection) = self.selections.build_columnar_selection(
12982 &display_map,
12983 DisplayRow(row),
12984 &positions,
12985 selection.reversed,
12986 &text_layout_details,
12987 ) {
12988 stack.push(selection.id);
12989 columnar_selections.push(selection);
12990 }
12991 }
12992 if !stack.is_empty() {
12993 if above {
12994 stack.reverse();
12995 }
12996 state.groups.push(AddSelectionsGroup { above, stack });
12997 }
12998 }
12999
13000 let mut final_selections = Vec::new();
13001 let end_row = if above {
13002 DisplayRow(0)
13003 } else {
13004 display_map.max_point().row()
13005 };
13006
13007 let mut last_added_item_per_group = HashMap::default();
13008 for group in state.groups.iter_mut() {
13009 if let Some(last_id) = group.stack.last() {
13010 last_added_item_per_group.insert(*last_id, group);
13011 }
13012 }
13013
13014 for selection in columnar_selections {
13015 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
13016 if above == group.above {
13017 let range = selection.display_range(&display_map).sorted();
13018 debug_assert_eq!(range.start.row(), range.end.row());
13019 let mut row = range.start.row();
13020 let positions =
13021 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
13022 px(start)..px(end)
13023 } else {
13024 let start_x =
13025 display_map.x_for_display_point(range.start, &text_layout_details);
13026 let end_x =
13027 display_map.x_for_display_point(range.end, &text_layout_details);
13028 start_x.min(end_x)..start_x.max(end_x)
13029 };
13030
13031 let mut maybe_new_selection = None;
13032 while row != end_row {
13033 if above {
13034 row.0 -= 1;
13035 } else {
13036 row.0 += 1;
13037 }
13038 if let Some(new_selection) = self.selections.build_columnar_selection(
13039 &display_map,
13040 row,
13041 &positions,
13042 selection.reversed,
13043 &text_layout_details,
13044 ) {
13045 maybe_new_selection = Some(new_selection);
13046 break;
13047 }
13048 }
13049
13050 if let Some(new_selection) = maybe_new_selection {
13051 group.stack.push(new_selection.id);
13052 if above {
13053 final_selections.push(new_selection);
13054 final_selections.push(selection);
13055 } else {
13056 final_selections.push(selection);
13057 final_selections.push(new_selection);
13058 }
13059 } else {
13060 final_selections.push(selection);
13061 }
13062 } else {
13063 group.stack.pop();
13064 }
13065 } else {
13066 final_selections.push(selection);
13067 }
13068 }
13069
13070 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13071 s.select(final_selections);
13072 });
13073
13074 let final_selection_ids: HashSet<_> = self
13075 .selections
13076 .all::<Point>(cx)
13077 .iter()
13078 .map(|s| s.id)
13079 .collect();
13080 state.groups.retain_mut(|group| {
13081 // selections might get merged above so we remove invalid items from stacks
13082 group.stack.retain(|id| final_selection_ids.contains(id));
13083
13084 // single selection in stack can be treated as initial state
13085 group.stack.len() > 1
13086 });
13087
13088 if !state.groups.is_empty() {
13089 self.add_selections_state = Some(state);
13090 }
13091 }
13092
13093 fn select_match_ranges(
13094 &mut self,
13095 range: Range<usize>,
13096 reversed: bool,
13097 replace_newest: bool,
13098 auto_scroll: Option<Autoscroll>,
13099 window: &mut Window,
13100 cx: &mut Context<Editor>,
13101 ) {
13102 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13103 self.change_selections(auto_scroll, window, cx, |s| {
13104 if replace_newest {
13105 s.delete(s.newest_anchor().id);
13106 }
13107 if reversed {
13108 s.insert_range(range.end..range.start);
13109 } else {
13110 s.insert_range(range);
13111 }
13112 });
13113 }
13114
13115 pub fn select_next_match_internal(
13116 &mut self,
13117 display_map: &DisplaySnapshot,
13118 replace_newest: bool,
13119 autoscroll: Option<Autoscroll>,
13120 window: &mut Window,
13121 cx: &mut Context<Self>,
13122 ) -> Result<()> {
13123 let buffer = &display_map.buffer_snapshot;
13124 let mut selections = self.selections.all::<usize>(cx);
13125 if let Some(mut select_next_state) = self.select_next_state.take() {
13126 let query = &select_next_state.query;
13127 if !select_next_state.done {
13128 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13129 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13130 let mut next_selected_range = None;
13131
13132 let bytes_after_last_selection =
13133 buffer.bytes_in_range(last_selection.end..buffer.len());
13134 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13135 let query_matches = query
13136 .stream_find_iter(bytes_after_last_selection)
13137 .map(|result| (last_selection.end, result))
13138 .chain(
13139 query
13140 .stream_find_iter(bytes_before_first_selection)
13141 .map(|result| (0, result)),
13142 );
13143
13144 for (start_offset, query_match) in query_matches {
13145 let query_match = query_match.unwrap(); // can only fail due to I/O
13146 let offset_range =
13147 start_offset + query_match.start()..start_offset + query_match.end();
13148 let display_range = offset_range.start.to_display_point(display_map)
13149 ..offset_range.end.to_display_point(display_map);
13150
13151 if !select_next_state.wordwise
13152 || (!movement::is_inside_word(display_map, display_range.start)
13153 && !movement::is_inside_word(display_map, display_range.end))
13154 {
13155 // TODO: This is n^2, because we might check all the selections
13156 if !selections
13157 .iter()
13158 .any(|selection| selection.range().overlaps(&offset_range))
13159 {
13160 next_selected_range = Some(offset_range);
13161 break;
13162 }
13163 }
13164 }
13165
13166 if let Some(next_selected_range) = next_selected_range {
13167 self.select_match_ranges(
13168 next_selected_range,
13169 last_selection.reversed,
13170 replace_newest,
13171 autoscroll,
13172 window,
13173 cx,
13174 );
13175 } else {
13176 select_next_state.done = true;
13177 }
13178 }
13179
13180 self.select_next_state = Some(select_next_state);
13181 } else {
13182 let mut only_carets = true;
13183 let mut same_text_selected = true;
13184 let mut selected_text = None;
13185
13186 let mut selections_iter = selections.iter().peekable();
13187 while let Some(selection) = selections_iter.next() {
13188 if selection.start != selection.end {
13189 only_carets = false;
13190 }
13191
13192 if same_text_selected {
13193 if selected_text.is_none() {
13194 selected_text =
13195 Some(buffer.text_for_range(selection.range()).collect::<String>());
13196 }
13197
13198 if let Some(next_selection) = selections_iter.peek() {
13199 if next_selection.range().len() == selection.range().len() {
13200 let next_selected_text = buffer
13201 .text_for_range(next_selection.range())
13202 .collect::<String>();
13203 if Some(next_selected_text) != selected_text {
13204 same_text_selected = false;
13205 selected_text = None;
13206 }
13207 } else {
13208 same_text_selected = false;
13209 selected_text = None;
13210 }
13211 }
13212 }
13213 }
13214
13215 if only_carets {
13216 for selection in &mut selections {
13217 let word_range = movement::surrounding_word(
13218 display_map,
13219 selection.start.to_display_point(display_map),
13220 );
13221 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13222 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13223 selection.goal = SelectionGoal::None;
13224 selection.reversed = false;
13225 self.select_match_ranges(
13226 selection.start..selection.end,
13227 selection.reversed,
13228 replace_newest,
13229 autoscroll,
13230 window,
13231 cx,
13232 );
13233 }
13234
13235 if selections.len() == 1 {
13236 let selection = selections
13237 .last()
13238 .expect("ensured that there's only one selection");
13239 let query = buffer
13240 .text_for_range(selection.start..selection.end)
13241 .collect::<String>();
13242 let is_empty = query.is_empty();
13243 let select_state = SelectNextState {
13244 query: AhoCorasick::new(&[query])?,
13245 wordwise: true,
13246 done: is_empty,
13247 };
13248 self.select_next_state = Some(select_state);
13249 } else {
13250 self.select_next_state = None;
13251 }
13252 } else if let Some(selected_text) = selected_text {
13253 self.select_next_state = Some(SelectNextState {
13254 query: AhoCorasick::new(&[selected_text])?,
13255 wordwise: false,
13256 done: false,
13257 });
13258 self.select_next_match_internal(
13259 display_map,
13260 replace_newest,
13261 autoscroll,
13262 window,
13263 cx,
13264 )?;
13265 }
13266 }
13267 Ok(())
13268 }
13269
13270 pub fn select_all_matches(
13271 &mut self,
13272 _action: &SelectAllMatches,
13273 window: &mut Window,
13274 cx: &mut Context<Self>,
13275 ) -> Result<()> {
13276 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13277
13278 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13279
13280 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13281 let Some(select_next_state) = self.select_next_state.as_mut() else {
13282 return Ok(());
13283 };
13284 if select_next_state.done {
13285 return Ok(());
13286 }
13287
13288 let mut new_selections = Vec::new();
13289
13290 let reversed = self.selections.oldest::<usize>(cx).reversed;
13291 let buffer = &display_map.buffer_snapshot;
13292 let query_matches = select_next_state
13293 .query
13294 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13295
13296 for query_match in query_matches.into_iter() {
13297 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13298 let offset_range = if reversed {
13299 query_match.end()..query_match.start()
13300 } else {
13301 query_match.start()..query_match.end()
13302 };
13303 let display_range = offset_range.start.to_display_point(&display_map)
13304 ..offset_range.end.to_display_point(&display_map);
13305
13306 if !select_next_state.wordwise
13307 || (!movement::is_inside_word(&display_map, display_range.start)
13308 && !movement::is_inside_word(&display_map, display_range.end))
13309 {
13310 new_selections.push(offset_range.start..offset_range.end);
13311 }
13312 }
13313
13314 select_next_state.done = true;
13315 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13316 self.change_selections(None, window, cx, |selections| {
13317 selections.select_ranges(new_selections)
13318 });
13319
13320 Ok(())
13321 }
13322
13323 pub fn select_next(
13324 &mut self,
13325 action: &SelectNext,
13326 window: &mut Window,
13327 cx: &mut Context<Self>,
13328 ) -> Result<()> {
13329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13330 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13331 self.select_next_match_internal(
13332 &display_map,
13333 action.replace_newest,
13334 Some(Autoscroll::newest()),
13335 window,
13336 cx,
13337 )?;
13338 Ok(())
13339 }
13340
13341 pub fn select_previous(
13342 &mut self,
13343 action: &SelectPrevious,
13344 window: &mut Window,
13345 cx: &mut Context<Self>,
13346 ) -> Result<()> {
13347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13348 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13349 let buffer = &display_map.buffer_snapshot;
13350 let mut selections = self.selections.all::<usize>(cx);
13351 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13352 let query = &select_prev_state.query;
13353 if !select_prev_state.done {
13354 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13355 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13356 let mut next_selected_range = None;
13357 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13358 let bytes_before_last_selection =
13359 buffer.reversed_bytes_in_range(0..last_selection.start);
13360 let bytes_after_first_selection =
13361 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13362 let query_matches = query
13363 .stream_find_iter(bytes_before_last_selection)
13364 .map(|result| (last_selection.start, result))
13365 .chain(
13366 query
13367 .stream_find_iter(bytes_after_first_selection)
13368 .map(|result| (buffer.len(), result)),
13369 );
13370 for (end_offset, query_match) in query_matches {
13371 let query_match = query_match.unwrap(); // can only fail due to I/O
13372 let offset_range =
13373 end_offset - query_match.end()..end_offset - query_match.start();
13374 let display_range = offset_range.start.to_display_point(&display_map)
13375 ..offset_range.end.to_display_point(&display_map);
13376
13377 if !select_prev_state.wordwise
13378 || (!movement::is_inside_word(&display_map, display_range.start)
13379 && !movement::is_inside_word(&display_map, display_range.end))
13380 {
13381 next_selected_range = Some(offset_range);
13382 break;
13383 }
13384 }
13385
13386 if let Some(next_selected_range) = next_selected_range {
13387 self.select_match_ranges(
13388 next_selected_range,
13389 last_selection.reversed,
13390 action.replace_newest,
13391 Some(Autoscroll::newest()),
13392 window,
13393 cx,
13394 );
13395 } else {
13396 select_prev_state.done = true;
13397 }
13398 }
13399
13400 self.select_prev_state = Some(select_prev_state);
13401 } else {
13402 let mut only_carets = true;
13403 let mut same_text_selected = true;
13404 let mut selected_text = None;
13405
13406 let mut selections_iter = selections.iter().peekable();
13407 while let Some(selection) = selections_iter.next() {
13408 if selection.start != selection.end {
13409 only_carets = false;
13410 }
13411
13412 if same_text_selected {
13413 if selected_text.is_none() {
13414 selected_text =
13415 Some(buffer.text_for_range(selection.range()).collect::<String>());
13416 }
13417
13418 if let Some(next_selection) = selections_iter.peek() {
13419 if next_selection.range().len() == selection.range().len() {
13420 let next_selected_text = buffer
13421 .text_for_range(next_selection.range())
13422 .collect::<String>();
13423 if Some(next_selected_text) != selected_text {
13424 same_text_selected = false;
13425 selected_text = None;
13426 }
13427 } else {
13428 same_text_selected = false;
13429 selected_text = None;
13430 }
13431 }
13432 }
13433 }
13434
13435 if only_carets {
13436 for selection in &mut selections {
13437 let word_range = movement::surrounding_word(
13438 &display_map,
13439 selection.start.to_display_point(&display_map),
13440 );
13441 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13442 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13443 selection.goal = SelectionGoal::None;
13444 selection.reversed = false;
13445 self.select_match_ranges(
13446 selection.start..selection.end,
13447 selection.reversed,
13448 action.replace_newest,
13449 Some(Autoscroll::newest()),
13450 window,
13451 cx,
13452 );
13453 }
13454 if selections.len() == 1 {
13455 let selection = selections
13456 .last()
13457 .expect("ensured that there's only one selection");
13458 let query = buffer
13459 .text_for_range(selection.start..selection.end)
13460 .collect::<String>();
13461 let is_empty = query.is_empty();
13462 let select_state = SelectNextState {
13463 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13464 wordwise: true,
13465 done: is_empty,
13466 };
13467 self.select_prev_state = Some(select_state);
13468 } else {
13469 self.select_prev_state = None;
13470 }
13471 } else if let Some(selected_text) = selected_text {
13472 self.select_prev_state = Some(SelectNextState {
13473 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13474 wordwise: false,
13475 done: false,
13476 });
13477 self.select_previous(action, window, cx)?;
13478 }
13479 }
13480 Ok(())
13481 }
13482
13483 pub fn find_next_match(
13484 &mut self,
13485 _: &FindNextMatch,
13486 window: &mut Window,
13487 cx: &mut Context<Self>,
13488 ) -> Result<()> {
13489 let selections = self.selections.disjoint_anchors();
13490 match selections.first() {
13491 Some(first) if selections.len() >= 2 => {
13492 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13493 s.select_ranges([first.range()]);
13494 });
13495 }
13496 _ => self.select_next(
13497 &SelectNext {
13498 replace_newest: true,
13499 },
13500 window,
13501 cx,
13502 )?,
13503 }
13504 Ok(())
13505 }
13506
13507 pub fn find_previous_match(
13508 &mut self,
13509 _: &FindPreviousMatch,
13510 window: &mut Window,
13511 cx: &mut Context<Self>,
13512 ) -> Result<()> {
13513 let selections = self.selections.disjoint_anchors();
13514 match selections.last() {
13515 Some(last) if selections.len() >= 2 => {
13516 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13517 s.select_ranges([last.range()]);
13518 });
13519 }
13520 _ => self.select_previous(
13521 &SelectPrevious {
13522 replace_newest: true,
13523 },
13524 window,
13525 cx,
13526 )?,
13527 }
13528 Ok(())
13529 }
13530
13531 pub fn toggle_comments(
13532 &mut self,
13533 action: &ToggleComments,
13534 window: &mut Window,
13535 cx: &mut Context<Self>,
13536 ) {
13537 if self.read_only(cx) {
13538 return;
13539 }
13540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13541 let text_layout_details = &self.text_layout_details(window);
13542 self.transact(window, cx, |this, window, cx| {
13543 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13544 let mut edits = Vec::new();
13545 let mut selection_edit_ranges = Vec::new();
13546 let mut last_toggled_row = None;
13547 let snapshot = this.buffer.read(cx).read(cx);
13548 let empty_str: Arc<str> = Arc::default();
13549 let mut suffixes_inserted = Vec::new();
13550 let ignore_indent = action.ignore_indent;
13551
13552 fn comment_prefix_range(
13553 snapshot: &MultiBufferSnapshot,
13554 row: MultiBufferRow,
13555 comment_prefix: &str,
13556 comment_prefix_whitespace: &str,
13557 ignore_indent: bool,
13558 ) -> Range<Point> {
13559 let indent_size = if ignore_indent {
13560 0
13561 } else {
13562 snapshot.indent_size_for_line(row).len
13563 };
13564
13565 let start = Point::new(row.0, indent_size);
13566
13567 let mut line_bytes = snapshot
13568 .bytes_in_range(start..snapshot.max_point())
13569 .flatten()
13570 .copied();
13571
13572 // If this line currently begins with the line comment prefix, then record
13573 // the range containing the prefix.
13574 if line_bytes
13575 .by_ref()
13576 .take(comment_prefix.len())
13577 .eq(comment_prefix.bytes())
13578 {
13579 // Include any whitespace that matches the comment prefix.
13580 let matching_whitespace_len = line_bytes
13581 .zip(comment_prefix_whitespace.bytes())
13582 .take_while(|(a, b)| a == b)
13583 .count() as u32;
13584 let end = Point::new(
13585 start.row,
13586 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13587 );
13588 start..end
13589 } else {
13590 start..start
13591 }
13592 }
13593
13594 fn comment_suffix_range(
13595 snapshot: &MultiBufferSnapshot,
13596 row: MultiBufferRow,
13597 comment_suffix: &str,
13598 comment_suffix_has_leading_space: bool,
13599 ) -> Range<Point> {
13600 let end = Point::new(row.0, snapshot.line_len(row));
13601 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13602
13603 let mut line_end_bytes = snapshot
13604 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13605 .flatten()
13606 .copied();
13607
13608 let leading_space_len = if suffix_start_column > 0
13609 && line_end_bytes.next() == Some(b' ')
13610 && comment_suffix_has_leading_space
13611 {
13612 1
13613 } else {
13614 0
13615 };
13616
13617 // If this line currently begins with the line comment prefix, then record
13618 // the range containing the prefix.
13619 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13620 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13621 start..end
13622 } else {
13623 end..end
13624 }
13625 }
13626
13627 // TODO: Handle selections that cross excerpts
13628 for selection in &mut selections {
13629 let start_column = snapshot
13630 .indent_size_for_line(MultiBufferRow(selection.start.row))
13631 .len;
13632 let language = if let Some(language) =
13633 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13634 {
13635 language
13636 } else {
13637 continue;
13638 };
13639
13640 selection_edit_ranges.clear();
13641
13642 // If multiple selections contain a given row, avoid processing that
13643 // row more than once.
13644 let mut start_row = MultiBufferRow(selection.start.row);
13645 if last_toggled_row == Some(start_row) {
13646 start_row = start_row.next_row();
13647 }
13648 let end_row =
13649 if selection.end.row > selection.start.row && selection.end.column == 0 {
13650 MultiBufferRow(selection.end.row - 1)
13651 } else {
13652 MultiBufferRow(selection.end.row)
13653 };
13654 last_toggled_row = Some(end_row);
13655
13656 if start_row > end_row {
13657 continue;
13658 }
13659
13660 // If the language has line comments, toggle those.
13661 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13662
13663 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13664 if ignore_indent {
13665 full_comment_prefixes = full_comment_prefixes
13666 .into_iter()
13667 .map(|s| Arc::from(s.trim_end()))
13668 .collect();
13669 }
13670
13671 if !full_comment_prefixes.is_empty() {
13672 let first_prefix = full_comment_prefixes
13673 .first()
13674 .expect("prefixes is non-empty");
13675 let prefix_trimmed_lengths = full_comment_prefixes
13676 .iter()
13677 .map(|p| p.trim_end_matches(' ').len())
13678 .collect::<SmallVec<[usize; 4]>>();
13679
13680 let mut all_selection_lines_are_comments = true;
13681
13682 for row in start_row.0..=end_row.0 {
13683 let row = MultiBufferRow(row);
13684 if start_row < end_row && snapshot.is_line_blank(row) {
13685 continue;
13686 }
13687
13688 let prefix_range = full_comment_prefixes
13689 .iter()
13690 .zip(prefix_trimmed_lengths.iter().copied())
13691 .map(|(prefix, trimmed_prefix_len)| {
13692 comment_prefix_range(
13693 snapshot.deref(),
13694 row,
13695 &prefix[..trimmed_prefix_len],
13696 &prefix[trimmed_prefix_len..],
13697 ignore_indent,
13698 )
13699 })
13700 .max_by_key(|range| range.end.column - range.start.column)
13701 .expect("prefixes is non-empty");
13702
13703 if prefix_range.is_empty() {
13704 all_selection_lines_are_comments = false;
13705 }
13706
13707 selection_edit_ranges.push(prefix_range);
13708 }
13709
13710 if all_selection_lines_are_comments {
13711 edits.extend(
13712 selection_edit_ranges
13713 .iter()
13714 .cloned()
13715 .map(|range| (range, empty_str.clone())),
13716 );
13717 } else {
13718 let min_column = selection_edit_ranges
13719 .iter()
13720 .map(|range| range.start.column)
13721 .min()
13722 .unwrap_or(0);
13723 edits.extend(selection_edit_ranges.iter().map(|range| {
13724 let position = Point::new(range.start.row, min_column);
13725 (position..position, first_prefix.clone())
13726 }));
13727 }
13728 } else if let Some((full_comment_prefix, comment_suffix)) =
13729 language.block_comment_delimiters()
13730 {
13731 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13732 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13733 let prefix_range = comment_prefix_range(
13734 snapshot.deref(),
13735 start_row,
13736 comment_prefix,
13737 comment_prefix_whitespace,
13738 ignore_indent,
13739 );
13740 let suffix_range = comment_suffix_range(
13741 snapshot.deref(),
13742 end_row,
13743 comment_suffix.trim_start_matches(' '),
13744 comment_suffix.starts_with(' '),
13745 );
13746
13747 if prefix_range.is_empty() || suffix_range.is_empty() {
13748 edits.push((
13749 prefix_range.start..prefix_range.start,
13750 full_comment_prefix.clone(),
13751 ));
13752 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13753 suffixes_inserted.push((end_row, comment_suffix.len()));
13754 } else {
13755 edits.push((prefix_range, empty_str.clone()));
13756 edits.push((suffix_range, empty_str.clone()));
13757 }
13758 } else {
13759 continue;
13760 }
13761 }
13762
13763 drop(snapshot);
13764 this.buffer.update(cx, |buffer, cx| {
13765 buffer.edit(edits, None, cx);
13766 });
13767
13768 // Adjust selections so that they end before any comment suffixes that
13769 // were inserted.
13770 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13771 let mut selections = this.selections.all::<Point>(cx);
13772 let snapshot = this.buffer.read(cx).read(cx);
13773 for selection in &mut selections {
13774 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13775 match row.cmp(&MultiBufferRow(selection.end.row)) {
13776 Ordering::Less => {
13777 suffixes_inserted.next();
13778 continue;
13779 }
13780 Ordering::Greater => break,
13781 Ordering::Equal => {
13782 if selection.end.column == snapshot.line_len(row) {
13783 if selection.is_empty() {
13784 selection.start.column -= suffix_len as u32;
13785 }
13786 selection.end.column -= suffix_len as u32;
13787 }
13788 break;
13789 }
13790 }
13791 }
13792 }
13793
13794 drop(snapshot);
13795 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13796 s.select(selections)
13797 });
13798
13799 let selections = this.selections.all::<Point>(cx);
13800 let selections_on_single_row = selections.windows(2).all(|selections| {
13801 selections[0].start.row == selections[1].start.row
13802 && selections[0].end.row == selections[1].end.row
13803 && selections[0].start.row == selections[0].end.row
13804 });
13805 let selections_selecting = selections
13806 .iter()
13807 .any(|selection| selection.start != selection.end);
13808 let advance_downwards = action.advance_downwards
13809 && selections_on_single_row
13810 && !selections_selecting
13811 && !matches!(this.mode, EditorMode::SingleLine { .. });
13812
13813 if advance_downwards {
13814 let snapshot = this.buffer.read(cx).snapshot(cx);
13815
13816 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13817 s.move_cursors_with(|display_snapshot, display_point, _| {
13818 let mut point = display_point.to_point(display_snapshot);
13819 point.row += 1;
13820 point = snapshot.clip_point(point, Bias::Left);
13821 let display_point = point.to_display_point(display_snapshot);
13822 let goal = SelectionGoal::HorizontalPosition(
13823 display_snapshot
13824 .x_for_display_point(display_point, text_layout_details)
13825 .into(),
13826 );
13827 (display_point, goal)
13828 })
13829 });
13830 }
13831 });
13832 }
13833
13834 pub fn select_enclosing_symbol(
13835 &mut self,
13836 _: &SelectEnclosingSymbol,
13837 window: &mut Window,
13838 cx: &mut Context<Self>,
13839 ) {
13840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13841
13842 let buffer = self.buffer.read(cx).snapshot(cx);
13843 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13844
13845 fn update_selection(
13846 selection: &Selection<usize>,
13847 buffer_snap: &MultiBufferSnapshot,
13848 ) -> Option<Selection<usize>> {
13849 let cursor = selection.head();
13850 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13851 for symbol in symbols.iter().rev() {
13852 let start = symbol.range.start.to_offset(buffer_snap);
13853 let end = symbol.range.end.to_offset(buffer_snap);
13854 let new_range = start..end;
13855 if start < selection.start || end > selection.end {
13856 return Some(Selection {
13857 id: selection.id,
13858 start: new_range.start,
13859 end: new_range.end,
13860 goal: SelectionGoal::None,
13861 reversed: selection.reversed,
13862 });
13863 }
13864 }
13865 None
13866 }
13867
13868 let mut selected_larger_symbol = false;
13869 let new_selections = old_selections
13870 .iter()
13871 .map(|selection| match update_selection(selection, &buffer) {
13872 Some(new_selection) => {
13873 if new_selection.range() != selection.range() {
13874 selected_larger_symbol = true;
13875 }
13876 new_selection
13877 }
13878 None => selection.clone(),
13879 })
13880 .collect::<Vec<_>>();
13881
13882 if selected_larger_symbol {
13883 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13884 s.select(new_selections);
13885 });
13886 }
13887 }
13888
13889 pub fn select_larger_syntax_node(
13890 &mut self,
13891 _: &SelectLargerSyntaxNode,
13892 window: &mut Window,
13893 cx: &mut Context<Self>,
13894 ) {
13895 let Some(visible_row_count) = self.visible_row_count() else {
13896 return;
13897 };
13898 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13899 if old_selections.is_empty() {
13900 return;
13901 }
13902
13903 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13904
13905 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13906 let buffer = self.buffer.read(cx).snapshot(cx);
13907
13908 let mut selected_larger_node = false;
13909 let mut new_selections = old_selections
13910 .iter()
13911 .map(|selection| {
13912 let old_range = selection.start..selection.end;
13913
13914 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13915 // manually select word at selection
13916 if ["string_content", "inline"].contains(&node.kind()) {
13917 let word_range = {
13918 let display_point = buffer
13919 .offset_to_point(old_range.start)
13920 .to_display_point(&display_map);
13921 let Range { start, end } =
13922 movement::surrounding_word(&display_map, display_point);
13923 start.to_point(&display_map).to_offset(&buffer)
13924 ..end.to_point(&display_map).to_offset(&buffer)
13925 };
13926 // ignore if word is already selected
13927 if !word_range.is_empty() && old_range != word_range {
13928 let last_word_range = {
13929 let display_point = buffer
13930 .offset_to_point(old_range.end)
13931 .to_display_point(&display_map);
13932 let Range { start, end } =
13933 movement::surrounding_word(&display_map, display_point);
13934 start.to_point(&display_map).to_offset(&buffer)
13935 ..end.to_point(&display_map).to_offset(&buffer)
13936 };
13937 // only select word if start and end point belongs to same word
13938 if word_range == last_word_range {
13939 selected_larger_node = true;
13940 return Selection {
13941 id: selection.id,
13942 start: word_range.start,
13943 end: word_range.end,
13944 goal: SelectionGoal::None,
13945 reversed: selection.reversed,
13946 };
13947 }
13948 }
13949 }
13950 }
13951
13952 let mut new_range = old_range.clone();
13953 while let Some((_node, containing_range)) =
13954 buffer.syntax_ancestor(new_range.clone())
13955 {
13956 new_range = match containing_range {
13957 MultiOrSingleBufferOffsetRange::Single(_) => break,
13958 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13959 };
13960 if !display_map.intersects_fold(new_range.start)
13961 && !display_map.intersects_fold(new_range.end)
13962 {
13963 break;
13964 }
13965 }
13966
13967 selected_larger_node |= new_range != old_range;
13968 Selection {
13969 id: selection.id,
13970 start: new_range.start,
13971 end: new_range.end,
13972 goal: SelectionGoal::None,
13973 reversed: selection.reversed,
13974 }
13975 })
13976 .collect::<Vec<_>>();
13977
13978 if !selected_larger_node {
13979 return; // don't put this call in the history
13980 }
13981
13982 // scroll based on transformation done to the last selection created by the user
13983 let (last_old, last_new) = old_selections
13984 .last()
13985 .zip(new_selections.last().cloned())
13986 .expect("old_selections isn't empty");
13987
13988 // revert selection
13989 let is_selection_reversed = {
13990 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13991 new_selections.last_mut().expect("checked above").reversed =
13992 should_newest_selection_be_reversed;
13993 should_newest_selection_be_reversed
13994 };
13995
13996 if selected_larger_node {
13997 self.select_syntax_node_history.disable_clearing = true;
13998 self.change_selections(None, window, cx, |s| {
13999 s.select(new_selections.clone());
14000 });
14001 self.select_syntax_node_history.disable_clearing = false;
14002 }
14003
14004 let start_row = last_new.start.to_display_point(&display_map).row().0;
14005 let end_row = last_new.end.to_display_point(&display_map).row().0;
14006 let selection_height = end_row - start_row + 1;
14007 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
14008
14009 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
14010 let scroll_behavior = if fits_on_the_screen {
14011 self.request_autoscroll(Autoscroll::fit(), cx);
14012 SelectSyntaxNodeScrollBehavior::FitSelection
14013 } else if is_selection_reversed {
14014 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14015 SelectSyntaxNodeScrollBehavior::CursorTop
14016 } else {
14017 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14018 SelectSyntaxNodeScrollBehavior::CursorBottom
14019 };
14020
14021 self.select_syntax_node_history.push((
14022 old_selections,
14023 scroll_behavior,
14024 is_selection_reversed,
14025 ));
14026 }
14027
14028 pub fn select_smaller_syntax_node(
14029 &mut self,
14030 _: &SelectSmallerSyntaxNode,
14031 window: &mut Window,
14032 cx: &mut Context<Self>,
14033 ) {
14034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14035
14036 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
14037 self.select_syntax_node_history.pop()
14038 {
14039 if let Some(selection) = selections.last_mut() {
14040 selection.reversed = is_selection_reversed;
14041 }
14042
14043 self.select_syntax_node_history.disable_clearing = true;
14044 self.change_selections(None, window, cx, |s| {
14045 s.select(selections.to_vec());
14046 });
14047 self.select_syntax_node_history.disable_clearing = false;
14048
14049 match scroll_behavior {
14050 SelectSyntaxNodeScrollBehavior::CursorTop => {
14051 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
14052 }
14053 SelectSyntaxNodeScrollBehavior::FitSelection => {
14054 self.request_autoscroll(Autoscroll::fit(), cx);
14055 }
14056 SelectSyntaxNodeScrollBehavior::CursorBottom => {
14057 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
14058 }
14059 }
14060 }
14061 }
14062
14063 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
14064 if !EditorSettings::get_global(cx).gutter.runnables {
14065 self.clear_tasks();
14066 return Task::ready(());
14067 }
14068 let project = self.project.as_ref().map(Entity::downgrade);
14069 let task_sources = self.lsp_task_sources(cx);
14070 let multi_buffer = self.buffer.downgrade();
14071 cx.spawn_in(window, async move |editor, cx| {
14072 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
14073 let Some(project) = project.and_then(|p| p.upgrade()) else {
14074 return;
14075 };
14076 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
14077 this.display_map.update(cx, |map, cx| map.snapshot(cx))
14078 }) else {
14079 return;
14080 };
14081
14082 let hide_runnables = project
14083 .update(cx, |project, cx| {
14084 // Do not display any test indicators in non-dev server remote projects.
14085 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
14086 })
14087 .unwrap_or(true);
14088 if hide_runnables {
14089 return;
14090 }
14091 let new_rows =
14092 cx.background_spawn({
14093 let snapshot = display_snapshot.clone();
14094 async move {
14095 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
14096 }
14097 })
14098 .await;
14099 let Ok(lsp_tasks) =
14100 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
14101 else {
14102 return;
14103 };
14104 let lsp_tasks = lsp_tasks.await;
14105
14106 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14107 lsp_tasks
14108 .into_iter()
14109 .flat_map(|(kind, tasks)| {
14110 tasks.into_iter().filter_map(move |(location, task)| {
14111 Some((kind.clone(), location?, task))
14112 })
14113 })
14114 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14115 let buffer = location.target.buffer;
14116 let buffer_snapshot = buffer.read(cx).snapshot();
14117 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14118 |(excerpt_id, snapshot, _)| {
14119 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14120 display_snapshot
14121 .buffer_snapshot
14122 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14123 } else {
14124 None
14125 }
14126 },
14127 );
14128 if let Some(offset) = offset {
14129 let task_buffer_range =
14130 location.target.range.to_point(&buffer_snapshot);
14131 let context_buffer_range =
14132 task_buffer_range.to_offset(&buffer_snapshot);
14133 let context_range = BufferOffset(context_buffer_range.start)
14134 ..BufferOffset(context_buffer_range.end);
14135
14136 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14137 .or_insert_with(|| RunnableTasks {
14138 templates: Vec::new(),
14139 offset,
14140 column: task_buffer_range.start.column,
14141 extra_variables: HashMap::default(),
14142 context_range,
14143 })
14144 .templates
14145 .push((kind, task.original_task().clone()));
14146 }
14147
14148 acc
14149 })
14150 }) else {
14151 return;
14152 };
14153
14154 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14155 buffer.language_settings(cx).tasks.prefer_lsp
14156 }) else {
14157 return;
14158 };
14159
14160 let rows = Self::runnable_rows(
14161 project,
14162 display_snapshot,
14163 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14164 new_rows,
14165 cx.clone(),
14166 )
14167 .await;
14168 editor
14169 .update(cx, |editor, _| {
14170 editor.clear_tasks();
14171 for (key, mut value) in rows {
14172 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14173 value.templates.extend(lsp_tasks.templates);
14174 }
14175
14176 editor.insert_tasks(key, value);
14177 }
14178 for (key, value) in lsp_tasks_by_rows {
14179 editor.insert_tasks(key, value);
14180 }
14181 })
14182 .ok();
14183 })
14184 }
14185 fn fetch_runnable_ranges(
14186 snapshot: &DisplaySnapshot,
14187 range: Range<Anchor>,
14188 ) -> Vec<language::RunnableRange> {
14189 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14190 }
14191
14192 fn runnable_rows(
14193 project: Entity<Project>,
14194 snapshot: DisplaySnapshot,
14195 prefer_lsp: bool,
14196 runnable_ranges: Vec<RunnableRange>,
14197 cx: AsyncWindowContext,
14198 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14199 cx.spawn(async move |cx| {
14200 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14201 for mut runnable in runnable_ranges {
14202 let Some(tasks) = cx
14203 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14204 .ok()
14205 else {
14206 continue;
14207 };
14208 let mut tasks = tasks.await;
14209
14210 if prefer_lsp {
14211 tasks.retain(|(task_kind, _)| {
14212 !matches!(task_kind, TaskSourceKind::Language { .. })
14213 });
14214 }
14215 if tasks.is_empty() {
14216 continue;
14217 }
14218
14219 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14220 let Some(row) = snapshot
14221 .buffer_snapshot
14222 .buffer_line_for_row(MultiBufferRow(point.row))
14223 .map(|(_, range)| range.start.row)
14224 else {
14225 continue;
14226 };
14227
14228 let context_range =
14229 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14230 runnable_rows.push((
14231 (runnable.buffer_id, row),
14232 RunnableTasks {
14233 templates: tasks,
14234 offset: snapshot
14235 .buffer_snapshot
14236 .anchor_before(runnable.run_range.start),
14237 context_range,
14238 column: point.column,
14239 extra_variables: runnable.extra_captures,
14240 },
14241 ));
14242 }
14243 runnable_rows
14244 })
14245 }
14246
14247 fn templates_with_tags(
14248 project: &Entity<Project>,
14249 runnable: &mut Runnable,
14250 cx: &mut App,
14251 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14252 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14253 let (worktree_id, file) = project
14254 .buffer_for_id(runnable.buffer, cx)
14255 .and_then(|buffer| buffer.read(cx).file())
14256 .map(|file| (file.worktree_id(cx), file.clone()))
14257 .unzip();
14258
14259 (
14260 project.task_store().read(cx).task_inventory().cloned(),
14261 worktree_id,
14262 file,
14263 )
14264 });
14265
14266 let tags = mem::take(&mut runnable.tags);
14267 let language = runnable.language.clone();
14268 cx.spawn(async move |cx| {
14269 let mut templates_with_tags = Vec::new();
14270 if let Some(inventory) = inventory {
14271 for RunnableTag(tag) in tags {
14272 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14273 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14274 }) else {
14275 return templates_with_tags;
14276 };
14277 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14278 move |(_, template)| {
14279 template.tags.iter().any(|source_tag| source_tag == &tag)
14280 },
14281 ));
14282 }
14283 }
14284 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14285
14286 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14287 // Strongest source wins; if we have worktree tag binding, prefer that to
14288 // global and language bindings;
14289 // if we have a global binding, prefer that to language binding.
14290 let first_mismatch = templates_with_tags
14291 .iter()
14292 .position(|(tag_source, _)| tag_source != leading_tag_source);
14293 if let Some(index) = first_mismatch {
14294 templates_with_tags.truncate(index);
14295 }
14296 }
14297
14298 templates_with_tags
14299 })
14300 }
14301
14302 pub fn move_to_enclosing_bracket(
14303 &mut self,
14304 _: &MoveToEnclosingBracket,
14305 window: &mut Window,
14306 cx: &mut Context<Self>,
14307 ) {
14308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14309 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14310 s.move_offsets_with(|snapshot, selection| {
14311 let Some(enclosing_bracket_ranges) =
14312 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14313 else {
14314 return;
14315 };
14316
14317 let mut best_length = usize::MAX;
14318 let mut best_inside = false;
14319 let mut best_in_bracket_range = false;
14320 let mut best_destination = None;
14321 for (open, close) in enclosing_bracket_ranges {
14322 let close = close.to_inclusive();
14323 let length = close.end() - open.start;
14324 let inside = selection.start >= open.end && selection.end <= *close.start();
14325 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14326 || close.contains(&selection.head());
14327
14328 // If best is next to a bracket and current isn't, skip
14329 if !in_bracket_range && best_in_bracket_range {
14330 continue;
14331 }
14332
14333 // Prefer smaller lengths unless best is inside and current isn't
14334 if length > best_length && (best_inside || !inside) {
14335 continue;
14336 }
14337
14338 best_length = length;
14339 best_inside = inside;
14340 best_in_bracket_range = in_bracket_range;
14341 best_destination = Some(
14342 if close.contains(&selection.start) && close.contains(&selection.end) {
14343 if inside { open.end } else { open.start }
14344 } else if inside {
14345 *close.start()
14346 } else {
14347 *close.end()
14348 },
14349 );
14350 }
14351
14352 if let Some(destination) = best_destination {
14353 selection.collapse_to(destination, SelectionGoal::None);
14354 }
14355 })
14356 });
14357 }
14358
14359 pub fn undo_selection(
14360 &mut self,
14361 _: &UndoSelection,
14362 window: &mut Window,
14363 cx: &mut Context<Self>,
14364 ) {
14365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14366 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14367 self.selection_history.mode = SelectionHistoryMode::Undoing;
14368 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14369 this.end_selection(window, cx);
14370 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14371 s.select_anchors(entry.selections.to_vec())
14372 });
14373 });
14374 self.selection_history.mode = SelectionHistoryMode::Normal;
14375
14376 self.select_next_state = entry.select_next_state;
14377 self.select_prev_state = entry.select_prev_state;
14378 self.add_selections_state = entry.add_selections_state;
14379 }
14380 }
14381
14382 pub fn redo_selection(
14383 &mut self,
14384 _: &RedoSelection,
14385 window: &mut Window,
14386 cx: &mut Context<Self>,
14387 ) {
14388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14389 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14390 self.selection_history.mode = SelectionHistoryMode::Redoing;
14391 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14392 this.end_selection(window, cx);
14393 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14394 s.select_anchors(entry.selections.to_vec())
14395 });
14396 });
14397 self.selection_history.mode = SelectionHistoryMode::Normal;
14398
14399 self.select_next_state = entry.select_next_state;
14400 self.select_prev_state = entry.select_prev_state;
14401 self.add_selections_state = entry.add_selections_state;
14402 }
14403 }
14404
14405 pub fn expand_excerpts(
14406 &mut self,
14407 action: &ExpandExcerpts,
14408 _: &mut Window,
14409 cx: &mut Context<Self>,
14410 ) {
14411 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14412 }
14413
14414 pub fn expand_excerpts_down(
14415 &mut self,
14416 action: &ExpandExcerptsDown,
14417 _: &mut Window,
14418 cx: &mut Context<Self>,
14419 ) {
14420 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14421 }
14422
14423 pub fn expand_excerpts_up(
14424 &mut self,
14425 action: &ExpandExcerptsUp,
14426 _: &mut Window,
14427 cx: &mut Context<Self>,
14428 ) {
14429 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14430 }
14431
14432 pub fn expand_excerpts_for_direction(
14433 &mut self,
14434 lines: u32,
14435 direction: ExpandExcerptDirection,
14436
14437 cx: &mut Context<Self>,
14438 ) {
14439 let selections = self.selections.disjoint_anchors();
14440
14441 let lines = if lines == 0 {
14442 EditorSettings::get_global(cx).expand_excerpt_lines
14443 } else {
14444 lines
14445 };
14446
14447 self.buffer.update(cx, |buffer, cx| {
14448 let snapshot = buffer.snapshot(cx);
14449 let mut excerpt_ids = selections
14450 .iter()
14451 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14452 .collect::<Vec<_>>();
14453 excerpt_ids.sort();
14454 excerpt_ids.dedup();
14455 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14456 })
14457 }
14458
14459 pub fn expand_excerpt(
14460 &mut self,
14461 excerpt: ExcerptId,
14462 direction: ExpandExcerptDirection,
14463 window: &mut Window,
14464 cx: &mut Context<Self>,
14465 ) {
14466 let current_scroll_position = self.scroll_position(cx);
14467 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14468 let mut should_scroll_up = false;
14469
14470 if direction == ExpandExcerptDirection::Down {
14471 let multi_buffer = self.buffer.read(cx);
14472 let snapshot = multi_buffer.snapshot(cx);
14473 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14474 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14475 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14476 let buffer_snapshot = buffer.read(cx).snapshot();
14477 let excerpt_end_row =
14478 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14479 let last_row = buffer_snapshot.max_point().row;
14480 let lines_below = last_row.saturating_sub(excerpt_end_row);
14481 should_scroll_up = lines_below >= lines_to_expand;
14482 }
14483 }
14484 }
14485 }
14486
14487 self.buffer.update(cx, |buffer, cx| {
14488 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14489 });
14490
14491 if should_scroll_up {
14492 let new_scroll_position =
14493 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14494 self.set_scroll_position(new_scroll_position, window, cx);
14495 }
14496 }
14497
14498 pub fn go_to_singleton_buffer_point(
14499 &mut self,
14500 point: Point,
14501 window: &mut Window,
14502 cx: &mut Context<Self>,
14503 ) {
14504 self.go_to_singleton_buffer_range(point..point, window, cx);
14505 }
14506
14507 pub fn go_to_singleton_buffer_range(
14508 &mut self,
14509 range: Range<Point>,
14510 window: &mut Window,
14511 cx: &mut Context<Self>,
14512 ) {
14513 let multibuffer = self.buffer().read(cx);
14514 let Some(buffer) = multibuffer.as_singleton() else {
14515 return;
14516 };
14517 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14518 return;
14519 };
14520 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14521 return;
14522 };
14523 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14524 s.select_anchor_ranges([start..end])
14525 });
14526 }
14527
14528 pub fn go_to_diagnostic(
14529 &mut self,
14530 _: &GoToDiagnostic,
14531 window: &mut Window,
14532 cx: &mut Context<Self>,
14533 ) {
14534 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14535 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14536 }
14537
14538 pub fn go_to_prev_diagnostic(
14539 &mut self,
14540 _: &GoToPreviousDiagnostic,
14541 window: &mut Window,
14542 cx: &mut Context<Self>,
14543 ) {
14544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14545 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14546 }
14547
14548 pub fn go_to_diagnostic_impl(
14549 &mut self,
14550 direction: Direction,
14551 window: &mut Window,
14552 cx: &mut Context<Self>,
14553 ) {
14554 let buffer = self.buffer.read(cx).snapshot(cx);
14555 let selection = self.selections.newest::<usize>(cx);
14556
14557 let mut active_group_id = None;
14558 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14559 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14560 active_group_id = Some(active_group.group_id);
14561 }
14562 }
14563
14564 fn filtered(
14565 snapshot: EditorSnapshot,
14566 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14567 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14568 diagnostics
14569 .filter(|entry| entry.range.start != entry.range.end)
14570 .filter(|entry| !entry.diagnostic.is_unnecessary)
14571 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14572 }
14573
14574 let snapshot = self.snapshot(window, cx);
14575 let before = filtered(
14576 snapshot.clone(),
14577 buffer
14578 .diagnostics_in_range(0..selection.start)
14579 .filter(|entry| entry.range.start <= selection.start),
14580 );
14581 let after = filtered(
14582 snapshot,
14583 buffer
14584 .diagnostics_in_range(selection.start..buffer.len())
14585 .filter(|entry| entry.range.start >= selection.start),
14586 );
14587
14588 let mut found: Option<DiagnosticEntry<usize>> = None;
14589 if direction == Direction::Prev {
14590 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14591 {
14592 for diagnostic in prev_diagnostics.into_iter().rev() {
14593 if diagnostic.range.start != selection.start
14594 || active_group_id
14595 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14596 {
14597 found = Some(diagnostic);
14598 break 'outer;
14599 }
14600 }
14601 }
14602 } else {
14603 for diagnostic in after.chain(before) {
14604 if diagnostic.range.start != selection.start
14605 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14606 {
14607 found = Some(diagnostic);
14608 break;
14609 }
14610 }
14611 }
14612 let Some(next_diagnostic) = found else {
14613 return;
14614 };
14615
14616 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14617 return;
14618 };
14619 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14620 s.select_ranges(vec![
14621 next_diagnostic.range.start..next_diagnostic.range.start,
14622 ])
14623 });
14624 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14625 self.refresh_inline_completion(false, true, window, cx);
14626 }
14627
14628 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14630 let snapshot = self.snapshot(window, cx);
14631 let selection = self.selections.newest::<Point>(cx);
14632 self.go_to_hunk_before_or_after_position(
14633 &snapshot,
14634 selection.head(),
14635 Direction::Next,
14636 window,
14637 cx,
14638 );
14639 }
14640
14641 pub fn go_to_hunk_before_or_after_position(
14642 &mut self,
14643 snapshot: &EditorSnapshot,
14644 position: Point,
14645 direction: Direction,
14646 window: &mut Window,
14647 cx: &mut Context<Editor>,
14648 ) {
14649 let row = if direction == Direction::Next {
14650 self.hunk_after_position(snapshot, position)
14651 .map(|hunk| hunk.row_range.start)
14652 } else {
14653 self.hunk_before_position(snapshot, position)
14654 };
14655
14656 if let Some(row) = row {
14657 let destination = Point::new(row.0, 0);
14658 let autoscroll = Autoscroll::center();
14659
14660 self.unfold_ranges(&[destination..destination], false, false, cx);
14661 self.change_selections(Some(autoscroll), window, cx, |s| {
14662 s.select_ranges([destination..destination]);
14663 });
14664 }
14665 }
14666
14667 fn hunk_after_position(
14668 &mut self,
14669 snapshot: &EditorSnapshot,
14670 position: Point,
14671 ) -> Option<MultiBufferDiffHunk> {
14672 snapshot
14673 .buffer_snapshot
14674 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14675 .find(|hunk| hunk.row_range.start.0 > position.row)
14676 .or_else(|| {
14677 snapshot
14678 .buffer_snapshot
14679 .diff_hunks_in_range(Point::zero()..position)
14680 .find(|hunk| hunk.row_range.end.0 < position.row)
14681 })
14682 }
14683
14684 fn go_to_prev_hunk(
14685 &mut self,
14686 _: &GoToPreviousHunk,
14687 window: &mut Window,
14688 cx: &mut Context<Self>,
14689 ) {
14690 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14691 let snapshot = self.snapshot(window, cx);
14692 let selection = self.selections.newest::<Point>(cx);
14693 self.go_to_hunk_before_or_after_position(
14694 &snapshot,
14695 selection.head(),
14696 Direction::Prev,
14697 window,
14698 cx,
14699 );
14700 }
14701
14702 fn hunk_before_position(
14703 &mut self,
14704 snapshot: &EditorSnapshot,
14705 position: Point,
14706 ) -> Option<MultiBufferRow> {
14707 snapshot
14708 .buffer_snapshot
14709 .diff_hunk_before(position)
14710 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14711 }
14712
14713 fn go_to_next_change(
14714 &mut self,
14715 _: &GoToNextChange,
14716 window: &mut Window,
14717 cx: &mut Context<Self>,
14718 ) {
14719 if let Some(selections) = self
14720 .change_list
14721 .next_change(1, Direction::Next)
14722 .map(|s| s.to_vec())
14723 {
14724 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14725 let map = s.display_map();
14726 s.select_display_ranges(selections.iter().map(|a| {
14727 let point = a.to_display_point(&map);
14728 point..point
14729 }))
14730 })
14731 }
14732 }
14733
14734 fn go_to_previous_change(
14735 &mut self,
14736 _: &GoToPreviousChange,
14737 window: &mut Window,
14738 cx: &mut Context<Self>,
14739 ) {
14740 if let Some(selections) = self
14741 .change_list
14742 .next_change(1, Direction::Prev)
14743 .map(|s| s.to_vec())
14744 {
14745 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14746 let map = s.display_map();
14747 s.select_display_ranges(selections.iter().map(|a| {
14748 let point = a.to_display_point(&map);
14749 point..point
14750 }))
14751 })
14752 }
14753 }
14754
14755 fn go_to_line<T: 'static>(
14756 &mut self,
14757 position: Anchor,
14758 highlight_color: Option<Hsla>,
14759 window: &mut Window,
14760 cx: &mut Context<Self>,
14761 ) {
14762 let snapshot = self.snapshot(window, cx).display_snapshot;
14763 let position = position.to_point(&snapshot.buffer_snapshot);
14764 let start = snapshot
14765 .buffer_snapshot
14766 .clip_point(Point::new(position.row, 0), Bias::Left);
14767 let end = start + Point::new(1, 0);
14768 let start = snapshot.buffer_snapshot.anchor_before(start);
14769 let end = snapshot.buffer_snapshot.anchor_before(end);
14770
14771 self.highlight_rows::<T>(
14772 start..end,
14773 highlight_color
14774 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14775 Default::default(),
14776 cx,
14777 );
14778
14779 if self.buffer.read(cx).is_singleton() {
14780 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14781 }
14782 }
14783
14784 pub fn go_to_definition(
14785 &mut self,
14786 _: &GoToDefinition,
14787 window: &mut Window,
14788 cx: &mut Context<Self>,
14789 ) -> Task<Result<Navigated>> {
14790 let definition =
14791 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14792 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14793 cx.spawn_in(window, async move |editor, cx| {
14794 if definition.await? == Navigated::Yes {
14795 return Ok(Navigated::Yes);
14796 }
14797 match fallback_strategy {
14798 GoToDefinitionFallback::None => Ok(Navigated::No),
14799 GoToDefinitionFallback::FindAllReferences => {
14800 match editor.update_in(cx, |editor, window, cx| {
14801 editor.find_all_references(&FindAllReferences, window, cx)
14802 })? {
14803 Some(references) => references.await,
14804 None => Ok(Navigated::No),
14805 }
14806 }
14807 }
14808 })
14809 }
14810
14811 pub fn go_to_declaration(
14812 &mut self,
14813 _: &GoToDeclaration,
14814 window: &mut Window,
14815 cx: &mut Context<Self>,
14816 ) -> Task<Result<Navigated>> {
14817 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14818 }
14819
14820 pub fn go_to_declaration_split(
14821 &mut self,
14822 _: &GoToDeclaration,
14823 window: &mut Window,
14824 cx: &mut Context<Self>,
14825 ) -> Task<Result<Navigated>> {
14826 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14827 }
14828
14829 pub fn go_to_implementation(
14830 &mut self,
14831 _: &GoToImplementation,
14832 window: &mut Window,
14833 cx: &mut Context<Self>,
14834 ) -> Task<Result<Navigated>> {
14835 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14836 }
14837
14838 pub fn go_to_implementation_split(
14839 &mut self,
14840 _: &GoToImplementationSplit,
14841 window: &mut Window,
14842 cx: &mut Context<Self>,
14843 ) -> Task<Result<Navigated>> {
14844 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14845 }
14846
14847 pub fn go_to_type_definition(
14848 &mut self,
14849 _: &GoToTypeDefinition,
14850 window: &mut Window,
14851 cx: &mut Context<Self>,
14852 ) -> Task<Result<Navigated>> {
14853 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14854 }
14855
14856 pub fn go_to_definition_split(
14857 &mut self,
14858 _: &GoToDefinitionSplit,
14859 window: &mut Window,
14860 cx: &mut Context<Self>,
14861 ) -> Task<Result<Navigated>> {
14862 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14863 }
14864
14865 pub fn go_to_type_definition_split(
14866 &mut self,
14867 _: &GoToTypeDefinitionSplit,
14868 window: &mut Window,
14869 cx: &mut Context<Self>,
14870 ) -> Task<Result<Navigated>> {
14871 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14872 }
14873
14874 fn go_to_definition_of_kind(
14875 &mut self,
14876 kind: GotoDefinitionKind,
14877 split: bool,
14878 window: &mut Window,
14879 cx: &mut Context<Self>,
14880 ) -> Task<Result<Navigated>> {
14881 let Some(provider) = self.semantics_provider.clone() else {
14882 return Task::ready(Ok(Navigated::No));
14883 };
14884 let head = self.selections.newest::<usize>(cx).head();
14885 let buffer = self.buffer.read(cx);
14886 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14887 text_anchor
14888 } else {
14889 return Task::ready(Ok(Navigated::No));
14890 };
14891
14892 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14893 return Task::ready(Ok(Navigated::No));
14894 };
14895
14896 cx.spawn_in(window, async move |editor, cx| {
14897 let definitions = definitions.await?;
14898 let navigated = editor
14899 .update_in(cx, |editor, window, cx| {
14900 editor.navigate_to_hover_links(
14901 Some(kind),
14902 definitions
14903 .into_iter()
14904 .filter(|location| {
14905 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14906 })
14907 .map(HoverLink::Text)
14908 .collect::<Vec<_>>(),
14909 split,
14910 window,
14911 cx,
14912 )
14913 })?
14914 .await?;
14915 anyhow::Ok(navigated)
14916 })
14917 }
14918
14919 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14920 let selection = self.selections.newest_anchor();
14921 let head = selection.head();
14922 let tail = selection.tail();
14923
14924 let Some((buffer, start_position)) =
14925 self.buffer.read(cx).text_anchor_for_position(head, cx)
14926 else {
14927 return;
14928 };
14929
14930 let end_position = if head != tail {
14931 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14932 return;
14933 };
14934 Some(pos)
14935 } else {
14936 None
14937 };
14938
14939 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14940 let url = if let Some(end_pos) = end_position {
14941 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14942 } else {
14943 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14944 };
14945
14946 if let Some(url) = url {
14947 editor.update(cx, |_, cx| {
14948 cx.open_url(&url);
14949 })
14950 } else {
14951 Ok(())
14952 }
14953 });
14954
14955 url_finder.detach();
14956 }
14957
14958 pub fn open_selected_filename(
14959 &mut self,
14960 _: &OpenSelectedFilename,
14961 window: &mut Window,
14962 cx: &mut Context<Self>,
14963 ) {
14964 let Some(workspace) = self.workspace() else {
14965 return;
14966 };
14967
14968 let position = self.selections.newest_anchor().head();
14969
14970 let Some((buffer, buffer_position)) =
14971 self.buffer.read(cx).text_anchor_for_position(position, cx)
14972 else {
14973 return;
14974 };
14975
14976 let project = self.project.clone();
14977
14978 cx.spawn_in(window, async move |_, cx| {
14979 let result = find_file(&buffer, project, buffer_position, cx).await;
14980
14981 if let Some((_, path)) = result {
14982 workspace
14983 .update_in(cx, |workspace, window, cx| {
14984 workspace.open_resolved_path(path, window, cx)
14985 })?
14986 .await?;
14987 }
14988 anyhow::Ok(())
14989 })
14990 .detach();
14991 }
14992
14993 pub(crate) fn navigate_to_hover_links(
14994 &mut self,
14995 kind: Option<GotoDefinitionKind>,
14996 mut definitions: Vec<HoverLink>,
14997 split: bool,
14998 window: &mut Window,
14999 cx: &mut Context<Editor>,
15000 ) -> Task<Result<Navigated>> {
15001 // If there is one definition, just open it directly
15002 if definitions.len() == 1 {
15003 let definition = definitions.pop().unwrap();
15004
15005 enum TargetTaskResult {
15006 Location(Option<Location>),
15007 AlreadyNavigated,
15008 }
15009
15010 let target_task = match definition {
15011 HoverLink::Text(link) => {
15012 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
15013 }
15014 HoverLink::InlayHint(lsp_location, server_id) => {
15015 let computation =
15016 self.compute_target_location(lsp_location, server_id, window, cx);
15017 cx.background_spawn(async move {
15018 let location = computation.await?;
15019 Ok(TargetTaskResult::Location(location))
15020 })
15021 }
15022 HoverLink::Url(url) => {
15023 cx.open_url(&url);
15024 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
15025 }
15026 HoverLink::File(path) => {
15027 if let Some(workspace) = self.workspace() {
15028 cx.spawn_in(window, async move |_, cx| {
15029 workspace
15030 .update_in(cx, |workspace, window, cx| {
15031 workspace.open_resolved_path(path, window, cx)
15032 })?
15033 .await
15034 .map(|_| TargetTaskResult::AlreadyNavigated)
15035 })
15036 } else {
15037 Task::ready(Ok(TargetTaskResult::Location(None)))
15038 }
15039 }
15040 };
15041 cx.spawn_in(window, async move |editor, cx| {
15042 let target = match target_task.await.context("target resolution task")? {
15043 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
15044 TargetTaskResult::Location(None) => return Ok(Navigated::No),
15045 TargetTaskResult::Location(Some(target)) => target,
15046 };
15047
15048 editor.update_in(cx, |editor, window, cx| {
15049 let Some(workspace) = editor.workspace() else {
15050 return Navigated::No;
15051 };
15052 let pane = workspace.read(cx).active_pane().clone();
15053
15054 let range = target.range.to_point(target.buffer.read(cx));
15055 let range = editor.range_for_match(&range);
15056 let range = collapse_multiline_range(range);
15057
15058 if !split
15059 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
15060 {
15061 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
15062 } else {
15063 window.defer(cx, move |window, cx| {
15064 let target_editor: Entity<Self> =
15065 workspace.update(cx, |workspace, cx| {
15066 let pane = if split {
15067 workspace.adjacent_pane(window, cx)
15068 } else {
15069 workspace.active_pane().clone()
15070 };
15071
15072 workspace.open_project_item(
15073 pane,
15074 target.buffer.clone(),
15075 true,
15076 true,
15077 window,
15078 cx,
15079 )
15080 });
15081 target_editor.update(cx, |target_editor, cx| {
15082 // When selecting a definition in a different buffer, disable the nav history
15083 // to avoid creating a history entry at the previous cursor location.
15084 pane.update(cx, |pane, _| pane.disable_history());
15085 target_editor.go_to_singleton_buffer_range(range, window, cx);
15086 pane.update(cx, |pane, _| pane.enable_history());
15087 });
15088 });
15089 }
15090 Navigated::Yes
15091 })
15092 })
15093 } else if !definitions.is_empty() {
15094 cx.spawn_in(window, async move |editor, cx| {
15095 let (title, location_tasks, workspace) = editor
15096 .update_in(cx, |editor, window, cx| {
15097 let tab_kind = match kind {
15098 Some(GotoDefinitionKind::Implementation) => "Implementations",
15099 _ => "Definitions",
15100 };
15101 let title = definitions
15102 .iter()
15103 .find_map(|definition| match definition {
15104 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15105 let buffer = origin.buffer.read(cx);
15106 format!(
15107 "{} for {}",
15108 tab_kind,
15109 buffer
15110 .text_for_range(origin.range.clone())
15111 .collect::<String>()
15112 )
15113 }),
15114 HoverLink::InlayHint(_, _) => None,
15115 HoverLink::Url(_) => None,
15116 HoverLink::File(_) => None,
15117 })
15118 .unwrap_or(tab_kind.to_string());
15119 let location_tasks = definitions
15120 .into_iter()
15121 .map(|definition| match definition {
15122 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15123 HoverLink::InlayHint(lsp_location, server_id) => editor
15124 .compute_target_location(lsp_location, server_id, window, cx),
15125 HoverLink::Url(_) => Task::ready(Ok(None)),
15126 HoverLink::File(_) => Task::ready(Ok(None)),
15127 })
15128 .collect::<Vec<_>>();
15129 (title, location_tasks, editor.workspace().clone())
15130 })
15131 .context("location tasks preparation")?;
15132
15133 let locations: Vec<Location> = future::join_all(location_tasks)
15134 .await
15135 .into_iter()
15136 .filter_map(|location| location.transpose())
15137 .collect::<Result<_>>()
15138 .context("location tasks")?;
15139
15140 if locations.is_empty() {
15141 return Ok(Navigated::No);
15142 }
15143
15144 let Some(workspace) = workspace else {
15145 return Ok(Navigated::No);
15146 };
15147
15148 let opened = workspace
15149 .update_in(cx, |workspace, window, cx| {
15150 Self::open_locations_in_multibuffer(
15151 workspace,
15152 locations,
15153 title,
15154 split,
15155 MultibufferSelectionMode::First,
15156 window,
15157 cx,
15158 )
15159 })
15160 .ok();
15161
15162 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15163 })
15164 } else {
15165 Task::ready(Ok(Navigated::No))
15166 }
15167 }
15168
15169 fn compute_target_location(
15170 &self,
15171 lsp_location: lsp::Location,
15172 server_id: LanguageServerId,
15173 window: &mut Window,
15174 cx: &mut Context<Self>,
15175 ) -> Task<anyhow::Result<Option<Location>>> {
15176 let Some(project) = self.project.clone() else {
15177 return Task::ready(Ok(None));
15178 };
15179
15180 cx.spawn_in(window, async move |editor, cx| {
15181 let location_task = editor.update(cx, |_, cx| {
15182 project.update(cx, |project, cx| {
15183 let language_server_name = project
15184 .language_server_statuses(cx)
15185 .find(|(id, _)| server_id == *id)
15186 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15187 language_server_name.map(|language_server_name| {
15188 project.open_local_buffer_via_lsp(
15189 lsp_location.uri.clone(),
15190 server_id,
15191 language_server_name,
15192 cx,
15193 )
15194 })
15195 })
15196 })?;
15197 let location = match location_task {
15198 Some(task) => Some({
15199 let target_buffer_handle = task.await.context("open local buffer")?;
15200 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15201 let target_start = target_buffer
15202 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15203 let target_end = target_buffer
15204 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15205 target_buffer.anchor_after(target_start)
15206 ..target_buffer.anchor_before(target_end)
15207 })?;
15208 Location {
15209 buffer: target_buffer_handle,
15210 range,
15211 }
15212 }),
15213 None => None,
15214 };
15215 Ok(location)
15216 })
15217 }
15218
15219 pub fn find_all_references(
15220 &mut self,
15221 _: &FindAllReferences,
15222 window: &mut Window,
15223 cx: &mut Context<Self>,
15224 ) -> Option<Task<Result<Navigated>>> {
15225 let selection = self.selections.newest::<usize>(cx);
15226 let multi_buffer = self.buffer.read(cx);
15227 let head = selection.head();
15228
15229 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15230 let head_anchor = multi_buffer_snapshot.anchor_at(
15231 head,
15232 if head < selection.tail() {
15233 Bias::Right
15234 } else {
15235 Bias::Left
15236 },
15237 );
15238
15239 match self
15240 .find_all_references_task_sources
15241 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15242 {
15243 Ok(_) => {
15244 log::info!(
15245 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15246 );
15247 return None;
15248 }
15249 Err(i) => {
15250 self.find_all_references_task_sources.insert(i, head_anchor);
15251 }
15252 }
15253
15254 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15255 let workspace = self.workspace()?;
15256 let project = workspace.read(cx).project().clone();
15257 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15258 Some(cx.spawn_in(window, async move |editor, cx| {
15259 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15260 if let Ok(i) = editor
15261 .find_all_references_task_sources
15262 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15263 {
15264 editor.find_all_references_task_sources.remove(i);
15265 }
15266 });
15267
15268 let locations = references.await?;
15269 if locations.is_empty() {
15270 return anyhow::Ok(Navigated::No);
15271 }
15272
15273 workspace.update_in(cx, |workspace, window, cx| {
15274 let title = locations
15275 .first()
15276 .as_ref()
15277 .map(|location| {
15278 let buffer = location.buffer.read(cx);
15279 format!(
15280 "References to `{}`",
15281 buffer
15282 .text_for_range(location.range.clone())
15283 .collect::<String>()
15284 )
15285 })
15286 .unwrap();
15287 Self::open_locations_in_multibuffer(
15288 workspace,
15289 locations,
15290 title,
15291 false,
15292 MultibufferSelectionMode::First,
15293 window,
15294 cx,
15295 );
15296 Navigated::Yes
15297 })
15298 }))
15299 }
15300
15301 /// Opens a multibuffer with the given project locations in it
15302 pub fn open_locations_in_multibuffer(
15303 workspace: &mut Workspace,
15304 mut locations: Vec<Location>,
15305 title: String,
15306 split: bool,
15307 multibuffer_selection_mode: MultibufferSelectionMode,
15308 window: &mut Window,
15309 cx: &mut Context<Workspace>,
15310 ) {
15311 if locations.is_empty() {
15312 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
15313 return;
15314 }
15315
15316 // If there are multiple definitions, open them in a multibuffer
15317 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15318 let mut locations = locations.into_iter().peekable();
15319 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15320 let capability = workspace.project().read(cx).capability();
15321
15322 let excerpt_buffer = cx.new(|cx| {
15323 let mut multibuffer = MultiBuffer::new(capability);
15324 while let Some(location) = locations.next() {
15325 let buffer = location.buffer.read(cx);
15326 let mut ranges_for_buffer = Vec::new();
15327 let range = location.range.to_point(buffer);
15328 ranges_for_buffer.push(range.clone());
15329
15330 while let Some(next_location) = locations.peek() {
15331 if next_location.buffer == location.buffer {
15332 ranges_for_buffer.push(next_location.range.to_point(buffer));
15333 locations.next();
15334 } else {
15335 break;
15336 }
15337 }
15338
15339 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15340 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15341 PathKey::for_buffer(&location.buffer, cx),
15342 location.buffer.clone(),
15343 ranges_for_buffer,
15344 DEFAULT_MULTIBUFFER_CONTEXT,
15345 cx,
15346 );
15347 ranges.extend(new_ranges)
15348 }
15349
15350 multibuffer.with_title(title)
15351 });
15352
15353 let editor = cx.new(|cx| {
15354 Editor::for_multibuffer(
15355 excerpt_buffer,
15356 Some(workspace.project().clone()),
15357 window,
15358 cx,
15359 )
15360 });
15361 editor.update(cx, |editor, cx| {
15362 match multibuffer_selection_mode {
15363 MultibufferSelectionMode::First => {
15364 if let Some(first_range) = ranges.first() {
15365 editor.change_selections(None, window, cx, |selections| {
15366 selections.clear_disjoint();
15367 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15368 });
15369 }
15370 editor.highlight_background::<Self>(
15371 &ranges,
15372 |theme| theme.colors().editor_highlighted_line_background,
15373 cx,
15374 );
15375 }
15376 MultibufferSelectionMode::All => {
15377 editor.change_selections(None, window, cx, |selections| {
15378 selections.clear_disjoint();
15379 selections.select_anchor_ranges(ranges);
15380 });
15381 }
15382 }
15383 editor.register_buffers_with_language_servers(cx);
15384 });
15385
15386 let item = Box::new(editor);
15387 let item_id = item.item_id();
15388
15389 if split {
15390 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15391 } else {
15392 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15393 let (preview_item_id, preview_item_idx) =
15394 workspace.active_pane().read_with(cx, |pane, _| {
15395 (pane.preview_item_id(), pane.preview_item_idx())
15396 });
15397
15398 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15399
15400 if let Some(preview_item_id) = preview_item_id {
15401 workspace.active_pane().update(cx, |pane, cx| {
15402 pane.remove_item(preview_item_id, false, false, window, cx);
15403 });
15404 }
15405 } else {
15406 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15407 }
15408 }
15409 workspace.active_pane().update(cx, |pane, cx| {
15410 pane.set_preview_item_id(Some(item_id), cx);
15411 });
15412 }
15413
15414 pub fn rename(
15415 &mut self,
15416 _: &Rename,
15417 window: &mut Window,
15418 cx: &mut Context<Self>,
15419 ) -> Option<Task<Result<()>>> {
15420 use language::ToOffset as _;
15421
15422 let provider = self.semantics_provider.clone()?;
15423 let selection = self.selections.newest_anchor().clone();
15424 let (cursor_buffer, cursor_buffer_position) = self
15425 .buffer
15426 .read(cx)
15427 .text_anchor_for_position(selection.head(), cx)?;
15428 let (tail_buffer, cursor_buffer_position_end) = self
15429 .buffer
15430 .read(cx)
15431 .text_anchor_for_position(selection.tail(), cx)?;
15432 if tail_buffer != cursor_buffer {
15433 return None;
15434 }
15435
15436 let snapshot = cursor_buffer.read(cx).snapshot();
15437 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15438 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15439 let prepare_rename = provider
15440 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15441 .unwrap_or_else(|| Task::ready(Ok(None)));
15442 drop(snapshot);
15443
15444 Some(cx.spawn_in(window, async move |this, cx| {
15445 let rename_range = if let Some(range) = prepare_rename.await? {
15446 Some(range)
15447 } else {
15448 this.update(cx, |this, cx| {
15449 let buffer = this.buffer.read(cx).snapshot(cx);
15450 let mut buffer_highlights = this
15451 .document_highlights_for_position(selection.head(), &buffer)
15452 .filter(|highlight| {
15453 highlight.start.excerpt_id == selection.head().excerpt_id
15454 && highlight.end.excerpt_id == selection.head().excerpt_id
15455 });
15456 buffer_highlights
15457 .next()
15458 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15459 })?
15460 };
15461 if let Some(rename_range) = rename_range {
15462 this.update_in(cx, |this, window, cx| {
15463 let snapshot = cursor_buffer.read(cx).snapshot();
15464 let rename_buffer_range = rename_range.to_offset(&snapshot);
15465 let cursor_offset_in_rename_range =
15466 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15467 let cursor_offset_in_rename_range_end =
15468 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15469
15470 this.take_rename(false, window, cx);
15471 let buffer = this.buffer.read(cx).read(cx);
15472 let cursor_offset = selection.head().to_offset(&buffer);
15473 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15474 let rename_end = rename_start + rename_buffer_range.len();
15475 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15476 let mut old_highlight_id = None;
15477 let old_name: Arc<str> = buffer
15478 .chunks(rename_start..rename_end, true)
15479 .map(|chunk| {
15480 if old_highlight_id.is_none() {
15481 old_highlight_id = chunk.syntax_highlight_id;
15482 }
15483 chunk.text
15484 })
15485 .collect::<String>()
15486 .into();
15487
15488 drop(buffer);
15489
15490 // Position the selection in the rename editor so that it matches the current selection.
15491 this.show_local_selections = false;
15492 let rename_editor = cx.new(|cx| {
15493 let mut editor = Editor::single_line(window, cx);
15494 editor.buffer.update(cx, |buffer, cx| {
15495 buffer.edit([(0..0, old_name.clone())], None, cx)
15496 });
15497 let rename_selection_range = match cursor_offset_in_rename_range
15498 .cmp(&cursor_offset_in_rename_range_end)
15499 {
15500 Ordering::Equal => {
15501 editor.select_all(&SelectAll, window, cx);
15502 return editor;
15503 }
15504 Ordering::Less => {
15505 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15506 }
15507 Ordering::Greater => {
15508 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15509 }
15510 };
15511 if rename_selection_range.end > old_name.len() {
15512 editor.select_all(&SelectAll, window, cx);
15513 } else {
15514 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15515 s.select_ranges([rename_selection_range]);
15516 });
15517 }
15518 editor
15519 });
15520 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15521 if e == &EditorEvent::Focused {
15522 cx.emit(EditorEvent::FocusedIn)
15523 }
15524 })
15525 .detach();
15526
15527 let write_highlights = this
15528 .clear_background_highlights::<DocumentHighlightWrite>(cx)
15529 .unwrap_or_default();
15530 let read_highlights = this
15531 .clear_background_highlights::<DocumentHighlightRead>(cx)
15532 .unwrap_or_default();
15533 let ranges = write_highlights
15534 .iter()
15535 .chain(read_highlights.iter())
15536 .cloned()
15537 .map(|highlight| {
15538 (
15539 highlight.range,
15540 HighlightStyle {
15541 fade_out: Some(0.6),
15542 ..Default::default()
15543 },
15544 )
15545 })
15546 .collect();
15547
15548 this.highlight_text::<Rename>(ranges, cx);
15549 let rename_focus_handle = rename_editor.focus_handle(cx);
15550 window.focus(&rename_focus_handle);
15551 let block_id = this.insert_blocks(
15552 [BlockProperties {
15553 style: BlockStyle::Flex,
15554 placement: BlockPlacement::Below(range.start),
15555 height: Some(1),
15556 render: Arc::new({
15557 let rename_editor = rename_editor.clone();
15558 move |cx: &mut BlockContext| {
15559 let mut text_style = cx.editor_style.text.clone();
15560 if let Some(highlight_style) = old_highlight_id
15561 .and_then(|h| h.style(&cx.editor_style.syntax))
15562 {
15563 text_style = text_style.highlight(highlight_style);
15564 }
15565 div()
15566 .block_mouse_except_scroll()
15567 .pl(cx.anchor_x)
15568 .child(EditorElement::new(
15569 &rename_editor,
15570 EditorStyle {
15571 background: cx.theme().system().transparent,
15572 local_player: cx.editor_style.local_player,
15573 text: text_style,
15574 scrollbar_width: cx.editor_style.scrollbar_width,
15575 syntax: cx.editor_style.syntax.clone(),
15576 status: cx.editor_style.status.clone(),
15577 inlay_hints_style: HighlightStyle {
15578 font_weight: Some(FontWeight::BOLD),
15579 ..make_inlay_hints_style(cx.app)
15580 },
15581 inline_completion_styles: make_suggestion_styles(
15582 cx.app,
15583 ),
15584 ..EditorStyle::default()
15585 },
15586 ))
15587 .into_any_element()
15588 }
15589 }),
15590 priority: 0,
15591 render_in_minimap: true,
15592 }],
15593 Some(Autoscroll::fit()),
15594 cx,
15595 )[0];
15596 this.pending_rename = Some(RenameState {
15597 range,
15598 old_name,
15599 editor: rename_editor,
15600 block_id,
15601 });
15602 })?;
15603 }
15604
15605 Ok(())
15606 }))
15607 }
15608
15609 pub fn confirm_rename(
15610 &mut self,
15611 _: &ConfirmRename,
15612 window: &mut Window,
15613 cx: &mut Context<Self>,
15614 ) -> Option<Task<Result<()>>> {
15615 let rename = self.take_rename(false, window, cx)?;
15616 let workspace = self.workspace()?.downgrade();
15617 let (buffer, start) = self
15618 .buffer
15619 .read(cx)
15620 .text_anchor_for_position(rename.range.start, cx)?;
15621 let (end_buffer, _) = self
15622 .buffer
15623 .read(cx)
15624 .text_anchor_for_position(rename.range.end, cx)?;
15625 if buffer != end_buffer {
15626 return None;
15627 }
15628
15629 let old_name = rename.old_name;
15630 let new_name = rename.editor.read(cx).text(cx);
15631
15632 let rename = self.semantics_provider.as_ref()?.perform_rename(
15633 &buffer,
15634 start,
15635 new_name.clone(),
15636 cx,
15637 )?;
15638
15639 Some(cx.spawn_in(window, async move |editor, cx| {
15640 let project_transaction = rename.await?;
15641 Self::open_project_transaction(
15642 &editor,
15643 workspace,
15644 project_transaction,
15645 format!("Rename: {} → {}", old_name, new_name),
15646 cx,
15647 )
15648 .await?;
15649
15650 editor.update(cx, |editor, cx| {
15651 editor.refresh_document_highlights(cx);
15652 })?;
15653 Ok(())
15654 }))
15655 }
15656
15657 fn take_rename(
15658 &mut self,
15659 moving_cursor: bool,
15660 window: &mut Window,
15661 cx: &mut Context<Self>,
15662 ) -> Option<RenameState> {
15663 let rename = self.pending_rename.take()?;
15664 if rename.editor.focus_handle(cx).is_focused(window) {
15665 window.focus(&self.focus_handle);
15666 }
15667
15668 self.remove_blocks(
15669 [rename.block_id].into_iter().collect(),
15670 Some(Autoscroll::fit()),
15671 cx,
15672 );
15673 self.clear_highlights::<Rename>(cx);
15674 self.show_local_selections = true;
15675
15676 if moving_cursor {
15677 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15678 editor.selections.newest::<usize>(cx).head()
15679 });
15680
15681 // Update the selection to match the position of the selection inside
15682 // the rename editor.
15683 let snapshot = self.buffer.read(cx).read(cx);
15684 let rename_range = rename.range.to_offset(&snapshot);
15685 let cursor_in_editor = snapshot
15686 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15687 .min(rename_range.end);
15688 drop(snapshot);
15689
15690 self.change_selections(None, window, cx, |s| {
15691 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15692 });
15693 } else {
15694 self.refresh_document_highlights(cx);
15695 }
15696
15697 Some(rename)
15698 }
15699
15700 pub fn pending_rename(&self) -> Option<&RenameState> {
15701 self.pending_rename.as_ref()
15702 }
15703
15704 fn format(
15705 &mut self,
15706 _: &Format,
15707 window: &mut Window,
15708 cx: &mut Context<Self>,
15709 ) -> Option<Task<Result<()>>> {
15710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15711
15712 let project = match &self.project {
15713 Some(project) => project.clone(),
15714 None => return None,
15715 };
15716
15717 Some(self.perform_format(
15718 project,
15719 FormatTrigger::Manual,
15720 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
15721 window,
15722 cx,
15723 ))
15724 }
15725
15726 fn format_selections(
15727 &mut self,
15728 _: &FormatSelections,
15729 window: &mut Window,
15730 cx: &mut Context<Self>,
15731 ) -> Option<Task<Result<()>>> {
15732 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15733
15734 let project = match &self.project {
15735 Some(project) => project.clone(),
15736 None => return None,
15737 };
15738
15739 let ranges = self
15740 .selections
15741 .all_adjusted(cx)
15742 .into_iter()
15743 .map(|selection| selection.range())
15744 .collect_vec();
15745
15746 Some(self.perform_format(
15747 project,
15748 FormatTrigger::Manual,
15749 FormatTarget::Ranges(ranges),
15750 window,
15751 cx,
15752 ))
15753 }
15754
15755 fn perform_format(
15756 &mut self,
15757 project: Entity<Project>,
15758 trigger: FormatTrigger,
15759 target: FormatTarget,
15760 window: &mut Window,
15761 cx: &mut Context<Self>,
15762 ) -> Task<Result<()>> {
15763 let buffer = self.buffer.clone();
15764 let (buffers, target) = match target {
15765 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
15766 FormatTarget::Ranges(selection_ranges) => {
15767 let multi_buffer = buffer.read(cx);
15768 let snapshot = multi_buffer.read(cx);
15769 let mut buffers = HashSet::default();
15770 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15771 BTreeMap::new();
15772 for selection_range in selection_ranges {
15773 for (buffer, buffer_range, _) in
15774 snapshot.range_to_buffer_ranges(selection_range)
15775 {
15776 let buffer_id = buffer.remote_id();
15777 let start = buffer.anchor_before(buffer_range.start);
15778 let end = buffer.anchor_after(buffer_range.end);
15779 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15780 buffer_id_to_ranges
15781 .entry(buffer_id)
15782 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15783 .or_insert_with(|| vec![start..end]);
15784 }
15785 }
15786 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15787 }
15788 };
15789
15790 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15791 let selections_prev = transaction_id_prev
15792 .and_then(|transaction_id_prev| {
15793 // default to selections as they were after the last edit, if we have them,
15794 // instead of how they are now.
15795 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15796 // will take you back to where you made the last edit, instead of staying where you scrolled
15797 self.selection_history
15798 .transaction(transaction_id_prev)
15799 .map(|t| t.0.clone())
15800 })
15801 .unwrap_or_else(|| {
15802 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15803 self.selections.disjoint_anchors()
15804 });
15805
15806 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15807 let format = project.update(cx, |project, cx| {
15808 project.format(buffers, target, true, trigger, cx)
15809 });
15810
15811 cx.spawn_in(window, async move |editor, cx| {
15812 let transaction = futures::select_biased! {
15813 transaction = format.log_err().fuse() => transaction,
15814 () = timeout => {
15815 log::warn!("timed out waiting for formatting");
15816 None
15817 }
15818 };
15819
15820 buffer
15821 .update(cx, |buffer, cx| {
15822 if let Some(transaction) = transaction {
15823 if !buffer.is_singleton() {
15824 buffer.push_transaction(&transaction.0, cx);
15825 }
15826 }
15827 cx.notify();
15828 })
15829 .ok();
15830
15831 if let Some(transaction_id_now) =
15832 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15833 {
15834 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15835 if has_new_transaction {
15836 _ = editor.update(cx, |editor, _| {
15837 editor
15838 .selection_history
15839 .insert_transaction(transaction_id_now, selections_prev);
15840 });
15841 }
15842 }
15843
15844 Ok(())
15845 })
15846 }
15847
15848 fn organize_imports(
15849 &mut self,
15850 _: &OrganizeImports,
15851 window: &mut Window,
15852 cx: &mut Context<Self>,
15853 ) -> Option<Task<Result<()>>> {
15854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15855 let project = match &self.project {
15856 Some(project) => project.clone(),
15857 None => return None,
15858 };
15859 Some(self.perform_code_action_kind(
15860 project,
15861 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15862 window,
15863 cx,
15864 ))
15865 }
15866
15867 fn perform_code_action_kind(
15868 &mut self,
15869 project: Entity<Project>,
15870 kind: CodeActionKind,
15871 window: &mut Window,
15872 cx: &mut Context<Self>,
15873 ) -> Task<Result<()>> {
15874 let buffer = self.buffer.clone();
15875 let buffers = buffer.read(cx).all_buffers();
15876 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15877 let apply_action = project.update(cx, |project, cx| {
15878 project.apply_code_action_kind(buffers, kind, true, cx)
15879 });
15880 cx.spawn_in(window, async move |_, cx| {
15881 let transaction = futures::select_biased! {
15882 () = timeout => {
15883 log::warn!("timed out waiting for executing code action");
15884 None
15885 }
15886 transaction = apply_action.log_err().fuse() => transaction,
15887 };
15888 buffer
15889 .update(cx, |buffer, cx| {
15890 // check if we need this
15891 if let Some(transaction) = transaction {
15892 if !buffer.is_singleton() {
15893 buffer.push_transaction(&transaction.0, cx);
15894 }
15895 }
15896 cx.notify();
15897 })
15898 .ok();
15899 Ok(())
15900 })
15901 }
15902
15903 fn restart_language_server(
15904 &mut self,
15905 _: &RestartLanguageServer,
15906 _: &mut Window,
15907 cx: &mut Context<Self>,
15908 ) {
15909 if let Some(project) = self.project.clone() {
15910 self.buffer.update(cx, |multi_buffer, cx| {
15911 project.update(cx, |project, cx| {
15912 project.restart_language_servers_for_buffers(
15913 multi_buffer.all_buffers().into_iter().collect(),
15914 cx,
15915 );
15916 });
15917 })
15918 }
15919 }
15920
15921 fn stop_language_server(
15922 &mut self,
15923 _: &StopLanguageServer,
15924 _: &mut Window,
15925 cx: &mut Context<Self>,
15926 ) {
15927 if let Some(project) = self.project.clone() {
15928 self.buffer.update(cx, |multi_buffer, cx| {
15929 project.update(cx, |project, cx| {
15930 project.stop_language_servers_for_buffers(
15931 multi_buffer.all_buffers().into_iter().collect(),
15932 cx,
15933 );
15934 cx.emit(project::Event::RefreshInlayHints);
15935 });
15936 });
15937 }
15938 }
15939
15940 fn cancel_language_server_work(
15941 workspace: &mut Workspace,
15942 _: &actions::CancelLanguageServerWork,
15943 _: &mut Window,
15944 cx: &mut Context<Workspace>,
15945 ) {
15946 let project = workspace.project();
15947 let buffers = workspace
15948 .active_item(cx)
15949 .and_then(|item| item.act_as::<Editor>(cx))
15950 .map_or(HashSet::default(), |editor| {
15951 editor.read(cx).buffer.read(cx).all_buffers()
15952 });
15953 project.update(cx, |project, cx| {
15954 project.cancel_language_server_work_for_buffers(buffers, cx);
15955 });
15956 }
15957
15958 fn show_character_palette(
15959 &mut self,
15960 _: &ShowCharacterPalette,
15961 window: &mut Window,
15962 _: &mut Context<Self>,
15963 ) {
15964 window.show_character_palette();
15965 }
15966
15967 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15968 if self.mode.is_minimap() {
15969 return;
15970 }
15971
15972 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15973 let buffer = self.buffer.read(cx).snapshot(cx);
15974 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15975 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15976 let is_valid = buffer
15977 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15978 .any(|entry| {
15979 entry.diagnostic.is_primary
15980 && !entry.range.is_empty()
15981 && entry.range.start == primary_range_start
15982 && entry.diagnostic.message == active_diagnostics.active_message
15983 });
15984
15985 if !is_valid {
15986 self.dismiss_diagnostics(cx);
15987 }
15988 }
15989 }
15990
15991 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15992 match &self.active_diagnostics {
15993 ActiveDiagnostic::Group(group) => Some(group),
15994 _ => None,
15995 }
15996 }
15997
15998 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15999 self.dismiss_diagnostics(cx);
16000 self.active_diagnostics = ActiveDiagnostic::All;
16001 }
16002
16003 fn activate_diagnostics(
16004 &mut self,
16005 buffer_id: BufferId,
16006 diagnostic: DiagnosticEntry<usize>,
16007 window: &mut Window,
16008 cx: &mut Context<Self>,
16009 ) {
16010 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16011 return;
16012 }
16013 self.dismiss_diagnostics(cx);
16014 let snapshot = self.snapshot(window, cx);
16015 let buffer = self.buffer.read(cx).snapshot(cx);
16016 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
16017 return;
16018 };
16019
16020 let diagnostic_group = buffer
16021 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
16022 .collect::<Vec<_>>();
16023
16024 let blocks =
16025 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
16026
16027 let blocks = self.display_map.update(cx, |display_map, cx| {
16028 display_map.insert_blocks(blocks, cx).into_iter().collect()
16029 });
16030 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
16031 active_range: buffer.anchor_before(diagnostic.range.start)
16032 ..buffer.anchor_after(diagnostic.range.end),
16033 active_message: diagnostic.diagnostic.message.clone(),
16034 group_id: diagnostic.diagnostic.group_id,
16035 blocks,
16036 });
16037 cx.notify();
16038 }
16039
16040 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
16041 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
16042 return;
16043 };
16044
16045 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
16046 if let ActiveDiagnostic::Group(group) = prev {
16047 self.display_map.update(cx, |display_map, cx| {
16048 display_map.remove_blocks(group.blocks, cx);
16049 });
16050 cx.notify();
16051 }
16052 }
16053
16054 /// Disable inline diagnostics rendering for this editor.
16055 pub fn disable_inline_diagnostics(&mut self) {
16056 self.inline_diagnostics_enabled = false;
16057 self.inline_diagnostics_update = Task::ready(());
16058 self.inline_diagnostics.clear();
16059 }
16060
16061 pub fn diagnostics_enabled(&self) -> bool {
16062 self.mode.is_full()
16063 }
16064
16065 pub fn inline_diagnostics_enabled(&self) -> bool {
16066 self.diagnostics_enabled() && self.inline_diagnostics_enabled
16067 }
16068
16069 pub fn show_inline_diagnostics(&self) -> bool {
16070 self.show_inline_diagnostics
16071 }
16072
16073 pub fn toggle_inline_diagnostics(
16074 &mut self,
16075 _: &ToggleInlineDiagnostics,
16076 window: &mut Window,
16077 cx: &mut Context<Editor>,
16078 ) {
16079 self.show_inline_diagnostics = !self.show_inline_diagnostics;
16080 self.refresh_inline_diagnostics(false, window, cx);
16081 }
16082
16083 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
16084 self.diagnostics_max_severity = severity;
16085 self.display_map.update(cx, |display_map, _| {
16086 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
16087 });
16088 }
16089
16090 pub fn toggle_diagnostics(
16091 &mut self,
16092 _: &ToggleDiagnostics,
16093 window: &mut Window,
16094 cx: &mut Context<Editor>,
16095 ) {
16096 if !self.diagnostics_enabled() {
16097 return;
16098 }
16099
16100 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16101 EditorSettings::get_global(cx)
16102 .diagnostics_max_severity
16103 .filter(|severity| severity != &DiagnosticSeverity::Off)
16104 .unwrap_or(DiagnosticSeverity::Hint)
16105 } else {
16106 DiagnosticSeverity::Off
16107 };
16108 self.set_max_diagnostics_severity(new_severity, cx);
16109 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16110 self.active_diagnostics = ActiveDiagnostic::None;
16111 self.inline_diagnostics_update = Task::ready(());
16112 self.inline_diagnostics.clear();
16113 } else {
16114 self.refresh_inline_diagnostics(false, window, cx);
16115 }
16116
16117 cx.notify();
16118 }
16119
16120 pub fn toggle_minimap(
16121 &mut self,
16122 _: &ToggleMinimap,
16123 window: &mut Window,
16124 cx: &mut Context<Editor>,
16125 ) {
16126 if self.supports_minimap(cx) {
16127 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16128 }
16129 }
16130
16131 fn refresh_inline_diagnostics(
16132 &mut self,
16133 debounce: bool,
16134 window: &mut Window,
16135 cx: &mut Context<Self>,
16136 ) {
16137 let max_severity = ProjectSettings::get_global(cx)
16138 .diagnostics
16139 .inline
16140 .max_severity
16141 .unwrap_or(self.diagnostics_max_severity);
16142
16143 if !self.inline_diagnostics_enabled()
16144 || !self.show_inline_diagnostics
16145 || max_severity == DiagnosticSeverity::Off
16146 {
16147 self.inline_diagnostics_update = Task::ready(());
16148 self.inline_diagnostics.clear();
16149 return;
16150 }
16151
16152 let debounce_ms = ProjectSettings::get_global(cx)
16153 .diagnostics
16154 .inline
16155 .update_debounce_ms;
16156 let debounce = if debounce && debounce_ms > 0 {
16157 Some(Duration::from_millis(debounce_ms))
16158 } else {
16159 None
16160 };
16161 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16162 if let Some(debounce) = debounce {
16163 cx.background_executor().timer(debounce).await;
16164 }
16165 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16166 editor
16167 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16168 .ok()
16169 }) else {
16170 return;
16171 };
16172
16173 let new_inline_diagnostics = cx
16174 .background_spawn(async move {
16175 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16176 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16177 let message = diagnostic_entry
16178 .diagnostic
16179 .message
16180 .split_once('\n')
16181 .map(|(line, _)| line)
16182 .map(SharedString::new)
16183 .unwrap_or_else(|| {
16184 SharedString::from(diagnostic_entry.diagnostic.message)
16185 });
16186 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16187 let (Ok(i) | Err(i)) = inline_diagnostics
16188 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16189 inline_diagnostics.insert(
16190 i,
16191 (
16192 start_anchor,
16193 InlineDiagnostic {
16194 message,
16195 group_id: diagnostic_entry.diagnostic.group_id,
16196 start: diagnostic_entry.range.start.to_point(&snapshot),
16197 is_primary: diagnostic_entry.diagnostic.is_primary,
16198 severity: diagnostic_entry.diagnostic.severity,
16199 },
16200 ),
16201 );
16202 }
16203 inline_diagnostics
16204 })
16205 .await;
16206
16207 editor
16208 .update(cx, |editor, cx| {
16209 editor.inline_diagnostics = new_inline_diagnostics;
16210 cx.notify();
16211 })
16212 .ok();
16213 });
16214 }
16215
16216 fn pull_diagnostics(
16217 &mut self,
16218 buffer_id: Option<BufferId>,
16219 window: &Window,
16220 cx: &mut Context<Self>,
16221 ) -> Option<()> {
16222 if !self.mode().is_full() {
16223 return None;
16224 }
16225 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16226 .diagnostics
16227 .lsp_pull_diagnostics;
16228 if !pull_diagnostics_settings.enabled {
16229 return None;
16230 }
16231 let project = self.project.as_ref()?.downgrade();
16232 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16233 let mut buffers = self.buffer.read(cx).all_buffers();
16234 if let Some(buffer_id) = buffer_id {
16235 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16236 }
16237
16238 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16239 cx.background_executor().timer(debounce).await;
16240
16241 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16242 buffers
16243 .into_iter()
16244 .flat_map(|buffer| {
16245 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16246 })
16247 .collect::<FuturesUnordered<_>>()
16248 }) else {
16249 return;
16250 };
16251
16252 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16253 match pull_task {
16254 Ok(()) => {
16255 if editor
16256 .update_in(cx, |editor, window, cx| {
16257 editor.update_diagnostics_state(window, cx);
16258 })
16259 .is_err()
16260 {
16261 return;
16262 }
16263 }
16264 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16265 }
16266 }
16267 });
16268
16269 Some(())
16270 }
16271
16272 pub fn set_selections_from_remote(
16273 &mut self,
16274 selections: Vec<Selection<Anchor>>,
16275 pending_selection: Option<Selection<Anchor>>,
16276 window: &mut Window,
16277 cx: &mut Context<Self>,
16278 ) {
16279 let old_cursor_position = self.selections.newest_anchor().head();
16280 self.selections.change_with(cx, |s| {
16281 s.select_anchors(selections);
16282 if let Some(pending_selection) = pending_selection {
16283 s.set_pending(pending_selection, SelectMode::Character);
16284 } else {
16285 s.clear_pending();
16286 }
16287 });
16288 self.selections_did_change(
16289 false,
16290 &old_cursor_position,
16291 SelectionEffects::default(),
16292 window,
16293 cx,
16294 );
16295 }
16296
16297 pub fn transact(
16298 &mut self,
16299 window: &mut Window,
16300 cx: &mut Context<Self>,
16301 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16302 ) -> Option<TransactionId> {
16303 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16304 this.start_transaction_at(Instant::now(), window, cx);
16305 update(this, window, cx);
16306 this.end_transaction_at(Instant::now(), cx)
16307 })
16308 }
16309
16310 pub fn start_transaction_at(
16311 &mut self,
16312 now: Instant,
16313 window: &mut Window,
16314 cx: &mut Context<Self>,
16315 ) {
16316 self.end_selection(window, cx);
16317 if let Some(tx_id) = self
16318 .buffer
16319 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16320 {
16321 self.selection_history
16322 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16323 cx.emit(EditorEvent::TransactionBegun {
16324 transaction_id: tx_id,
16325 })
16326 }
16327 }
16328
16329 pub fn end_transaction_at(
16330 &mut self,
16331 now: Instant,
16332 cx: &mut Context<Self>,
16333 ) -> Option<TransactionId> {
16334 if let Some(transaction_id) = self
16335 .buffer
16336 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16337 {
16338 if let Some((_, end_selections)) =
16339 self.selection_history.transaction_mut(transaction_id)
16340 {
16341 *end_selections = Some(self.selections.disjoint_anchors());
16342 } else {
16343 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16344 }
16345
16346 cx.emit(EditorEvent::Edited { transaction_id });
16347 Some(transaction_id)
16348 } else {
16349 None
16350 }
16351 }
16352
16353 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16354 if self.selection_mark_mode {
16355 self.change_selections(None, window, cx, |s| {
16356 s.move_with(|_, sel| {
16357 sel.collapse_to(sel.head(), SelectionGoal::None);
16358 });
16359 })
16360 }
16361 self.selection_mark_mode = true;
16362 cx.notify();
16363 }
16364
16365 pub fn swap_selection_ends(
16366 &mut self,
16367 _: &actions::SwapSelectionEnds,
16368 window: &mut Window,
16369 cx: &mut Context<Self>,
16370 ) {
16371 self.change_selections(None, window, cx, |s| {
16372 s.move_with(|_, sel| {
16373 if sel.start != sel.end {
16374 sel.reversed = !sel.reversed
16375 }
16376 });
16377 });
16378 self.request_autoscroll(Autoscroll::newest(), cx);
16379 cx.notify();
16380 }
16381
16382 pub fn toggle_fold(
16383 &mut self,
16384 _: &actions::ToggleFold,
16385 window: &mut Window,
16386 cx: &mut Context<Self>,
16387 ) {
16388 if self.is_singleton(cx) {
16389 let selection = self.selections.newest::<Point>(cx);
16390
16391 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16392 let range = if selection.is_empty() {
16393 let point = selection.head().to_display_point(&display_map);
16394 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16395 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16396 .to_point(&display_map);
16397 start..end
16398 } else {
16399 selection.range()
16400 };
16401 if display_map.folds_in_range(range).next().is_some() {
16402 self.unfold_lines(&Default::default(), window, cx)
16403 } else {
16404 self.fold(&Default::default(), window, cx)
16405 }
16406 } else {
16407 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16408 let buffer_ids: HashSet<_> = self
16409 .selections
16410 .disjoint_anchor_ranges()
16411 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16412 .collect();
16413
16414 let should_unfold = buffer_ids
16415 .iter()
16416 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16417
16418 for buffer_id in buffer_ids {
16419 if should_unfold {
16420 self.unfold_buffer(buffer_id, cx);
16421 } else {
16422 self.fold_buffer(buffer_id, cx);
16423 }
16424 }
16425 }
16426 }
16427
16428 pub fn toggle_fold_recursive(
16429 &mut self,
16430 _: &actions::ToggleFoldRecursive,
16431 window: &mut Window,
16432 cx: &mut Context<Self>,
16433 ) {
16434 let selection = self.selections.newest::<Point>(cx);
16435
16436 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16437 let range = if selection.is_empty() {
16438 let point = selection.head().to_display_point(&display_map);
16439 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16440 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16441 .to_point(&display_map);
16442 start..end
16443 } else {
16444 selection.range()
16445 };
16446 if display_map.folds_in_range(range).next().is_some() {
16447 self.unfold_recursive(&Default::default(), window, cx)
16448 } else {
16449 self.fold_recursive(&Default::default(), window, cx)
16450 }
16451 }
16452
16453 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16454 if self.is_singleton(cx) {
16455 let mut to_fold = Vec::new();
16456 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16457 let selections = self.selections.all_adjusted(cx);
16458
16459 for selection in selections {
16460 let range = selection.range().sorted();
16461 let buffer_start_row = range.start.row;
16462
16463 if range.start.row != range.end.row {
16464 let mut found = false;
16465 let mut row = range.start.row;
16466 while row <= range.end.row {
16467 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16468 {
16469 found = true;
16470 row = crease.range().end.row + 1;
16471 to_fold.push(crease);
16472 } else {
16473 row += 1
16474 }
16475 }
16476 if found {
16477 continue;
16478 }
16479 }
16480
16481 for row in (0..=range.start.row).rev() {
16482 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16483 if crease.range().end.row >= buffer_start_row {
16484 to_fold.push(crease);
16485 if row <= range.start.row {
16486 break;
16487 }
16488 }
16489 }
16490 }
16491 }
16492
16493 self.fold_creases(to_fold, true, window, cx);
16494 } else {
16495 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16496 let buffer_ids = self
16497 .selections
16498 .disjoint_anchor_ranges()
16499 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16500 .collect::<HashSet<_>>();
16501 for buffer_id in buffer_ids {
16502 self.fold_buffer(buffer_id, cx);
16503 }
16504 }
16505 }
16506
16507 fn fold_at_level(
16508 &mut self,
16509 fold_at: &FoldAtLevel,
16510 window: &mut Window,
16511 cx: &mut Context<Self>,
16512 ) {
16513 if !self.buffer.read(cx).is_singleton() {
16514 return;
16515 }
16516
16517 let fold_at_level = fold_at.0;
16518 let snapshot = self.buffer.read(cx).snapshot(cx);
16519 let mut to_fold = Vec::new();
16520 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16521
16522 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16523 while start_row < end_row {
16524 match self
16525 .snapshot(window, cx)
16526 .crease_for_buffer_row(MultiBufferRow(start_row))
16527 {
16528 Some(crease) => {
16529 let nested_start_row = crease.range().start.row + 1;
16530 let nested_end_row = crease.range().end.row;
16531
16532 if current_level < fold_at_level {
16533 stack.push((nested_start_row, nested_end_row, current_level + 1));
16534 } else if current_level == fold_at_level {
16535 to_fold.push(crease);
16536 }
16537
16538 start_row = nested_end_row + 1;
16539 }
16540 None => start_row += 1,
16541 }
16542 }
16543 }
16544
16545 self.fold_creases(to_fold, true, window, cx);
16546 }
16547
16548 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16549 if self.buffer.read(cx).is_singleton() {
16550 let mut fold_ranges = Vec::new();
16551 let snapshot = self.buffer.read(cx).snapshot(cx);
16552
16553 for row in 0..snapshot.max_row().0 {
16554 if let Some(foldable_range) = self
16555 .snapshot(window, cx)
16556 .crease_for_buffer_row(MultiBufferRow(row))
16557 {
16558 fold_ranges.push(foldable_range);
16559 }
16560 }
16561
16562 self.fold_creases(fold_ranges, true, window, cx);
16563 } else {
16564 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16565 editor
16566 .update_in(cx, |editor, _, cx| {
16567 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16568 editor.fold_buffer(buffer_id, cx);
16569 }
16570 })
16571 .ok();
16572 });
16573 }
16574 }
16575
16576 pub fn fold_function_bodies(
16577 &mut self,
16578 _: &actions::FoldFunctionBodies,
16579 window: &mut Window,
16580 cx: &mut Context<Self>,
16581 ) {
16582 let snapshot = self.buffer.read(cx).snapshot(cx);
16583
16584 let ranges = snapshot
16585 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16586 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16587 .collect::<Vec<_>>();
16588
16589 let creases = ranges
16590 .into_iter()
16591 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16592 .collect();
16593
16594 self.fold_creases(creases, true, window, cx);
16595 }
16596
16597 pub fn fold_recursive(
16598 &mut self,
16599 _: &actions::FoldRecursive,
16600 window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) {
16603 let mut to_fold = Vec::new();
16604 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16605 let selections = self.selections.all_adjusted(cx);
16606
16607 for selection in selections {
16608 let range = selection.range().sorted();
16609 let buffer_start_row = range.start.row;
16610
16611 if range.start.row != range.end.row {
16612 let mut found = false;
16613 for row in range.start.row..=range.end.row {
16614 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16615 found = true;
16616 to_fold.push(crease);
16617 }
16618 }
16619 if found {
16620 continue;
16621 }
16622 }
16623
16624 for row in (0..=range.start.row).rev() {
16625 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16626 if crease.range().end.row >= buffer_start_row {
16627 to_fold.push(crease);
16628 } else {
16629 break;
16630 }
16631 }
16632 }
16633 }
16634
16635 self.fold_creases(to_fold, true, window, cx);
16636 }
16637
16638 pub fn fold_at(
16639 &mut self,
16640 buffer_row: MultiBufferRow,
16641 window: &mut Window,
16642 cx: &mut Context<Self>,
16643 ) {
16644 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16645
16646 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16647 let autoscroll = self
16648 .selections
16649 .all::<Point>(cx)
16650 .iter()
16651 .any(|selection| crease.range().overlaps(&selection.range()));
16652
16653 self.fold_creases(vec![crease], autoscroll, window, cx);
16654 }
16655 }
16656
16657 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16658 if self.is_singleton(cx) {
16659 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16660 let buffer = &display_map.buffer_snapshot;
16661 let selections = self.selections.all::<Point>(cx);
16662 let ranges = selections
16663 .iter()
16664 .map(|s| {
16665 let range = s.display_range(&display_map).sorted();
16666 let mut start = range.start.to_point(&display_map);
16667 let mut end = range.end.to_point(&display_map);
16668 start.column = 0;
16669 end.column = buffer.line_len(MultiBufferRow(end.row));
16670 start..end
16671 })
16672 .collect::<Vec<_>>();
16673
16674 self.unfold_ranges(&ranges, true, true, cx);
16675 } else {
16676 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16677 let buffer_ids = self
16678 .selections
16679 .disjoint_anchor_ranges()
16680 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16681 .collect::<HashSet<_>>();
16682 for buffer_id in buffer_ids {
16683 self.unfold_buffer(buffer_id, cx);
16684 }
16685 }
16686 }
16687
16688 pub fn unfold_recursive(
16689 &mut self,
16690 _: &UnfoldRecursive,
16691 _window: &mut Window,
16692 cx: &mut Context<Self>,
16693 ) {
16694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16695 let selections = self.selections.all::<Point>(cx);
16696 let ranges = selections
16697 .iter()
16698 .map(|s| {
16699 let mut range = s.display_range(&display_map).sorted();
16700 *range.start.column_mut() = 0;
16701 *range.end.column_mut() = display_map.line_len(range.end.row());
16702 let start = range.start.to_point(&display_map);
16703 let end = range.end.to_point(&display_map);
16704 start..end
16705 })
16706 .collect::<Vec<_>>();
16707
16708 self.unfold_ranges(&ranges, true, true, cx);
16709 }
16710
16711 pub fn unfold_at(
16712 &mut self,
16713 buffer_row: MultiBufferRow,
16714 _window: &mut Window,
16715 cx: &mut Context<Self>,
16716 ) {
16717 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16718
16719 let intersection_range = Point::new(buffer_row.0, 0)
16720 ..Point::new(
16721 buffer_row.0,
16722 display_map.buffer_snapshot.line_len(buffer_row),
16723 );
16724
16725 let autoscroll = self
16726 .selections
16727 .all::<Point>(cx)
16728 .iter()
16729 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16730
16731 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16732 }
16733
16734 pub fn unfold_all(
16735 &mut self,
16736 _: &actions::UnfoldAll,
16737 _window: &mut Window,
16738 cx: &mut Context<Self>,
16739 ) {
16740 if self.buffer.read(cx).is_singleton() {
16741 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16742 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16743 } else {
16744 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16745 editor
16746 .update(cx, |editor, cx| {
16747 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16748 editor.unfold_buffer(buffer_id, cx);
16749 }
16750 })
16751 .ok();
16752 });
16753 }
16754 }
16755
16756 pub fn fold_selected_ranges(
16757 &mut self,
16758 _: &FoldSelectedRanges,
16759 window: &mut Window,
16760 cx: &mut Context<Self>,
16761 ) {
16762 let selections = self.selections.all_adjusted(cx);
16763 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16764 let ranges = selections
16765 .into_iter()
16766 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16767 .collect::<Vec<_>>();
16768 self.fold_creases(ranges, true, window, cx);
16769 }
16770
16771 pub fn fold_ranges<T: ToOffset + Clone>(
16772 &mut self,
16773 ranges: Vec<Range<T>>,
16774 auto_scroll: bool,
16775 window: &mut Window,
16776 cx: &mut Context<Self>,
16777 ) {
16778 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16779 let ranges = ranges
16780 .into_iter()
16781 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16782 .collect::<Vec<_>>();
16783 self.fold_creases(ranges, auto_scroll, window, cx);
16784 }
16785
16786 pub fn fold_creases<T: ToOffset + Clone>(
16787 &mut self,
16788 creases: Vec<Crease<T>>,
16789 auto_scroll: bool,
16790 _window: &mut Window,
16791 cx: &mut Context<Self>,
16792 ) {
16793 if creases.is_empty() {
16794 return;
16795 }
16796
16797 let mut buffers_affected = HashSet::default();
16798 let multi_buffer = self.buffer().read(cx);
16799 for crease in &creases {
16800 if let Some((_, buffer, _)) =
16801 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16802 {
16803 buffers_affected.insert(buffer.read(cx).remote_id());
16804 };
16805 }
16806
16807 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16808
16809 if auto_scroll {
16810 self.request_autoscroll(Autoscroll::fit(), cx);
16811 }
16812
16813 cx.notify();
16814
16815 self.scrollbar_marker_state.dirty = true;
16816 self.folds_did_change(cx);
16817 }
16818
16819 /// Removes any folds whose ranges intersect any of the given ranges.
16820 pub fn unfold_ranges<T: ToOffset + Clone>(
16821 &mut self,
16822 ranges: &[Range<T>],
16823 inclusive: bool,
16824 auto_scroll: bool,
16825 cx: &mut Context<Self>,
16826 ) {
16827 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16828 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16829 });
16830 self.folds_did_change(cx);
16831 }
16832
16833 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16834 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16835 return;
16836 }
16837 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16838 self.display_map.update(cx, |display_map, cx| {
16839 display_map.fold_buffers([buffer_id], cx)
16840 });
16841 cx.emit(EditorEvent::BufferFoldToggled {
16842 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16843 folded: true,
16844 });
16845 cx.notify();
16846 }
16847
16848 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16849 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16850 return;
16851 }
16852 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16853 self.display_map.update(cx, |display_map, cx| {
16854 display_map.unfold_buffers([buffer_id], cx);
16855 });
16856 cx.emit(EditorEvent::BufferFoldToggled {
16857 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16858 folded: false,
16859 });
16860 cx.notify();
16861 }
16862
16863 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16864 self.display_map.read(cx).is_buffer_folded(buffer)
16865 }
16866
16867 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16868 self.display_map.read(cx).folded_buffers()
16869 }
16870
16871 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16872 self.display_map.update(cx, |display_map, cx| {
16873 display_map.disable_header_for_buffer(buffer_id, cx);
16874 });
16875 cx.notify();
16876 }
16877
16878 /// Removes any folds with the given ranges.
16879 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16880 &mut self,
16881 ranges: &[Range<T>],
16882 type_id: TypeId,
16883 auto_scroll: bool,
16884 cx: &mut Context<Self>,
16885 ) {
16886 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16887 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16888 });
16889 self.folds_did_change(cx);
16890 }
16891
16892 fn remove_folds_with<T: ToOffset + Clone>(
16893 &mut self,
16894 ranges: &[Range<T>],
16895 auto_scroll: bool,
16896 cx: &mut Context<Self>,
16897 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16898 ) {
16899 if ranges.is_empty() {
16900 return;
16901 }
16902
16903 let mut buffers_affected = HashSet::default();
16904 let multi_buffer = self.buffer().read(cx);
16905 for range in ranges {
16906 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16907 buffers_affected.insert(buffer.read(cx).remote_id());
16908 };
16909 }
16910
16911 self.display_map.update(cx, update);
16912
16913 if auto_scroll {
16914 self.request_autoscroll(Autoscroll::fit(), cx);
16915 }
16916
16917 cx.notify();
16918 self.scrollbar_marker_state.dirty = true;
16919 self.active_indent_guides_state.dirty = true;
16920 }
16921
16922 pub fn update_fold_widths(
16923 &mut self,
16924 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16925 cx: &mut Context<Self>,
16926 ) -> bool {
16927 self.display_map
16928 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16929 }
16930
16931 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16932 self.display_map.read(cx).fold_placeholder.clone()
16933 }
16934
16935 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16936 self.buffer.update(cx, |buffer, cx| {
16937 buffer.set_all_diff_hunks_expanded(cx);
16938 });
16939 }
16940
16941 pub fn expand_all_diff_hunks(
16942 &mut self,
16943 _: &ExpandAllDiffHunks,
16944 _window: &mut Window,
16945 cx: &mut Context<Self>,
16946 ) {
16947 self.buffer.update(cx, |buffer, cx| {
16948 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16949 });
16950 }
16951
16952 pub fn toggle_selected_diff_hunks(
16953 &mut self,
16954 _: &ToggleSelectedDiffHunks,
16955 _window: &mut Window,
16956 cx: &mut Context<Self>,
16957 ) {
16958 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16959 self.toggle_diff_hunks_in_ranges(ranges, cx);
16960 }
16961
16962 pub fn diff_hunks_in_ranges<'a>(
16963 &'a self,
16964 ranges: &'a [Range<Anchor>],
16965 buffer: &'a MultiBufferSnapshot,
16966 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16967 ranges.iter().flat_map(move |range| {
16968 let end_excerpt_id = range.end.excerpt_id;
16969 let range = range.to_point(buffer);
16970 let mut peek_end = range.end;
16971 if range.end.row < buffer.max_row().0 {
16972 peek_end = Point::new(range.end.row + 1, 0);
16973 }
16974 buffer
16975 .diff_hunks_in_range(range.start..peek_end)
16976 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16977 })
16978 }
16979
16980 pub fn has_stageable_diff_hunks_in_ranges(
16981 &self,
16982 ranges: &[Range<Anchor>],
16983 snapshot: &MultiBufferSnapshot,
16984 ) -> bool {
16985 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16986 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16987 }
16988
16989 pub fn toggle_staged_selected_diff_hunks(
16990 &mut self,
16991 _: &::git::ToggleStaged,
16992 _: &mut Window,
16993 cx: &mut Context<Self>,
16994 ) {
16995 let snapshot = self.buffer.read(cx).snapshot(cx);
16996 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16997 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16998 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16999 }
17000
17001 pub fn set_render_diff_hunk_controls(
17002 &mut self,
17003 render_diff_hunk_controls: RenderDiffHunkControlsFn,
17004 cx: &mut Context<Self>,
17005 ) {
17006 self.render_diff_hunk_controls = render_diff_hunk_controls;
17007 cx.notify();
17008 }
17009
17010 pub fn stage_and_next(
17011 &mut self,
17012 _: &::git::StageAndNext,
17013 window: &mut Window,
17014 cx: &mut Context<Self>,
17015 ) {
17016 self.do_stage_or_unstage_and_next(true, window, cx);
17017 }
17018
17019 pub fn unstage_and_next(
17020 &mut self,
17021 _: &::git::UnstageAndNext,
17022 window: &mut Window,
17023 cx: &mut Context<Self>,
17024 ) {
17025 self.do_stage_or_unstage_and_next(false, window, cx);
17026 }
17027
17028 pub fn stage_or_unstage_diff_hunks(
17029 &mut self,
17030 stage: bool,
17031 ranges: Vec<Range<Anchor>>,
17032 cx: &mut Context<Self>,
17033 ) {
17034 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
17035 cx.spawn(async move |this, cx| {
17036 task.await?;
17037 this.update(cx, |this, cx| {
17038 let snapshot = this.buffer.read(cx).snapshot(cx);
17039 let chunk_by = this
17040 .diff_hunks_in_ranges(&ranges, &snapshot)
17041 .chunk_by(|hunk| hunk.buffer_id);
17042 for (buffer_id, hunks) in &chunk_by {
17043 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
17044 }
17045 })
17046 })
17047 .detach_and_log_err(cx);
17048 }
17049
17050 fn save_buffers_for_ranges_if_needed(
17051 &mut self,
17052 ranges: &[Range<Anchor>],
17053 cx: &mut Context<Editor>,
17054 ) -> Task<Result<()>> {
17055 let multibuffer = self.buffer.read(cx);
17056 let snapshot = multibuffer.read(cx);
17057 let buffer_ids: HashSet<_> = ranges
17058 .iter()
17059 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
17060 .collect();
17061 drop(snapshot);
17062
17063 let mut buffers = HashSet::default();
17064 for buffer_id in buffer_ids {
17065 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
17066 let buffer = buffer_entity.read(cx);
17067 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
17068 {
17069 buffers.insert(buffer_entity);
17070 }
17071 }
17072 }
17073
17074 if let Some(project) = &self.project {
17075 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
17076 } else {
17077 Task::ready(Ok(()))
17078 }
17079 }
17080
17081 fn do_stage_or_unstage_and_next(
17082 &mut self,
17083 stage: bool,
17084 window: &mut Window,
17085 cx: &mut Context<Self>,
17086 ) {
17087 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
17088
17089 if ranges.iter().any(|range| range.start != range.end) {
17090 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17091 return;
17092 }
17093
17094 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
17095 let snapshot = self.snapshot(window, cx);
17096 let position = self.selections.newest::<Point>(cx).head();
17097 let mut row = snapshot
17098 .buffer_snapshot
17099 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
17100 .find(|hunk| hunk.row_range.start.0 > position.row)
17101 .map(|hunk| hunk.row_range.start);
17102
17103 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
17104 // Outside of the project diff editor, wrap around to the beginning.
17105 if !all_diff_hunks_expanded {
17106 row = row.or_else(|| {
17107 snapshot
17108 .buffer_snapshot
17109 .diff_hunks_in_range(Point::zero()..position)
17110 .find(|hunk| hunk.row_range.end.0 < position.row)
17111 .map(|hunk| hunk.row_range.start)
17112 });
17113 }
17114
17115 if let Some(row) = row {
17116 let destination = Point::new(row.0, 0);
17117 let autoscroll = Autoscroll::center();
17118
17119 self.unfold_ranges(&[destination..destination], false, false, cx);
17120 self.change_selections(Some(autoscroll), window, cx, |s| {
17121 s.select_ranges([destination..destination]);
17122 });
17123 }
17124 }
17125
17126 fn do_stage_or_unstage(
17127 &self,
17128 stage: bool,
17129 buffer_id: BufferId,
17130 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17131 cx: &mut App,
17132 ) -> Option<()> {
17133 let project = self.project.as_ref()?;
17134 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17135 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17136 let buffer_snapshot = buffer.read(cx).snapshot();
17137 let file_exists = buffer_snapshot
17138 .file()
17139 .is_some_and(|file| file.disk_state().exists());
17140 diff.update(cx, |diff, cx| {
17141 diff.stage_or_unstage_hunks(
17142 stage,
17143 &hunks
17144 .map(|hunk| buffer_diff::DiffHunk {
17145 buffer_range: hunk.buffer_range,
17146 diff_base_byte_range: hunk.diff_base_byte_range,
17147 secondary_status: hunk.secondary_status,
17148 range: Point::zero()..Point::zero(), // unused
17149 })
17150 .collect::<Vec<_>>(),
17151 &buffer_snapshot,
17152 file_exists,
17153 cx,
17154 )
17155 });
17156 None
17157 }
17158
17159 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17160 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17161 self.buffer
17162 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17163 }
17164
17165 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17166 self.buffer.update(cx, |buffer, cx| {
17167 let ranges = vec![Anchor::min()..Anchor::max()];
17168 if !buffer.all_diff_hunks_expanded()
17169 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17170 {
17171 buffer.collapse_diff_hunks(ranges, cx);
17172 true
17173 } else {
17174 false
17175 }
17176 })
17177 }
17178
17179 fn toggle_diff_hunks_in_ranges(
17180 &mut self,
17181 ranges: Vec<Range<Anchor>>,
17182 cx: &mut Context<Editor>,
17183 ) {
17184 self.buffer.update(cx, |buffer, cx| {
17185 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17186 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17187 })
17188 }
17189
17190 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17191 self.buffer.update(cx, |buffer, cx| {
17192 let snapshot = buffer.snapshot(cx);
17193 let excerpt_id = range.end.excerpt_id;
17194 let point_range = range.to_point(&snapshot);
17195 let expand = !buffer.single_hunk_is_expanded(range, cx);
17196 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17197 })
17198 }
17199
17200 pub(crate) fn apply_all_diff_hunks(
17201 &mut self,
17202 _: &ApplyAllDiffHunks,
17203 window: &mut Window,
17204 cx: &mut Context<Self>,
17205 ) {
17206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17207
17208 let buffers = self.buffer.read(cx).all_buffers();
17209 for branch_buffer in buffers {
17210 branch_buffer.update(cx, |branch_buffer, cx| {
17211 branch_buffer.merge_into_base(Vec::new(), cx);
17212 });
17213 }
17214
17215 if let Some(project) = self.project.clone() {
17216 self.save(
17217 SaveOptions {
17218 format: true,
17219 autosave: false,
17220 },
17221 project,
17222 window,
17223 cx,
17224 )
17225 .detach_and_log_err(cx);
17226 }
17227 }
17228
17229 pub(crate) fn apply_selected_diff_hunks(
17230 &mut self,
17231 _: &ApplyDiffHunk,
17232 window: &mut Window,
17233 cx: &mut Context<Self>,
17234 ) {
17235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17236 let snapshot = self.snapshot(window, cx);
17237 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17238 let mut ranges_by_buffer = HashMap::default();
17239 self.transact(window, cx, |editor, _window, cx| {
17240 for hunk in hunks {
17241 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17242 ranges_by_buffer
17243 .entry(buffer.clone())
17244 .or_insert_with(Vec::new)
17245 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17246 }
17247 }
17248
17249 for (buffer, ranges) in ranges_by_buffer {
17250 buffer.update(cx, |buffer, cx| {
17251 buffer.merge_into_base(ranges, cx);
17252 });
17253 }
17254 });
17255
17256 if let Some(project) = self.project.clone() {
17257 self.save(
17258 SaveOptions {
17259 format: true,
17260 autosave: false,
17261 },
17262 project,
17263 window,
17264 cx,
17265 )
17266 .detach_and_log_err(cx);
17267 }
17268 }
17269
17270 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17271 if hovered != self.gutter_hovered {
17272 self.gutter_hovered = hovered;
17273 cx.notify();
17274 }
17275 }
17276
17277 pub fn insert_blocks(
17278 &mut self,
17279 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17280 autoscroll: Option<Autoscroll>,
17281 cx: &mut Context<Self>,
17282 ) -> Vec<CustomBlockId> {
17283 let blocks = self
17284 .display_map
17285 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17286 if let Some(autoscroll) = autoscroll {
17287 self.request_autoscroll(autoscroll, cx);
17288 }
17289 cx.notify();
17290 blocks
17291 }
17292
17293 pub fn resize_blocks(
17294 &mut self,
17295 heights: HashMap<CustomBlockId, u32>,
17296 autoscroll: Option<Autoscroll>,
17297 cx: &mut Context<Self>,
17298 ) {
17299 self.display_map
17300 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17301 if let Some(autoscroll) = autoscroll {
17302 self.request_autoscroll(autoscroll, cx);
17303 }
17304 cx.notify();
17305 }
17306
17307 pub fn replace_blocks(
17308 &mut self,
17309 renderers: HashMap<CustomBlockId, RenderBlock>,
17310 autoscroll: Option<Autoscroll>,
17311 cx: &mut Context<Self>,
17312 ) {
17313 self.display_map
17314 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17315 if let Some(autoscroll) = autoscroll {
17316 self.request_autoscroll(autoscroll, cx);
17317 }
17318 cx.notify();
17319 }
17320
17321 pub fn remove_blocks(
17322 &mut self,
17323 block_ids: HashSet<CustomBlockId>,
17324 autoscroll: Option<Autoscroll>,
17325 cx: &mut Context<Self>,
17326 ) {
17327 self.display_map.update(cx, |display_map, cx| {
17328 display_map.remove_blocks(block_ids, cx)
17329 });
17330 if let Some(autoscroll) = autoscroll {
17331 self.request_autoscroll(autoscroll, cx);
17332 }
17333 cx.notify();
17334 }
17335
17336 pub fn row_for_block(
17337 &self,
17338 block_id: CustomBlockId,
17339 cx: &mut Context<Self>,
17340 ) -> Option<DisplayRow> {
17341 self.display_map
17342 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17343 }
17344
17345 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17346 self.focused_block = Some(focused_block);
17347 }
17348
17349 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17350 self.focused_block.take()
17351 }
17352
17353 pub fn insert_creases(
17354 &mut self,
17355 creases: impl IntoIterator<Item = Crease<Anchor>>,
17356 cx: &mut Context<Self>,
17357 ) -> Vec<CreaseId> {
17358 self.display_map
17359 .update(cx, |map, cx| map.insert_creases(creases, cx))
17360 }
17361
17362 pub fn remove_creases(
17363 &mut self,
17364 ids: impl IntoIterator<Item = CreaseId>,
17365 cx: &mut Context<Self>,
17366 ) -> Vec<(CreaseId, Range<Anchor>)> {
17367 self.display_map
17368 .update(cx, |map, cx| map.remove_creases(ids, cx))
17369 }
17370
17371 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17372 self.display_map
17373 .update(cx, |map, cx| map.snapshot(cx))
17374 .longest_row()
17375 }
17376
17377 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17378 self.display_map
17379 .update(cx, |map, cx| map.snapshot(cx))
17380 .max_point()
17381 }
17382
17383 pub fn text(&self, cx: &App) -> String {
17384 self.buffer.read(cx).read(cx).text()
17385 }
17386
17387 pub fn is_empty(&self, cx: &App) -> bool {
17388 self.buffer.read(cx).read(cx).is_empty()
17389 }
17390
17391 pub fn text_option(&self, cx: &App) -> Option<String> {
17392 let text = self.text(cx);
17393 let text = text.trim();
17394
17395 if text.is_empty() {
17396 return None;
17397 }
17398
17399 Some(text.to_string())
17400 }
17401
17402 pub fn set_text(
17403 &mut self,
17404 text: impl Into<Arc<str>>,
17405 window: &mut Window,
17406 cx: &mut Context<Self>,
17407 ) {
17408 self.transact(window, cx, |this, _, cx| {
17409 this.buffer
17410 .read(cx)
17411 .as_singleton()
17412 .expect("you can only call set_text on editors for singleton buffers")
17413 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17414 });
17415 }
17416
17417 pub fn display_text(&self, cx: &mut App) -> String {
17418 self.display_map
17419 .update(cx, |map, cx| map.snapshot(cx))
17420 .text()
17421 }
17422
17423 fn create_minimap(
17424 &self,
17425 minimap_settings: MinimapSettings,
17426 window: &mut Window,
17427 cx: &mut Context<Self>,
17428 ) -> Option<Entity<Self>> {
17429 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17430 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17431 }
17432
17433 fn initialize_new_minimap(
17434 &self,
17435 minimap_settings: MinimapSettings,
17436 window: &mut Window,
17437 cx: &mut Context<Self>,
17438 ) -> Entity<Self> {
17439 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17440
17441 let mut minimap = Editor::new_internal(
17442 EditorMode::Minimap {
17443 parent: cx.weak_entity(),
17444 },
17445 self.buffer.clone(),
17446 self.project.clone(),
17447 Some(self.display_map.clone()),
17448 window,
17449 cx,
17450 );
17451 minimap.scroll_manager.clone_state(&self.scroll_manager);
17452 minimap.set_text_style_refinement(TextStyleRefinement {
17453 font_size: Some(MINIMAP_FONT_SIZE),
17454 font_weight: Some(MINIMAP_FONT_WEIGHT),
17455 ..Default::default()
17456 });
17457 minimap.update_minimap_configuration(minimap_settings, cx);
17458 cx.new(|_| minimap)
17459 }
17460
17461 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17462 let current_line_highlight = minimap_settings
17463 .current_line_highlight
17464 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17465 self.set_current_line_highlight(Some(current_line_highlight));
17466 }
17467
17468 pub fn minimap(&self) -> Option<&Entity<Self>> {
17469 self.minimap
17470 .as_ref()
17471 .filter(|_| self.minimap_visibility.visible())
17472 }
17473
17474 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17475 let mut wrap_guides = smallvec![];
17476
17477 if self.show_wrap_guides == Some(false) {
17478 return wrap_guides;
17479 }
17480
17481 let settings = self.buffer.read(cx).language_settings(cx);
17482 if settings.show_wrap_guides {
17483 match self.soft_wrap_mode(cx) {
17484 SoftWrap::Column(soft_wrap) => {
17485 wrap_guides.push((soft_wrap as usize, true));
17486 }
17487 SoftWrap::Bounded(soft_wrap) => {
17488 wrap_guides.push((soft_wrap as usize, true));
17489 }
17490 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17491 }
17492 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17493 }
17494
17495 wrap_guides
17496 }
17497
17498 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17499 let settings = self.buffer.read(cx).language_settings(cx);
17500 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17501 match mode {
17502 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17503 SoftWrap::None
17504 }
17505 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17506 language_settings::SoftWrap::PreferredLineLength => {
17507 SoftWrap::Column(settings.preferred_line_length)
17508 }
17509 language_settings::SoftWrap::Bounded => {
17510 SoftWrap::Bounded(settings.preferred_line_length)
17511 }
17512 }
17513 }
17514
17515 pub fn set_soft_wrap_mode(
17516 &mut self,
17517 mode: language_settings::SoftWrap,
17518
17519 cx: &mut Context<Self>,
17520 ) {
17521 self.soft_wrap_mode_override = Some(mode);
17522 cx.notify();
17523 }
17524
17525 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17526 self.hard_wrap = hard_wrap;
17527 cx.notify();
17528 }
17529
17530 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17531 self.text_style_refinement = Some(style);
17532 }
17533
17534 /// called by the Element so we know what style we were most recently rendered with.
17535 pub(crate) fn set_style(
17536 &mut self,
17537 style: EditorStyle,
17538 window: &mut Window,
17539 cx: &mut Context<Self>,
17540 ) {
17541 // We intentionally do not inform the display map about the minimap style
17542 // so that wrapping is not recalculated and stays consistent for the editor
17543 // and its linked minimap.
17544 if !self.mode.is_minimap() {
17545 let rem_size = window.rem_size();
17546 self.display_map.update(cx, |map, cx| {
17547 map.set_font(
17548 style.text.font(),
17549 style.text.font_size.to_pixels(rem_size),
17550 cx,
17551 )
17552 });
17553 }
17554 self.style = Some(style);
17555 }
17556
17557 pub fn style(&self) -> Option<&EditorStyle> {
17558 self.style.as_ref()
17559 }
17560
17561 // Called by the element. This method is not designed to be called outside of the editor
17562 // element's layout code because it does not notify when rewrapping is computed synchronously.
17563 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17564 self.display_map
17565 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17566 }
17567
17568 pub fn set_soft_wrap(&mut self) {
17569 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17570 }
17571
17572 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17573 if self.soft_wrap_mode_override.is_some() {
17574 self.soft_wrap_mode_override.take();
17575 } else {
17576 let soft_wrap = match self.soft_wrap_mode(cx) {
17577 SoftWrap::GitDiff => return,
17578 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17579 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17580 language_settings::SoftWrap::None
17581 }
17582 };
17583 self.soft_wrap_mode_override = Some(soft_wrap);
17584 }
17585 cx.notify();
17586 }
17587
17588 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17589 let Some(workspace) = self.workspace() else {
17590 return;
17591 };
17592 let fs = workspace.read(cx).app_state().fs.clone();
17593 let current_show = TabBarSettings::get_global(cx).show;
17594 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17595 setting.show = Some(!current_show);
17596 });
17597 }
17598
17599 pub fn toggle_indent_guides(
17600 &mut self,
17601 _: &ToggleIndentGuides,
17602 _: &mut Window,
17603 cx: &mut Context<Self>,
17604 ) {
17605 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17606 self.buffer
17607 .read(cx)
17608 .language_settings(cx)
17609 .indent_guides
17610 .enabled
17611 });
17612 self.show_indent_guides = Some(!currently_enabled);
17613 cx.notify();
17614 }
17615
17616 fn should_show_indent_guides(&self) -> Option<bool> {
17617 self.show_indent_guides
17618 }
17619
17620 pub fn toggle_line_numbers(
17621 &mut self,
17622 _: &ToggleLineNumbers,
17623 _: &mut Window,
17624 cx: &mut Context<Self>,
17625 ) {
17626 let mut editor_settings = EditorSettings::get_global(cx).clone();
17627 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17628 EditorSettings::override_global(editor_settings, cx);
17629 }
17630
17631 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17632 if let Some(show_line_numbers) = self.show_line_numbers {
17633 return show_line_numbers;
17634 }
17635 EditorSettings::get_global(cx).gutter.line_numbers
17636 }
17637
17638 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17639 self.use_relative_line_numbers
17640 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17641 }
17642
17643 pub fn toggle_relative_line_numbers(
17644 &mut self,
17645 _: &ToggleRelativeLineNumbers,
17646 _: &mut Window,
17647 cx: &mut Context<Self>,
17648 ) {
17649 let is_relative = self.should_use_relative_line_numbers(cx);
17650 self.set_relative_line_number(Some(!is_relative), cx)
17651 }
17652
17653 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17654 self.use_relative_line_numbers = is_relative;
17655 cx.notify();
17656 }
17657
17658 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17659 self.show_gutter = show_gutter;
17660 cx.notify();
17661 }
17662
17663 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17664 self.show_scrollbars = ScrollbarAxes {
17665 horizontal: show,
17666 vertical: show,
17667 };
17668 cx.notify();
17669 }
17670
17671 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17672 self.show_scrollbars.vertical = show;
17673 cx.notify();
17674 }
17675
17676 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17677 self.show_scrollbars.horizontal = show;
17678 cx.notify();
17679 }
17680
17681 pub fn set_minimap_visibility(
17682 &mut self,
17683 minimap_visibility: MinimapVisibility,
17684 window: &mut Window,
17685 cx: &mut Context<Self>,
17686 ) {
17687 if self.minimap_visibility != minimap_visibility {
17688 if minimap_visibility.visible() && self.minimap.is_none() {
17689 let minimap_settings = EditorSettings::get_global(cx).minimap;
17690 self.minimap =
17691 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17692 }
17693 self.minimap_visibility = minimap_visibility;
17694 cx.notify();
17695 }
17696 }
17697
17698 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17699 self.set_show_scrollbars(false, cx);
17700 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17701 }
17702
17703 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17704 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17705 }
17706
17707 /// Normally the text in full mode and auto height editors is padded on the
17708 /// left side by roughly half a character width for improved hit testing.
17709 ///
17710 /// Use this method to disable this for cases where this is not wanted (e.g.
17711 /// if you want to align the editor text with some other text above or below)
17712 /// or if you want to add this padding to single-line editors.
17713 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17714 self.offset_content = offset_content;
17715 cx.notify();
17716 }
17717
17718 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17719 self.show_line_numbers = Some(show_line_numbers);
17720 cx.notify();
17721 }
17722
17723 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17724 self.disable_expand_excerpt_buttons = true;
17725 cx.notify();
17726 }
17727
17728 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17729 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17730 cx.notify();
17731 }
17732
17733 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17734 self.show_code_actions = Some(show_code_actions);
17735 cx.notify();
17736 }
17737
17738 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17739 self.show_runnables = Some(show_runnables);
17740 cx.notify();
17741 }
17742
17743 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17744 self.show_breakpoints = Some(show_breakpoints);
17745 cx.notify();
17746 }
17747
17748 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17749 if self.display_map.read(cx).masked != masked {
17750 self.display_map.update(cx, |map, _| map.masked = masked);
17751 }
17752 cx.notify()
17753 }
17754
17755 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17756 self.show_wrap_guides = Some(show_wrap_guides);
17757 cx.notify();
17758 }
17759
17760 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17761 self.show_indent_guides = Some(show_indent_guides);
17762 cx.notify();
17763 }
17764
17765 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17766 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17767 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17768 if let Some(dir) = file.abs_path(cx).parent() {
17769 return Some(dir.to_owned());
17770 }
17771 }
17772
17773 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17774 return Some(project_path.path.to_path_buf());
17775 }
17776 }
17777
17778 None
17779 }
17780
17781 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17782 self.active_excerpt(cx)?
17783 .1
17784 .read(cx)
17785 .file()
17786 .and_then(|f| f.as_local())
17787 }
17788
17789 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17790 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17791 let buffer = buffer.read(cx);
17792 if let Some(project_path) = buffer.project_path(cx) {
17793 let project = self.project.as_ref()?.read(cx);
17794 project.absolute_path(&project_path, cx)
17795 } else {
17796 buffer
17797 .file()
17798 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17799 }
17800 })
17801 }
17802
17803 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17804 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17805 let project_path = buffer.read(cx).project_path(cx)?;
17806 let project = self.project.as_ref()?.read(cx);
17807 let entry = project.entry_for_path(&project_path, cx)?;
17808 let path = entry.path.to_path_buf();
17809 Some(path)
17810 })
17811 }
17812
17813 pub fn reveal_in_finder(
17814 &mut self,
17815 _: &RevealInFileManager,
17816 _window: &mut Window,
17817 cx: &mut Context<Self>,
17818 ) {
17819 if let Some(target) = self.target_file(cx) {
17820 cx.reveal_path(&target.abs_path(cx));
17821 }
17822 }
17823
17824 pub fn copy_path(
17825 &mut self,
17826 _: &zed_actions::workspace::CopyPath,
17827 _window: &mut Window,
17828 cx: &mut Context<Self>,
17829 ) {
17830 if let Some(path) = self.target_file_abs_path(cx) {
17831 if let Some(path) = path.to_str() {
17832 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17833 }
17834 }
17835 }
17836
17837 pub fn copy_relative_path(
17838 &mut self,
17839 _: &zed_actions::workspace::CopyRelativePath,
17840 _window: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) {
17843 if let Some(path) = self.target_file_path(cx) {
17844 if let Some(path) = path.to_str() {
17845 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17846 }
17847 }
17848 }
17849
17850 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17851 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17852 buffer.read(cx).project_path(cx)
17853 } else {
17854 None
17855 }
17856 }
17857
17858 // Returns true if the editor handled a go-to-line request
17859 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17860 maybe!({
17861 let breakpoint_store = self.breakpoint_store.as_ref()?;
17862
17863 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17864 else {
17865 self.clear_row_highlights::<ActiveDebugLine>();
17866 return None;
17867 };
17868
17869 let position = active_stack_frame.position;
17870 let buffer_id = position.buffer_id?;
17871 let snapshot = self
17872 .project
17873 .as_ref()?
17874 .read(cx)
17875 .buffer_for_id(buffer_id, cx)?
17876 .read(cx)
17877 .snapshot();
17878
17879 let mut handled = false;
17880 for (id, ExcerptRange { context, .. }) in
17881 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17882 {
17883 if context.start.cmp(&position, &snapshot).is_ge()
17884 || context.end.cmp(&position, &snapshot).is_lt()
17885 {
17886 continue;
17887 }
17888 let snapshot = self.buffer.read(cx).snapshot(cx);
17889 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17890
17891 handled = true;
17892 self.clear_row_highlights::<ActiveDebugLine>();
17893
17894 self.go_to_line::<ActiveDebugLine>(
17895 multibuffer_anchor,
17896 Some(cx.theme().colors().editor_debugger_active_line_background),
17897 window,
17898 cx,
17899 );
17900
17901 cx.notify();
17902 }
17903
17904 handled.then_some(())
17905 })
17906 .is_some()
17907 }
17908
17909 pub fn copy_file_name_without_extension(
17910 &mut self,
17911 _: &CopyFileNameWithoutExtension,
17912 _: &mut Window,
17913 cx: &mut Context<Self>,
17914 ) {
17915 if let Some(file) = self.target_file(cx) {
17916 if let Some(file_stem) = file.path().file_stem() {
17917 if let Some(name) = file_stem.to_str() {
17918 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17919 }
17920 }
17921 }
17922 }
17923
17924 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17925 if let Some(file) = self.target_file(cx) {
17926 if let Some(file_name) = file.path().file_name() {
17927 if let Some(name) = file_name.to_str() {
17928 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17929 }
17930 }
17931 }
17932 }
17933
17934 pub fn toggle_git_blame(
17935 &mut self,
17936 _: &::git::Blame,
17937 window: &mut Window,
17938 cx: &mut Context<Self>,
17939 ) {
17940 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17941
17942 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17943 self.start_git_blame(true, window, cx);
17944 }
17945
17946 cx.notify();
17947 }
17948
17949 pub fn toggle_git_blame_inline(
17950 &mut self,
17951 _: &ToggleGitBlameInline,
17952 window: &mut Window,
17953 cx: &mut Context<Self>,
17954 ) {
17955 self.toggle_git_blame_inline_internal(true, window, cx);
17956 cx.notify();
17957 }
17958
17959 pub fn open_git_blame_commit(
17960 &mut self,
17961 _: &OpenGitBlameCommit,
17962 window: &mut Window,
17963 cx: &mut Context<Self>,
17964 ) {
17965 self.open_git_blame_commit_internal(window, cx);
17966 }
17967
17968 fn open_git_blame_commit_internal(
17969 &mut self,
17970 window: &mut Window,
17971 cx: &mut Context<Self>,
17972 ) -> Option<()> {
17973 let blame = self.blame.as_ref()?;
17974 let snapshot = self.snapshot(window, cx);
17975 let cursor = self.selections.newest::<Point>(cx).head();
17976 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17977 let blame_entry = blame
17978 .update(cx, |blame, cx| {
17979 blame
17980 .blame_for_rows(
17981 &[RowInfo {
17982 buffer_id: Some(buffer.remote_id()),
17983 buffer_row: Some(point.row),
17984 ..Default::default()
17985 }],
17986 cx,
17987 )
17988 .next()
17989 })
17990 .flatten()?;
17991 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17992 let repo = blame.read(cx).repository(cx)?;
17993 let workspace = self.workspace()?.downgrade();
17994 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17995 None
17996 }
17997
17998 pub fn git_blame_inline_enabled(&self) -> bool {
17999 self.git_blame_inline_enabled
18000 }
18001
18002 pub fn toggle_selection_menu(
18003 &mut self,
18004 _: &ToggleSelectionMenu,
18005 _: &mut Window,
18006 cx: &mut Context<Self>,
18007 ) {
18008 self.show_selection_menu = self
18009 .show_selection_menu
18010 .map(|show_selections_menu| !show_selections_menu)
18011 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
18012
18013 cx.notify();
18014 }
18015
18016 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
18017 self.show_selection_menu
18018 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
18019 }
18020
18021 fn start_git_blame(
18022 &mut self,
18023 user_triggered: bool,
18024 window: &mut Window,
18025 cx: &mut Context<Self>,
18026 ) {
18027 if let Some(project) = self.project.as_ref() {
18028 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
18029 return;
18030 };
18031
18032 if buffer.read(cx).file().is_none() {
18033 return;
18034 }
18035
18036 let focused = self.focus_handle(cx).contains_focused(window, cx);
18037
18038 let project = project.clone();
18039 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
18040 self.blame_subscription =
18041 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
18042 self.blame = Some(blame);
18043 }
18044 }
18045
18046 fn toggle_git_blame_inline_internal(
18047 &mut self,
18048 user_triggered: bool,
18049 window: &mut Window,
18050 cx: &mut Context<Self>,
18051 ) {
18052 if self.git_blame_inline_enabled {
18053 self.git_blame_inline_enabled = false;
18054 self.show_git_blame_inline = false;
18055 self.show_git_blame_inline_delay_task.take();
18056 } else {
18057 self.git_blame_inline_enabled = true;
18058 self.start_git_blame_inline(user_triggered, window, cx);
18059 }
18060
18061 cx.notify();
18062 }
18063
18064 fn start_git_blame_inline(
18065 &mut self,
18066 user_triggered: bool,
18067 window: &mut Window,
18068 cx: &mut Context<Self>,
18069 ) {
18070 self.start_git_blame(user_triggered, window, cx);
18071
18072 if ProjectSettings::get_global(cx)
18073 .git
18074 .inline_blame_delay()
18075 .is_some()
18076 {
18077 self.start_inline_blame_timer(window, cx);
18078 } else {
18079 self.show_git_blame_inline = true
18080 }
18081 }
18082
18083 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
18084 self.blame.as_ref()
18085 }
18086
18087 pub fn show_git_blame_gutter(&self) -> bool {
18088 self.show_git_blame_gutter
18089 }
18090
18091 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
18092 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
18093 }
18094
18095 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
18096 self.show_git_blame_inline
18097 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
18098 && !self.newest_selection_head_on_empty_line(cx)
18099 && self.has_blame_entries(cx)
18100 }
18101
18102 fn has_blame_entries(&self, cx: &App) -> bool {
18103 self.blame()
18104 .map_or(false, |blame| blame.read(cx).has_generated_entries())
18105 }
18106
18107 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
18108 let cursor_anchor = self.selections.newest_anchor().head();
18109
18110 let snapshot = self.buffer.read(cx).snapshot(cx);
18111 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
18112
18113 snapshot.line_len(buffer_row) == 0
18114 }
18115
18116 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
18117 let buffer_and_selection = maybe!({
18118 let selection = self.selections.newest::<Point>(cx);
18119 let selection_range = selection.range();
18120
18121 let multi_buffer = self.buffer().read(cx);
18122 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18123 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
18124
18125 let (buffer, range, _) = if selection.reversed {
18126 buffer_ranges.first()
18127 } else {
18128 buffer_ranges.last()
18129 }?;
18130
18131 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18132 ..text::ToPoint::to_point(&range.end, &buffer).row;
18133 Some((
18134 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18135 selection,
18136 ))
18137 });
18138
18139 let Some((buffer, selection)) = buffer_and_selection else {
18140 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18141 };
18142
18143 let Some(project) = self.project.as_ref() else {
18144 return Task::ready(Err(anyhow!("editor does not have project")));
18145 };
18146
18147 project.update(cx, |project, cx| {
18148 project.get_permalink_to_line(&buffer, selection, cx)
18149 })
18150 }
18151
18152 pub fn copy_permalink_to_line(
18153 &mut self,
18154 _: &CopyPermalinkToLine,
18155 window: &mut Window,
18156 cx: &mut Context<Self>,
18157 ) {
18158 let permalink_task = self.get_permalink_to_line(cx);
18159 let workspace = self.workspace();
18160
18161 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18162 Ok(permalink) => {
18163 cx.update(|_, cx| {
18164 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18165 })
18166 .ok();
18167 }
18168 Err(err) => {
18169 let message = format!("Failed to copy permalink: {err}");
18170
18171 anyhow::Result::<()>::Err(err).log_err();
18172
18173 if let Some(workspace) = workspace {
18174 workspace
18175 .update_in(cx, |workspace, _, cx| {
18176 struct CopyPermalinkToLine;
18177
18178 workspace.show_toast(
18179 Toast::new(
18180 NotificationId::unique::<CopyPermalinkToLine>(),
18181 message,
18182 ),
18183 cx,
18184 )
18185 })
18186 .ok();
18187 }
18188 }
18189 })
18190 .detach();
18191 }
18192
18193 pub fn copy_file_location(
18194 &mut self,
18195 _: &CopyFileLocation,
18196 _: &mut Window,
18197 cx: &mut Context<Self>,
18198 ) {
18199 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18200 if let Some(file) = self.target_file(cx) {
18201 if let Some(path) = file.path().to_str() {
18202 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18203 }
18204 }
18205 }
18206
18207 pub fn open_permalink_to_line(
18208 &mut self,
18209 _: &OpenPermalinkToLine,
18210 window: &mut Window,
18211 cx: &mut Context<Self>,
18212 ) {
18213 let permalink_task = self.get_permalink_to_line(cx);
18214 let workspace = self.workspace();
18215
18216 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18217 Ok(permalink) => {
18218 cx.update(|_, cx| {
18219 cx.open_url(permalink.as_ref());
18220 })
18221 .ok();
18222 }
18223 Err(err) => {
18224 let message = format!("Failed to open permalink: {err}");
18225
18226 anyhow::Result::<()>::Err(err).log_err();
18227
18228 if let Some(workspace) = workspace {
18229 workspace
18230 .update(cx, |workspace, cx| {
18231 struct OpenPermalinkToLine;
18232
18233 workspace.show_toast(
18234 Toast::new(
18235 NotificationId::unique::<OpenPermalinkToLine>(),
18236 message,
18237 ),
18238 cx,
18239 )
18240 })
18241 .ok();
18242 }
18243 }
18244 })
18245 .detach();
18246 }
18247
18248 pub fn insert_uuid_v4(
18249 &mut self,
18250 _: &InsertUuidV4,
18251 window: &mut Window,
18252 cx: &mut Context<Self>,
18253 ) {
18254 self.insert_uuid(UuidVersion::V4, window, cx);
18255 }
18256
18257 pub fn insert_uuid_v7(
18258 &mut self,
18259 _: &InsertUuidV7,
18260 window: &mut Window,
18261 cx: &mut Context<Self>,
18262 ) {
18263 self.insert_uuid(UuidVersion::V7, window, cx);
18264 }
18265
18266 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18267 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18268 self.transact(window, cx, |this, window, cx| {
18269 let edits = this
18270 .selections
18271 .all::<Point>(cx)
18272 .into_iter()
18273 .map(|selection| {
18274 let uuid = match version {
18275 UuidVersion::V4 => uuid::Uuid::new_v4(),
18276 UuidVersion::V7 => uuid::Uuid::now_v7(),
18277 };
18278
18279 (selection.range(), uuid.to_string())
18280 });
18281 this.edit(edits, cx);
18282 this.refresh_inline_completion(true, false, window, cx);
18283 });
18284 }
18285
18286 pub fn open_selections_in_multibuffer(
18287 &mut self,
18288 _: &OpenSelectionsInMultibuffer,
18289 window: &mut Window,
18290 cx: &mut Context<Self>,
18291 ) {
18292 let multibuffer = self.buffer.read(cx);
18293
18294 let Some(buffer) = multibuffer.as_singleton() else {
18295 return;
18296 };
18297
18298 let Some(workspace) = self.workspace() else {
18299 return;
18300 };
18301
18302 let title = multibuffer.title(cx).to_string();
18303
18304 let locations = self
18305 .selections
18306 .all_anchors(cx)
18307 .into_iter()
18308 .map(|selection| Location {
18309 buffer: buffer.clone(),
18310 range: selection.start.text_anchor..selection.end.text_anchor,
18311 })
18312 .collect::<Vec<_>>();
18313
18314 cx.spawn_in(window, async move |_, cx| {
18315 workspace.update_in(cx, |workspace, window, cx| {
18316 Self::open_locations_in_multibuffer(
18317 workspace,
18318 locations,
18319 format!("Selections for '{title}'"),
18320 false,
18321 MultibufferSelectionMode::All,
18322 window,
18323 cx,
18324 );
18325 })
18326 })
18327 .detach();
18328 }
18329
18330 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18331 /// last highlight added will be used.
18332 ///
18333 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18334 pub fn highlight_rows<T: 'static>(
18335 &mut self,
18336 range: Range<Anchor>,
18337 color: Hsla,
18338 options: RowHighlightOptions,
18339 cx: &mut Context<Self>,
18340 ) {
18341 let snapshot = self.buffer().read(cx).snapshot(cx);
18342 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18343 let ix = row_highlights.binary_search_by(|highlight| {
18344 Ordering::Equal
18345 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18346 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18347 });
18348
18349 if let Err(mut ix) = ix {
18350 let index = post_inc(&mut self.highlight_order);
18351
18352 // If this range intersects with the preceding highlight, then merge it with
18353 // the preceding highlight. Otherwise insert a new highlight.
18354 let mut merged = false;
18355 if ix > 0 {
18356 let prev_highlight = &mut row_highlights[ix - 1];
18357 if prev_highlight
18358 .range
18359 .end
18360 .cmp(&range.start, &snapshot)
18361 .is_ge()
18362 {
18363 ix -= 1;
18364 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18365 prev_highlight.range.end = range.end;
18366 }
18367 merged = true;
18368 prev_highlight.index = index;
18369 prev_highlight.color = color;
18370 prev_highlight.options = options;
18371 }
18372 }
18373
18374 if !merged {
18375 row_highlights.insert(
18376 ix,
18377 RowHighlight {
18378 range: range.clone(),
18379 index,
18380 color,
18381 options,
18382 type_id: TypeId::of::<T>(),
18383 },
18384 );
18385 }
18386
18387 // If any of the following highlights intersect with this one, merge them.
18388 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18389 let highlight = &row_highlights[ix];
18390 if next_highlight
18391 .range
18392 .start
18393 .cmp(&highlight.range.end, &snapshot)
18394 .is_le()
18395 {
18396 if next_highlight
18397 .range
18398 .end
18399 .cmp(&highlight.range.end, &snapshot)
18400 .is_gt()
18401 {
18402 row_highlights[ix].range.end = next_highlight.range.end;
18403 }
18404 row_highlights.remove(ix + 1);
18405 } else {
18406 break;
18407 }
18408 }
18409 }
18410 }
18411
18412 /// Remove any highlighted row ranges of the given type that intersect the
18413 /// given ranges.
18414 pub fn remove_highlighted_rows<T: 'static>(
18415 &mut self,
18416 ranges_to_remove: Vec<Range<Anchor>>,
18417 cx: &mut Context<Self>,
18418 ) {
18419 let snapshot = self.buffer().read(cx).snapshot(cx);
18420 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18421 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18422 row_highlights.retain(|highlight| {
18423 while let Some(range_to_remove) = ranges_to_remove.peek() {
18424 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18425 Ordering::Less | Ordering::Equal => {
18426 ranges_to_remove.next();
18427 }
18428 Ordering::Greater => {
18429 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18430 Ordering::Less | Ordering::Equal => {
18431 return false;
18432 }
18433 Ordering::Greater => break,
18434 }
18435 }
18436 }
18437 }
18438
18439 true
18440 })
18441 }
18442
18443 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18444 pub fn clear_row_highlights<T: 'static>(&mut self) {
18445 self.highlighted_rows.remove(&TypeId::of::<T>());
18446 }
18447
18448 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18449 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18450 self.highlighted_rows
18451 .get(&TypeId::of::<T>())
18452 .map_or(&[] as &[_], |vec| vec.as_slice())
18453 .iter()
18454 .map(|highlight| (highlight.range.clone(), highlight.color))
18455 }
18456
18457 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18458 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18459 /// Allows to ignore certain kinds of highlights.
18460 pub fn highlighted_display_rows(
18461 &self,
18462 window: &mut Window,
18463 cx: &mut App,
18464 ) -> BTreeMap<DisplayRow, LineHighlight> {
18465 let snapshot = self.snapshot(window, cx);
18466 let mut used_highlight_orders = HashMap::default();
18467 self.highlighted_rows
18468 .iter()
18469 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18470 .fold(
18471 BTreeMap::<DisplayRow, LineHighlight>::new(),
18472 |mut unique_rows, highlight| {
18473 let start = highlight.range.start.to_display_point(&snapshot);
18474 let end = highlight.range.end.to_display_point(&snapshot);
18475 let start_row = start.row().0;
18476 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18477 && end.column() == 0
18478 {
18479 end.row().0.saturating_sub(1)
18480 } else {
18481 end.row().0
18482 };
18483 for row in start_row..=end_row {
18484 let used_index =
18485 used_highlight_orders.entry(row).or_insert(highlight.index);
18486 if highlight.index >= *used_index {
18487 *used_index = highlight.index;
18488 unique_rows.insert(
18489 DisplayRow(row),
18490 LineHighlight {
18491 include_gutter: highlight.options.include_gutter,
18492 border: None,
18493 background: highlight.color.into(),
18494 type_id: Some(highlight.type_id),
18495 },
18496 );
18497 }
18498 }
18499 unique_rows
18500 },
18501 )
18502 }
18503
18504 pub fn highlighted_display_row_for_autoscroll(
18505 &self,
18506 snapshot: &DisplaySnapshot,
18507 ) -> Option<DisplayRow> {
18508 self.highlighted_rows
18509 .values()
18510 .flat_map(|highlighted_rows| highlighted_rows.iter())
18511 .filter_map(|highlight| {
18512 if highlight.options.autoscroll {
18513 Some(highlight.range.start.to_display_point(snapshot).row())
18514 } else {
18515 None
18516 }
18517 })
18518 .min()
18519 }
18520
18521 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18522 self.highlight_background::<SearchWithinRange>(
18523 ranges,
18524 |theme| theme.colors().editor_document_highlight_read_background,
18525 cx,
18526 )
18527 }
18528
18529 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18530 self.breadcrumb_header = Some(new_header);
18531 }
18532
18533 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18534 self.clear_background_highlights::<SearchWithinRange>(cx);
18535 }
18536
18537 pub fn highlight_background<T: 'static>(
18538 &mut self,
18539 ranges: &[Range<Anchor>],
18540 color_fetcher: fn(&Theme) -> Hsla,
18541 cx: &mut Context<Self>,
18542 ) {
18543 let highlights = ranges
18544 .iter()
18545 .map(|range| BackgroundHighlight {
18546 range: range.clone(),
18547 color_fetcher,
18548 })
18549 .collect();
18550 self.background_highlights
18551 .insert(TypeId::of::<T>(), highlights);
18552 self.scrollbar_marker_state.dirty = true;
18553 cx.notify();
18554 }
18555
18556 pub fn highlight_background_ranges<T: 'static>(
18557 &mut self,
18558 background_highlights: Vec<BackgroundHighlight>,
18559 cx: &mut Context<'_, Editor>,
18560 ) {
18561 self.background_highlights
18562 .insert(TypeId::of::<T>(), background_highlights);
18563 self.scrollbar_marker_state.dirty = true;
18564 cx.notify();
18565 }
18566
18567 pub fn clear_background_highlights<T: 'static>(
18568 &mut self,
18569 cx: &mut Context<Self>,
18570 ) -> Option<Vec<BackgroundHighlight>> {
18571 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18572 if !text_highlights.is_empty() {
18573 self.scrollbar_marker_state.dirty = true;
18574 cx.notify();
18575 }
18576 Some(text_highlights)
18577 }
18578
18579 pub fn highlight_gutter<T: 'static>(
18580 &mut self,
18581 ranges: impl Into<Vec<Range<Anchor>>>,
18582 color_fetcher: fn(&App) -> Hsla,
18583 cx: &mut Context<Self>,
18584 ) {
18585 self.gutter_highlights
18586 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18587 cx.notify();
18588 }
18589
18590 pub fn clear_gutter_highlights<T: 'static>(
18591 &mut self,
18592 cx: &mut Context<Self>,
18593 ) -> Option<GutterHighlight> {
18594 cx.notify();
18595 self.gutter_highlights.remove(&TypeId::of::<T>())
18596 }
18597
18598 pub fn insert_gutter_highlight<T: 'static>(
18599 &mut self,
18600 range: Range<Anchor>,
18601 color_fetcher: fn(&App) -> Hsla,
18602 cx: &mut Context<Self>,
18603 ) {
18604 let snapshot = self.buffer().read(cx).snapshot(cx);
18605 let mut highlights = self
18606 .gutter_highlights
18607 .remove(&TypeId::of::<T>())
18608 .map(|(_, highlights)| highlights)
18609 .unwrap_or_default();
18610 let ix = highlights.binary_search_by(|highlight| {
18611 Ordering::Equal
18612 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18613 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18614 });
18615 if let Err(ix) = ix {
18616 highlights.insert(ix, range);
18617 }
18618 self.gutter_highlights
18619 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18620 }
18621
18622 pub fn remove_gutter_highlights<T: 'static>(
18623 &mut self,
18624 ranges_to_remove: Vec<Range<Anchor>>,
18625 cx: &mut Context<Self>,
18626 ) {
18627 let snapshot = self.buffer().read(cx).snapshot(cx);
18628 let Some((color_fetcher, mut gutter_highlights)) =
18629 self.gutter_highlights.remove(&TypeId::of::<T>())
18630 else {
18631 return;
18632 };
18633 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18634 gutter_highlights.retain(|highlight| {
18635 while let Some(range_to_remove) = ranges_to_remove.peek() {
18636 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18637 Ordering::Less | Ordering::Equal => {
18638 ranges_to_remove.next();
18639 }
18640 Ordering::Greater => {
18641 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18642 Ordering::Less | Ordering::Equal => {
18643 return false;
18644 }
18645 Ordering::Greater => break,
18646 }
18647 }
18648 }
18649 }
18650
18651 true
18652 });
18653 self.gutter_highlights
18654 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18655 }
18656
18657 #[cfg(feature = "test-support")]
18658 pub fn all_text_background_highlights(
18659 &self,
18660 window: &mut Window,
18661 cx: &mut Context<Self>,
18662 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18663 let snapshot = self.snapshot(window, cx);
18664 let buffer = &snapshot.buffer_snapshot;
18665 let start = buffer.anchor_before(0);
18666 let end = buffer.anchor_after(buffer.len());
18667 let theme = cx.theme();
18668 self.background_highlights_in_range(start..end, &snapshot, theme)
18669 }
18670
18671 #[cfg(feature = "test-support")]
18672 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18673 let snapshot = self.buffer().read(cx).snapshot(cx);
18674
18675 let highlights = self
18676 .background_highlights
18677 .get(&TypeId::of::<items::BufferSearchHighlights>());
18678
18679 if let Some(highlights) = highlights {
18680 highlights
18681 .iter()
18682 .map(|highlight| {
18683 highlight.range.start.to_point(&snapshot)
18684 ..highlight.range.end.to_point(&snapshot)
18685 })
18686 .collect_vec()
18687 } else {
18688 vec![]
18689 }
18690 }
18691
18692 fn document_highlights_for_position<'a>(
18693 &'a self,
18694 position: Anchor,
18695 buffer: &'a MultiBufferSnapshot,
18696 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18697 let read_highlights = self
18698 .background_highlights
18699 .get(&TypeId::of::<DocumentHighlightRead>());
18700 let write_highlights = self
18701 .background_highlights
18702 .get(&TypeId::of::<DocumentHighlightWrite>());
18703 let left_position = position.bias_left(buffer);
18704 let right_position = position.bias_right(buffer);
18705 read_highlights
18706 .into_iter()
18707 .chain(write_highlights)
18708 .flat_map(move |highlights| {
18709 let start_ix = match highlights.binary_search_by(|probe| {
18710 let cmp = probe.range.end.cmp(&left_position, buffer);
18711 if cmp.is_ge() {
18712 Ordering::Greater
18713 } else {
18714 Ordering::Less
18715 }
18716 }) {
18717 Ok(i) | Err(i) => i,
18718 };
18719
18720 highlights[start_ix..]
18721 .iter()
18722 .take_while(move |highlight| {
18723 highlight.range.start.cmp(&right_position, buffer).is_le()
18724 })
18725 .map(|highlight| &highlight.range)
18726 })
18727 }
18728
18729 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18730 self.background_highlights
18731 .get(&TypeId::of::<T>())
18732 .map_or(false, |highlights| !highlights.is_empty())
18733 }
18734
18735 pub fn background_highlights_in_range(
18736 &self,
18737 search_range: Range<Anchor>,
18738 display_snapshot: &DisplaySnapshot,
18739 theme: &Theme,
18740 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18741 let mut results = Vec::new();
18742 for highlights in self.background_highlights.values() {
18743 let start_ix = match highlights.binary_search_by(|probe| {
18744 let cmp = probe
18745 .range
18746 .end
18747 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18748 if cmp.is_gt() {
18749 Ordering::Greater
18750 } else {
18751 Ordering::Less
18752 }
18753 }) {
18754 Ok(i) | Err(i) => i,
18755 };
18756 for highlight in &highlights[start_ix..] {
18757 if highlight
18758 .range
18759 .start
18760 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18761 .is_ge()
18762 {
18763 break;
18764 }
18765
18766 let start = highlight.range.start.to_display_point(display_snapshot);
18767 let end = highlight.range.end.to_display_point(display_snapshot);
18768 let color = (highlight.color_fetcher)(theme);
18769 results.push((start..end, color))
18770 }
18771 }
18772 results
18773 }
18774
18775 pub fn background_highlight_row_ranges<T: 'static>(
18776 &self,
18777 search_range: Range<Anchor>,
18778 display_snapshot: &DisplaySnapshot,
18779 count: usize,
18780 ) -> Vec<RangeInclusive<DisplayPoint>> {
18781 let mut results = Vec::new();
18782 let Some(highlights) = self.background_highlights.get(&TypeId::of::<T>()) else {
18783 return vec![];
18784 };
18785
18786 let start_ix = match highlights.binary_search_by(|probe| {
18787 let cmp = probe
18788 .range
18789 .end
18790 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18791 if cmp.is_gt() {
18792 Ordering::Greater
18793 } else {
18794 Ordering::Less
18795 }
18796 }) {
18797 Ok(i) | Err(i) => i,
18798 };
18799 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18800 if let (Some(start_display), Some(end_display)) = (start, end) {
18801 results.push(
18802 start_display.to_display_point(display_snapshot)
18803 ..=end_display.to_display_point(display_snapshot),
18804 );
18805 }
18806 };
18807 let mut start_row: Option<Point> = None;
18808 let mut end_row: Option<Point> = None;
18809 if highlights.len() > count {
18810 return Vec::new();
18811 }
18812 for highlight in &highlights[start_ix..] {
18813 if highlight
18814 .range
18815 .start
18816 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18817 .is_ge()
18818 {
18819 break;
18820 }
18821 let end = highlight
18822 .range
18823 .end
18824 .to_point(&display_snapshot.buffer_snapshot);
18825 if let Some(current_row) = &end_row {
18826 if end.row == current_row.row {
18827 continue;
18828 }
18829 }
18830 let start = highlight
18831 .range
18832 .start
18833 .to_point(&display_snapshot.buffer_snapshot);
18834 if start_row.is_none() {
18835 assert_eq!(end_row, None);
18836 start_row = Some(start);
18837 end_row = Some(end);
18838 continue;
18839 }
18840 if let Some(current_end) = end_row.as_mut() {
18841 if start.row > current_end.row + 1 {
18842 push_region(start_row, end_row);
18843 start_row = Some(start);
18844 end_row = Some(end);
18845 } else {
18846 // Merge two hunks.
18847 *current_end = end;
18848 }
18849 } else {
18850 unreachable!();
18851 }
18852 }
18853 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18854 push_region(start_row, end_row);
18855 results
18856 }
18857
18858 pub fn gutter_highlights_in_range(
18859 &self,
18860 search_range: Range<Anchor>,
18861 display_snapshot: &DisplaySnapshot,
18862 cx: &App,
18863 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18864 let mut results = Vec::new();
18865 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18866 let color = color_fetcher(cx);
18867 let start_ix = match ranges.binary_search_by(|probe| {
18868 let cmp = probe
18869 .end
18870 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18871 if cmp.is_gt() {
18872 Ordering::Greater
18873 } else {
18874 Ordering::Less
18875 }
18876 }) {
18877 Ok(i) | Err(i) => i,
18878 };
18879 for range in &ranges[start_ix..] {
18880 if range
18881 .start
18882 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18883 .is_ge()
18884 {
18885 break;
18886 }
18887
18888 let start = range.start.to_display_point(display_snapshot);
18889 let end = range.end.to_display_point(display_snapshot);
18890 results.push((start..end, color))
18891 }
18892 }
18893 results
18894 }
18895
18896 /// Get the text ranges corresponding to the redaction query
18897 pub fn redacted_ranges(
18898 &self,
18899 search_range: Range<Anchor>,
18900 display_snapshot: &DisplaySnapshot,
18901 cx: &App,
18902 ) -> Vec<Range<DisplayPoint>> {
18903 display_snapshot
18904 .buffer_snapshot
18905 .redacted_ranges(search_range, |file| {
18906 if let Some(file) = file {
18907 file.is_private()
18908 && EditorSettings::get(
18909 Some(SettingsLocation {
18910 worktree_id: file.worktree_id(cx),
18911 path: file.path().as_ref(),
18912 }),
18913 cx,
18914 )
18915 .redact_private_values
18916 } else {
18917 false
18918 }
18919 })
18920 .map(|range| {
18921 range.start.to_display_point(display_snapshot)
18922 ..range.end.to_display_point(display_snapshot)
18923 })
18924 .collect()
18925 }
18926
18927 pub fn highlight_text<T: 'static>(
18928 &mut self,
18929 ranges: Vec<(Range<Anchor>, HighlightStyle)>,
18930 cx: &mut Context<Self>,
18931 ) {
18932 self.display_map
18933 .update(cx, |map, _| map.highlight_text(TypeId::of::<T>(), ranges));
18934 cx.notify();
18935 }
18936
18937 pub(crate) fn highlight_inlays<T: 'static>(
18938 &mut self,
18939 highlights: Vec<InlayHighlight>,
18940 style: HighlightStyle,
18941 cx: &mut Context<Self>,
18942 ) {
18943 self.display_map.update(cx, |map, _| {
18944 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18945 });
18946 cx.notify();
18947 }
18948
18949 pub fn text_highlights<'a, T: 'static>(
18950 &'a self,
18951 cx: &'a App,
18952 ) -> Option<&'a [(Range<Anchor>, HighlightStyle)]> {
18953 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18954 }
18955
18956 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18957 let cleared = self
18958 .display_map
18959 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18960 if cleared {
18961 cx.notify();
18962 }
18963 }
18964
18965 pub fn remove_text_highlights<T: 'static>(
18966 &mut self,
18967 cx: &mut Context<Self>,
18968 ) -> Option<Vec<(Range<Anchor>, HighlightStyle)>> {
18969 self.display_map
18970 .update(cx, |map, _| map.remove_text_highlights(TypeId::of::<T>()))
18971 }
18972
18973 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18974 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18975 && self.focus_handle.is_focused(window)
18976 }
18977
18978 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18979 self.show_cursor_when_unfocused = is_enabled;
18980 cx.notify();
18981 }
18982
18983 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18984 cx.notify();
18985 }
18986
18987 fn on_debug_session_event(
18988 &mut self,
18989 _session: Entity<Session>,
18990 event: &SessionEvent,
18991 cx: &mut Context<Self>,
18992 ) {
18993 match event {
18994 SessionEvent::InvalidateInlineValue => {
18995 self.refresh_inline_values(cx);
18996 }
18997 _ => {}
18998 }
18999 }
19000
19001 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
19002 let Some(project) = self.project.clone() else {
19003 return;
19004 };
19005
19006 if !self.inline_value_cache.enabled {
19007 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
19008 self.splice_inlays(&inlays, Vec::new(), cx);
19009 return;
19010 }
19011
19012 let current_execution_position = self
19013 .highlighted_rows
19014 .get(&TypeId::of::<ActiveDebugLine>())
19015 .and_then(|lines| lines.last().map(|line| line.range.start));
19016
19017 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
19018 let inline_values = editor
19019 .update(cx, |editor, cx| {
19020 let Some(current_execution_position) = current_execution_position else {
19021 return Some(Task::ready(Ok(Vec::new())));
19022 };
19023
19024 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
19025 let snapshot = buffer.snapshot(cx);
19026
19027 let excerpt = snapshot.excerpt_containing(
19028 current_execution_position..current_execution_position,
19029 )?;
19030
19031 editor.buffer.read(cx).buffer(excerpt.buffer_id())
19032 })?;
19033
19034 let range =
19035 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
19036
19037 project.inline_values(buffer, range, cx)
19038 })
19039 .ok()
19040 .flatten()?
19041 .await
19042 .context("refreshing debugger inlays")
19043 .log_err()?;
19044
19045 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
19046
19047 for (buffer_id, inline_value) in inline_values
19048 .into_iter()
19049 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
19050 {
19051 buffer_inline_values
19052 .entry(buffer_id)
19053 .or_default()
19054 .push(inline_value);
19055 }
19056
19057 editor
19058 .update(cx, |editor, cx| {
19059 let snapshot = editor.buffer.read(cx).snapshot(cx);
19060 let mut new_inlays = Vec::default();
19061
19062 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
19063 let buffer_id = buffer_snapshot.remote_id();
19064 buffer_inline_values
19065 .get(&buffer_id)
19066 .into_iter()
19067 .flatten()
19068 .for_each(|hint| {
19069 let inlay = Inlay::debugger_hint(
19070 post_inc(&mut editor.next_inlay_id),
19071 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
19072 hint.text(),
19073 );
19074
19075 new_inlays.push(inlay);
19076 });
19077 }
19078
19079 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
19080 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
19081
19082 editor.splice_inlays(&inlay_ids, new_inlays, cx);
19083 })
19084 .ok()?;
19085 Some(())
19086 });
19087 }
19088
19089 fn on_buffer_event(
19090 &mut self,
19091 multibuffer: &Entity<MultiBuffer>,
19092 event: &multi_buffer::Event,
19093 window: &mut Window,
19094 cx: &mut Context<Self>,
19095 ) {
19096 match event {
19097 multi_buffer::Event::Edited {
19098 singleton_buffer_edited,
19099 edited_buffer,
19100 } => {
19101 self.scrollbar_marker_state.dirty = true;
19102 self.active_indent_guides_state.dirty = true;
19103 self.refresh_active_diagnostics(cx);
19104 self.refresh_code_actions(window, cx);
19105 self.refresh_selected_text_highlights(true, window, cx);
19106 refresh_matching_bracket_highlights(self, window, cx);
19107 if self.has_active_inline_completion() {
19108 self.update_visible_inline_completion(window, cx);
19109 }
19110 if let Some(project) = self.project.as_ref() {
19111 if let Some(edited_buffer) = edited_buffer {
19112 project.update(cx, |project, cx| {
19113 self.registered_buffers
19114 .entry(edited_buffer.read(cx).remote_id())
19115 .or_insert_with(|| {
19116 project
19117 .register_buffer_with_language_servers(&edited_buffer, cx)
19118 });
19119 });
19120 if edited_buffer.read(cx).file().is_some() {
19121 self.pull_diagnostics(
19122 Some(edited_buffer.read(cx).remote_id()),
19123 window,
19124 cx,
19125 );
19126 }
19127 }
19128 }
19129 cx.emit(EditorEvent::BufferEdited);
19130 cx.emit(SearchEvent::MatchesInvalidated);
19131 if *singleton_buffer_edited {
19132 if let Some(buffer) = edited_buffer {
19133 if buffer.read(cx).file().is_none() {
19134 cx.emit(EditorEvent::TitleChanged);
19135 }
19136 }
19137 if let Some(project) = &self.project {
19138 #[allow(clippy::mutable_key_type)]
19139 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
19140 multibuffer
19141 .all_buffers()
19142 .into_iter()
19143 .filter_map(|buffer| {
19144 buffer.update(cx, |buffer, cx| {
19145 let language = buffer.language()?;
19146 let should_discard = project.update(cx, |project, cx| {
19147 project.is_local()
19148 && !project.has_language_servers_for(buffer, cx)
19149 });
19150 should_discard.not().then_some(language.clone())
19151 })
19152 })
19153 .collect::<HashSet<_>>()
19154 });
19155 if !languages_affected.is_empty() {
19156 self.refresh_inlay_hints(
19157 InlayHintRefreshReason::BufferEdited(languages_affected),
19158 cx,
19159 );
19160 }
19161 }
19162 }
19163
19164 let Some(project) = &self.project else { return };
19165 let (telemetry, is_via_ssh) = {
19166 let project = project.read(cx);
19167 let telemetry = project.client().telemetry().clone();
19168 let is_via_ssh = project.is_via_ssh();
19169 (telemetry, is_via_ssh)
19170 };
19171 refresh_linked_ranges(self, window, cx);
19172 telemetry.log_edit_event("editor", is_via_ssh);
19173 }
19174 multi_buffer::Event::ExcerptsAdded {
19175 buffer,
19176 predecessor,
19177 excerpts,
19178 } => {
19179 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19180 let buffer_id = buffer.read(cx).remote_id();
19181 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19182 if let Some(project) = &self.project {
19183 update_uncommitted_diff_for_buffer(
19184 cx.entity(),
19185 project,
19186 [buffer.clone()],
19187 self.buffer.clone(),
19188 cx,
19189 )
19190 .detach();
19191 }
19192 }
19193 cx.emit(EditorEvent::ExcerptsAdded {
19194 buffer: buffer.clone(),
19195 predecessor: *predecessor,
19196 excerpts: excerpts.clone(),
19197 });
19198 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19199 }
19200 multi_buffer::Event::ExcerptsRemoved {
19201 ids,
19202 removed_buffer_ids,
19203 } => {
19204 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19205 let buffer = self.buffer.read(cx);
19206 self.registered_buffers
19207 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19208 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19209 cx.emit(EditorEvent::ExcerptsRemoved {
19210 ids: ids.clone(),
19211 removed_buffer_ids: removed_buffer_ids.clone(),
19212 })
19213 }
19214 multi_buffer::Event::ExcerptsEdited {
19215 excerpt_ids,
19216 buffer_ids,
19217 } => {
19218 self.display_map.update(cx, |map, cx| {
19219 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19220 });
19221 cx.emit(EditorEvent::ExcerptsEdited {
19222 ids: excerpt_ids.clone(),
19223 })
19224 }
19225 multi_buffer::Event::ExcerptsExpanded { ids } => {
19226 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19227 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19228 }
19229 multi_buffer::Event::Reparsed(buffer_id) => {
19230 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19231 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19232
19233 cx.emit(EditorEvent::Reparsed(*buffer_id));
19234 }
19235 multi_buffer::Event::DiffHunksToggled => {
19236 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19237 }
19238 multi_buffer::Event::LanguageChanged(buffer_id) => {
19239 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19240 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19241 cx.emit(EditorEvent::Reparsed(*buffer_id));
19242 cx.notify();
19243 }
19244 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19245 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19246 multi_buffer::Event::FileHandleChanged
19247 | multi_buffer::Event::Reloaded
19248 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19249 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19250 multi_buffer::Event::DiagnosticsUpdated => {
19251 self.update_diagnostics_state(window, cx);
19252 }
19253 _ => {}
19254 };
19255 }
19256
19257 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19258 self.refresh_active_diagnostics(cx);
19259 self.refresh_inline_diagnostics(true, window, cx);
19260 self.scrollbar_marker_state.dirty = true;
19261 cx.notify();
19262 }
19263
19264 pub fn start_temporary_diff_override(&mut self) {
19265 self.load_diff_task.take();
19266 self.temporary_diff_override = true;
19267 }
19268
19269 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19270 self.temporary_diff_override = false;
19271 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19272 self.buffer.update(cx, |buffer, cx| {
19273 buffer.set_all_diff_hunks_collapsed(cx);
19274 });
19275
19276 if let Some(project) = self.project.clone() {
19277 self.load_diff_task = Some(
19278 update_uncommitted_diff_for_buffer(
19279 cx.entity(),
19280 &project,
19281 self.buffer.read(cx).all_buffers(),
19282 self.buffer.clone(),
19283 cx,
19284 )
19285 .shared(),
19286 );
19287 }
19288 }
19289
19290 fn on_display_map_changed(
19291 &mut self,
19292 _: Entity<DisplayMap>,
19293 _: &mut Window,
19294 cx: &mut Context<Self>,
19295 ) {
19296 cx.notify();
19297 }
19298
19299 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19300 let new_severity = if self.diagnostics_enabled() {
19301 EditorSettings::get_global(cx)
19302 .diagnostics_max_severity
19303 .unwrap_or(DiagnosticSeverity::Hint)
19304 } else {
19305 DiagnosticSeverity::Off
19306 };
19307 self.set_max_diagnostics_severity(new_severity, cx);
19308 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19309 self.update_edit_prediction_settings(cx);
19310 self.refresh_inline_completion(true, false, window, cx);
19311 self.refresh_inlay_hints(
19312 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19313 self.selections.newest_anchor().head(),
19314 &self.buffer.read(cx).snapshot(cx),
19315 cx,
19316 )),
19317 cx,
19318 );
19319
19320 let old_cursor_shape = self.cursor_shape;
19321
19322 {
19323 let editor_settings = EditorSettings::get_global(cx);
19324 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19325 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19326 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19327 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19328 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19329 }
19330
19331 if old_cursor_shape != self.cursor_shape {
19332 cx.emit(EditorEvent::CursorShapeChanged);
19333 }
19334
19335 let project_settings = ProjectSettings::get_global(cx);
19336 self.serialize_dirty_buffers =
19337 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19338
19339 if self.mode.is_full() {
19340 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19341 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19342 if self.show_inline_diagnostics != show_inline_diagnostics {
19343 self.show_inline_diagnostics = show_inline_diagnostics;
19344 self.refresh_inline_diagnostics(false, window, cx);
19345 }
19346
19347 if self.git_blame_inline_enabled != inline_blame_enabled {
19348 self.toggle_git_blame_inline_internal(false, window, cx);
19349 }
19350
19351 let minimap_settings = EditorSettings::get_global(cx).minimap;
19352 if self.minimap_visibility != MinimapVisibility::Disabled {
19353 if self.minimap_visibility.settings_visibility()
19354 != minimap_settings.minimap_enabled()
19355 {
19356 self.set_minimap_visibility(
19357 MinimapVisibility::for_mode(self.mode(), cx),
19358 window,
19359 cx,
19360 );
19361 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19362 minimap_entity.update(cx, |minimap_editor, cx| {
19363 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19364 })
19365 }
19366 }
19367 }
19368
19369 cx.notify();
19370 }
19371
19372 pub fn set_searchable(&mut self, searchable: bool) {
19373 self.searchable = searchable;
19374 }
19375
19376 pub fn searchable(&self) -> bool {
19377 self.searchable
19378 }
19379
19380 fn open_proposed_changes_editor(
19381 &mut self,
19382 _: &OpenProposedChangesEditor,
19383 window: &mut Window,
19384 cx: &mut Context<Self>,
19385 ) {
19386 let Some(workspace) = self.workspace() else {
19387 cx.propagate();
19388 return;
19389 };
19390
19391 let selections = self.selections.all::<usize>(cx);
19392 let multi_buffer = self.buffer.read(cx);
19393 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19394 let mut new_selections_by_buffer = HashMap::default();
19395 for selection in selections {
19396 for (buffer, range, _) in
19397 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19398 {
19399 let mut range = range.to_point(buffer);
19400 range.start.column = 0;
19401 range.end.column = buffer.line_len(range.end.row);
19402 new_selections_by_buffer
19403 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19404 .or_insert(Vec::new())
19405 .push(range)
19406 }
19407 }
19408
19409 let proposed_changes_buffers = new_selections_by_buffer
19410 .into_iter()
19411 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19412 .collect::<Vec<_>>();
19413 let proposed_changes_editor = cx.new(|cx| {
19414 ProposedChangesEditor::new(
19415 "Proposed changes",
19416 proposed_changes_buffers,
19417 self.project.clone(),
19418 window,
19419 cx,
19420 )
19421 });
19422
19423 window.defer(cx, move |window, cx| {
19424 workspace.update(cx, |workspace, cx| {
19425 workspace.active_pane().update(cx, |pane, cx| {
19426 pane.add_item(
19427 Box::new(proposed_changes_editor),
19428 true,
19429 true,
19430 None,
19431 window,
19432 cx,
19433 );
19434 });
19435 });
19436 });
19437 }
19438
19439 pub fn open_excerpts_in_split(
19440 &mut self,
19441 _: &OpenExcerptsSplit,
19442 window: &mut Window,
19443 cx: &mut Context<Self>,
19444 ) {
19445 self.open_excerpts_common(None, true, window, cx)
19446 }
19447
19448 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19449 self.open_excerpts_common(None, false, window, cx)
19450 }
19451
19452 fn open_excerpts_common(
19453 &mut self,
19454 jump_data: Option<JumpData>,
19455 split: bool,
19456 window: &mut Window,
19457 cx: &mut Context<Self>,
19458 ) {
19459 let Some(workspace) = self.workspace() else {
19460 cx.propagate();
19461 return;
19462 };
19463
19464 if self.buffer.read(cx).is_singleton() {
19465 cx.propagate();
19466 return;
19467 }
19468
19469 let mut new_selections_by_buffer = HashMap::default();
19470 match &jump_data {
19471 Some(JumpData::MultiBufferPoint {
19472 excerpt_id,
19473 position,
19474 anchor,
19475 line_offset_from_top,
19476 }) => {
19477 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19478 if let Some(buffer) = multi_buffer_snapshot
19479 .buffer_id_for_excerpt(*excerpt_id)
19480 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19481 {
19482 let buffer_snapshot = buffer.read(cx).snapshot();
19483 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19484 language::ToPoint::to_point(anchor, &buffer_snapshot)
19485 } else {
19486 buffer_snapshot.clip_point(*position, Bias::Left)
19487 };
19488 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19489 new_selections_by_buffer.insert(
19490 buffer,
19491 (
19492 vec![jump_to_offset..jump_to_offset],
19493 Some(*line_offset_from_top),
19494 ),
19495 );
19496 }
19497 }
19498 Some(JumpData::MultiBufferRow {
19499 row,
19500 line_offset_from_top,
19501 }) => {
19502 let point = MultiBufferPoint::new(row.0, 0);
19503 if let Some((buffer, buffer_point, _)) =
19504 self.buffer.read(cx).point_to_buffer_point(point, cx)
19505 {
19506 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19507 new_selections_by_buffer
19508 .entry(buffer)
19509 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19510 .0
19511 .push(buffer_offset..buffer_offset)
19512 }
19513 }
19514 None => {
19515 let selections = self.selections.all::<usize>(cx);
19516 let multi_buffer = self.buffer.read(cx);
19517 for selection in selections {
19518 for (snapshot, range, _, anchor) in multi_buffer
19519 .snapshot(cx)
19520 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19521 {
19522 if let Some(anchor) = anchor {
19523 // selection is in a deleted hunk
19524 let Some(buffer_id) = anchor.buffer_id else {
19525 continue;
19526 };
19527 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19528 continue;
19529 };
19530 let offset = text::ToOffset::to_offset(
19531 &anchor.text_anchor,
19532 &buffer_handle.read(cx).snapshot(),
19533 );
19534 let range = offset..offset;
19535 new_selections_by_buffer
19536 .entry(buffer_handle)
19537 .or_insert((Vec::new(), None))
19538 .0
19539 .push(range)
19540 } else {
19541 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19542 else {
19543 continue;
19544 };
19545 new_selections_by_buffer
19546 .entry(buffer_handle)
19547 .or_insert((Vec::new(), None))
19548 .0
19549 .push(range)
19550 }
19551 }
19552 }
19553 }
19554 }
19555
19556 new_selections_by_buffer
19557 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19558
19559 if new_selections_by_buffer.is_empty() {
19560 return;
19561 }
19562
19563 // We defer the pane interaction because we ourselves are a workspace item
19564 // and activating a new item causes the pane to call a method on us reentrantly,
19565 // which panics if we're on the stack.
19566 window.defer(cx, move |window, cx| {
19567 workspace.update(cx, |workspace, cx| {
19568 let pane = if split {
19569 workspace.adjacent_pane(window, cx)
19570 } else {
19571 workspace.active_pane().clone()
19572 };
19573
19574 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19575 let editor = buffer
19576 .read(cx)
19577 .file()
19578 .is_none()
19579 .then(|| {
19580 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19581 // so `workspace.open_project_item` will never find them, always opening a new editor.
19582 // Instead, we try to activate the existing editor in the pane first.
19583 let (editor, pane_item_index) =
19584 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19585 let editor = item.downcast::<Editor>()?;
19586 let singleton_buffer =
19587 editor.read(cx).buffer().read(cx).as_singleton()?;
19588 if singleton_buffer == buffer {
19589 Some((editor, i))
19590 } else {
19591 None
19592 }
19593 })?;
19594 pane.update(cx, |pane, cx| {
19595 pane.activate_item(pane_item_index, true, true, window, cx)
19596 });
19597 Some(editor)
19598 })
19599 .flatten()
19600 .unwrap_or_else(|| {
19601 workspace.open_project_item::<Self>(
19602 pane.clone(),
19603 buffer,
19604 true,
19605 true,
19606 window,
19607 cx,
19608 )
19609 });
19610
19611 editor.update(cx, |editor, cx| {
19612 let autoscroll = match scroll_offset {
19613 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19614 None => Autoscroll::newest(),
19615 };
19616 let nav_history = editor.nav_history.take();
19617 editor.change_selections(Some(autoscroll), window, cx, |s| {
19618 s.select_ranges(ranges);
19619 });
19620 editor.nav_history = nav_history;
19621 });
19622 }
19623 })
19624 });
19625 }
19626
19627 // For now, don't allow opening excerpts in buffers that aren't backed by
19628 // regular project files.
19629 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19630 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19631 }
19632
19633 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19634 let snapshot = self.buffer.read(cx).read(cx);
19635 let ranges = self.text_highlights::<InputComposition>(cx)?;
19636 Some(
19637 ranges
19638 .iter()
19639 .map(move |(range, _)| {
19640 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19641 })
19642 .collect(),
19643 )
19644 }
19645
19646 fn selection_replacement_ranges(
19647 &self,
19648 range: Range<OffsetUtf16>,
19649 cx: &mut App,
19650 ) -> Vec<Range<OffsetUtf16>> {
19651 let selections = self.selections.all::<OffsetUtf16>(cx);
19652 let newest_selection = selections
19653 .iter()
19654 .max_by_key(|selection| selection.id)
19655 .unwrap();
19656 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19657 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19658 let snapshot = self.buffer.read(cx).read(cx);
19659 selections
19660 .into_iter()
19661 .map(|mut selection| {
19662 selection.start.0 =
19663 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19664 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19665 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19666 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19667 })
19668 .collect()
19669 }
19670
19671 fn report_editor_event(
19672 &self,
19673 event_type: &'static str,
19674 file_extension: Option<String>,
19675 cx: &App,
19676 ) {
19677 if cfg!(any(test, feature = "test-support")) {
19678 return;
19679 }
19680
19681 let Some(project) = &self.project else { return };
19682
19683 // If None, we are in a file without an extension
19684 let file = self
19685 .buffer
19686 .read(cx)
19687 .as_singleton()
19688 .and_then(|b| b.read(cx).file());
19689 let file_extension = file_extension.or(file
19690 .as_ref()
19691 .and_then(|file| Path::new(file.file_name(cx)).extension())
19692 .and_then(|e| e.to_str())
19693 .map(|a| a.to_string()));
19694
19695 let vim_mode = vim_enabled(cx);
19696
19697 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19698 let copilot_enabled = edit_predictions_provider
19699 == language::language_settings::EditPredictionProvider::Copilot;
19700 let copilot_enabled_for_language = self
19701 .buffer
19702 .read(cx)
19703 .language_settings(cx)
19704 .show_edit_predictions;
19705
19706 let project = project.read(cx);
19707 telemetry::event!(
19708 event_type,
19709 file_extension,
19710 vim_mode,
19711 copilot_enabled,
19712 copilot_enabled_for_language,
19713 edit_predictions_provider,
19714 is_via_ssh = project.is_via_ssh(),
19715 );
19716 }
19717
19718 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19719 /// with each line being an array of {text, highlight} objects.
19720 fn copy_highlight_json(
19721 &mut self,
19722 _: &CopyHighlightJson,
19723 window: &mut Window,
19724 cx: &mut Context<Self>,
19725 ) {
19726 #[derive(Serialize)]
19727 struct Chunk<'a> {
19728 text: String,
19729 highlight: Option<&'a str>,
19730 }
19731
19732 let snapshot = self.buffer.read(cx).snapshot(cx);
19733 let range = self
19734 .selected_text_range(false, window, cx)
19735 .and_then(|selection| {
19736 if selection.range.is_empty() {
19737 None
19738 } else {
19739 Some(selection.range)
19740 }
19741 })
19742 .unwrap_or_else(|| 0..snapshot.len());
19743
19744 let chunks = snapshot.chunks(range, true);
19745 let mut lines = Vec::new();
19746 let mut line: VecDeque<Chunk> = VecDeque::new();
19747
19748 let Some(style) = self.style.as_ref() else {
19749 return;
19750 };
19751
19752 for chunk in chunks {
19753 let highlight = chunk
19754 .syntax_highlight_id
19755 .and_then(|id| id.name(&style.syntax));
19756 let mut chunk_lines = chunk.text.split('\n').peekable();
19757 while let Some(text) = chunk_lines.next() {
19758 let mut merged_with_last_token = false;
19759 if let Some(last_token) = line.back_mut() {
19760 if last_token.highlight == highlight {
19761 last_token.text.push_str(text);
19762 merged_with_last_token = true;
19763 }
19764 }
19765
19766 if !merged_with_last_token {
19767 line.push_back(Chunk {
19768 text: text.into(),
19769 highlight,
19770 });
19771 }
19772
19773 if chunk_lines.peek().is_some() {
19774 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19775 line.pop_front();
19776 }
19777 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19778 line.pop_back();
19779 }
19780
19781 lines.push(mem::take(&mut line));
19782 }
19783 }
19784 }
19785
19786 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19787 return;
19788 };
19789 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19790 }
19791
19792 pub fn open_context_menu(
19793 &mut self,
19794 _: &OpenContextMenu,
19795 window: &mut Window,
19796 cx: &mut Context<Self>,
19797 ) {
19798 self.request_autoscroll(Autoscroll::newest(), cx);
19799 let position = self.selections.newest_display(cx).start;
19800 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19801 }
19802
19803 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19804 &self.inlay_hint_cache
19805 }
19806
19807 pub fn replay_insert_event(
19808 &mut self,
19809 text: &str,
19810 relative_utf16_range: Option<Range<isize>>,
19811 window: &mut Window,
19812 cx: &mut Context<Self>,
19813 ) {
19814 if !self.input_enabled {
19815 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19816 return;
19817 }
19818 if let Some(relative_utf16_range) = relative_utf16_range {
19819 let selections = self.selections.all::<OffsetUtf16>(cx);
19820 self.change_selections(None, window, cx, |s| {
19821 let new_ranges = selections.into_iter().map(|range| {
19822 let start = OffsetUtf16(
19823 range
19824 .head()
19825 .0
19826 .saturating_add_signed(relative_utf16_range.start),
19827 );
19828 let end = OffsetUtf16(
19829 range
19830 .head()
19831 .0
19832 .saturating_add_signed(relative_utf16_range.end),
19833 );
19834 start..end
19835 });
19836 s.select_ranges(new_ranges);
19837 });
19838 }
19839
19840 self.handle_input(text, window, cx);
19841 }
19842
19843 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19844 let Some(provider) = self.semantics_provider.as_ref() else {
19845 return false;
19846 };
19847
19848 let mut supports = false;
19849 self.buffer().update(cx, |this, cx| {
19850 this.for_each_buffer(|buffer| {
19851 supports |= provider.supports_inlay_hints(buffer, cx);
19852 });
19853 });
19854
19855 supports
19856 }
19857
19858 pub fn is_focused(&self, window: &Window) -> bool {
19859 self.focus_handle.is_focused(window)
19860 }
19861
19862 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19863 cx.emit(EditorEvent::Focused);
19864
19865 if let Some(descendant) = self
19866 .last_focused_descendant
19867 .take()
19868 .and_then(|descendant| descendant.upgrade())
19869 {
19870 window.focus(&descendant);
19871 } else {
19872 if let Some(blame) = self.blame.as_ref() {
19873 blame.update(cx, GitBlame::focus)
19874 }
19875
19876 self.blink_manager.update(cx, BlinkManager::enable);
19877 self.show_cursor_names(window, cx);
19878 self.buffer.update(cx, |buffer, cx| {
19879 buffer.finalize_last_transaction(cx);
19880 if self.leader_id.is_none() {
19881 buffer.set_active_selections(
19882 &self.selections.disjoint_anchors(),
19883 self.selections.line_mode,
19884 self.cursor_shape,
19885 cx,
19886 );
19887 }
19888 });
19889 }
19890 }
19891
19892 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19893 cx.emit(EditorEvent::FocusedIn)
19894 }
19895
19896 fn handle_focus_out(
19897 &mut self,
19898 event: FocusOutEvent,
19899 _window: &mut Window,
19900 cx: &mut Context<Self>,
19901 ) {
19902 if event.blurred != self.focus_handle {
19903 self.last_focused_descendant = Some(event.blurred);
19904 }
19905 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19906 }
19907
19908 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19909 self.blink_manager.update(cx, BlinkManager::disable);
19910 self.buffer
19911 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19912
19913 if let Some(blame) = self.blame.as_ref() {
19914 blame.update(cx, GitBlame::blur)
19915 }
19916 if !self.hover_state.focused(window, cx) {
19917 hide_hover(self, cx);
19918 }
19919 if !self
19920 .context_menu
19921 .borrow()
19922 .as_ref()
19923 .is_some_and(|context_menu| context_menu.focused(window, cx))
19924 {
19925 self.hide_context_menu(window, cx);
19926 }
19927 self.discard_inline_completion(false, cx);
19928 cx.emit(EditorEvent::Blurred);
19929 cx.notify();
19930 }
19931
19932 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19933 let mut pending: String = window
19934 .pending_input_keystrokes()
19935 .into_iter()
19936 .flatten()
19937 .filter_map(|keystroke| {
19938 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19939 keystroke.key_char.clone()
19940 } else {
19941 None
19942 }
19943 })
19944 .collect();
19945
19946 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19947 pending = "".to_string();
19948 }
19949
19950 let existing_pending = self.text_highlights::<PendingInput>(cx).map(|ranges| {
19951 ranges
19952 .iter()
19953 .map(|(range, _)| range.clone())
19954 .collect::<Vec<_>>()
19955 });
19956 if existing_pending.is_none() && pending.is_empty() {
19957 return;
19958 }
19959 let transaction =
19960 self.transact(window, cx, |this, window, cx| {
19961 let selections = this.selections.all::<usize>(cx);
19962 let edits = selections
19963 .iter()
19964 .map(|selection| (selection.end..selection.end, pending.clone()));
19965 this.edit(edits, cx);
19966 this.change_selections(None, window, cx, |s| {
19967 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19968 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19969 }));
19970 });
19971 if let Some(existing_ranges) = existing_pending {
19972 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19973 this.edit(edits, cx);
19974 }
19975 });
19976
19977 let snapshot = self.snapshot(window, cx);
19978 let ranges = self
19979 .selections
19980 .all::<usize>(cx)
19981 .into_iter()
19982 .map(|selection| {
19983 (
19984 snapshot.buffer_snapshot.anchor_after(selection.end)
19985 ..snapshot
19986 .buffer_snapshot
19987 .anchor_before(selection.end + pending.len()),
19988 HighlightStyle {
19989 underline: Some(UnderlineStyle {
19990 thickness: px(1.),
19991 color: None,
19992 wavy: false,
19993 }),
19994 ..Default::default()
19995 },
19996 )
19997 })
19998 .collect();
19999
20000 if pending.is_empty() {
20001 self.clear_highlights::<PendingInput>(cx);
20002 } else {
20003 self.highlight_text::<PendingInput>(ranges, cx);
20004 }
20005
20006 self.ime_transaction = self.ime_transaction.or(transaction);
20007 if let Some(transaction) = self.ime_transaction {
20008 self.buffer.update(cx, |buffer, cx| {
20009 buffer.group_until_transaction(transaction, cx);
20010 });
20011 }
20012
20013 if self.text_highlights::<PendingInput>(cx).is_none() {
20014 self.ime_transaction.take();
20015 }
20016 }
20017
20018 pub fn register_action_renderer(
20019 &mut self,
20020 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
20021 ) -> Subscription {
20022 let id = self.next_editor_action_id.post_inc();
20023 self.editor_actions
20024 .borrow_mut()
20025 .insert(id, Box::new(listener));
20026
20027 let editor_actions = self.editor_actions.clone();
20028 Subscription::new(move || {
20029 editor_actions.borrow_mut().remove(&id);
20030 })
20031 }
20032
20033 pub fn register_action<A: Action>(
20034 &mut self,
20035 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
20036 ) -> Subscription {
20037 let id = self.next_editor_action_id.post_inc();
20038 let listener = Arc::new(listener);
20039 self.editor_actions.borrow_mut().insert(
20040 id,
20041 Box::new(move |_, window, _| {
20042 let listener = listener.clone();
20043 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
20044 let action = action.downcast_ref().unwrap();
20045 if phase == DispatchPhase::Bubble {
20046 listener(action, window, cx)
20047 }
20048 })
20049 }),
20050 );
20051
20052 let editor_actions = self.editor_actions.clone();
20053 Subscription::new(move || {
20054 editor_actions.borrow_mut().remove(&id);
20055 })
20056 }
20057
20058 pub fn file_header_size(&self) -> u32 {
20059 FILE_HEADER_HEIGHT
20060 }
20061
20062 pub fn restore(
20063 &mut self,
20064 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
20065 window: &mut Window,
20066 cx: &mut Context<Self>,
20067 ) {
20068 let workspace = self.workspace();
20069 let project = self.project.as_ref();
20070 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
20071 let mut tasks = Vec::new();
20072 for (buffer_id, changes) in revert_changes {
20073 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
20074 buffer.update(cx, |buffer, cx| {
20075 buffer.edit(
20076 changes
20077 .into_iter()
20078 .map(|(range, text)| (range, text.to_string())),
20079 None,
20080 cx,
20081 );
20082 });
20083
20084 if let Some(project) =
20085 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
20086 {
20087 project.update(cx, |project, cx| {
20088 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
20089 })
20090 }
20091 }
20092 }
20093 tasks
20094 });
20095 cx.spawn_in(window, async move |_, cx| {
20096 for (buffer, task) in save_tasks {
20097 let result = task.await;
20098 if result.is_err() {
20099 let Some(path) = buffer
20100 .read_with(cx, |buffer, cx| buffer.project_path(cx))
20101 .ok()
20102 else {
20103 continue;
20104 };
20105 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
20106 let Some(task) = cx
20107 .update_window_entity(&workspace, |workspace, window, cx| {
20108 workspace
20109 .open_path_preview(path, None, false, false, false, window, cx)
20110 })
20111 .ok()
20112 else {
20113 continue;
20114 };
20115 task.await.log_err();
20116 }
20117 }
20118 }
20119 })
20120 .detach();
20121 self.change_selections(None, window, cx, |selections| selections.refresh());
20122 }
20123
20124 pub fn to_pixel_point(
20125 &self,
20126 source: multi_buffer::Anchor,
20127 editor_snapshot: &EditorSnapshot,
20128 window: &mut Window,
20129 ) -> Option<gpui::Point<Pixels>> {
20130 let source_point = source.to_display_point(editor_snapshot);
20131 self.display_to_pixel_point(source_point, editor_snapshot, window)
20132 }
20133
20134 pub fn display_to_pixel_point(
20135 &self,
20136 source: DisplayPoint,
20137 editor_snapshot: &EditorSnapshot,
20138 window: &mut Window,
20139 ) -> Option<gpui::Point<Pixels>> {
20140 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
20141 let text_layout_details = self.text_layout_details(window);
20142 let scroll_top = text_layout_details
20143 .scroll_anchor
20144 .scroll_position(editor_snapshot)
20145 .y;
20146
20147 if source.row().as_f32() < scroll_top.floor() {
20148 return None;
20149 }
20150 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
20151 let source_y = line_height * (source.row().as_f32() - scroll_top);
20152 Some(gpui::Point::new(source_x, source_y))
20153 }
20154
20155 pub fn has_visible_completions_menu(&self) -> bool {
20156 !self.edit_prediction_preview_is_active()
20157 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
20158 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
20159 })
20160 }
20161
20162 pub fn register_addon<T: Addon>(&mut self, instance: T) {
20163 if self.mode.is_minimap() {
20164 return;
20165 }
20166 self.addons
20167 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
20168 }
20169
20170 pub fn unregister_addon<T: Addon>(&mut self) {
20171 self.addons.remove(&std::any::TypeId::of::<T>());
20172 }
20173
20174 pub fn addon<T: Addon>(&self) -> Option<&T> {
20175 let type_id = std::any::TypeId::of::<T>();
20176 self.addons
20177 .get(&type_id)
20178 .and_then(|item| item.to_any().downcast_ref::<T>())
20179 }
20180
20181 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20182 let type_id = std::any::TypeId::of::<T>();
20183 self.addons
20184 .get_mut(&type_id)
20185 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20186 }
20187
20188 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20189 let text_layout_details = self.text_layout_details(window);
20190 let style = &text_layout_details.editor_style;
20191 let font_id = window.text_system().resolve_font(&style.text.font());
20192 let font_size = style.text.font_size.to_pixels(window.rem_size());
20193 let line_height = style.text.line_height_in_pixels(window.rem_size());
20194 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20195
20196 gpui::Size::new(em_width, line_height)
20197 }
20198
20199 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20200 self.load_diff_task.clone()
20201 }
20202
20203 fn read_metadata_from_db(
20204 &mut self,
20205 item_id: u64,
20206 workspace_id: WorkspaceId,
20207 window: &mut Window,
20208 cx: &mut Context<Editor>,
20209 ) {
20210 if self.is_singleton(cx)
20211 && !self.mode.is_minimap()
20212 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20213 {
20214 let buffer_snapshot = OnceCell::new();
20215
20216 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20217 if !folds.is_empty() {
20218 let snapshot =
20219 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20220 self.fold_ranges(
20221 folds
20222 .into_iter()
20223 .map(|(start, end)| {
20224 snapshot.clip_offset(start, Bias::Left)
20225 ..snapshot.clip_offset(end, Bias::Right)
20226 })
20227 .collect(),
20228 false,
20229 window,
20230 cx,
20231 );
20232 }
20233 }
20234
20235 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20236 if !selections.is_empty() {
20237 let snapshot =
20238 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20239 // skip adding the initial selection to selection history
20240 self.selection_history.mode = SelectionHistoryMode::Skipping;
20241 self.change_selections(None, window, cx, |s| {
20242 s.select_ranges(selections.into_iter().map(|(start, end)| {
20243 snapshot.clip_offset(start, Bias::Left)
20244 ..snapshot.clip_offset(end, Bias::Right)
20245 }));
20246 });
20247 self.selection_history.mode = SelectionHistoryMode::Normal;
20248 }
20249 };
20250 }
20251
20252 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20253 }
20254}
20255
20256fn vim_enabled(cx: &App) -> bool {
20257 cx.global::<SettingsStore>()
20258 .raw_user_settings()
20259 .get("vim_mode")
20260 == Some(&serde_json::Value::Bool(true))
20261}
20262
20263fn process_completion_for_edit(
20264 completion: &Completion,
20265 intent: CompletionIntent,
20266 buffer: &Entity<Buffer>,
20267 cursor_position: &text::Anchor,
20268 cx: &mut Context<Editor>,
20269) -> CompletionEdit {
20270 let buffer = buffer.read(cx);
20271 let buffer_snapshot = buffer.snapshot();
20272 let (snippet, new_text) = if completion.is_snippet() {
20273 // Workaround for typescript language server issues so that methods don't expand within
20274 // strings and functions with type expressions. The previous point is used because the query
20275 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20276 let mut snippet_source = completion.new_text.clone();
20277 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20278 previous_point.column = previous_point.column.saturating_sub(1);
20279 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20280 if scope.prefers_label_for_snippet_in_completion() {
20281 if let Some(label) = completion.label() {
20282 if matches!(
20283 completion.kind(),
20284 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20285 ) {
20286 snippet_source = label;
20287 }
20288 }
20289 }
20290 }
20291 match Snippet::parse(&snippet_source).log_err() {
20292 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20293 None => (None, completion.new_text.clone()),
20294 }
20295 } else {
20296 (None, completion.new_text.clone())
20297 };
20298
20299 let mut range_to_replace = {
20300 let replace_range = &completion.replace_range;
20301 if let CompletionSource::Lsp {
20302 insert_range: Some(insert_range),
20303 ..
20304 } = &completion.source
20305 {
20306 debug_assert_eq!(
20307 insert_range.start, replace_range.start,
20308 "insert_range and replace_range should start at the same position"
20309 );
20310 debug_assert!(
20311 insert_range
20312 .start
20313 .cmp(&cursor_position, &buffer_snapshot)
20314 .is_le(),
20315 "insert_range should start before or at cursor position"
20316 );
20317 debug_assert!(
20318 replace_range
20319 .start
20320 .cmp(&cursor_position, &buffer_snapshot)
20321 .is_le(),
20322 "replace_range should start before or at cursor position"
20323 );
20324 debug_assert!(
20325 insert_range
20326 .end
20327 .cmp(&cursor_position, &buffer_snapshot)
20328 .is_le(),
20329 "insert_range should end before or at cursor position"
20330 );
20331
20332 let should_replace = match intent {
20333 CompletionIntent::CompleteWithInsert => false,
20334 CompletionIntent::CompleteWithReplace => true,
20335 CompletionIntent::Complete | CompletionIntent::Compose => {
20336 let insert_mode =
20337 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20338 .completions
20339 .lsp_insert_mode;
20340 match insert_mode {
20341 LspInsertMode::Insert => false,
20342 LspInsertMode::Replace => true,
20343 LspInsertMode::ReplaceSubsequence => {
20344 let mut text_to_replace = buffer.chars_for_range(
20345 buffer.anchor_before(replace_range.start)
20346 ..buffer.anchor_after(replace_range.end),
20347 );
20348 let mut current_needle = text_to_replace.next();
20349 for haystack_ch in completion.label.text.chars() {
20350 if let Some(needle_ch) = current_needle {
20351 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20352 current_needle = text_to_replace.next();
20353 }
20354 }
20355 }
20356 current_needle.is_none()
20357 }
20358 LspInsertMode::ReplaceSuffix => {
20359 if replace_range
20360 .end
20361 .cmp(&cursor_position, &buffer_snapshot)
20362 .is_gt()
20363 {
20364 let range_after_cursor = *cursor_position..replace_range.end;
20365 let text_after_cursor = buffer
20366 .text_for_range(
20367 buffer.anchor_before(range_after_cursor.start)
20368 ..buffer.anchor_after(range_after_cursor.end),
20369 )
20370 .collect::<String>()
20371 .to_ascii_lowercase();
20372 completion
20373 .label
20374 .text
20375 .to_ascii_lowercase()
20376 .ends_with(&text_after_cursor)
20377 } else {
20378 true
20379 }
20380 }
20381 }
20382 }
20383 };
20384
20385 if should_replace {
20386 replace_range.clone()
20387 } else {
20388 insert_range.clone()
20389 }
20390 } else {
20391 replace_range.clone()
20392 }
20393 };
20394
20395 if range_to_replace
20396 .end
20397 .cmp(&cursor_position, &buffer_snapshot)
20398 .is_lt()
20399 {
20400 range_to_replace.end = *cursor_position;
20401 }
20402
20403 CompletionEdit {
20404 new_text,
20405 replace_range: range_to_replace.to_offset(&buffer),
20406 snippet,
20407 }
20408}
20409
20410struct CompletionEdit {
20411 new_text: String,
20412 replace_range: Range<usize>,
20413 snippet: Option<Snippet>,
20414}
20415
20416fn insert_extra_newline_brackets(
20417 buffer: &MultiBufferSnapshot,
20418 range: Range<usize>,
20419 language: &language::LanguageScope,
20420) -> bool {
20421 let leading_whitespace_len = buffer
20422 .reversed_chars_at(range.start)
20423 .take_while(|c| c.is_whitespace() && *c != '\n')
20424 .map(|c| c.len_utf8())
20425 .sum::<usize>();
20426 let trailing_whitespace_len = buffer
20427 .chars_at(range.end)
20428 .take_while(|c| c.is_whitespace() && *c != '\n')
20429 .map(|c| c.len_utf8())
20430 .sum::<usize>();
20431 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20432
20433 language.brackets().any(|(pair, enabled)| {
20434 let pair_start = pair.start.trim_end();
20435 let pair_end = pair.end.trim_start();
20436
20437 enabled
20438 && pair.newline
20439 && buffer.contains_str_at(range.end, pair_end)
20440 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20441 })
20442}
20443
20444fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20445 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20446 [(buffer, range, _)] => (*buffer, range.clone()),
20447 _ => return false,
20448 };
20449 let pair = {
20450 let mut result: Option<BracketMatch> = None;
20451
20452 for pair in buffer
20453 .all_bracket_ranges(range.clone())
20454 .filter(move |pair| {
20455 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20456 })
20457 {
20458 let len = pair.close_range.end - pair.open_range.start;
20459
20460 if let Some(existing) = &result {
20461 let existing_len = existing.close_range.end - existing.open_range.start;
20462 if len > existing_len {
20463 continue;
20464 }
20465 }
20466
20467 result = Some(pair);
20468 }
20469
20470 result
20471 };
20472 let Some(pair) = pair else {
20473 return false;
20474 };
20475 pair.newline_only
20476 && buffer
20477 .chars_for_range(pair.open_range.end..range.start)
20478 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20479 .all(|c| c.is_whitespace() && c != '\n')
20480}
20481
20482fn update_uncommitted_diff_for_buffer(
20483 editor: Entity<Editor>,
20484 project: &Entity<Project>,
20485 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20486 buffer: Entity<MultiBuffer>,
20487 cx: &mut App,
20488) -> Task<()> {
20489 let mut tasks = Vec::new();
20490 project.update(cx, |project, cx| {
20491 for buffer in buffers {
20492 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20493 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20494 }
20495 }
20496 });
20497 cx.spawn(async move |cx| {
20498 let diffs = future::join_all(tasks).await;
20499 if editor
20500 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20501 .unwrap_or(false)
20502 {
20503 return;
20504 }
20505
20506 buffer
20507 .update(cx, |buffer, cx| {
20508 for diff in diffs.into_iter().flatten() {
20509 buffer.add_diff(diff, cx);
20510 }
20511 })
20512 .ok();
20513 })
20514}
20515
20516fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20517 let tab_size = tab_size.get() as usize;
20518 let mut width = offset;
20519
20520 for ch in text.chars() {
20521 width += if ch == '\t' {
20522 tab_size - (width % tab_size)
20523 } else {
20524 1
20525 };
20526 }
20527
20528 width - offset
20529}
20530
20531#[cfg(test)]
20532mod tests {
20533 use super::*;
20534
20535 #[test]
20536 fn test_string_size_with_expanded_tabs() {
20537 let nz = |val| NonZeroU32::new(val).unwrap();
20538 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20539 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20540 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20541 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20542 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20543 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20544 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20545 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20546 }
20547}
20548
20549/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20550struct WordBreakingTokenizer<'a> {
20551 input: &'a str,
20552}
20553
20554impl<'a> WordBreakingTokenizer<'a> {
20555 fn new(input: &'a str) -> Self {
20556 Self { input }
20557 }
20558}
20559
20560fn is_char_ideographic(ch: char) -> bool {
20561 use unicode_script::Script::*;
20562 use unicode_script::UnicodeScript;
20563 matches!(ch.script(), Han | Tangut | Yi)
20564}
20565
20566fn is_grapheme_ideographic(text: &str) -> bool {
20567 text.chars().any(is_char_ideographic)
20568}
20569
20570fn is_grapheme_whitespace(text: &str) -> bool {
20571 text.chars().any(|x| x.is_whitespace())
20572}
20573
20574fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20575 text.chars().next().map_or(false, |ch| {
20576 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20577 })
20578}
20579
20580#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20581enum WordBreakToken<'a> {
20582 Word { token: &'a str, grapheme_len: usize },
20583 InlineWhitespace { token: &'a str, grapheme_len: usize },
20584 Newline,
20585}
20586
20587impl<'a> Iterator for WordBreakingTokenizer<'a> {
20588 /// Yields a span, the count of graphemes in the token, and whether it was
20589 /// whitespace. Note that it also breaks at word boundaries.
20590 type Item = WordBreakToken<'a>;
20591
20592 fn next(&mut self) -> Option<Self::Item> {
20593 use unicode_segmentation::UnicodeSegmentation;
20594 if self.input.is_empty() {
20595 return None;
20596 }
20597
20598 let mut iter = self.input.graphemes(true).peekable();
20599 let mut offset = 0;
20600 let mut grapheme_len = 0;
20601 if let Some(first_grapheme) = iter.next() {
20602 let is_newline = first_grapheme == "\n";
20603 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20604 offset += first_grapheme.len();
20605 grapheme_len += 1;
20606 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20607 if let Some(grapheme) = iter.peek().copied() {
20608 if should_stay_with_preceding_ideograph(grapheme) {
20609 offset += grapheme.len();
20610 grapheme_len += 1;
20611 }
20612 }
20613 } else {
20614 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20615 let mut next_word_bound = words.peek().copied();
20616 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20617 next_word_bound = words.next();
20618 }
20619 while let Some(grapheme) = iter.peek().copied() {
20620 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20621 break;
20622 };
20623 if is_grapheme_whitespace(grapheme) != is_whitespace
20624 || (grapheme == "\n") != is_newline
20625 {
20626 break;
20627 };
20628 offset += grapheme.len();
20629 grapheme_len += 1;
20630 iter.next();
20631 }
20632 }
20633 let token = &self.input[..offset];
20634 self.input = &self.input[offset..];
20635 if token == "\n" {
20636 Some(WordBreakToken::Newline)
20637 } else if is_whitespace {
20638 Some(WordBreakToken::InlineWhitespace {
20639 token,
20640 grapheme_len,
20641 })
20642 } else {
20643 Some(WordBreakToken::Word {
20644 token,
20645 grapheme_len,
20646 })
20647 }
20648 } else {
20649 None
20650 }
20651 }
20652}
20653
20654#[test]
20655fn test_word_breaking_tokenizer() {
20656 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20657 ("", &[]),
20658 (" ", &[whitespace(" ", 2)]),
20659 ("Ʒ", &[word("Ʒ", 1)]),
20660 ("Ǽ", &[word("Ǽ", 1)]),
20661 ("⋑", &[word("⋑", 1)]),
20662 ("⋑⋑", &[word("⋑⋑", 2)]),
20663 (
20664 "原理,进而",
20665 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20666 ),
20667 (
20668 "hello world",
20669 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20670 ),
20671 (
20672 "hello, world",
20673 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20674 ),
20675 (
20676 " hello world",
20677 &[
20678 whitespace(" ", 2),
20679 word("hello", 5),
20680 whitespace(" ", 1),
20681 word("world", 5),
20682 ],
20683 ),
20684 (
20685 "这是什么 \n 钢笔",
20686 &[
20687 word("这", 1),
20688 word("是", 1),
20689 word("什", 1),
20690 word("么", 1),
20691 whitespace(" ", 1),
20692 newline(),
20693 whitespace(" ", 1),
20694 word("钢", 1),
20695 word("笔", 1),
20696 ],
20697 ),
20698 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20699 ];
20700
20701 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20702 WordBreakToken::Word {
20703 token,
20704 grapheme_len,
20705 }
20706 }
20707
20708 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20709 WordBreakToken::InlineWhitespace {
20710 token,
20711 grapheme_len,
20712 }
20713 }
20714
20715 fn newline() -> WordBreakToken<'static> {
20716 WordBreakToken::Newline
20717 }
20718
20719 for (input, result) in tests {
20720 assert_eq!(
20721 WordBreakingTokenizer::new(input)
20722 .collect::<Vec<_>>()
20723 .as_slice(),
20724 *result,
20725 );
20726 }
20727}
20728
20729fn wrap_with_prefix(
20730 line_prefix: String,
20731 unwrapped_text: String,
20732 wrap_column: usize,
20733 tab_size: NonZeroU32,
20734 preserve_existing_whitespace: bool,
20735) -> String {
20736 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20737 let mut wrapped_text = String::new();
20738 let mut current_line = line_prefix.clone();
20739
20740 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20741 let mut current_line_len = line_prefix_len;
20742 let mut in_whitespace = false;
20743 for token in tokenizer {
20744 let have_preceding_whitespace = in_whitespace;
20745 match token {
20746 WordBreakToken::Word {
20747 token,
20748 grapheme_len,
20749 } => {
20750 in_whitespace = false;
20751 if current_line_len + grapheme_len > wrap_column
20752 && current_line_len != line_prefix_len
20753 {
20754 wrapped_text.push_str(current_line.trim_end());
20755 wrapped_text.push('\n');
20756 current_line.truncate(line_prefix.len());
20757 current_line_len = line_prefix_len;
20758 }
20759 current_line.push_str(token);
20760 current_line_len += grapheme_len;
20761 }
20762 WordBreakToken::InlineWhitespace {
20763 mut token,
20764 mut grapheme_len,
20765 } => {
20766 in_whitespace = true;
20767 if have_preceding_whitespace && !preserve_existing_whitespace {
20768 continue;
20769 }
20770 if !preserve_existing_whitespace {
20771 token = " ";
20772 grapheme_len = 1;
20773 }
20774 if current_line_len + grapheme_len > wrap_column {
20775 wrapped_text.push_str(current_line.trim_end());
20776 wrapped_text.push('\n');
20777 current_line.truncate(line_prefix.len());
20778 current_line_len = line_prefix_len;
20779 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20780 current_line.push_str(token);
20781 current_line_len += grapheme_len;
20782 }
20783 }
20784 WordBreakToken::Newline => {
20785 in_whitespace = true;
20786 if preserve_existing_whitespace {
20787 wrapped_text.push_str(current_line.trim_end());
20788 wrapped_text.push('\n');
20789 current_line.truncate(line_prefix.len());
20790 current_line_len = line_prefix_len;
20791 } else if have_preceding_whitespace {
20792 continue;
20793 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20794 {
20795 wrapped_text.push_str(current_line.trim_end());
20796 wrapped_text.push('\n');
20797 current_line.truncate(line_prefix.len());
20798 current_line_len = line_prefix_len;
20799 } else if current_line_len != line_prefix_len {
20800 current_line.push(' ');
20801 current_line_len += 1;
20802 }
20803 }
20804 }
20805 }
20806
20807 if !current_line.is_empty() {
20808 wrapped_text.push_str(¤t_line);
20809 }
20810 wrapped_text
20811}
20812
20813#[test]
20814fn test_wrap_with_prefix() {
20815 assert_eq!(
20816 wrap_with_prefix(
20817 "# ".to_string(),
20818 "abcdefg".to_string(),
20819 4,
20820 NonZeroU32::new(4).unwrap(),
20821 false,
20822 ),
20823 "# abcdefg"
20824 );
20825 assert_eq!(
20826 wrap_with_prefix(
20827 "".to_string(),
20828 "\thello world".to_string(),
20829 8,
20830 NonZeroU32::new(4).unwrap(),
20831 false,
20832 ),
20833 "hello\nworld"
20834 );
20835 assert_eq!(
20836 wrap_with_prefix(
20837 "// ".to_string(),
20838 "xx \nyy zz aa bb cc".to_string(),
20839 12,
20840 NonZeroU32::new(4).unwrap(),
20841 false,
20842 ),
20843 "// xx yy zz\n// aa bb cc"
20844 );
20845 assert_eq!(
20846 wrap_with_prefix(
20847 String::new(),
20848 "这是什么 \n 钢笔".to_string(),
20849 3,
20850 NonZeroU32::new(4).unwrap(),
20851 false,
20852 ),
20853 "这是什\n么 钢\n笔"
20854 );
20855}
20856
20857pub trait CollaborationHub {
20858 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20859 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20860 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20861}
20862
20863impl CollaborationHub for Entity<Project> {
20864 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20865 self.read(cx).collaborators()
20866 }
20867
20868 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20869 self.read(cx).user_store().read(cx).participant_indices()
20870 }
20871
20872 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20873 let this = self.read(cx);
20874 let user_ids = this.collaborators().values().map(|c| c.user_id);
20875 this.user_store().read(cx).participant_names(user_ids, cx)
20876 }
20877}
20878
20879pub trait SemanticsProvider {
20880 fn hover(
20881 &self,
20882 buffer: &Entity<Buffer>,
20883 position: text::Anchor,
20884 cx: &mut App,
20885 ) -> Option<Task<Vec<project::Hover>>>;
20886
20887 fn inline_values(
20888 &self,
20889 buffer_handle: Entity<Buffer>,
20890 range: Range<text::Anchor>,
20891 cx: &mut App,
20892 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20893
20894 fn inlay_hints(
20895 &self,
20896 buffer_handle: Entity<Buffer>,
20897 range: Range<text::Anchor>,
20898 cx: &mut App,
20899 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20900
20901 fn resolve_inlay_hint(
20902 &self,
20903 hint: InlayHint,
20904 buffer_handle: Entity<Buffer>,
20905 server_id: LanguageServerId,
20906 cx: &mut App,
20907 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20908
20909 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20910
20911 fn document_highlights(
20912 &self,
20913 buffer: &Entity<Buffer>,
20914 position: text::Anchor,
20915 cx: &mut App,
20916 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20917
20918 fn definitions(
20919 &self,
20920 buffer: &Entity<Buffer>,
20921 position: text::Anchor,
20922 kind: GotoDefinitionKind,
20923 cx: &mut App,
20924 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20925
20926 fn range_for_rename(
20927 &self,
20928 buffer: &Entity<Buffer>,
20929 position: text::Anchor,
20930 cx: &mut App,
20931 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20932
20933 fn perform_rename(
20934 &self,
20935 buffer: &Entity<Buffer>,
20936 position: text::Anchor,
20937 new_name: String,
20938 cx: &mut App,
20939 ) -> Option<Task<Result<ProjectTransaction>>>;
20940
20941 fn pull_diagnostics_for_buffer(
20942 &self,
20943 buffer: Entity<Buffer>,
20944 cx: &mut App,
20945 ) -> Task<anyhow::Result<()>>;
20946}
20947
20948pub trait CompletionProvider {
20949 fn completions(
20950 &self,
20951 excerpt_id: ExcerptId,
20952 buffer: &Entity<Buffer>,
20953 buffer_position: text::Anchor,
20954 trigger: CompletionContext,
20955 window: &mut Window,
20956 cx: &mut Context<Editor>,
20957 ) -> Task<Result<Vec<CompletionResponse>>>;
20958
20959 fn resolve_completions(
20960 &self,
20961 _buffer: Entity<Buffer>,
20962 _completion_indices: Vec<usize>,
20963 _completions: Rc<RefCell<Box<[Completion]>>>,
20964 _cx: &mut Context<Editor>,
20965 ) -> Task<Result<bool>> {
20966 Task::ready(Ok(false))
20967 }
20968
20969 fn apply_additional_edits_for_completion(
20970 &self,
20971 _buffer: Entity<Buffer>,
20972 _completions: Rc<RefCell<Box<[Completion]>>>,
20973 _completion_index: usize,
20974 _push_to_history: bool,
20975 _cx: &mut Context<Editor>,
20976 ) -> Task<Result<Option<language::Transaction>>> {
20977 Task::ready(Ok(None))
20978 }
20979
20980 fn is_completion_trigger(
20981 &self,
20982 buffer: &Entity<Buffer>,
20983 position: language::Anchor,
20984 text: &str,
20985 trigger_in_words: bool,
20986 menu_is_open: bool,
20987 cx: &mut Context<Editor>,
20988 ) -> bool;
20989
20990 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20991
20992 fn sort_completions(&self) -> bool {
20993 true
20994 }
20995
20996 fn filter_completions(&self) -> bool {
20997 true
20998 }
20999}
21000
21001pub trait CodeActionProvider {
21002 fn id(&self) -> Arc<str>;
21003
21004 fn code_actions(
21005 &self,
21006 buffer: &Entity<Buffer>,
21007 range: Range<text::Anchor>,
21008 window: &mut Window,
21009 cx: &mut App,
21010 ) -> Task<Result<Vec<CodeAction>>>;
21011
21012 fn apply_code_action(
21013 &self,
21014 buffer_handle: Entity<Buffer>,
21015 action: CodeAction,
21016 excerpt_id: ExcerptId,
21017 push_to_history: bool,
21018 window: &mut Window,
21019 cx: &mut App,
21020 ) -> Task<Result<ProjectTransaction>>;
21021}
21022
21023impl CodeActionProvider for Entity<Project> {
21024 fn id(&self) -> Arc<str> {
21025 "project".into()
21026 }
21027
21028 fn code_actions(
21029 &self,
21030 buffer: &Entity<Buffer>,
21031 range: Range<text::Anchor>,
21032 _window: &mut Window,
21033 cx: &mut App,
21034 ) -> Task<Result<Vec<CodeAction>>> {
21035 self.update(cx, |project, cx| {
21036 let code_lens = project.code_lens(buffer, range.clone(), cx);
21037 let code_actions = project.code_actions(buffer, range, None, cx);
21038 cx.background_spawn(async move {
21039 let (code_lens, code_actions) = join(code_lens, code_actions).await;
21040 Ok(code_lens
21041 .context("code lens fetch")?
21042 .into_iter()
21043 .chain(code_actions.context("code action fetch")?)
21044 .collect())
21045 })
21046 })
21047 }
21048
21049 fn apply_code_action(
21050 &self,
21051 buffer_handle: Entity<Buffer>,
21052 action: CodeAction,
21053 _excerpt_id: ExcerptId,
21054 push_to_history: bool,
21055 _window: &mut Window,
21056 cx: &mut App,
21057 ) -> Task<Result<ProjectTransaction>> {
21058 self.update(cx, |project, cx| {
21059 project.apply_code_action(buffer_handle, action, push_to_history, cx)
21060 })
21061 }
21062}
21063
21064fn snippet_completions(
21065 project: &Project,
21066 buffer: &Entity<Buffer>,
21067 buffer_position: text::Anchor,
21068 cx: &mut App,
21069) -> Task<Result<CompletionResponse>> {
21070 let languages = buffer.read(cx).languages_at(buffer_position);
21071 let snippet_store = project.snippets().read(cx);
21072
21073 let scopes: Vec<_> = languages
21074 .iter()
21075 .filter_map(|language| {
21076 let language_name = language.lsp_id();
21077 let snippets = snippet_store.snippets_for(Some(language_name), cx);
21078
21079 if snippets.is_empty() {
21080 None
21081 } else {
21082 Some((language.default_scope(), snippets))
21083 }
21084 })
21085 .collect();
21086
21087 if scopes.is_empty() {
21088 return Task::ready(Ok(CompletionResponse {
21089 completions: vec![],
21090 is_incomplete: false,
21091 }));
21092 }
21093
21094 let snapshot = buffer.read(cx).text_snapshot();
21095 let chars: String = snapshot
21096 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
21097 .collect();
21098 let executor = cx.background_executor().clone();
21099
21100 cx.background_spawn(async move {
21101 let mut is_incomplete = false;
21102 let mut completions: Vec<Completion> = Vec::new();
21103 for (scope, snippets) in scopes.into_iter() {
21104 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
21105 let mut last_word = chars
21106 .chars()
21107 .take_while(|c| classifier.is_word(*c))
21108 .collect::<String>();
21109 last_word = last_word.chars().rev().collect();
21110
21111 if last_word.is_empty() {
21112 return Ok(CompletionResponse {
21113 completions: vec![],
21114 is_incomplete: true,
21115 });
21116 }
21117
21118 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
21119 let to_lsp = |point: &text::Anchor| {
21120 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
21121 point_to_lsp(end)
21122 };
21123 let lsp_end = to_lsp(&buffer_position);
21124
21125 let candidates = snippets
21126 .iter()
21127 .enumerate()
21128 .flat_map(|(ix, snippet)| {
21129 snippet
21130 .prefix
21131 .iter()
21132 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
21133 })
21134 .collect::<Vec<StringMatchCandidate>>();
21135
21136 const MAX_RESULTS: usize = 100;
21137 let mut matches = fuzzy::match_strings(
21138 &candidates,
21139 &last_word,
21140 last_word.chars().any(|c| c.is_uppercase()),
21141 MAX_RESULTS,
21142 &Default::default(),
21143 executor.clone(),
21144 )
21145 .await;
21146
21147 if matches.len() >= MAX_RESULTS {
21148 is_incomplete = true;
21149 }
21150
21151 // Remove all candidates where the query's start does not match the start of any word in the candidate
21152 if let Some(query_start) = last_word.chars().next() {
21153 matches.retain(|string_match| {
21154 split_words(&string_match.string).any(|word| {
21155 // Check that the first codepoint of the word as lowercase matches the first
21156 // codepoint of the query as lowercase
21157 word.chars()
21158 .flat_map(|codepoint| codepoint.to_lowercase())
21159 .zip(query_start.to_lowercase())
21160 .all(|(word_cp, query_cp)| word_cp == query_cp)
21161 })
21162 });
21163 }
21164
21165 let matched_strings = matches
21166 .into_iter()
21167 .map(|m| m.string)
21168 .collect::<HashSet<_>>();
21169
21170 completions.extend(snippets.iter().filter_map(|snippet| {
21171 let matching_prefix = snippet
21172 .prefix
21173 .iter()
21174 .find(|prefix| matched_strings.contains(*prefix))?;
21175 let start = as_offset - last_word.len();
21176 let start = snapshot.anchor_before(start);
21177 let range = start..buffer_position;
21178 let lsp_start = to_lsp(&start);
21179 let lsp_range = lsp::Range {
21180 start: lsp_start,
21181 end: lsp_end,
21182 };
21183 Some(Completion {
21184 replace_range: range,
21185 new_text: snippet.body.clone(),
21186 source: CompletionSource::Lsp {
21187 insert_range: None,
21188 server_id: LanguageServerId(usize::MAX),
21189 resolved: true,
21190 lsp_completion: Box::new(lsp::CompletionItem {
21191 label: snippet.prefix.first().unwrap().clone(),
21192 kind: Some(CompletionItemKind::SNIPPET),
21193 label_details: snippet.description.as_ref().map(|description| {
21194 lsp::CompletionItemLabelDetails {
21195 detail: Some(description.clone()),
21196 description: None,
21197 }
21198 }),
21199 insert_text_format: Some(InsertTextFormat::SNIPPET),
21200 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21201 lsp::InsertReplaceEdit {
21202 new_text: snippet.body.clone(),
21203 insert: lsp_range,
21204 replace: lsp_range,
21205 },
21206 )),
21207 filter_text: Some(snippet.body.clone()),
21208 sort_text: Some(char::MAX.to_string()),
21209 ..lsp::CompletionItem::default()
21210 }),
21211 lsp_defaults: None,
21212 },
21213 label: CodeLabel {
21214 text: matching_prefix.clone(),
21215 runs: Vec::new(),
21216 filter_range: 0..matching_prefix.len(),
21217 },
21218 icon_path: None,
21219 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21220 single_line: snippet.name.clone().into(),
21221 plain_text: snippet
21222 .description
21223 .clone()
21224 .map(|description| description.into()),
21225 }),
21226 insert_text_mode: None,
21227 confirm: None,
21228 })
21229 }))
21230 }
21231
21232 Ok(CompletionResponse {
21233 completions,
21234 is_incomplete,
21235 })
21236 })
21237}
21238
21239impl CompletionProvider for Entity<Project> {
21240 fn completions(
21241 &self,
21242 _excerpt_id: ExcerptId,
21243 buffer: &Entity<Buffer>,
21244 buffer_position: text::Anchor,
21245 options: CompletionContext,
21246 _window: &mut Window,
21247 cx: &mut Context<Editor>,
21248 ) -> Task<Result<Vec<CompletionResponse>>> {
21249 self.update(cx, |project, cx| {
21250 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21251 let project_completions = project.completions(buffer, buffer_position, options, cx);
21252 cx.background_spawn(async move {
21253 let mut responses = project_completions.await?;
21254 let snippets = snippets.await?;
21255 if !snippets.completions.is_empty() {
21256 responses.push(snippets);
21257 }
21258 Ok(responses)
21259 })
21260 })
21261 }
21262
21263 fn resolve_completions(
21264 &self,
21265 buffer: Entity<Buffer>,
21266 completion_indices: Vec<usize>,
21267 completions: Rc<RefCell<Box<[Completion]>>>,
21268 cx: &mut Context<Editor>,
21269 ) -> Task<Result<bool>> {
21270 self.update(cx, |project, cx| {
21271 project.lsp_store().update(cx, |lsp_store, cx| {
21272 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21273 })
21274 })
21275 }
21276
21277 fn apply_additional_edits_for_completion(
21278 &self,
21279 buffer: Entity<Buffer>,
21280 completions: Rc<RefCell<Box<[Completion]>>>,
21281 completion_index: usize,
21282 push_to_history: bool,
21283 cx: &mut Context<Editor>,
21284 ) -> Task<Result<Option<language::Transaction>>> {
21285 self.update(cx, |project, cx| {
21286 project.lsp_store().update(cx, |lsp_store, cx| {
21287 lsp_store.apply_additional_edits_for_completion(
21288 buffer,
21289 completions,
21290 completion_index,
21291 push_to_history,
21292 cx,
21293 )
21294 })
21295 })
21296 }
21297
21298 fn is_completion_trigger(
21299 &self,
21300 buffer: &Entity<Buffer>,
21301 position: language::Anchor,
21302 text: &str,
21303 trigger_in_words: bool,
21304 menu_is_open: bool,
21305 cx: &mut Context<Editor>,
21306 ) -> bool {
21307 let mut chars = text.chars();
21308 let char = if let Some(char) = chars.next() {
21309 char
21310 } else {
21311 return false;
21312 };
21313 if chars.next().is_some() {
21314 return false;
21315 }
21316
21317 let buffer = buffer.read(cx);
21318 let snapshot = buffer.snapshot();
21319 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21320 return false;
21321 }
21322 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21323 if trigger_in_words && classifier.is_word(char) {
21324 return true;
21325 }
21326
21327 buffer.completion_triggers().contains(text)
21328 }
21329}
21330
21331impl SemanticsProvider for Entity<Project> {
21332 fn hover(
21333 &self,
21334 buffer: &Entity<Buffer>,
21335 position: text::Anchor,
21336 cx: &mut App,
21337 ) -> Option<Task<Vec<project::Hover>>> {
21338 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21339 }
21340
21341 fn document_highlights(
21342 &self,
21343 buffer: &Entity<Buffer>,
21344 position: text::Anchor,
21345 cx: &mut App,
21346 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21347 Some(self.update(cx, |project, cx| {
21348 project.document_highlights(buffer, position, cx)
21349 }))
21350 }
21351
21352 fn definitions(
21353 &self,
21354 buffer: &Entity<Buffer>,
21355 position: text::Anchor,
21356 kind: GotoDefinitionKind,
21357 cx: &mut App,
21358 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21359 Some(self.update(cx, |project, cx| match kind {
21360 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21361 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21362 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21363 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21364 }))
21365 }
21366
21367 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21368 // TODO: make this work for remote projects
21369 self.update(cx, |project, cx| {
21370 if project
21371 .active_debug_session(cx)
21372 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21373 {
21374 return true;
21375 }
21376
21377 buffer.update(cx, |buffer, cx| {
21378 project.any_language_server_supports_inlay_hints(buffer, cx)
21379 })
21380 })
21381 }
21382
21383 fn inline_values(
21384 &self,
21385 buffer_handle: Entity<Buffer>,
21386
21387 range: Range<text::Anchor>,
21388 cx: &mut App,
21389 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21390 self.update(cx, |project, cx| {
21391 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21392
21393 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21394 })
21395 }
21396
21397 fn inlay_hints(
21398 &self,
21399 buffer_handle: Entity<Buffer>,
21400 range: Range<text::Anchor>,
21401 cx: &mut App,
21402 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21403 Some(self.update(cx, |project, cx| {
21404 project.inlay_hints(buffer_handle, range, cx)
21405 }))
21406 }
21407
21408 fn resolve_inlay_hint(
21409 &self,
21410 hint: InlayHint,
21411 buffer_handle: Entity<Buffer>,
21412 server_id: LanguageServerId,
21413 cx: &mut App,
21414 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21415 Some(self.update(cx, |project, cx| {
21416 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21417 }))
21418 }
21419
21420 fn range_for_rename(
21421 &self,
21422 buffer: &Entity<Buffer>,
21423 position: text::Anchor,
21424 cx: &mut App,
21425 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21426 Some(self.update(cx, |project, cx| {
21427 let buffer = buffer.clone();
21428 let task = project.prepare_rename(buffer.clone(), position, cx);
21429 cx.spawn(async move |_, cx| {
21430 Ok(match task.await? {
21431 PrepareRenameResponse::Success(range) => Some(range),
21432 PrepareRenameResponse::InvalidPosition => None,
21433 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21434 // Fallback on using TreeSitter info to determine identifier range
21435 buffer.read_with(cx, |buffer, _| {
21436 let snapshot = buffer.snapshot();
21437 let (range, kind) = snapshot.surrounding_word(position);
21438 if kind != Some(CharKind::Word) {
21439 return None;
21440 }
21441 Some(
21442 snapshot.anchor_before(range.start)
21443 ..snapshot.anchor_after(range.end),
21444 )
21445 })?
21446 }
21447 })
21448 })
21449 }))
21450 }
21451
21452 fn perform_rename(
21453 &self,
21454 buffer: &Entity<Buffer>,
21455 position: text::Anchor,
21456 new_name: String,
21457 cx: &mut App,
21458 ) -> Option<Task<Result<ProjectTransaction>>> {
21459 Some(self.update(cx, |project, cx| {
21460 project.perform_rename(buffer.clone(), position, new_name, cx)
21461 }))
21462 }
21463
21464 fn pull_diagnostics_for_buffer(
21465 &self,
21466 buffer: Entity<Buffer>,
21467 cx: &mut App,
21468 ) -> Task<anyhow::Result<()>> {
21469 let diagnostics = self.update(cx, |project, cx| {
21470 project
21471 .lsp_store()
21472 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21473 });
21474 let project = self.clone();
21475 cx.spawn(async move |cx| {
21476 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21477 project.update(cx, |project, cx| {
21478 project.lsp_store().update(cx, |lsp_store, cx| {
21479 for diagnostics_set in diagnostics {
21480 let LspPullDiagnostics::Response {
21481 server_id,
21482 uri,
21483 diagnostics,
21484 } = diagnostics_set
21485 else {
21486 continue;
21487 };
21488
21489 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21490 let disk_based_sources = adapter
21491 .as_ref()
21492 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21493 .unwrap_or(&[]);
21494 match diagnostics {
21495 PulledDiagnostics::Unchanged { result_id } => {
21496 lsp_store
21497 .merge_diagnostics(
21498 server_id,
21499 lsp::PublishDiagnosticsParams {
21500 uri: uri.clone(),
21501 diagnostics: Vec::new(),
21502 version: None,
21503 },
21504 Some(result_id),
21505 DiagnosticSourceKind::Pulled,
21506 disk_based_sources,
21507 |_, _| true,
21508 cx,
21509 )
21510 .log_err();
21511 }
21512 PulledDiagnostics::Changed {
21513 diagnostics,
21514 result_id,
21515 } => {
21516 lsp_store
21517 .merge_diagnostics(
21518 server_id,
21519 lsp::PublishDiagnosticsParams {
21520 uri: uri.clone(),
21521 diagnostics,
21522 version: None,
21523 },
21524 result_id,
21525 DiagnosticSourceKind::Pulled,
21526 disk_based_sources,
21527 |old_diagnostic, _| match old_diagnostic.source_kind {
21528 DiagnosticSourceKind::Pulled => false,
21529 DiagnosticSourceKind::Other
21530 | DiagnosticSourceKind::Pushed => true,
21531 },
21532 cx,
21533 )
21534 .log_err();
21535 }
21536 }
21537 }
21538 })
21539 })
21540 })
21541 }
21542}
21543
21544fn inlay_hint_settings(
21545 location: Anchor,
21546 snapshot: &MultiBufferSnapshot,
21547 cx: &mut Context<Editor>,
21548) -> InlayHintSettings {
21549 let file = snapshot.file_at(location);
21550 let language = snapshot.language_at(location).map(|l| l.name());
21551 language_settings(language, file, cx).inlay_hints
21552}
21553
21554fn consume_contiguous_rows(
21555 contiguous_row_selections: &mut Vec<Selection<Point>>,
21556 selection: &Selection<Point>,
21557 display_map: &DisplaySnapshot,
21558 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21559) -> (MultiBufferRow, MultiBufferRow) {
21560 contiguous_row_selections.push(selection.clone());
21561 let start_row = MultiBufferRow(selection.start.row);
21562 let mut end_row = ending_row(selection, display_map);
21563
21564 while let Some(next_selection) = selections.peek() {
21565 if next_selection.start.row <= end_row.0 {
21566 end_row = ending_row(next_selection, display_map);
21567 contiguous_row_selections.push(selections.next().unwrap().clone());
21568 } else {
21569 break;
21570 }
21571 }
21572 (start_row, end_row)
21573}
21574
21575fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21576 if next_selection.end.column > 0 || next_selection.is_empty() {
21577 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21578 } else {
21579 MultiBufferRow(next_selection.end.row)
21580 }
21581}
21582
21583impl EditorSnapshot {
21584 pub fn remote_selections_in_range<'a>(
21585 &'a self,
21586 range: &'a Range<Anchor>,
21587 collaboration_hub: &dyn CollaborationHub,
21588 cx: &'a App,
21589 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21590 let participant_names = collaboration_hub.user_names(cx);
21591 let participant_indices = collaboration_hub.user_participant_indices(cx);
21592 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21593 let collaborators_by_replica_id = collaborators_by_peer_id
21594 .values()
21595 .map(|collaborator| (collaborator.replica_id, collaborator))
21596 .collect::<HashMap<_, _>>();
21597 self.buffer_snapshot
21598 .selections_in_range(range, false)
21599 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21600 if replica_id == AGENT_REPLICA_ID {
21601 Some(RemoteSelection {
21602 replica_id,
21603 selection,
21604 cursor_shape,
21605 line_mode,
21606 collaborator_id: CollaboratorId::Agent,
21607 user_name: Some("Agent".into()),
21608 color: cx.theme().players().agent(),
21609 })
21610 } else {
21611 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21612 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21613 let user_name = participant_names.get(&collaborator.user_id).cloned();
21614 Some(RemoteSelection {
21615 replica_id,
21616 selection,
21617 cursor_shape,
21618 line_mode,
21619 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21620 user_name,
21621 color: if let Some(index) = participant_index {
21622 cx.theme().players().color_for_participant(index.0)
21623 } else {
21624 cx.theme().players().absent()
21625 },
21626 })
21627 }
21628 })
21629 }
21630
21631 pub fn hunks_for_ranges(
21632 &self,
21633 ranges: impl IntoIterator<Item = Range<Point>>,
21634 ) -> Vec<MultiBufferDiffHunk> {
21635 let mut hunks = Vec::new();
21636 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21637 HashMap::default();
21638 for query_range in ranges {
21639 let query_rows =
21640 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21641 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21642 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21643 ) {
21644 // Include deleted hunks that are adjacent to the query range, because
21645 // otherwise they would be missed.
21646 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21647 if hunk.status().is_deleted() {
21648 intersects_range |= hunk.row_range.start == query_rows.end;
21649 intersects_range |= hunk.row_range.end == query_rows.start;
21650 }
21651 if intersects_range {
21652 if !processed_buffer_rows
21653 .entry(hunk.buffer_id)
21654 .or_default()
21655 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21656 {
21657 continue;
21658 }
21659 hunks.push(hunk);
21660 }
21661 }
21662 }
21663
21664 hunks
21665 }
21666
21667 fn display_diff_hunks_for_rows<'a>(
21668 &'a self,
21669 display_rows: Range<DisplayRow>,
21670 folded_buffers: &'a HashSet<BufferId>,
21671 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21672 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21673 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21674
21675 self.buffer_snapshot
21676 .diff_hunks_in_range(buffer_start..buffer_end)
21677 .filter_map(|hunk| {
21678 if folded_buffers.contains(&hunk.buffer_id) {
21679 return None;
21680 }
21681
21682 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21683 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21684
21685 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21686 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21687
21688 let display_hunk = if hunk_display_start.column() != 0 {
21689 DisplayDiffHunk::Folded {
21690 display_row: hunk_display_start.row(),
21691 }
21692 } else {
21693 let mut end_row = hunk_display_end.row();
21694 if hunk_display_end.column() > 0 {
21695 end_row.0 += 1;
21696 }
21697 let is_created_file = hunk.is_created_file();
21698 DisplayDiffHunk::Unfolded {
21699 status: hunk.status(),
21700 diff_base_byte_range: hunk.diff_base_byte_range,
21701 display_row_range: hunk_display_start.row()..end_row,
21702 multi_buffer_range: Anchor::range_in_buffer(
21703 hunk.excerpt_id,
21704 hunk.buffer_id,
21705 hunk.buffer_range,
21706 ),
21707 is_created_file,
21708 }
21709 };
21710
21711 Some(display_hunk)
21712 })
21713 }
21714
21715 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21716 self.display_snapshot.buffer_snapshot.language_at(position)
21717 }
21718
21719 pub fn is_focused(&self) -> bool {
21720 self.is_focused
21721 }
21722
21723 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21724 self.placeholder_text.as_ref()
21725 }
21726
21727 pub fn scroll_position(&self) -> gpui::Point<f32> {
21728 self.scroll_anchor.scroll_position(&self.display_snapshot)
21729 }
21730
21731 fn gutter_dimensions(
21732 &self,
21733 font_id: FontId,
21734 font_size: Pixels,
21735 max_line_number_width: Pixels,
21736 cx: &App,
21737 ) -> Option<GutterDimensions> {
21738 if !self.show_gutter {
21739 return None;
21740 }
21741
21742 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21743 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21744
21745 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21746 matches!(
21747 ProjectSettings::get_global(cx).git.git_gutter,
21748 Some(GitGutterSetting::TrackedFiles)
21749 )
21750 });
21751 let gutter_settings = EditorSettings::get_global(cx).gutter;
21752 let show_line_numbers = self
21753 .show_line_numbers
21754 .unwrap_or(gutter_settings.line_numbers);
21755 let line_gutter_width = if show_line_numbers {
21756 // Avoid flicker-like gutter resizes when the line number gains another digit by
21757 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21758 let min_width_for_number_on_gutter =
21759 ch_advance * gutter_settings.min_line_number_digits as f32;
21760 max_line_number_width.max(min_width_for_number_on_gutter)
21761 } else {
21762 0.0.into()
21763 };
21764
21765 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21766 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21767
21768 let git_blame_entries_width =
21769 self.git_blame_gutter_max_author_length
21770 .map(|max_author_length| {
21771 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21772 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21773
21774 /// The number of characters to dedicate to gaps and margins.
21775 const SPACING_WIDTH: usize = 4;
21776
21777 let max_char_count = max_author_length.min(renderer.max_author_length())
21778 + ::git::SHORT_SHA_LENGTH
21779 + MAX_RELATIVE_TIMESTAMP.len()
21780 + SPACING_WIDTH;
21781
21782 ch_advance * max_char_count
21783 });
21784
21785 let is_singleton = self.buffer_snapshot.is_singleton();
21786
21787 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21788 left_padding += if !is_singleton {
21789 ch_width * 4.0
21790 } else if show_runnables || show_breakpoints {
21791 ch_width * 3.0
21792 } else if show_git_gutter && show_line_numbers {
21793 ch_width * 2.0
21794 } else if show_git_gutter || show_line_numbers {
21795 ch_width
21796 } else {
21797 px(0.)
21798 };
21799
21800 let shows_folds = is_singleton && gutter_settings.folds;
21801
21802 let right_padding = if shows_folds && show_line_numbers {
21803 ch_width * 4.0
21804 } else if shows_folds || (!is_singleton && show_line_numbers) {
21805 ch_width * 3.0
21806 } else if show_line_numbers {
21807 ch_width
21808 } else {
21809 px(0.)
21810 };
21811
21812 Some(GutterDimensions {
21813 left_padding,
21814 right_padding,
21815 width: line_gutter_width + left_padding + right_padding,
21816 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21817 git_blame_entries_width,
21818 })
21819 }
21820
21821 pub fn render_crease_toggle(
21822 &self,
21823 buffer_row: MultiBufferRow,
21824 row_contains_cursor: bool,
21825 editor: Entity<Editor>,
21826 window: &mut Window,
21827 cx: &mut App,
21828 ) -> Option<AnyElement> {
21829 let folded = self.is_line_folded(buffer_row);
21830 let mut is_foldable = false;
21831
21832 if let Some(crease) = self
21833 .crease_snapshot
21834 .query_row(buffer_row, &self.buffer_snapshot)
21835 {
21836 is_foldable = true;
21837 match crease {
21838 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21839 if let Some(render_toggle) = render_toggle {
21840 let toggle_callback =
21841 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21842 if folded {
21843 editor.update(cx, |editor, cx| {
21844 editor.fold_at(buffer_row, window, cx)
21845 });
21846 } else {
21847 editor.update(cx, |editor, cx| {
21848 editor.unfold_at(buffer_row, window, cx)
21849 });
21850 }
21851 });
21852 return Some((render_toggle)(
21853 buffer_row,
21854 folded,
21855 toggle_callback,
21856 window,
21857 cx,
21858 ));
21859 }
21860 }
21861 }
21862 }
21863
21864 is_foldable |= self.starts_indent(buffer_row);
21865
21866 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21867 Some(
21868 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21869 .toggle_state(folded)
21870 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21871 if folded {
21872 this.unfold_at(buffer_row, window, cx);
21873 } else {
21874 this.fold_at(buffer_row, window, cx);
21875 }
21876 }))
21877 .into_any_element(),
21878 )
21879 } else {
21880 None
21881 }
21882 }
21883
21884 pub fn render_crease_trailer(
21885 &self,
21886 buffer_row: MultiBufferRow,
21887 window: &mut Window,
21888 cx: &mut App,
21889 ) -> Option<AnyElement> {
21890 let folded = self.is_line_folded(buffer_row);
21891 if let Crease::Inline { render_trailer, .. } = self
21892 .crease_snapshot
21893 .query_row(buffer_row, &self.buffer_snapshot)?
21894 {
21895 let render_trailer = render_trailer.as_ref()?;
21896 Some(render_trailer(buffer_row, folded, window, cx))
21897 } else {
21898 None
21899 }
21900 }
21901}
21902
21903impl Deref for EditorSnapshot {
21904 type Target = DisplaySnapshot;
21905
21906 fn deref(&self) -> &Self::Target {
21907 &self.display_snapshot
21908 }
21909}
21910
21911#[derive(Clone, Debug, PartialEq, Eq)]
21912pub enum EditorEvent {
21913 InputIgnored {
21914 text: Arc<str>,
21915 },
21916 InputHandled {
21917 utf16_range_to_replace: Option<Range<isize>>,
21918 text: Arc<str>,
21919 },
21920 ExcerptsAdded {
21921 buffer: Entity<Buffer>,
21922 predecessor: ExcerptId,
21923 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21924 },
21925 ExcerptsRemoved {
21926 ids: Vec<ExcerptId>,
21927 removed_buffer_ids: Vec<BufferId>,
21928 },
21929 BufferFoldToggled {
21930 ids: Vec<ExcerptId>,
21931 folded: bool,
21932 },
21933 ExcerptsEdited {
21934 ids: Vec<ExcerptId>,
21935 },
21936 ExcerptsExpanded {
21937 ids: Vec<ExcerptId>,
21938 },
21939 BufferEdited,
21940 Edited {
21941 transaction_id: clock::Lamport,
21942 },
21943 Reparsed(BufferId),
21944 Focused,
21945 FocusedIn,
21946 Blurred,
21947 DirtyChanged,
21948 Saved,
21949 TitleChanged,
21950 DiffBaseChanged,
21951 SelectionsChanged {
21952 local: bool,
21953 },
21954 ScrollPositionChanged {
21955 local: bool,
21956 autoscroll: bool,
21957 },
21958 Closed,
21959 TransactionUndone {
21960 transaction_id: clock::Lamport,
21961 },
21962 TransactionBegun {
21963 transaction_id: clock::Lamport,
21964 },
21965 Reloaded,
21966 CursorShapeChanged,
21967 PushedToNavHistory {
21968 anchor: Anchor,
21969 is_deactivate: bool,
21970 },
21971}
21972
21973impl EventEmitter<EditorEvent> for Editor {}
21974
21975impl Focusable for Editor {
21976 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21977 self.focus_handle.clone()
21978 }
21979}
21980
21981impl Render for Editor {
21982 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21983 let settings = ThemeSettings::get_global(cx);
21984
21985 let mut text_style = match self.mode {
21986 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21987 color: cx.theme().colors().editor_foreground,
21988 font_family: settings.ui_font.family.clone(),
21989 font_features: settings.ui_font.features.clone(),
21990 font_fallbacks: settings.ui_font.fallbacks.clone(),
21991 font_size: rems(0.875).into(),
21992 font_weight: settings.ui_font.weight,
21993 line_height: relative(settings.buffer_line_height.value()),
21994 ..Default::default()
21995 },
21996 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21997 color: cx.theme().colors().editor_foreground,
21998 font_family: settings.buffer_font.family.clone(),
21999 font_features: settings.buffer_font.features.clone(),
22000 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22001 font_size: settings.buffer_font_size(cx).into(),
22002 font_weight: settings.buffer_font.weight,
22003 line_height: relative(settings.buffer_line_height.value()),
22004 ..Default::default()
22005 },
22006 };
22007 if let Some(text_style_refinement) = &self.text_style_refinement {
22008 text_style.refine(text_style_refinement)
22009 }
22010
22011 let background = match self.mode {
22012 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
22013 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
22014 EditorMode::Full { .. } => cx.theme().colors().editor_background,
22015 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
22016 };
22017
22018 EditorElement::new(
22019 &cx.entity(),
22020 EditorStyle {
22021 background,
22022 local_player: cx.theme().players().local(),
22023 text: text_style,
22024 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
22025 syntax: cx.theme().syntax().clone(),
22026 status: cx.theme().status().clone(),
22027 inlay_hints_style: make_inlay_hints_style(cx),
22028 inline_completion_styles: make_suggestion_styles(cx),
22029 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
22030 show_underlines: !self.mode.is_minimap(),
22031 },
22032 )
22033 }
22034}
22035
22036impl EntityInputHandler for Editor {
22037 fn text_for_range(
22038 &mut self,
22039 range_utf16: Range<usize>,
22040 adjusted_range: &mut Option<Range<usize>>,
22041 _: &mut Window,
22042 cx: &mut Context<Self>,
22043 ) -> Option<String> {
22044 let snapshot = self.buffer.read(cx).read(cx);
22045 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
22046 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
22047 if (start.0..end.0) != range_utf16 {
22048 adjusted_range.replace(start.0..end.0);
22049 }
22050 Some(snapshot.text_for_range(start..end).collect())
22051 }
22052
22053 fn selected_text_range(
22054 &mut self,
22055 ignore_disabled_input: bool,
22056 _: &mut Window,
22057 cx: &mut Context<Self>,
22058 ) -> Option<UTF16Selection> {
22059 // Prevent the IME menu from appearing when holding down an alphabetic key
22060 // while input is disabled.
22061 if !ignore_disabled_input && !self.input_enabled {
22062 return None;
22063 }
22064
22065 let selection = self.selections.newest::<OffsetUtf16>(cx);
22066 let range = selection.range();
22067
22068 Some(UTF16Selection {
22069 range: range.start.0..range.end.0,
22070 reversed: selection.reversed,
22071 })
22072 }
22073
22074 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
22075 let snapshot = self.buffer.read(cx).read(cx);
22076 let (range, _) = self.text_highlights::<InputComposition>(cx)?.first()?;
22077 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
22078 }
22079
22080 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22081 self.clear_highlights::<InputComposition>(cx);
22082 self.ime_transaction.take();
22083 }
22084
22085 fn replace_text_in_range(
22086 &mut self,
22087 range_utf16: Option<Range<usize>>,
22088 text: &str,
22089 window: &mut Window,
22090 cx: &mut Context<Self>,
22091 ) {
22092 if !self.input_enabled {
22093 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22094 return;
22095 }
22096
22097 self.transact(window, cx, |this, window, cx| {
22098 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
22099 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22100 Some(this.selection_replacement_ranges(range_utf16, cx))
22101 } else {
22102 this.marked_text_ranges(cx)
22103 };
22104
22105 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
22106 let newest_selection_id = this.selections.newest_anchor().id;
22107 this.selections
22108 .all::<OffsetUtf16>(cx)
22109 .iter()
22110 .zip(ranges_to_replace.iter())
22111 .find_map(|(selection, range)| {
22112 if selection.id == newest_selection_id {
22113 Some(
22114 (range.start.0 as isize - selection.head().0 as isize)
22115 ..(range.end.0 as isize - selection.head().0 as isize),
22116 )
22117 } else {
22118 None
22119 }
22120 })
22121 });
22122
22123 cx.emit(EditorEvent::InputHandled {
22124 utf16_range_to_replace: range_to_replace,
22125 text: text.into(),
22126 });
22127
22128 if let Some(new_selected_ranges) = new_selected_ranges {
22129 this.change_selections(None, window, cx, |selections| {
22130 selections.select_ranges(new_selected_ranges)
22131 });
22132 this.backspace(&Default::default(), window, cx);
22133 }
22134
22135 this.handle_input(text, window, cx);
22136 });
22137
22138 if let Some(transaction) = self.ime_transaction {
22139 self.buffer.update(cx, |buffer, cx| {
22140 buffer.group_until_transaction(transaction, cx);
22141 });
22142 }
22143
22144 self.unmark_text(window, cx);
22145 }
22146
22147 fn replace_and_mark_text_in_range(
22148 &mut self,
22149 range_utf16: Option<Range<usize>>,
22150 text: &str,
22151 new_selected_range_utf16: Option<Range<usize>>,
22152 window: &mut Window,
22153 cx: &mut Context<Self>,
22154 ) {
22155 if !self.input_enabled {
22156 return;
22157 }
22158
22159 let transaction = self.transact(window, cx, |this, window, cx| {
22160 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
22161 let snapshot = this.buffer.read(cx).read(cx);
22162 if let Some(relative_range_utf16) = range_utf16.as_ref() {
22163 for marked_range in &mut marked_ranges {
22164 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
22165 marked_range.start.0 += relative_range_utf16.start;
22166 marked_range.start =
22167 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
22168 marked_range.end =
22169 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22170 }
22171 }
22172 Some(marked_ranges)
22173 } else if let Some(range_utf16) = range_utf16 {
22174 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22175 Some(this.selection_replacement_ranges(range_utf16, cx))
22176 } else {
22177 None
22178 };
22179
22180 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22181 let newest_selection_id = this.selections.newest_anchor().id;
22182 this.selections
22183 .all::<OffsetUtf16>(cx)
22184 .iter()
22185 .zip(ranges_to_replace.iter())
22186 .find_map(|(selection, range)| {
22187 if selection.id == newest_selection_id {
22188 Some(
22189 (range.start.0 as isize - selection.head().0 as isize)
22190 ..(range.end.0 as isize - selection.head().0 as isize),
22191 )
22192 } else {
22193 None
22194 }
22195 })
22196 });
22197
22198 cx.emit(EditorEvent::InputHandled {
22199 utf16_range_to_replace: range_to_replace,
22200 text: text.into(),
22201 });
22202
22203 if let Some(ranges) = ranges_to_replace {
22204 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22205 }
22206
22207 let marked_ranges = {
22208 let snapshot = this.buffer.read(cx).read(cx);
22209 this.selections
22210 .disjoint_anchors()
22211 .iter()
22212 .map(|selection| {
22213 (
22214 selection.start.bias_left(&snapshot)
22215 ..selection.end.bias_right(&snapshot),
22216 HighlightStyle {
22217 underline: Some(UnderlineStyle {
22218 thickness: px(1.),
22219 color: None,
22220 wavy: false,
22221 }),
22222 ..Default::default()
22223 },
22224 )
22225 })
22226 .collect::<Vec<_>>()
22227 };
22228
22229 if text.is_empty() {
22230 this.unmark_text(window, cx);
22231 } else {
22232 this.highlight_text::<InputComposition>(marked_ranges.clone(), cx);
22233 }
22234
22235 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22236 let use_autoclose = this.use_autoclose;
22237 let use_auto_surround = this.use_auto_surround;
22238 this.set_use_autoclose(false);
22239 this.set_use_auto_surround(false);
22240 this.handle_input(text, window, cx);
22241 this.set_use_autoclose(use_autoclose);
22242 this.set_use_auto_surround(use_auto_surround);
22243
22244 if let Some(new_selected_range) = new_selected_range_utf16 {
22245 let snapshot = this.buffer.read(cx).read(cx);
22246 let new_selected_ranges = marked_ranges
22247 .into_iter()
22248 .map(|(marked_range, _)| {
22249 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22250 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22251 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22252 snapshot.clip_offset_utf16(new_start, Bias::Left)
22253 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22254 })
22255 .collect::<Vec<_>>();
22256
22257 drop(snapshot);
22258 this.change_selections(None, window, cx, |selections| {
22259 selections.select_ranges(new_selected_ranges)
22260 });
22261 }
22262 });
22263
22264 self.ime_transaction = self.ime_transaction.or(transaction);
22265 if let Some(transaction) = self.ime_transaction {
22266 self.buffer.update(cx, |buffer, cx| {
22267 buffer.group_until_transaction(transaction, cx);
22268 });
22269 }
22270
22271 if self.text_highlights::<InputComposition>(cx).is_none() {
22272 self.ime_transaction.take();
22273 }
22274 }
22275
22276 fn bounds_for_range(
22277 &mut self,
22278 range_utf16: Range<usize>,
22279 element_bounds: gpui::Bounds<Pixels>,
22280 window: &mut Window,
22281 cx: &mut Context<Self>,
22282 ) -> Option<gpui::Bounds<Pixels>> {
22283 let text_layout_details = self.text_layout_details(window);
22284 let gpui::Size {
22285 width: em_width,
22286 height: line_height,
22287 } = self.character_size(window);
22288
22289 let snapshot = self.snapshot(window, cx);
22290 let scroll_position = snapshot.scroll_position();
22291 let scroll_left = scroll_position.x * em_width;
22292
22293 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22294 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22295 + self.gutter_dimensions.width
22296 + self.gutter_dimensions.margin;
22297 let y = line_height * (start.row().as_f32() - scroll_position.y);
22298
22299 Some(Bounds {
22300 origin: element_bounds.origin + point(x, y),
22301 size: size(em_width, line_height),
22302 })
22303 }
22304
22305 fn character_index_for_point(
22306 &mut self,
22307 point: gpui::Point<Pixels>,
22308 _window: &mut Window,
22309 _cx: &mut Context<Self>,
22310 ) -> Option<usize> {
22311 let position_map = self.last_position_map.as_ref()?;
22312 if !position_map.text_hitbox.contains(&point) {
22313 return None;
22314 }
22315 let display_point = position_map.point_for_position(point).previous_valid;
22316 let anchor = position_map
22317 .snapshot
22318 .display_point_to_anchor(display_point, Bias::Left);
22319 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22320 Some(utf16_offset.0)
22321 }
22322}
22323
22324trait SelectionExt {
22325 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22326 fn spanned_rows(
22327 &self,
22328 include_end_if_at_line_start: bool,
22329 map: &DisplaySnapshot,
22330 ) -> Range<MultiBufferRow>;
22331}
22332
22333impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22334 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22335 let start = self
22336 .start
22337 .to_point(&map.buffer_snapshot)
22338 .to_display_point(map);
22339 let end = self
22340 .end
22341 .to_point(&map.buffer_snapshot)
22342 .to_display_point(map);
22343 if self.reversed {
22344 end..start
22345 } else {
22346 start..end
22347 }
22348 }
22349
22350 fn spanned_rows(
22351 &self,
22352 include_end_if_at_line_start: bool,
22353 map: &DisplaySnapshot,
22354 ) -> Range<MultiBufferRow> {
22355 let start = self.start.to_point(&map.buffer_snapshot);
22356 let mut end = self.end.to_point(&map.buffer_snapshot);
22357 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22358 end.row -= 1;
22359 }
22360
22361 let buffer_start = map.prev_line_boundary(start).0;
22362 let buffer_end = map.next_line_boundary(end).0;
22363 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22364 }
22365}
22366
22367impl<T: InvalidationRegion> InvalidationStack<T> {
22368 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22369 where
22370 S: Clone + ToOffset,
22371 {
22372 while let Some(region) = self.last() {
22373 let all_selections_inside_invalidation_ranges =
22374 if selections.len() == region.ranges().len() {
22375 selections
22376 .iter()
22377 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22378 .all(|(selection, invalidation_range)| {
22379 let head = selection.head().to_offset(buffer);
22380 invalidation_range.start <= head && invalidation_range.end >= head
22381 })
22382 } else {
22383 false
22384 };
22385
22386 if all_selections_inside_invalidation_ranges {
22387 break;
22388 } else {
22389 self.pop();
22390 }
22391 }
22392 }
22393}
22394
22395impl<T> Default for InvalidationStack<T> {
22396 fn default() -> Self {
22397 Self(Default::default())
22398 }
22399}
22400
22401impl<T> Deref for InvalidationStack<T> {
22402 type Target = Vec<T>;
22403
22404 fn deref(&self) -> &Self::Target {
22405 &self.0
22406 }
22407}
22408
22409impl<T> DerefMut for InvalidationStack<T> {
22410 fn deref_mut(&mut self) -> &mut Self::Target {
22411 &mut self.0
22412 }
22413}
22414
22415impl InvalidationRegion for SnippetState {
22416 fn ranges(&self) -> &[Range<Anchor>] {
22417 &self.ranges[self.active_index]
22418 }
22419}
22420
22421fn inline_completion_edit_text(
22422 current_snapshot: &BufferSnapshot,
22423 edits: &[(Range<Anchor>, String)],
22424 edit_preview: &EditPreview,
22425 include_deletions: bool,
22426 cx: &App,
22427) -> HighlightedText {
22428 let edits = edits
22429 .iter()
22430 .map(|(anchor, text)| {
22431 (
22432 anchor.start.text_anchor..anchor.end.text_anchor,
22433 text.clone(),
22434 )
22435 })
22436 .collect::<Vec<_>>();
22437
22438 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22439}
22440
22441pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22442 match severity {
22443 lsp::DiagnosticSeverity::ERROR => colors.error,
22444 lsp::DiagnosticSeverity::WARNING => colors.warning,
22445 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22446 lsp::DiagnosticSeverity::HINT => colors.info,
22447 _ => colors.ignored,
22448 }
22449}
22450
22451pub fn styled_runs_for_code_label<'a>(
22452 label: &'a CodeLabel,
22453 syntax_theme: &'a theme::SyntaxTheme,
22454) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22455 let fade_out = HighlightStyle {
22456 fade_out: Some(0.35),
22457 ..Default::default()
22458 };
22459
22460 let mut prev_end = label.filter_range.end;
22461 label
22462 .runs
22463 .iter()
22464 .enumerate()
22465 .flat_map(move |(ix, (range, highlight_id))| {
22466 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22467 style
22468 } else {
22469 return Default::default();
22470 };
22471 let mut muted_style = style;
22472 muted_style.highlight(fade_out);
22473
22474 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22475 if range.start >= label.filter_range.end {
22476 if range.start > prev_end {
22477 runs.push((prev_end..range.start, fade_out));
22478 }
22479 runs.push((range.clone(), muted_style));
22480 } else if range.end <= label.filter_range.end {
22481 runs.push((range.clone(), style));
22482 } else {
22483 runs.push((range.start..label.filter_range.end, style));
22484 runs.push((label.filter_range.end..range.end, muted_style));
22485 }
22486 prev_end = cmp::max(prev_end, range.end);
22487
22488 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22489 runs.push((prev_end..label.text.len(), fade_out));
22490 }
22491
22492 runs
22493 })
22494}
22495
22496pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22497 let mut prev_index = 0;
22498 let mut prev_codepoint: Option<char> = None;
22499 text.char_indices()
22500 .chain([(text.len(), '\0')])
22501 .filter_map(move |(index, codepoint)| {
22502 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22503 let is_boundary = index == text.len()
22504 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22505 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22506 if is_boundary {
22507 let chunk = &text[prev_index..index];
22508 prev_index = index;
22509 Some(chunk)
22510 } else {
22511 None
22512 }
22513 })
22514}
22515
22516pub trait RangeToAnchorExt: Sized {
22517 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22518
22519 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22520 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22521 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22522 }
22523}
22524
22525impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22526 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22527 let start_offset = self.start.to_offset(snapshot);
22528 let end_offset = self.end.to_offset(snapshot);
22529 if start_offset == end_offset {
22530 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22531 } else {
22532 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22533 }
22534 }
22535}
22536
22537pub trait RowExt {
22538 fn as_f32(&self) -> f32;
22539
22540 fn next_row(&self) -> Self;
22541
22542 fn previous_row(&self) -> Self;
22543
22544 fn minus(&self, other: Self) -> u32;
22545}
22546
22547impl RowExt for DisplayRow {
22548 fn as_f32(&self) -> f32 {
22549 self.0 as f32
22550 }
22551
22552 fn next_row(&self) -> Self {
22553 Self(self.0 + 1)
22554 }
22555
22556 fn previous_row(&self) -> Self {
22557 Self(self.0.saturating_sub(1))
22558 }
22559
22560 fn minus(&self, other: Self) -> u32 {
22561 self.0 - other.0
22562 }
22563}
22564
22565impl RowExt for MultiBufferRow {
22566 fn as_f32(&self) -> f32 {
22567 self.0 as f32
22568 }
22569
22570 fn next_row(&self) -> Self {
22571 Self(self.0 + 1)
22572 }
22573
22574 fn previous_row(&self) -> Self {
22575 Self(self.0.saturating_sub(1))
22576 }
22577
22578 fn minus(&self, other: Self) -> u32 {
22579 self.0 - other.0
22580 }
22581}
22582
22583trait RowRangeExt {
22584 type Row;
22585
22586 fn len(&self) -> usize;
22587
22588 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22589}
22590
22591impl RowRangeExt for Range<MultiBufferRow> {
22592 type Row = MultiBufferRow;
22593
22594 fn len(&self) -> usize {
22595 (self.end.0 - self.start.0) as usize
22596 }
22597
22598 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22599 (self.start.0..self.end.0).map(MultiBufferRow)
22600 }
22601}
22602
22603impl RowRangeExt for Range<DisplayRow> {
22604 type Row = DisplayRow;
22605
22606 fn len(&self) -> usize {
22607 (self.end.0 - self.start.0) as usize
22608 }
22609
22610 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22611 (self.start.0..self.end.0).map(DisplayRow)
22612 }
22613}
22614
22615/// If select range has more than one line, we
22616/// just point the cursor to range.start.
22617fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22618 if range.start.row == range.end.row {
22619 range
22620 } else {
22621 range.start..range.start
22622 }
22623}
22624pub struct KillRing(ClipboardItem);
22625impl Global for KillRing {}
22626
22627const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22628
22629enum BreakpointPromptEditAction {
22630 Log,
22631 Condition,
22632 HitCondition,
22633}
22634
22635struct BreakpointPromptEditor {
22636 pub(crate) prompt: Entity<Editor>,
22637 editor: WeakEntity<Editor>,
22638 breakpoint_anchor: Anchor,
22639 breakpoint: Breakpoint,
22640 edit_action: BreakpointPromptEditAction,
22641 block_ids: HashSet<CustomBlockId>,
22642 editor_margins: Arc<Mutex<EditorMargins>>,
22643 _subscriptions: Vec<Subscription>,
22644}
22645
22646impl BreakpointPromptEditor {
22647 const MAX_LINES: u8 = 4;
22648
22649 fn new(
22650 editor: WeakEntity<Editor>,
22651 breakpoint_anchor: Anchor,
22652 breakpoint: Breakpoint,
22653 edit_action: BreakpointPromptEditAction,
22654 window: &mut Window,
22655 cx: &mut Context<Self>,
22656 ) -> Self {
22657 let base_text = match edit_action {
22658 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22659 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22660 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22661 }
22662 .map(|msg| msg.to_string())
22663 .unwrap_or_default();
22664
22665 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22666 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22667
22668 let prompt = cx.new(|cx| {
22669 let mut prompt = Editor::new(
22670 EditorMode::AutoHeight {
22671 min_lines: 1,
22672 max_lines: Self::MAX_LINES as usize,
22673 },
22674 buffer,
22675 None,
22676 window,
22677 cx,
22678 );
22679 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22680 prompt.set_show_cursor_when_unfocused(false, cx);
22681 prompt.set_placeholder_text(
22682 match edit_action {
22683 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22684 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22685 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22686 },
22687 cx,
22688 );
22689
22690 prompt
22691 });
22692
22693 Self {
22694 prompt,
22695 editor,
22696 breakpoint_anchor,
22697 breakpoint,
22698 edit_action,
22699 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22700 block_ids: Default::default(),
22701 _subscriptions: vec![],
22702 }
22703 }
22704
22705 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22706 self.block_ids.extend(block_ids)
22707 }
22708
22709 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22710 if let Some(editor) = self.editor.upgrade() {
22711 let message = self
22712 .prompt
22713 .read(cx)
22714 .buffer
22715 .read(cx)
22716 .as_singleton()
22717 .expect("A multi buffer in breakpoint prompt isn't possible")
22718 .read(cx)
22719 .as_rope()
22720 .to_string();
22721
22722 editor.update(cx, |editor, cx| {
22723 editor.edit_breakpoint_at_anchor(
22724 self.breakpoint_anchor,
22725 self.breakpoint.clone(),
22726 match self.edit_action {
22727 BreakpointPromptEditAction::Log => {
22728 BreakpointEditAction::EditLogMessage(message.into())
22729 }
22730 BreakpointPromptEditAction::Condition => {
22731 BreakpointEditAction::EditCondition(message.into())
22732 }
22733 BreakpointPromptEditAction::HitCondition => {
22734 BreakpointEditAction::EditHitCondition(message.into())
22735 }
22736 },
22737 cx,
22738 );
22739
22740 editor.remove_blocks(self.block_ids.clone(), None, cx);
22741 cx.focus_self(window);
22742 });
22743 }
22744 }
22745
22746 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22747 self.editor
22748 .update(cx, |editor, cx| {
22749 editor.remove_blocks(self.block_ids.clone(), None, cx);
22750 window.focus(&editor.focus_handle);
22751 })
22752 .log_err();
22753 }
22754
22755 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22756 let settings = ThemeSettings::get_global(cx);
22757 let text_style = TextStyle {
22758 color: if self.prompt.read(cx).read_only(cx) {
22759 cx.theme().colors().text_disabled
22760 } else {
22761 cx.theme().colors().text
22762 },
22763 font_family: settings.buffer_font.family.clone(),
22764 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22765 font_size: settings.buffer_font_size(cx).into(),
22766 font_weight: settings.buffer_font.weight,
22767 line_height: relative(settings.buffer_line_height.value()),
22768 ..Default::default()
22769 };
22770 EditorElement::new(
22771 &self.prompt,
22772 EditorStyle {
22773 background: cx.theme().colors().editor_background,
22774 local_player: cx.theme().players().local(),
22775 text: text_style,
22776 ..Default::default()
22777 },
22778 )
22779 }
22780}
22781
22782impl Render for BreakpointPromptEditor {
22783 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22784 let editor_margins = *self.editor_margins.lock();
22785 let gutter_dimensions = editor_margins.gutter;
22786 h_flex()
22787 .key_context("Editor")
22788 .bg(cx.theme().colors().editor_background)
22789 .border_y_1()
22790 .border_color(cx.theme().status().info_border)
22791 .size_full()
22792 .py(window.line_height() / 2.5)
22793 .on_action(cx.listener(Self::confirm))
22794 .on_action(cx.listener(Self::cancel))
22795 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22796 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22797 }
22798}
22799
22800impl Focusable for BreakpointPromptEditor {
22801 fn focus_handle(&self, cx: &App) -> FocusHandle {
22802 self.prompt.focus_handle(cx)
22803 }
22804}
22805
22806fn all_edits_insertions_or_deletions(
22807 edits: &Vec<(Range<Anchor>, String)>,
22808 snapshot: &MultiBufferSnapshot,
22809) -> bool {
22810 let mut all_insertions = true;
22811 let mut all_deletions = true;
22812
22813 for (range, new_text) in edits.iter() {
22814 let range_is_empty = range.to_offset(&snapshot).is_empty();
22815 let text_is_empty = new_text.is_empty();
22816
22817 if range_is_empty != text_is_empty {
22818 if range_is_empty {
22819 all_deletions = false;
22820 } else {
22821 all_insertions = false;
22822 }
22823 } else {
22824 return false;
22825 }
22826
22827 if !all_insertions && !all_deletions {
22828 return false;
22829 }
22830 }
22831 all_insertions || all_deletions
22832}
22833
22834struct MissingEditPredictionKeybindingTooltip;
22835
22836impl Render for MissingEditPredictionKeybindingTooltip {
22837 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22838 ui::tooltip_container(window, cx, |container, _, cx| {
22839 container
22840 .flex_shrink_0()
22841 .max_w_80()
22842 .min_h(rems_from_px(124.))
22843 .justify_between()
22844 .child(
22845 v_flex()
22846 .flex_1()
22847 .text_ui_sm(cx)
22848 .child(Label::new("Conflict with Accept Keybinding"))
22849 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22850 )
22851 .child(
22852 h_flex()
22853 .pb_1()
22854 .gap_1()
22855 .items_end()
22856 .w_full()
22857 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22858 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22859 }))
22860 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22861 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22862 })),
22863 )
22864 })
22865 }
22866}
22867
22868#[derive(Debug, Clone, Copy, PartialEq)]
22869pub struct LineHighlight {
22870 pub background: Background,
22871 pub border: Option<gpui::Hsla>,
22872 pub include_gutter: bool,
22873 pub type_id: Option<TypeId>,
22874}
22875
22876fn render_diff_hunk_controls(
22877 row: u32,
22878 status: &DiffHunkStatus,
22879 hunk_range: Range<Anchor>,
22880 is_created_file: bool,
22881 line_height: Pixels,
22882 editor: &Entity<Editor>,
22883 _window: &mut Window,
22884 cx: &mut App,
22885) -> AnyElement {
22886 h_flex()
22887 .h(line_height)
22888 .mr_1()
22889 .gap_1()
22890 .px_0p5()
22891 .pb_1()
22892 .border_x_1()
22893 .border_b_1()
22894 .border_color(cx.theme().colors().border_variant)
22895 .rounded_b_lg()
22896 .bg(cx.theme().colors().editor_background)
22897 .gap_1()
22898 .block_mouse_except_scroll()
22899 .shadow_md()
22900 .child(if status.has_secondary_hunk() {
22901 Button::new(("stage", row as u64), "Stage")
22902 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22903 .tooltip({
22904 let focus_handle = editor.focus_handle(cx);
22905 move |window, cx| {
22906 Tooltip::for_action_in(
22907 "Stage Hunk",
22908 &::git::ToggleStaged,
22909 &focus_handle,
22910 window,
22911 cx,
22912 )
22913 }
22914 })
22915 .on_click({
22916 let editor = editor.clone();
22917 move |_event, _window, cx| {
22918 editor.update(cx, |editor, cx| {
22919 editor.stage_or_unstage_diff_hunks(
22920 true,
22921 vec![hunk_range.start..hunk_range.start],
22922 cx,
22923 );
22924 });
22925 }
22926 })
22927 } else {
22928 Button::new(("unstage", row as u64), "Unstage")
22929 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22930 .tooltip({
22931 let focus_handle = editor.focus_handle(cx);
22932 move |window, cx| {
22933 Tooltip::for_action_in(
22934 "Unstage Hunk",
22935 &::git::ToggleStaged,
22936 &focus_handle,
22937 window,
22938 cx,
22939 )
22940 }
22941 })
22942 .on_click({
22943 let editor = editor.clone();
22944 move |_event, _window, cx| {
22945 editor.update(cx, |editor, cx| {
22946 editor.stage_or_unstage_diff_hunks(
22947 false,
22948 vec![hunk_range.start..hunk_range.start],
22949 cx,
22950 );
22951 });
22952 }
22953 })
22954 })
22955 .child(
22956 Button::new(("restore", row as u64), "Restore")
22957 .tooltip({
22958 let focus_handle = editor.focus_handle(cx);
22959 move |window, cx| {
22960 Tooltip::for_action_in(
22961 "Restore Hunk",
22962 &::git::Restore,
22963 &focus_handle,
22964 window,
22965 cx,
22966 )
22967 }
22968 })
22969 .on_click({
22970 let editor = editor.clone();
22971 move |_event, window, cx| {
22972 editor.update(cx, |editor, cx| {
22973 let snapshot = editor.snapshot(window, cx);
22974 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22975 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22976 });
22977 }
22978 })
22979 .disabled(is_created_file),
22980 )
22981 .when(
22982 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22983 |el| {
22984 el.child(
22985 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22986 .shape(IconButtonShape::Square)
22987 .icon_size(IconSize::Small)
22988 // .disabled(!has_multiple_hunks)
22989 .tooltip({
22990 let focus_handle = editor.focus_handle(cx);
22991 move |window, cx| {
22992 Tooltip::for_action_in(
22993 "Next Hunk",
22994 &GoToHunk,
22995 &focus_handle,
22996 window,
22997 cx,
22998 )
22999 }
23000 })
23001 .on_click({
23002 let editor = editor.clone();
23003 move |_event, window, cx| {
23004 editor.update(cx, |editor, cx| {
23005 let snapshot = editor.snapshot(window, cx);
23006 let position =
23007 hunk_range.end.to_point(&snapshot.buffer_snapshot);
23008 editor.go_to_hunk_before_or_after_position(
23009 &snapshot,
23010 position,
23011 Direction::Next,
23012 window,
23013 cx,
23014 );
23015 editor.expand_selected_diff_hunks(cx);
23016 });
23017 }
23018 }),
23019 )
23020 .child(
23021 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
23022 .shape(IconButtonShape::Square)
23023 .icon_size(IconSize::Small)
23024 // .disabled(!has_multiple_hunks)
23025 .tooltip({
23026 let focus_handle = editor.focus_handle(cx);
23027 move |window, cx| {
23028 Tooltip::for_action_in(
23029 "Previous Hunk",
23030 &GoToPreviousHunk,
23031 &focus_handle,
23032 window,
23033 cx,
23034 )
23035 }
23036 })
23037 .on_click({
23038 let editor = editor.clone();
23039 move |_event, window, cx| {
23040 editor.update(cx, |editor, cx| {
23041 let snapshot = editor.snapshot(window, cx);
23042 let point =
23043 hunk_range.start.to_point(&snapshot.buffer_snapshot);
23044 editor.go_to_hunk_before_or_after_position(
23045 &snapshot,
23046 point,
23047 Direction::Prev,
23048 window,
23049 cx,
23050 );
23051 editor.expand_selected_diff_hunks(cx);
23052 });
23053 }
23054 }),
23055 )
23056 },
23057 )
23058 .into_any_element()
23059}