1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, SearchSettings,
67 ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt,
78 future::{self, Shared, join},
79};
80use fuzzy::{StringMatch, StringMatchCandidate};
81
82use ::git::blame::BlameEntry;
83use ::git::{Restore, blame::ParsedCommitMessage};
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use git::blame::{GitBlame, GlobalBlameRenderer};
89use gpui::{
90 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
91 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
92 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
93 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
94 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
95 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
96 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
97 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
98};
99use highlight_matching_bracket::refresh_matching_bracket_highlights;
100use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
101pub use hover_popover::hover_markdown_style;
102use hover_popover::{HoverState, hide_hover};
103use indent_guides::ActiveIndentGuidesState;
104use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
105pub use inline_completion::Direction;
106use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
107pub use items::MAX_TAB_TITLE_LEN;
108use itertools::Itertools;
109use language::{
110 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
111 CursorShape, DiagnosticEntry, DiffOptions, DocumentationConfig, EditPredictionsMode,
112 EditPreview, HighlightedText, IndentKind, IndentSize, Language, OffsetRangeExt, Point,
113 Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
114 language_settings::{
115 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
116 all_language_settings, language_settings,
117 },
118 point_from_lsp, text_diff_with_options,
119};
120use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
121use linked_editing_ranges::refresh_linked_ranges;
122use markdown::Markdown;
123use mouse_context_menu::MouseContextMenu;
124use persistence::DB;
125use project::{
126 BreakpointWithPosition, ProjectPath,
127 debugger::{
128 breakpoint_store::{
129 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
130 BreakpointStoreEvent,
131 },
132 session::{Session, SessionEvent},
133 },
134 project_settings::DiagnosticSeverity,
135};
136
137pub use git::blame::BlameRenderer;
138pub use proposed_changes_editor::{
139 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
140};
141use std::{cell::OnceCell, iter::Peekable, ops::Not};
142use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
143
144pub use lsp::CompletionContext;
145use lsp::{
146 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
147 LanguageServerId, LanguageServerName,
148};
149
150use language::BufferSnapshot;
151pub use lsp_ext::lsp_tasks;
152use movement::TextLayoutDetails;
153pub use multi_buffer::{
154 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
155 RowInfo, ToOffset, ToPoint,
156};
157use multi_buffer::{
158 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
159 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
160};
161use parking_lot::Mutex;
162use project::{
163 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
164 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
165 TaskSourceKind,
166 debugger::breakpoint_store::Breakpoint,
167 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
168 project_settings::{GitGutterSetting, ProjectSettings},
169};
170use rand::prelude::*;
171use rpc::{ErrorExt, proto::*};
172use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
173use selections_collection::{
174 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
175};
176use serde::{Deserialize, Serialize};
177use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
178use smallvec::{SmallVec, smallvec};
179use snippet::Snippet;
180use std::sync::Arc;
181use std::{
182 any::TypeId,
183 borrow::Cow,
184 cell::RefCell,
185 cmp::{self, Ordering, Reverse},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 time::{Duration, Instant},
192};
193pub use sum_tree::Bias;
194use sum_tree::TreeMap;
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
196use theme::{
197 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc, wrap_with_prefix};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemHandle, PreviewTabsSettings},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::hover_links::{find_url, find_url_from_range};
215use crate::signature_help::{SignatureHelpHiddenBy, SignatureHelpState};
216
217pub const FILE_HEADER_HEIGHT: u32 = 2;
218pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
219pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
220const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
221const MAX_LINE_LEN: usize = 1024;
222const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
223const MAX_SELECTION_HISTORY_LEN: usize = 1024;
224pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
225#[doc(hidden)]
226pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
227const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
228
229pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
230pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
232
233pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
234pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
235pub(crate) const MIN_LINE_NUMBER_DIGITS: u32 = 4;
236pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
237
238pub type RenderDiffHunkControlsFn = Arc<
239 dyn Fn(
240 u32,
241 &DiffHunkStatus,
242 Range<Anchor>,
243 bool,
244 Pixels,
245 &Entity<Editor>,
246 &mut Window,
247 &mut App,
248 ) -> AnyElement,
249>;
250
251const COLUMNAR_SELECTION_MODIFIERS: Modifiers = Modifiers {
252 alt: true,
253 shift: true,
254 control: false,
255 platform: false,
256 function: false,
257};
258
259struct InlineValueCache {
260 enabled: bool,
261 inlays: Vec<InlayId>,
262 refresh_task: Task<Option<()>>,
263}
264
265impl InlineValueCache {
266 fn new(enabled: bool) -> Self {
267 Self {
268 enabled,
269 inlays: Vec::new(),
270 refresh_task: Task::ready(None),
271 }
272 }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
276pub enum InlayId {
277 InlineCompletion(usize),
278 Hint(usize),
279 DebuggerValue(usize),
280}
281
282impl InlayId {
283 fn id(&self) -> usize {
284 match self {
285 Self::InlineCompletion(id) => *id,
286 Self::Hint(id) => *id,
287 Self::DebuggerValue(id) => *id,
288 }
289 }
290}
291
292pub enum ActiveDebugLine {}
293pub enum DebugStackFrameLine {}
294enum DocumentHighlightRead {}
295enum DocumentHighlightWrite {}
296enum InputComposition {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 goal_column: u32,
451 },
452 Extend {
453 position: DisplayPoint,
454 click_count: usize,
455 },
456 Update {
457 position: DisplayPoint,
458 goal_column: u32,
459 scroll_delta: gpui::Point<f32>,
460 },
461 End,
462}
463
464#[derive(Clone, Debug)]
465pub enum SelectMode {
466 Character,
467 Word(Range<Anchor>),
468 Line(Range<Anchor>),
469 All,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine {
475 auto_width: bool,
476 },
477 AutoHeight {
478 max_lines: usize,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// When set to `true`, the editor's height will be determined by its content.
486 sized_by_content: bool,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sized_by_content: false,
499 }
500 }
501
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub local_player: PlayerColor,
532 pub text: TextStyle,
533 pub scrollbar_width: Pixels,
534 pub syntax: Arc<SyntaxTheme>,
535 pub status: StatusColors,
536 pub inlay_hints_style: HighlightStyle,
537 pub inline_completion_styles: InlineCompletionStyles,
538 pub unnecessary_code_fade: f32,
539 pub show_underlines: bool,
540}
541
542impl Default for EditorStyle {
543 fn default() -> Self {
544 Self {
545 background: Hsla::default(),
546 local_player: PlayerColor::default(),
547 text: TextStyle::default(),
548 scrollbar_width: Pixels::default(),
549 syntax: Default::default(),
550 // HACK: Status colors don't have a real default.
551 // We should look into removing the status colors from the editor
552 // style and retrieve them directly from the theme.
553 status: StatusColors::dark(),
554 inlay_hints_style: HighlightStyle::default(),
555 inline_completion_styles: InlineCompletionStyles {
556 insertion: HighlightStyle::default(),
557 whitespace: HighlightStyle::default(),
558 },
559 unnecessary_code_fade: Default::default(),
560 show_underlines: true,
561 }
562 }
563}
564
565pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
566 let show_background = language_settings::language_settings(None, None, cx)
567 .inlay_hints
568 .show_background;
569
570 HighlightStyle {
571 color: Some(cx.theme().status().hint),
572 background_color: show_background.then(|| cx.theme().status().hint_background),
573 ..HighlightStyle::default()
574 }
575}
576
577pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
578 InlineCompletionStyles {
579 insertion: HighlightStyle {
580 color: Some(cx.theme().status().predictive),
581 ..HighlightStyle::default()
582 },
583 whitespace: HighlightStyle {
584 background_color: Some(cx.theme().status().created_background),
585 ..HighlightStyle::default()
586 },
587 }
588}
589
590type CompletionId = usize;
591
592pub(crate) enum EditDisplayMode {
593 TabAccept,
594 DiffPopover,
595 Inline,
596}
597
598enum InlineCompletion {
599 Edit {
600 edits: Vec<(Range<Anchor>, String)>,
601 edit_preview: Option<EditPreview>,
602 display_mode: EditDisplayMode,
603 snapshot: BufferSnapshot,
604 },
605 Move {
606 target: Anchor,
607 snapshot: BufferSnapshot,
608 },
609}
610
611struct InlineCompletionState {
612 inlay_ids: Vec<InlayId>,
613 completion: InlineCompletion,
614 completion_id: Option<SharedString>,
615 invalidation_range: Range<Anchor>,
616}
617
618enum EditPredictionSettings {
619 Disabled,
620 Enabled {
621 show_in_menu: bool,
622 preview_requires_modifier: bool,
623 },
624}
625
626enum InlineCompletionHighlight {}
627
628#[derive(Debug, Clone)]
629struct InlineDiagnostic {
630 message: SharedString,
631 group_id: usize,
632 is_primary: bool,
633 start: Point,
634 severity: lsp::DiagnosticSeverity,
635}
636
637pub enum MenuInlineCompletionsPolicy {
638 Never,
639 ByProvider,
640}
641
642pub enum EditPredictionPreview {
643 /// Modifier is not pressed
644 Inactive { released_too_fast: bool },
645 /// Modifier pressed
646 Active {
647 since: Instant,
648 previous_scroll_position: Option<ScrollAnchor>,
649 },
650}
651
652impl EditPredictionPreview {
653 pub fn released_too_fast(&self) -> bool {
654 match self {
655 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
656 EditPredictionPreview::Active { .. } => false,
657 }
658 }
659
660 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
661 if let EditPredictionPreview::Active {
662 previous_scroll_position,
663 ..
664 } = self
665 {
666 *previous_scroll_position = scroll_position;
667 }
668 }
669}
670
671pub struct ContextMenuOptions {
672 pub min_entries_visible: usize,
673 pub max_entries_visible: usize,
674 pub placement: Option<ContextMenuPlacement>,
675}
676
677#[derive(Debug, Clone, PartialEq, Eq)]
678pub enum ContextMenuPlacement {
679 Above,
680 Below,
681}
682
683#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
684struct EditorActionId(usize);
685
686impl EditorActionId {
687 pub fn post_inc(&mut self) -> Self {
688 let answer = self.0;
689
690 *self = Self(answer + 1);
691
692 Self(answer)
693 }
694}
695
696// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
697// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
698
699type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
700type GutterHighlight = (fn(&App) -> Hsla, Arc<[Range<Anchor>]>);
701
702#[derive(Default)]
703struct ScrollbarMarkerState {
704 scrollbar_size: Size<Pixels>,
705 dirty: bool,
706 markers: Arc<[PaintQuad]>,
707 pending_refresh: Option<Task<Result<()>>>,
708}
709
710impl ScrollbarMarkerState {
711 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
712 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
713 }
714}
715
716#[derive(Clone, Copy, PartialEq, Eq)]
717pub enum MinimapVisibility {
718 Disabled,
719 Enabled {
720 /// The configuration currently present in the users settings.
721 setting_configuration: bool,
722 /// Whether to override the currently set visibility from the users setting.
723 toggle_override: bool,
724 },
725}
726
727impl MinimapVisibility {
728 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
729 if mode.is_full() {
730 Self::Enabled {
731 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
732 toggle_override: false,
733 }
734 } else {
735 Self::Disabled
736 }
737 }
738
739 fn hidden(&self) -> Self {
740 match *self {
741 Self::Enabled {
742 setting_configuration,
743 ..
744 } => Self::Enabled {
745 setting_configuration,
746 toggle_override: setting_configuration,
747 },
748 Self::Disabled => Self::Disabled,
749 }
750 }
751
752 fn disabled(&self) -> bool {
753 match *self {
754 Self::Disabled => true,
755 _ => false,
756 }
757 }
758
759 fn settings_visibility(&self) -> bool {
760 match *self {
761 Self::Enabled {
762 setting_configuration,
763 ..
764 } => setting_configuration,
765 _ => false,
766 }
767 }
768
769 fn visible(&self) -> bool {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 toggle_override,
774 } => setting_configuration ^ toggle_override,
775 _ => false,
776 }
777 }
778
779 fn toggle_visibility(&self) -> Self {
780 match *self {
781 Self::Enabled {
782 toggle_override,
783 setting_configuration,
784 } => Self::Enabled {
785 setting_configuration,
786 toggle_override: !toggle_override,
787 },
788 Self::Disabled => Self::Disabled,
789 }
790 }
791}
792
793#[derive(Clone, Debug)]
794struct RunnableTasks {
795 templates: Vec<(TaskSourceKind, TaskTemplate)>,
796 offset: multi_buffer::Anchor,
797 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
798 column: u32,
799 // Values of all named captures, including those starting with '_'
800 extra_variables: HashMap<String, String>,
801 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
802 context_range: Range<BufferOffset>,
803}
804
805impl RunnableTasks {
806 fn resolve<'a>(
807 &'a self,
808 cx: &'a task::TaskContext,
809 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
810 self.templates.iter().filter_map(|(kind, template)| {
811 template
812 .resolve_task(&kind.to_id_base(), cx)
813 .map(|task| (kind.clone(), task))
814 })
815 }
816}
817
818#[derive(Clone)]
819pub struct ResolvedTasks {
820 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
821 position: Anchor,
822}
823
824#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
825struct BufferOffset(usize);
826
827// Addons allow storing per-editor state in other crates (e.g. Vim)
828pub trait Addon: 'static {
829 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
830
831 fn render_buffer_header_controls(
832 &self,
833 _: &ExcerptInfo,
834 _: &Window,
835 _: &App,
836 ) -> Option<AnyElement> {
837 None
838 }
839
840 fn to_any(&self) -> &dyn std::any::Any;
841
842 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
843 None
844 }
845}
846
847/// A set of caret positions, registered when the editor was edited.
848pub struct ChangeList {
849 changes: Vec<Vec<Anchor>>,
850 /// Currently "selected" change.
851 position: Option<usize>,
852}
853
854impl ChangeList {
855 pub fn new() -> Self {
856 Self {
857 changes: Vec::new(),
858 position: None,
859 }
860 }
861
862 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
863 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
864 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
865 if self.changes.is_empty() {
866 return None;
867 }
868
869 let prev = self.position.unwrap_or(self.changes.len());
870 let next = if direction == Direction::Prev {
871 prev.saturating_sub(count)
872 } else {
873 (prev + count).min(self.changes.len() - 1)
874 };
875 self.position = Some(next);
876 self.changes.get(next).map(|anchors| anchors.as_slice())
877 }
878
879 /// Adds a new change to the list, resetting the change list position.
880 pub fn push_to_change_list(&mut self, pop_state: bool, new_positions: Vec<Anchor>) {
881 self.position.take();
882 if pop_state {
883 self.changes.pop();
884 }
885 self.changes.push(new_positions.clone());
886 }
887
888 pub fn last(&self) -> Option<&[Anchor]> {
889 self.changes.last().map(|anchors| anchors.as_slice())
890 }
891}
892
893#[derive(Clone)]
894struct InlineBlamePopoverState {
895 scroll_handle: ScrollHandle,
896 commit_message: Option<ParsedCommitMessage>,
897 markdown: Entity<Markdown>,
898}
899
900struct InlineBlamePopover {
901 position: gpui::Point<Pixels>,
902 show_task: Option<Task<()>>,
903 hide_task: Option<Task<()>>,
904 popover_bounds: Option<Bounds<Pixels>>,
905 popover_state: InlineBlamePopoverState,
906}
907
908/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
909/// a breakpoint on them.
910#[derive(Clone, Copy, Debug)]
911struct PhantomBreakpointIndicator {
912 display_row: DisplayRow,
913 /// There's a small debounce between hovering over the line and showing the indicator.
914 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
915 is_active: bool,
916 collides_with_existing_breakpoint: bool,
917}
918/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
919///
920/// See the [module level documentation](self) for more information.
921pub struct Editor {
922 focus_handle: FocusHandle,
923 last_focused_descendant: Option<WeakFocusHandle>,
924 /// The text buffer being edited
925 buffer: Entity<MultiBuffer>,
926 /// Map of how text in the buffer should be displayed.
927 /// Handles soft wraps, folds, fake inlay text insertions, etc.
928 pub display_map: Entity<DisplayMap>,
929 pub selections: SelectionsCollection,
930 pub scroll_manager: ScrollManager,
931 /// When inline assist editors are linked, they all render cursors because
932 /// typing enters text into each of them, even the ones that aren't focused.
933 pub(crate) show_cursor_when_unfocused: bool,
934 columnar_selection_tail: Option<Anchor>,
935 add_selections_state: Option<AddSelectionsState>,
936 select_next_state: Option<SelectNextState>,
937 select_prev_state: Option<SelectNextState>,
938 selection_history: SelectionHistory,
939 autoclose_regions: Vec<AutocloseRegion>,
940 snippet_stack: InvalidationStack<SnippetState>,
941 select_syntax_node_history: SelectSyntaxNodeHistory,
942 ime_transaction: Option<TransactionId>,
943 pub diagnostics_max_severity: DiagnosticSeverity,
944 active_diagnostics: ActiveDiagnostic,
945 show_inline_diagnostics: bool,
946 inline_diagnostics_update: Task<()>,
947 inline_diagnostics_enabled: bool,
948 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
949 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
950 hard_wrap: Option<usize>,
951
952 // TODO: make this a access method
953 pub project: Option<Entity<Project>>,
954 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
955 completion_provider: Option<Rc<dyn CompletionProvider>>,
956 collaboration_hub: Option<Box<dyn CollaborationHub>>,
957 blink_manager: Entity<BlinkManager>,
958 show_cursor_names: bool,
959 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
960 pub show_local_selections: bool,
961 mode: EditorMode,
962 show_breadcrumbs: bool,
963 show_gutter: bool,
964 show_scrollbars: bool,
965 minimap_visibility: MinimapVisibility,
966 offset_content: bool,
967 disable_expand_excerpt_buttons: bool,
968 show_line_numbers: Option<bool>,
969 use_relative_line_numbers: Option<bool>,
970 show_git_diff_gutter: Option<bool>,
971 show_code_actions: Option<bool>,
972 show_runnables: Option<bool>,
973 show_breakpoints: Option<bool>,
974 show_wrap_guides: Option<bool>,
975 show_indent_guides: Option<bool>,
976 placeholder_text: Option<Arc<str>>,
977 highlight_order: usize,
978 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
979 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
980 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
981 scrollbar_marker_state: ScrollbarMarkerState,
982 active_indent_guides_state: ActiveIndentGuidesState,
983 nav_history: Option<ItemNavHistory>,
984 context_menu: RefCell<Option<CodeContextMenu>>,
985 context_menu_options: Option<ContextMenuOptions>,
986 mouse_context_menu: Option<MouseContextMenu>,
987 completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
988 inline_blame_popover: Option<InlineBlamePopover>,
989 signature_help_state: SignatureHelpState,
990 auto_signature_help: Option<bool>,
991 find_all_references_task_sources: Vec<Anchor>,
992 next_completion_id: CompletionId,
993 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
994 code_actions_task: Option<Task<Result<()>>>,
995 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
996 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
997 document_highlights_task: Option<Task<()>>,
998 linked_editing_range_task: Option<Task<Option<()>>>,
999 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1000 pending_rename: Option<RenameState>,
1001 searchable: bool,
1002 cursor_shape: CursorShape,
1003 current_line_highlight: Option<CurrentLineHighlight>,
1004 collapse_matches: bool,
1005 autoindent_mode: Option<AutoindentMode>,
1006 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1007 input_enabled: bool,
1008 use_modal_editing: bool,
1009 read_only: bool,
1010 leader_id: Option<CollaboratorId>,
1011 remote_id: Option<ViewId>,
1012 pub hover_state: HoverState,
1013 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1014 gutter_hovered: bool,
1015 hovered_link_state: Option<HoveredLinkState>,
1016 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1017 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1018 active_inline_completion: Option<InlineCompletionState>,
1019 /// Used to prevent flickering as the user types while the menu is open
1020 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1021 edit_prediction_settings: EditPredictionSettings,
1022 inline_completions_hidden_for_vim_mode: bool,
1023 show_inline_completions_override: Option<bool>,
1024 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1025 edit_prediction_preview: EditPredictionPreview,
1026 edit_prediction_indent_conflict: bool,
1027 edit_prediction_requires_modifier_in_indent_conflict: bool,
1028 inlay_hint_cache: InlayHintCache,
1029 next_inlay_id: usize,
1030 _subscriptions: Vec<Subscription>,
1031 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1032 gutter_dimensions: GutterDimensions,
1033 style: Option<EditorStyle>,
1034 text_style_refinement: Option<TextStyleRefinement>,
1035 next_editor_action_id: EditorActionId,
1036 editor_actions:
1037 Rc<RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&mut Window, &mut Context<Self>)>>>>,
1038 use_autoclose: bool,
1039 use_auto_surround: bool,
1040 auto_replace_emoji_shortcode: bool,
1041 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1042 show_git_blame_gutter: bool,
1043 show_git_blame_inline: bool,
1044 show_git_blame_inline_delay_task: Option<Task<()>>,
1045 git_blame_inline_enabled: bool,
1046 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1047 serialize_dirty_buffers: bool,
1048 show_selection_menu: Option<bool>,
1049 blame: Option<Entity<GitBlame>>,
1050 blame_subscription: Option<Subscription>,
1051 custom_context_menu: Option<
1052 Box<
1053 dyn 'static
1054 + Fn(
1055 &mut Self,
1056 DisplayPoint,
1057 &mut Window,
1058 &mut Context<Self>,
1059 ) -> Option<Entity<ui::ContextMenu>>,
1060 >,
1061 >,
1062 last_bounds: Option<Bounds<Pixels>>,
1063 last_position_map: Option<Rc<PositionMap>>,
1064 expect_bounds_change: Option<Bounds<Pixels>>,
1065 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1066 tasks_update_task: Option<Task<()>>,
1067 breakpoint_store: Option<Entity<BreakpointStore>>,
1068 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1069 in_project_search: bool,
1070 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1071 breadcrumb_header: Option<String>,
1072 focused_block: Option<FocusedBlock>,
1073 next_scroll_position: NextScrollCursorCenterTopBottom,
1074 addons: HashMap<TypeId, Box<dyn Addon>>,
1075 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1076 load_diff_task: Option<Shared<Task<()>>>,
1077 /// Whether we are temporarily displaying a diff other than git's
1078 temporary_diff_override: bool,
1079 selection_mark_mode: bool,
1080 toggle_fold_multiple_buffers: Task<()>,
1081 _scroll_cursor_center_top_bottom_task: Task<()>,
1082 serialize_selections: Task<()>,
1083 serialize_folds: Task<()>,
1084 mouse_cursor_hidden: bool,
1085 minimap: Option<Entity<Self>>,
1086 hide_mouse_mode: HideMouseMode,
1087 pub change_list: ChangeList,
1088 inline_value_cache: InlineValueCache,
1089}
1090
1091#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1092enum NextScrollCursorCenterTopBottom {
1093 #[default]
1094 Center,
1095 Top,
1096 Bottom,
1097}
1098
1099impl NextScrollCursorCenterTopBottom {
1100 fn next(&self) -> Self {
1101 match self {
1102 Self::Center => Self::Top,
1103 Self::Top => Self::Bottom,
1104 Self::Bottom => Self::Center,
1105 }
1106 }
1107}
1108
1109#[derive(Clone)]
1110pub struct EditorSnapshot {
1111 pub mode: EditorMode,
1112 show_gutter: bool,
1113 show_line_numbers: Option<bool>,
1114 show_git_diff_gutter: Option<bool>,
1115 show_code_actions: Option<bool>,
1116 show_runnables: Option<bool>,
1117 show_breakpoints: Option<bool>,
1118 git_blame_gutter_max_author_length: Option<usize>,
1119 pub display_snapshot: DisplaySnapshot,
1120 pub placeholder_text: Option<Arc<str>>,
1121 is_focused: bool,
1122 scroll_anchor: ScrollAnchor,
1123 ongoing_scroll: OngoingScroll,
1124 current_line_highlight: CurrentLineHighlight,
1125 gutter_hovered: bool,
1126}
1127
1128#[derive(Default, Debug, Clone, Copy)]
1129pub struct GutterDimensions {
1130 pub left_padding: Pixels,
1131 pub right_padding: Pixels,
1132 pub width: Pixels,
1133 pub margin: Pixels,
1134 pub git_blame_entries_width: Option<Pixels>,
1135}
1136
1137impl GutterDimensions {
1138 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1139 Self {
1140 margin: Self::default_gutter_margin(font_id, font_size, cx),
1141 ..Default::default()
1142 }
1143 }
1144
1145 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1146 -cx.text_system().descent(font_id, font_size)
1147 }
1148 /// The full width of the space taken up by the gutter.
1149 pub fn full_width(&self) -> Pixels {
1150 self.margin + self.width
1151 }
1152
1153 /// The width of the space reserved for the fold indicators,
1154 /// use alongside 'justify_end' and `gutter_width` to
1155 /// right align content with the line numbers
1156 pub fn fold_area_width(&self) -> Pixels {
1157 self.margin + self.right_padding
1158 }
1159}
1160
1161#[derive(Debug)]
1162pub struct RemoteSelection {
1163 pub replica_id: ReplicaId,
1164 pub selection: Selection<Anchor>,
1165 pub cursor_shape: CursorShape,
1166 pub collaborator_id: CollaboratorId,
1167 pub line_mode: bool,
1168 pub user_name: Option<SharedString>,
1169 pub color: PlayerColor,
1170}
1171
1172#[derive(Clone, Debug)]
1173struct SelectionHistoryEntry {
1174 selections: Arc<[Selection<Anchor>]>,
1175 select_next_state: Option<SelectNextState>,
1176 select_prev_state: Option<SelectNextState>,
1177 add_selections_state: Option<AddSelectionsState>,
1178}
1179
1180enum SelectionHistoryMode {
1181 Normal,
1182 Undoing,
1183 Redoing,
1184}
1185
1186#[derive(Clone, PartialEq, Eq, Hash)]
1187struct HoveredCursor {
1188 replica_id: u16,
1189 selection_id: usize,
1190}
1191
1192impl Default for SelectionHistoryMode {
1193 fn default() -> Self {
1194 Self::Normal
1195 }
1196}
1197
1198#[derive(Default)]
1199struct SelectionHistory {
1200 #[allow(clippy::type_complexity)]
1201 selections_by_transaction:
1202 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1203 mode: SelectionHistoryMode,
1204 undo_stack: VecDeque<SelectionHistoryEntry>,
1205 redo_stack: VecDeque<SelectionHistoryEntry>,
1206}
1207
1208impl SelectionHistory {
1209 fn insert_transaction(
1210 &mut self,
1211 transaction_id: TransactionId,
1212 selections: Arc<[Selection<Anchor>]>,
1213 ) {
1214 self.selections_by_transaction
1215 .insert(transaction_id, (selections, None));
1216 }
1217
1218 #[allow(clippy::type_complexity)]
1219 fn transaction(
1220 &self,
1221 transaction_id: TransactionId,
1222 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1223 self.selections_by_transaction.get(&transaction_id)
1224 }
1225
1226 #[allow(clippy::type_complexity)]
1227 fn transaction_mut(
1228 &mut self,
1229 transaction_id: TransactionId,
1230 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1231 self.selections_by_transaction.get_mut(&transaction_id)
1232 }
1233
1234 fn push(&mut self, entry: SelectionHistoryEntry) {
1235 if !entry.selections.is_empty() {
1236 match self.mode {
1237 SelectionHistoryMode::Normal => {
1238 self.push_undo(entry);
1239 self.redo_stack.clear();
1240 }
1241 SelectionHistoryMode::Undoing => self.push_redo(entry),
1242 SelectionHistoryMode::Redoing => self.push_undo(entry),
1243 }
1244 }
1245 }
1246
1247 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1248 if self
1249 .undo_stack
1250 .back()
1251 .map_or(true, |e| e.selections != entry.selections)
1252 {
1253 self.undo_stack.push_back(entry);
1254 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1255 self.undo_stack.pop_front();
1256 }
1257 }
1258 }
1259
1260 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1261 if self
1262 .redo_stack
1263 .back()
1264 .map_or(true, |e| e.selections != entry.selections)
1265 {
1266 self.redo_stack.push_back(entry);
1267 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1268 self.redo_stack.pop_front();
1269 }
1270 }
1271 }
1272}
1273
1274#[derive(Clone, Copy)]
1275pub struct RowHighlightOptions {
1276 pub autoscroll: bool,
1277 pub include_gutter: bool,
1278}
1279
1280impl Default for RowHighlightOptions {
1281 fn default() -> Self {
1282 Self {
1283 autoscroll: Default::default(),
1284 include_gutter: true,
1285 }
1286 }
1287}
1288
1289struct RowHighlight {
1290 index: usize,
1291 range: Range<Anchor>,
1292 color: Hsla,
1293 options: RowHighlightOptions,
1294 type_id: TypeId,
1295}
1296
1297#[derive(Clone, Debug)]
1298struct AddSelectionsState {
1299 above: bool,
1300 stack: Vec<usize>,
1301}
1302
1303#[derive(Clone)]
1304struct SelectNextState {
1305 query: AhoCorasick,
1306 wordwise: bool,
1307 done: bool,
1308}
1309
1310impl std::fmt::Debug for SelectNextState {
1311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1312 f.debug_struct(std::any::type_name::<Self>())
1313 .field("wordwise", &self.wordwise)
1314 .field("done", &self.done)
1315 .finish()
1316 }
1317}
1318
1319#[derive(Debug)]
1320struct AutocloseRegion {
1321 selection_id: usize,
1322 range: Range<Anchor>,
1323 pair: BracketPair,
1324}
1325
1326#[derive(Debug)]
1327struct SnippetState {
1328 ranges: Vec<Vec<Range<Anchor>>>,
1329 active_index: usize,
1330 choices: Vec<Option<Vec<String>>>,
1331}
1332
1333#[doc(hidden)]
1334pub struct RenameState {
1335 pub range: Range<Anchor>,
1336 pub old_name: Arc<str>,
1337 pub editor: Entity<Editor>,
1338 block_id: CustomBlockId,
1339}
1340
1341struct InvalidationStack<T>(Vec<T>);
1342
1343struct RegisteredInlineCompletionProvider {
1344 provider: Arc<dyn InlineCompletionProviderHandle>,
1345 _subscription: Subscription,
1346}
1347
1348#[derive(Debug, PartialEq, Eq)]
1349pub struct ActiveDiagnosticGroup {
1350 pub active_range: Range<Anchor>,
1351 pub active_message: String,
1352 pub group_id: usize,
1353 pub blocks: HashSet<CustomBlockId>,
1354}
1355
1356#[derive(Debug, PartialEq, Eq)]
1357
1358pub(crate) enum ActiveDiagnostic {
1359 None,
1360 All,
1361 Group(ActiveDiagnosticGroup),
1362}
1363
1364#[derive(Serialize, Deserialize, Clone, Debug)]
1365pub struct ClipboardSelection {
1366 /// The number of bytes in this selection.
1367 pub len: usize,
1368 /// Whether this was a full-line selection.
1369 pub is_entire_line: bool,
1370 /// The indentation of the first line when this content was originally copied.
1371 pub first_line_indent: u32,
1372}
1373
1374// selections, scroll behavior, was newest selection reversed
1375type SelectSyntaxNodeHistoryState = (
1376 Box<[Selection<usize>]>,
1377 SelectSyntaxNodeScrollBehavior,
1378 bool,
1379);
1380
1381#[derive(Default)]
1382struct SelectSyntaxNodeHistory {
1383 stack: Vec<SelectSyntaxNodeHistoryState>,
1384 // disable temporarily to allow changing selections without losing the stack
1385 pub disable_clearing: bool,
1386}
1387
1388impl SelectSyntaxNodeHistory {
1389 pub fn try_clear(&mut self) {
1390 if !self.disable_clearing {
1391 self.stack.clear();
1392 }
1393 }
1394
1395 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1396 self.stack.push(selection);
1397 }
1398
1399 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1400 self.stack.pop()
1401 }
1402}
1403
1404enum SelectSyntaxNodeScrollBehavior {
1405 CursorTop,
1406 FitSelection,
1407 CursorBottom,
1408}
1409
1410#[derive(Debug)]
1411pub(crate) struct NavigationData {
1412 cursor_anchor: Anchor,
1413 cursor_position: Point,
1414 scroll_anchor: ScrollAnchor,
1415 scroll_top_row: u32,
1416}
1417
1418#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1419pub enum GotoDefinitionKind {
1420 Symbol,
1421 Declaration,
1422 Type,
1423 Implementation,
1424}
1425
1426#[derive(Debug, Clone)]
1427enum InlayHintRefreshReason {
1428 ModifiersChanged(bool),
1429 Toggle(bool),
1430 SettingsChange(InlayHintSettings),
1431 NewLinesShown,
1432 BufferEdited(HashSet<Arc<Language>>),
1433 RefreshRequested,
1434 ExcerptsRemoved(Vec<ExcerptId>),
1435}
1436
1437impl InlayHintRefreshReason {
1438 fn description(&self) -> &'static str {
1439 match self {
1440 Self::ModifiersChanged(_) => "modifiers changed",
1441 Self::Toggle(_) => "toggle",
1442 Self::SettingsChange(_) => "settings change",
1443 Self::NewLinesShown => "new lines shown",
1444 Self::BufferEdited(_) => "buffer edited",
1445 Self::RefreshRequested => "refresh requested",
1446 Self::ExcerptsRemoved(_) => "excerpts removed",
1447 }
1448 }
1449}
1450
1451pub enum FormatTarget {
1452 Buffers,
1453 Ranges(Vec<Range<MultiBufferPoint>>),
1454}
1455
1456pub(crate) struct FocusedBlock {
1457 id: BlockId,
1458 focus_handle: WeakFocusHandle,
1459}
1460
1461#[derive(Clone)]
1462enum JumpData {
1463 MultiBufferRow {
1464 row: MultiBufferRow,
1465 line_offset_from_top: u32,
1466 },
1467 MultiBufferPoint {
1468 excerpt_id: ExcerptId,
1469 position: Point,
1470 anchor: text::Anchor,
1471 line_offset_from_top: u32,
1472 },
1473}
1474
1475pub enum MultibufferSelectionMode {
1476 First,
1477 All,
1478}
1479
1480#[derive(Clone, Copy, Debug, Default)]
1481pub struct RewrapOptions {
1482 pub override_language_settings: bool,
1483 pub preserve_existing_whitespace: bool,
1484}
1485
1486impl Editor {
1487 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1488 let buffer = cx.new(|cx| Buffer::local("", cx));
1489 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1490 Self::new(
1491 EditorMode::SingleLine { auto_width: false },
1492 buffer,
1493 None,
1494 window,
1495 cx,
1496 )
1497 }
1498
1499 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1500 let buffer = cx.new(|cx| Buffer::local("", cx));
1501 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1502 Self::new(EditorMode::full(), buffer, None, window, cx)
1503 }
1504
1505 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1506 let buffer = cx.new(|cx| Buffer::local("", cx));
1507 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1508 Self::new(
1509 EditorMode::SingleLine { auto_width: true },
1510 buffer,
1511 None,
1512 window,
1513 cx,
1514 )
1515 }
1516
1517 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1518 let buffer = cx.new(|cx| Buffer::local("", cx));
1519 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1520 Self::new(
1521 EditorMode::AutoHeight { max_lines },
1522 buffer,
1523 None,
1524 window,
1525 cx,
1526 )
1527 }
1528
1529 pub fn for_buffer(
1530 buffer: Entity<Buffer>,
1531 project: Option<Entity<Project>>,
1532 window: &mut Window,
1533 cx: &mut Context<Self>,
1534 ) -> Self {
1535 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1536 Self::new(EditorMode::full(), buffer, project, window, cx)
1537 }
1538
1539 pub fn for_multibuffer(
1540 buffer: Entity<MultiBuffer>,
1541 project: Option<Entity<Project>>,
1542 window: &mut Window,
1543 cx: &mut Context<Self>,
1544 ) -> Self {
1545 Self::new(EditorMode::full(), buffer, project, window, cx)
1546 }
1547
1548 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1549 let mut clone = Self::new(
1550 self.mode.clone(),
1551 self.buffer.clone(),
1552 self.project.clone(),
1553 window,
1554 cx,
1555 );
1556 self.display_map.update(cx, |display_map, cx| {
1557 let snapshot = display_map.snapshot(cx);
1558 clone.display_map.update(cx, |display_map, cx| {
1559 display_map.set_state(&snapshot, cx);
1560 });
1561 });
1562 clone.folds_did_change(cx);
1563 clone.selections.clone_state(&self.selections);
1564 clone.scroll_manager.clone_state(&self.scroll_manager);
1565 clone.searchable = self.searchable;
1566 clone.read_only = self.read_only;
1567 clone
1568 }
1569
1570 pub fn new(
1571 mode: EditorMode,
1572 buffer: Entity<MultiBuffer>,
1573 project: Option<Entity<Project>>,
1574 window: &mut Window,
1575 cx: &mut Context<Self>,
1576 ) -> Self {
1577 Editor::new_internal(mode, buffer, project, None, window, cx)
1578 }
1579
1580 fn new_internal(
1581 mode: EditorMode,
1582 buffer: Entity<MultiBuffer>,
1583 project: Option<Entity<Project>>,
1584 display_map: Option<Entity<DisplayMap>>,
1585 window: &mut Window,
1586 cx: &mut Context<Self>,
1587 ) -> Self {
1588 debug_assert!(
1589 display_map.is_none() || mode.is_minimap(),
1590 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1591 );
1592
1593 let full_mode = mode.is_full();
1594 let diagnostics_max_severity = if full_mode {
1595 EditorSettings::get_global(cx)
1596 .diagnostics_max_severity
1597 .unwrap_or(DiagnosticSeverity::Hint)
1598 } else {
1599 DiagnosticSeverity::Off
1600 };
1601 let style = window.text_style();
1602 let font_size = style.font_size.to_pixels(window.rem_size());
1603 let editor = cx.entity().downgrade();
1604 let fold_placeholder = FoldPlaceholder {
1605 constrain_width: true,
1606 render: Arc::new(move |fold_id, fold_range, cx| {
1607 let editor = editor.clone();
1608 div()
1609 .id(fold_id)
1610 .bg(cx.theme().colors().ghost_element_background)
1611 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1612 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1613 .rounded_xs()
1614 .size_full()
1615 .cursor_pointer()
1616 .child("⋯")
1617 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1618 .on_click(move |_, _window, cx| {
1619 editor
1620 .update(cx, |editor, cx| {
1621 editor.unfold_ranges(
1622 &[fold_range.start..fold_range.end],
1623 true,
1624 false,
1625 cx,
1626 );
1627 cx.stop_propagation();
1628 })
1629 .ok();
1630 })
1631 .into_any()
1632 }),
1633 merge_adjacent: true,
1634 ..FoldPlaceholder::default()
1635 };
1636 let display_map = display_map.unwrap_or_else(|| {
1637 cx.new(|cx| {
1638 DisplayMap::new(
1639 buffer.clone(),
1640 style.font(),
1641 font_size,
1642 None,
1643 FILE_HEADER_HEIGHT,
1644 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1645 fold_placeholder,
1646 diagnostics_max_severity,
1647 cx,
1648 )
1649 })
1650 });
1651
1652 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1653
1654 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1655
1656 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1657 .then(|| language_settings::SoftWrap::None);
1658
1659 let mut project_subscriptions = Vec::new();
1660 if mode.is_full() {
1661 if let Some(project) = project.as_ref() {
1662 project_subscriptions.push(cx.subscribe_in(
1663 project,
1664 window,
1665 |editor, _, event, window, cx| match event {
1666 project::Event::RefreshCodeLens => {
1667 // we always query lens with actions, without storing them, always refreshing them
1668 }
1669 project::Event::RefreshInlayHints => {
1670 editor
1671 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1672 }
1673 project::Event::SnippetEdit(id, snippet_edits) => {
1674 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1675 let focus_handle = editor.focus_handle(cx);
1676 if focus_handle.is_focused(window) {
1677 let snapshot = buffer.read(cx).snapshot();
1678 for (range, snippet) in snippet_edits {
1679 let editor_range =
1680 language::range_from_lsp(*range).to_offset(&snapshot);
1681 editor
1682 .insert_snippet(
1683 &[editor_range],
1684 snippet.clone(),
1685 window,
1686 cx,
1687 )
1688 .ok();
1689 }
1690 }
1691 }
1692 }
1693 _ => {}
1694 },
1695 ));
1696 if let Some(task_inventory) = project
1697 .read(cx)
1698 .task_store()
1699 .read(cx)
1700 .task_inventory()
1701 .cloned()
1702 {
1703 project_subscriptions.push(cx.observe_in(
1704 &task_inventory,
1705 window,
1706 |editor, _, window, cx| {
1707 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1708 },
1709 ));
1710 };
1711
1712 project_subscriptions.push(cx.subscribe_in(
1713 &project.read(cx).breakpoint_store(),
1714 window,
1715 |editor, _, event, window, cx| match event {
1716 BreakpointStoreEvent::ClearDebugLines => {
1717 editor.clear_row_highlights::<ActiveDebugLine>();
1718 editor.refresh_inline_values(cx);
1719 }
1720 BreakpointStoreEvent::SetDebugLine => {
1721 if editor.go_to_active_debug_line(window, cx) {
1722 cx.stop_propagation();
1723 }
1724
1725 editor.refresh_inline_values(cx);
1726 }
1727 _ => {}
1728 },
1729 ));
1730 }
1731 }
1732
1733 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1734
1735 let inlay_hint_settings =
1736 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1737 let focus_handle = cx.focus_handle();
1738 cx.on_focus(&focus_handle, window, Self::handle_focus)
1739 .detach();
1740 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1741 .detach();
1742 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1743 .detach();
1744 cx.on_blur(&focus_handle, window, Self::handle_blur)
1745 .detach();
1746
1747 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1748 Some(false)
1749 } else {
1750 None
1751 };
1752
1753 let breakpoint_store = match (&mode, project.as_ref()) {
1754 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1755 _ => None,
1756 };
1757
1758 let mut code_action_providers = Vec::new();
1759 let mut load_uncommitted_diff = None;
1760 if let Some(project) = project.clone() {
1761 load_uncommitted_diff = Some(
1762 update_uncommitted_diff_for_buffer(
1763 cx.entity(),
1764 &project,
1765 buffer.read(cx).all_buffers(),
1766 buffer.clone(),
1767 cx,
1768 )
1769 .shared(),
1770 );
1771 code_action_providers.push(Rc::new(project) as Rc<_>);
1772 }
1773
1774 let mut this = Self {
1775 focus_handle,
1776 show_cursor_when_unfocused: false,
1777 last_focused_descendant: None,
1778 buffer: buffer.clone(),
1779 display_map: display_map.clone(),
1780 selections,
1781 scroll_manager: ScrollManager::new(cx),
1782 columnar_selection_tail: None,
1783 add_selections_state: None,
1784 select_next_state: None,
1785 select_prev_state: None,
1786 selection_history: SelectionHistory::default(),
1787 autoclose_regions: Vec::new(),
1788 snippet_stack: InvalidationStack::default(),
1789 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1790 ime_transaction: None,
1791 active_diagnostics: ActiveDiagnostic::None,
1792 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1793 inline_diagnostics_update: Task::ready(()),
1794 inline_diagnostics: Vec::new(),
1795 soft_wrap_mode_override,
1796 diagnostics_max_severity,
1797 hard_wrap: None,
1798 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1799 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1800 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1801 project,
1802 blink_manager: blink_manager.clone(),
1803 show_local_selections: true,
1804 show_scrollbars: full_mode,
1805 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1806 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1807 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1808 show_gutter: mode.is_full(),
1809 show_line_numbers: None,
1810 use_relative_line_numbers: None,
1811 disable_expand_excerpt_buttons: false,
1812 show_git_diff_gutter: None,
1813 show_code_actions: None,
1814 show_runnables: None,
1815 show_breakpoints: None,
1816 show_wrap_guides: None,
1817 show_indent_guides,
1818 placeholder_text: None,
1819 highlight_order: 0,
1820 highlighted_rows: HashMap::default(),
1821 background_highlights: TreeMap::default(),
1822 gutter_highlights: TreeMap::default(),
1823 scrollbar_marker_state: ScrollbarMarkerState::default(),
1824 active_indent_guides_state: ActiveIndentGuidesState::default(),
1825 nav_history: None,
1826 context_menu: RefCell::new(None),
1827 context_menu_options: None,
1828 mouse_context_menu: None,
1829 completion_tasks: Vec::new(),
1830 inline_blame_popover: None,
1831 signature_help_state: SignatureHelpState::default(),
1832 auto_signature_help: None,
1833 find_all_references_task_sources: Vec::new(),
1834 next_completion_id: 0,
1835 next_inlay_id: 0,
1836 code_action_providers,
1837 available_code_actions: None,
1838 code_actions_task: None,
1839 quick_selection_highlight_task: None,
1840 debounced_selection_highlight_task: None,
1841 document_highlights_task: None,
1842 linked_editing_range_task: None,
1843 pending_rename: None,
1844 searchable: true,
1845 cursor_shape: EditorSettings::get_global(cx)
1846 .cursor_shape
1847 .unwrap_or_default(),
1848 current_line_highlight: None,
1849 autoindent_mode: Some(AutoindentMode::EachLine),
1850 collapse_matches: false,
1851 workspace: None,
1852 input_enabled: true,
1853 use_modal_editing: mode.is_full(),
1854 read_only: mode.is_minimap(),
1855 use_autoclose: true,
1856 use_auto_surround: true,
1857 auto_replace_emoji_shortcode: false,
1858 jsx_tag_auto_close_enabled_in_any_buffer: false,
1859 leader_id: None,
1860 remote_id: None,
1861 hover_state: HoverState::default(),
1862 pending_mouse_down: None,
1863 hovered_link_state: None,
1864 edit_prediction_provider: None,
1865 active_inline_completion: None,
1866 stale_inline_completion_in_menu: None,
1867 edit_prediction_preview: EditPredictionPreview::Inactive {
1868 released_too_fast: false,
1869 },
1870 inline_diagnostics_enabled: mode.is_full(),
1871 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1872 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1873
1874 gutter_hovered: false,
1875 pixel_position_of_newest_cursor: None,
1876 last_bounds: None,
1877 last_position_map: None,
1878 expect_bounds_change: None,
1879 gutter_dimensions: GutterDimensions::default(),
1880 style: None,
1881 show_cursor_names: false,
1882 hovered_cursors: HashMap::default(),
1883 next_editor_action_id: EditorActionId::default(),
1884 editor_actions: Rc::default(),
1885 inline_completions_hidden_for_vim_mode: false,
1886 show_inline_completions_override: None,
1887 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1888 edit_prediction_settings: EditPredictionSettings::Disabled,
1889 edit_prediction_indent_conflict: false,
1890 edit_prediction_requires_modifier_in_indent_conflict: true,
1891 custom_context_menu: None,
1892 show_git_blame_gutter: false,
1893 show_git_blame_inline: false,
1894 show_selection_menu: None,
1895 show_git_blame_inline_delay_task: None,
1896 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1897 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1898 serialize_dirty_buffers: !mode.is_minimap()
1899 && ProjectSettings::get_global(cx)
1900 .session
1901 .restore_unsaved_buffers,
1902 blame: None,
1903 blame_subscription: None,
1904 tasks: BTreeMap::default(),
1905
1906 breakpoint_store,
1907 gutter_breakpoint_indicator: (None, None),
1908 _subscriptions: vec![
1909 cx.observe(&buffer, Self::on_buffer_changed),
1910 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1911 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1912 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1913 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1914 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1915 cx.observe_window_activation(window, |editor, window, cx| {
1916 let active = window.is_window_active();
1917 editor.blink_manager.update(cx, |blink_manager, cx| {
1918 if active {
1919 blink_manager.enable(cx);
1920 } else {
1921 blink_manager.disable(cx);
1922 }
1923 });
1924 if active {
1925 editor.show_mouse_cursor();
1926 }
1927 }),
1928 ],
1929 tasks_update_task: None,
1930 linked_edit_ranges: Default::default(),
1931 in_project_search: false,
1932 previous_search_ranges: None,
1933 breadcrumb_header: None,
1934 focused_block: None,
1935 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1936 addons: HashMap::default(),
1937 registered_buffers: HashMap::default(),
1938 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1939 selection_mark_mode: false,
1940 toggle_fold_multiple_buffers: Task::ready(()),
1941 serialize_selections: Task::ready(()),
1942 serialize_folds: Task::ready(()),
1943 text_style_refinement: None,
1944 load_diff_task: load_uncommitted_diff,
1945 temporary_diff_override: false,
1946 mouse_cursor_hidden: false,
1947 minimap: None,
1948 hide_mouse_mode: EditorSettings::get_global(cx)
1949 .hide_mouse
1950 .unwrap_or_default(),
1951 change_list: ChangeList::new(),
1952 mode,
1953 };
1954 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1955 this._subscriptions
1956 .push(cx.observe(breakpoints, |_, _, cx| {
1957 cx.notify();
1958 }));
1959 }
1960 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1961 this._subscriptions.extend(project_subscriptions);
1962
1963 this._subscriptions.push(cx.subscribe_in(
1964 &cx.entity(),
1965 window,
1966 |editor, _, e: &EditorEvent, window, cx| match e {
1967 EditorEvent::ScrollPositionChanged { local, .. } => {
1968 if *local {
1969 let new_anchor = editor.scroll_manager.anchor();
1970 let snapshot = editor.snapshot(window, cx);
1971 editor.update_restoration_data(cx, move |data| {
1972 data.scroll_position = (
1973 new_anchor.top_row(&snapshot.buffer_snapshot),
1974 new_anchor.offset,
1975 );
1976 });
1977 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1978 editor.inline_blame_popover.take();
1979 }
1980 }
1981 EditorEvent::Edited { .. } => {
1982 if !vim_enabled(cx) {
1983 let (map, selections) = editor.selections.all_adjusted_display(cx);
1984 let pop_state = editor
1985 .change_list
1986 .last()
1987 .map(|previous| {
1988 previous.len() == selections.len()
1989 && previous.iter().enumerate().all(|(ix, p)| {
1990 p.to_display_point(&map).row()
1991 == selections[ix].head().row()
1992 })
1993 })
1994 .unwrap_or(false);
1995 let new_positions = selections
1996 .into_iter()
1997 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
1998 .collect();
1999 editor
2000 .change_list
2001 .push_to_change_list(pop_state, new_positions);
2002 }
2003 }
2004 _ => (),
2005 },
2006 ));
2007
2008 if let Some(dap_store) = this
2009 .project
2010 .as_ref()
2011 .map(|project| project.read(cx).dap_store())
2012 {
2013 let weak_editor = cx.weak_entity();
2014
2015 this._subscriptions
2016 .push(
2017 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2018 let session_entity = cx.entity();
2019 weak_editor
2020 .update(cx, |editor, cx| {
2021 editor._subscriptions.push(
2022 cx.subscribe(&session_entity, Self::on_debug_session_event),
2023 );
2024 })
2025 .ok();
2026 }),
2027 );
2028
2029 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2030 this._subscriptions
2031 .push(cx.subscribe(&session, Self::on_debug_session_event));
2032 }
2033 }
2034
2035 this.end_selection(window, cx);
2036 this.scroll_manager.show_scrollbars(window, cx);
2037 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
2038
2039 if full_mode {
2040 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2041 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2042
2043 if this.git_blame_inline_enabled {
2044 this.start_git_blame_inline(false, window, cx);
2045 }
2046
2047 this.go_to_active_debug_line(window, cx);
2048
2049 if let Some(buffer) = buffer.read(cx).as_singleton() {
2050 if let Some(project) = this.project.as_ref() {
2051 let handle = project.update(cx, |project, cx| {
2052 project.register_buffer_with_language_servers(&buffer, cx)
2053 });
2054 this.registered_buffers
2055 .insert(buffer.read(cx).remote_id(), handle);
2056 }
2057 }
2058
2059 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2060 }
2061
2062 this.report_editor_event("Editor Opened", None, cx);
2063 this
2064 }
2065
2066 pub fn deploy_mouse_context_menu(
2067 &mut self,
2068 position: gpui::Point<Pixels>,
2069 context_menu: Entity<ContextMenu>,
2070 window: &mut Window,
2071 cx: &mut Context<Self>,
2072 ) {
2073 self.mouse_context_menu = Some(MouseContextMenu::new(
2074 self,
2075 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2076 context_menu,
2077 window,
2078 cx,
2079 ));
2080 }
2081
2082 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2083 self.mouse_context_menu
2084 .as_ref()
2085 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2086 }
2087
2088 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2089 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2090 }
2091
2092 fn key_context_internal(
2093 &self,
2094 has_active_edit_prediction: bool,
2095 window: &Window,
2096 cx: &App,
2097 ) -> KeyContext {
2098 let mut key_context = KeyContext::new_with_defaults();
2099 key_context.add("Editor");
2100 let mode = match self.mode {
2101 EditorMode::SingleLine { .. } => "single_line",
2102 EditorMode::AutoHeight { .. } => "auto_height",
2103 EditorMode::Minimap { .. } => "minimap",
2104 EditorMode::Full { .. } => "full",
2105 };
2106
2107 if EditorSettings::jupyter_enabled(cx) {
2108 key_context.add("jupyter");
2109 }
2110
2111 key_context.set("mode", mode);
2112 if self.pending_rename.is_some() {
2113 key_context.add("renaming");
2114 }
2115
2116 match self.context_menu.borrow().as_ref() {
2117 Some(CodeContextMenu::Completions(_)) => {
2118 key_context.add("menu");
2119 key_context.add("showing_completions");
2120 }
2121 Some(CodeContextMenu::CodeActions(_)) => {
2122 key_context.add("menu");
2123 key_context.add("showing_code_actions")
2124 }
2125 None => {}
2126 }
2127
2128 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2129 if !self.focus_handle(cx).contains_focused(window, cx)
2130 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2131 {
2132 for addon in self.addons.values() {
2133 addon.extend_key_context(&mut key_context, cx)
2134 }
2135 }
2136
2137 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2138 if let Some(extension) = singleton_buffer
2139 .read(cx)
2140 .file()
2141 .and_then(|file| file.path().extension()?.to_str())
2142 {
2143 key_context.set("extension", extension.to_string());
2144 }
2145 } else {
2146 key_context.add("multibuffer");
2147 }
2148
2149 if has_active_edit_prediction {
2150 if self.edit_prediction_in_conflict() {
2151 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2152 } else {
2153 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2154 key_context.add("copilot_suggestion");
2155 }
2156 }
2157
2158 if self.selection_mark_mode {
2159 key_context.add("selection_mode");
2160 }
2161
2162 key_context
2163 }
2164
2165 fn show_mouse_cursor(&mut self) {
2166 self.mouse_cursor_hidden = false;
2167 }
2168
2169 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2170 self.mouse_cursor_hidden = match origin {
2171 HideMouseCursorOrigin::TypingAction => {
2172 matches!(
2173 self.hide_mouse_mode,
2174 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2175 )
2176 }
2177 HideMouseCursorOrigin::MovementAction => {
2178 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2179 }
2180 };
2181 }
2182
2183 pub fn edit_prediction_in_conflict(&self) -> bool {
2184 if !self.show_edit_predictions_in_menu() {
2185 return false;
2186 }
2187
2188 let showing_completions = self
2189 .context_menu
2190 .borrow()
2191 .as_ref()
2192 .map_or(false, |context| {
2193 matches!(context, CodeContextMenu::Completions(_))
2194 });
2195
2196 showing_completions
2197 || self.edit_prediction_requires_modifier()
2198 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2199 // bindings to insert tab characters.
2200 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2201 }
2202
2203 pub fn accept_edit_prediction_keybind(
2204 &self,
2205 window: &Window,
2206 cx: &App,
2207 ) -> AcceptEditPredictionBinding {
2208 let key_context = self.key_context_internal(true, window, cx);
2209 let in_conflict = self.edit_prediction_in_conflict();
2210
2211 AcceptEditPredictionBinding(
2212 window
2213 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2214 .into_iter()
2215 .filter(|binding| {
2216 !in_conflict
2217 || binding
2218 .keystrokes()
2219 .first()
2220 .map_or(false, |keystroke| keystroke.modifiers.modified())
2221 })
2222 .rev()
2223 .min_by_key(|binding| {
2224 binding
2225 .keystrokes()
2226 .first()
2227 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2228 }),
2229 )
2230 }
2231
2232 pub fn new_file(
2233 workspace: &mut Workspace,
2234 _: &workspace::NewFile,
2235 window: &mut Window,
2236 cx: &mut Context<Workspace>,
2237 ) {
2238 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2239 "Failed to create buffer",
2240 window,
2241 cx,
2242 |e, _, _| match e.error_code() {
2243 ErrorCode::RemoteUpgradeRequired => Some(format!(
2244 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2245 e.error_tag("required").unwrap_or("the latest version")
2246 )),
2247 _ => None,
2248 },
2249 );
2250 }
2251
2252 pub fn new_in_workspace(
2253 workspace: &mut Workspace,
2254 window: &mut Window,
2255 cx: &mut Context<Workspace>,
2256 ) -> Task<Result<Entity<Editor>>> {
2257 let project = workspace.project().clone();
2258 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2259
2260 cx.spawn_in(window, async move |workspace, cx| {
2261 let buffer = create.await?;
2262 workspace.update_in(cx, |workspace, window, cx| {
2263 let editor =
2264 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2265 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2266 editor
2267 })
2268 })
2269 }
2270
2271 fn new_file_vertical(
2272 workspace: &mut Workspace,
2273 _: &workspace::NewFileSplitVertical,
2274 window: &mut Window,
2275 cx: &mut Context<Workspace>,
2276 ) {
2277 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2278 }
2279
2280 fn new_file_horizontal(
2281 workspace: &mut Workspace,
2282 _: &workspace::NewFileSplitHorizontal,
2283 window: &mut Window,
2284 cx: &mut Context<Workspace>,
2285 ) {
2286 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2287 }
2288
2289 fn new_file_in_direction(
2290 workspace: &mut Workspace,
2291 direction: SplitDirection,
2292 window: &mut Window,
2293 cx: &mut Context<Workspace>,
2294 ) {
2295 let project = workspace.project().clone();
2296 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2297
2298 cx.spawn_in(window, async move |workspace, cx| {
2299 let buffer = create.await?;
2300 workspace.update_in(cx, move |workspace, window, cx| {
2301 workspace.split_item(
2302 direction,
2303 Box::new(
2304 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2305 ),
2306 window,
2307 cx,
2308 )
2309 })?;
2310 anyhow::Ok(())
2311 })
2312 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2313 match e.error_code() {
2314 ErrorCode::RemoteUpgradeRequired => Some(format!(
2315 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2316 e.error_tag("required").unwrap_or("the latest version")
2317 )),
2318 _ => None,
2319 }
2320 });
2321 }
2322
2323 pub fn leader_id(&self) -> Option<CollaboratorId> {
2324 self.leader_id
2325 }
2326
2327 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2328 &self.buffer
2329 }
2330
2331 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2332 self.workspace.as_ref()?.0.upgrade()
2333 }
2334
2335 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2336 self.buffer().read(cx).title(cx)
2337 }
2338
2339 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2340 let git_blame_gutter_max_author_length = self
2341 .render_git_blame_gutter(cx)
2342 .then(|| {
2343 if let Some(blame) = self.blame.as_ref() {
2344 let max_author_length =
2345 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2346 Some(max_author_length)
2347 } else {
2348 None
2349 }
2350 })
2351 .flatten();
2352
2353 EditorSnapshot {
2354 mode: self.mode.clone(),
2355 show_gutter: self.show_gutter,
2356 show_line_numbers: self.show_line_numbers,
2357 show_git_diff_gutter: self.show_git_diff_gutter,
2358 show_code_actions: self.show_code_actions,
2359 show_runnables: self.show_runnables,
2360 show_breakpoints: self.show_breakpoints,
2361 git_blame_gutter_max_author_length,
2362 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2363 scroll_anchor: self.scroll_manager.anchor(),
2364 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2365 placeholder_text: self.placeholder_text.clone(),
2366 is_focused: self.focus_handle.is_focused(window),
2367 current_line_highlight: self
2368 .current_line_highlight
2369 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2370 gutter_hovered: self.gutter_hovered,
2371 }
2372 }
2373
2374 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2375 self.buffer.read(cx).language_at(point, cx)
2376 }
2377
2378 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2379 self.buffer.read(cx).read(cx).file_at(point).cloned()
2380 }
2381
2382 pub fn active_excerpt(
2383 &self,
2384 cx: &App,
2385 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2386 self.buffer
2387 .read(cx)
2388 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2389 }
2390
2391 pub fn mode(&self) -> &EditorMode {
2392 &self.mode
2393 }
2394
2395 pub fn set_mode(&mut self, mode: EditorMode) {
2396 self.mode = mode;
2397 }
2398
2399 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2400 self.collaboration_hub.as_deref()
2401 }
2402
2403 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2404 self.collaboration_hub = Some(hub);
2405 }
2406
2407 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2408 self.in_project_search = in_project_search;
2409 }
2410
2411 pub fn set_custom_context_menu(
2412 &mut self,
2413 f: impl 'static
2414 + Fn(
2415 &mut Self,
2416 DisplayPoint,
2417 &mut Window,
2418 &mut Context<Self>,
2419 ) -> Option<Entity<ui::ContextMenu>>,
2420 ) {
2421 self.custom_context_menu = Some(Box::new(f))
2422 }
2423
2424 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2425 self.completion_provider = provider;
2426 }
2427
2428 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2429 self.semantics_provider.clone()
2430 }
2431
2432 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2433 self.semantics_provider = provider;
2434 }
2435
2436 pub fn set_edit_prediction_provider<T>(
2437 &mut self,
2438 provider: Option<Entity<T>>,
2439 window: &mut Window,
2440 cx: &mut Context<Self>,
2441 ) where
2442 T: EditPredictionProvider,
2443 {
2444 self.edit_prediction_provider =
2445 provider.map(|provider| RegisteredInlineCompletionProvider {
2446 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2447 if this.focus_handle.is_focused(window) {
2448 this.update_visible_inline_completion(window, cx);
2449 }
2450 }),
2451 provider: Arc::new(provider),
2452 });
2453 self.update_edit_prediction_settings(cx);
2454 self.refresh_inline_completion(false, false, window, cx);
2455 }
2456
2457 pub fn placeholder_text(&self) -> Option<&str> {
2458 self.placeholder_text.as_deref()
2459 }
2460
2461 pub fn set_placeholder_text(
2462 &mut self,
2463 placeholder_text: impl Into<Arc<str>>,
2464 cx: &mut Context<Self>,
2465 ) {
2466 let placeholder_text = Some(placeholder_text.into());
2467 if self.placeholder_text != placeholder_text {
2468 self.placeholder_text = placeholder_text;
2469 cx.notify();
2470 }
2471 }
2472
2473 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2474 self.cursor_shape = cursor_shape;
2475
2476 // Disrupt blink for immediate user feedback that the cursor shape has changed
2477 self.blink_manager.update(cx, BlinkManager::show_cursor);
2478
2479 cx.notify();
2480 }
2481
2482 pub fn set_current_line_highlight(
2483 &mut self,
2484 current_line_highlight: Option<CurrentLineHighlight>,
2485 ) {
2486 self.current_line_highlight = current_line_highlight;
2487 }
2488
2489 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2490 self.collapse_matches = collapse_matches;
2491 }
2492
2493 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2494 let buffers = self.buffer.read(cx).all_buffers();
2495 let Some(project) = self.project.as_ref() else {
2496 return;
2497 };
2498 project.update(cx, |project, cx| {
2499 for buffer in buffers {
2500 self.registered_buffers
2501 .entry(buffer.read(cx).remote_id())
2502 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2503 }
2504 })
2505 }
2506
2507 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2508 if self.collapse_matches {
2509 return range.start..range.start;
2510 }
2511 range.clone()
2512 }
2513
2514 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2515 if self.display_map.read(cx).clip_at_line_ends != clip {
2516 self.display_map
2517 .update(cx, |map, _| map.clip_at_line_ends = clip);
2518 }
2519 }
2520
2521 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2522 self.input_enabled = input_enabled;
2523 }
2524
2525 pub fn set_inline_completions_hidden_for_vim_mode(
2526 &mut self,
2527 hidden: bool,
2528 window: &mut Window,
2529 cx: &mut Context<Self>,
2530 ) {
2531 if hidden != self.inline_completions_hidden_for_vim_mode {
2532 self.inline_completions_hidden_for_vim_mode = hidden;
2533 if hidden {
2534 self.update_visible_inline_completion(window, cx);
2535 } else {
2536 self.refresh_inline_completion(true, false, window, cx);
2537 }
2538 }
2539 }
2540
2541 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2542 self.menu_inline_completions_policy = value;
2543 }
2544
2545 pub fn set_autoindent(&mut self, autoindent: bool) {
2546 if autoindent {
2547 self.autoindent_mode = Some(AutoindentMode::EachLine);
2548 } else {
2549 self.autoindent_mode = None;
2550 }
2551 }
2552
2553 pub fn read_only(&self, cx: &App) -> bool {
2554 self.read_only || self.buffer.read(cx).read_only()
2555 }
2556
2557 pub fn set_read_only(&mut self, read_only: bool) {
2558 self.read_only = read_only;
2559 }
2560
2561 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2562 self.use_autoclose = autoclose;
2563 }
2564
2565 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2566 self.use_auto_surround = auto_surround;
2567 }
2568
2569 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2570 self.auto_replace_emoji_shortcode = auto_replace;
2571 }
2572
2573 pub fn toggle_edit_predictions(
2574 &mut self,
2575 _: &ToggleEditPrediction,
2576 window: &mut Window,
2577 cx: &mut Context<Self>,
2578 ) {
2579 if self.show_inline_completions_override.is_some() {
2580 self.set_show_edit_predictions(None, window, cx);
2581 } else {
2582 let show_edit_predictions = !self.edit_predictions_enabled();
2583 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2584 }
2585 }
2586
2587 pub fn set_show_edit_predictions(
2588 &mut self,
2589 show_edit_predictions: Option<bool>,
2590 window: &mut Window,
2591 cx: &mut Context<Self>,
2592 ) {
2593 self.show_inline_completions_override = show_edit_predictions;
2594 self.update_edit_prediction_settings(cx);
2595
2596 if let Some(false) = show_edit_predictions {
2597 self.discard_inline_completion(false, cx);
2598 } else {
2599 self.refresh_inline_completion(false, true, window, cx);
2600 }
2601 }
2602
2603 fn inline_completions_disabled_in_scope(
2604 &self,
2605 buffer: &Entity<Buffer>,
2606 buffer_position: language::Anchor,
2607 cx: &App,
2608 ) -> bool {
2609 let snapshot = buffer.read(cx).snapshot();
2610 let settings = snapshot.settings_at(buffer_position, cx);
2611
2612 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2613 return false;
2614 };
2615
2616 scope.override_name().map_or(false, |scope_name| {
2617 settings
2618 .edit_predictions_disabled_in
2619 .iter()
2620 .any(|s| s == scope_name)
2621 })
2622 }
2623
2624 pub fn set_use_modal_editing(&mut self, to: bool) {
2625 self.use_modal_editing = to;
2626 }
2627
2628 pub fn use_modal_editing(&self) -> bool {
2629 self.use_modal_editing
2630 }
2631
2632 fn selections_did_change(
2633 &mut self,
2634 local: bool,
2635 old_cursor_position: &Anchor,
2636 show_completions: bool,
2637 window: &mut Window,
2638 cx: &mut Context<Self>,
2639 ) {
2640 window.invalidate_character_coordinates();
2641
2642 // Copy selections to primary selection buffer
2643 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2644 if local {
2645 let selections = self.selections.all::<usize>(cx);
2646 let buffer_handle = self.buffer.read(cx).read(cx);
2647
2648 let mut text = String::new();
2649 for (index, selection) in selections.iter().enumerate() {
2650 let text_for_selection = buffer_handle
2651 .text_for_range(selection.start..selection.end)
2652 .collect::<String>();
2653
2654 text.push_str(&text_for_selection);
2655 if index != selections.len() - 1 {
2656 text.push('\n');
2657 }
2658 }
2659
2660 if !text.is_empty() {
2661 cx.write_to_primary(ClipboardItem::new_string(text));
2662 }
2663 }
2664
2665 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2666 self.buffer.update(cx, |buffer, cx| {
2667 buffer.set_active_selections(
2668 &self.selections.disjoint_anchors(),
2669 self.selections.line_mode,
2670 self.cursor_shape,
2671 cx,
2672 )
2673 });
2674 }
2675 let display_map = self
2676 .display_map
2677 .update(cx, |display_map, cx| display_map.snapshot(cx));
2678 let buffer = &display_map.buffer_snapshot;
2679 self.add_selections_state = None;
2680 self.select_next_state = None;
2681 self.select_prev_state = None;
2682 self.select_syntax_node_history.try_clear();
2683 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2684 self.snippet_stack
2685 .invalidate(&self.selections.disjoint_anchors(), buffer);
2686 self.take_rename(false, window, cx);
2687
2688 let new_cursor_position = self.selections.newest_anchor().head();
2689
2690 self.push_to_nav_history(
2691 *old_cursor_position,
2692 Some(new_cursor_position.to_point(buffer)),
2693 false,
2694 cx,
2695 );
2696
2697 if local {
2698 let new_cursor_position = self.selections.newest_anchor().head();
2699 let mut context_menu = self.context_menu.borrow_mut();
2700 let completion_menu = match context_menu.as_ref() {
2701 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2702 _ => {
2703 *context_menu = None;
2704 None
2705 }
2706 };
2707 if let Some(buffer_id) = new_cursor_position.buffer_id {
2708 if !self.registered_buffers.contains_key(&buffer_id) {
2709 if let Some(project) = self.project.as_ref() {
2710 project.update(cx, |project, cx| {
2711 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2712 return;
2713 };
2714 self.registered_buffers.insert(
2715 buffer_id,
2716 project.register_buffer_with_language_servers(&buffer, cx),
2717 );
2718 })
2719 }
2720 }
2721 }
2722
2723 if let Some(completion_menu) = completion_menu {
2724 let cursor_position = new_cursor_position.to_offset(buffer);
2725 let (word_range, kind) =
2726 buffer.surrounding_word(completion_menu.initial_position, true);
2727 if kind == Some(CharKind::Word)
2728 && word_range.to_inclusive().contains(&cursor_position)
2729 {
2730 let mut completion_menu = completion_menu.clone();
2731 drop(context_menu);
2732
2733 let query = Self::completion_query(buffer, cursor_position);
2734 let completion_provider = self.completion_provider.clone();
2735 cx.spawn_in(window, async move |this, cx| {
2736 completion_menu
2737 .filter(query.as_deref(), completion_provider, this.clone(), cx)
2738 .await;
2739
2740 this.update(cx, |this, cx| {
2741 let mut context_menu = this.context_menu.borrow_mut();
2742 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2743 else {
2744 return;
2745 };
2746
2747 if menu.id > completion_menu.id {
2748 return;
2749 }
2750
2751 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2752 drop(context_menu);
2753 cx.notify();
2754 })
2755 })
2756 .detach();
2757
2758 if show_completions {
2759 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2760 }
2761 } else {
2762 drop(context_menu);
2763 self.hide_context_menu(window, cx);
2764 }
2765 } else {
2766 drop(context_menu);
2767 }
2768
2769 hide_hover(self, cx);
2770
2771 if old_cursor_position.to_display_point(&display_map).row()
2772 != new_cursor_position.to_display_point(&display_map).row()
2773 {
2774 self.available_code_actions.take();
2775 }
2776 self.refresh_code_actions(window, cx);
2777 self.refresh_document_highlights(cx);
2778 self.refresh_selected_text_highlights(false, window, cx);
2779 refresh_matching_bracket_highlights(self, window, cx);
2780 self.update_visible_inline_completion(window, cx);
2781 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2782 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2783 self.inline_blame_popover.take();
2784 if self.git_blame_inline_enabled {
2785 self.start_inline_blame_timer(window, cx);
2786 }
2787 }
2788
2789 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2790 cx.emit(EditorEvent::SelectionsChanged { local });
2791
2792 let selections = &self.selections.disjoint;
2793 if selections.len() == 1 {
2794 cx.emit(SearchEvent::ActiveMatchChanged)
2795 }
2796 if local {
2797 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2798 let inmemory_selections = selections
2799 .iter()
2800 .map(|s| {
2801 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2802 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2803 })
2804 .collect();
2805 self.update_restoration_data(cx, |data| {
2806 data.selections = inmemory_selections;
2807 });
2808
2809 if WorkspaceSettings::get(None, cx).restore_on_startup
2810 != RestoreOnStartupBehavior::None
2811 {
2812 if let Some(workspace_id) =
2813 self.workspace.as_ref().and_then(|workspace| workspace.1)
2814 {
2815 let snapshot = self.buffer().read(cx).snapshot(cx);
2816 let selections = selections.clone();
2817 let background_executor = cx.background_executor().clone();
2818 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2819 self.serialize_selections = cx.background_spawn(async move {
2820 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2821 let db_selections = selections
2822 .iter()
2823 .map(|selection| {
2824 (
2825 selection.start.to_offset(&snapshot),
2826 selection.end.to_offset(&snapshot),
2827 )
2828 })
2829 .collect();
2830
2831 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2832 .await
2833 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2834 .log_err();
2835 });
2836 }
2837 }
2838 }
2839 }
2840
2841 cx.notify();
2842 }
2843
2844 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2845 use text::ToOffset as _;
2846 use text::ToPoint as _;
2847
2848 if self.mode.is_minimap()
2849 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2850 {
2851 return;
2852 }
2853
2854 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2855 return;
2856 };
2857
2858 let snapshot = singleton.read(cx).snapshot();
2859 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2860 let display_snapshot = display_map.snapshot(cx);
2861
2862 display_snapshot
2863 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2864 .map(|fold| {
2865 fold.range.start.text_anchor.to_point(&snapshot)
2866 ..fold.range.end.text_anchor.to_point(&snapshot)
2867 })
2868 .collect()
2869 });
2870 self.update_restoration_data(cx, |data| {
2871 data.folds = inmemory_folds;
2872 });
2873
2874 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2875 return;
2876 };
2877 let background_executor = cx.background_executor().clone();
2878 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2879 let db_folds = self.display_map.update(cx, |display_map, cx| {
2880 display_map
2881 .snapshot(cx)
2882 .folds_in_range(0..snapshot.len())
2883 .map(|fold| {
2884 (
2885 fold.range.start.text_anchor.to_offset(&snapshot),
2886 fold.range.end.text_anchor.to_offset(&snapshot),
2887 )
2888 })
2889 .collect()
2890 });
2891 self.serialize_folds = cx.background_spawn(async move {
2892 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2893 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2894 .await
2895 .with_context(|| {
2896 format!(
2897 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2898 )
2899 })
2900 .log_err();
2901 });
2902 }
2903
2904 pub fn sync_selections(
2905 &mut self,
2906 other: Entity<Editor>,
2907 cx: &mut Context<Self>,
2908 ) -> gpui::Subscription {
2909 let other_selections = other.read(cx).selections.disjoint.to_vec();
2910 self.selections.change_with(cx, |selections| {
2911 selections.select_anchors(other_selections);
2912 });
2913
2914 let other_subscription =
2915 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2916 EditorEvent::SelectionsChanged { local: true } => {
2917 let other_selections = other.read(cx).selections.disjoint.to_vec();
2918 if other_selections.is_empty() {
2919 return;
2920 }
2921 this.selections.change_with(cx, |selections| {
2922 selections.select_anchors(other_selections);
2923 });
2924 }
2925 _ => {}
2926 });
2927
2928 let this_subscription =
2929 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2930 EditorEvent::SelectionsChanged { local: true } => {
2931 let these_selections = this.selections.disjoint.to_vec();
2932 if these_selections.is_empty() {
2933 return;
2934 }
2935 other.update(cx, |other_editor, cx| {
2936 other_editor.selections.change_with(cx, |selections| {
2937 selections.select_anchors(these_selections);
2938 })
2939 });
2940 }
2941 _ => {}
2942 });
2943
2944 Subscription::join(other_subscription, this_subscription)
2945 }
2946
2947 pub fn change_selections<R>(
2948 &mut self,
2949 autoscroll: Option<Autoscroll>,
2950 window: &mut Window,
2951 cx: &mut Context<Self>,
2952 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2953 ) -> R {
2954 self.change_selections_inner(autoscroll, true, window, cx, change)
2955 }
2956
2957 fn change_selections_inner<R>(
2958 &mut self,
2959 autoscroll: Option<Autoscroll>,
2960 request_completions: bool,
2961 window: &mut Window,
2962 cx: &mut Context<Self>,
2963 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2964 ) -> R {
2965 let old_cursor_position = self.selections.newest_anchor().head();
2966 self.push_to_selection_history();
2967
2968 let (changed, result) = self.selections.change_with(cx, change);
2969
2970 if changed {
2971 if let Some(autoscroll) = autoscroll {
2972 self.request_autoscroll(autoscroll, cx);
2973 }
2974 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2975
2976 if self.should_open_signature_help_automatically(
2977 &old_cursor_position,
2978 self.signature_help_state.backspace_pressed(),
2979 cx,
2980 ) {
2981 self.show_signature_help(&ShowSignatureHelp, window, cx);
2982 }
2983 self.signature_help_state.set_backspace_pressed(false);
2984 }
2985
2986 result
2987 }
2988
2989 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2990 where
2991 I: IntoIterator<Item = (Range<S>, T)>,
2992 S: ToOffset,
2993 T: Into<Arc<str>>,
2994 {
2995 if self.read_only(cx) {
2996 return;
2997 }
2998
2999 self.buffer
3000 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3001 }
3002
3003 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3004 where
3005 I: IntoIterator<Item = (Range<S>, T)>,
3006 S: ToOffset,
3007 T: Into<Arc<str>>,
3008 {
3009 if self.read_only(cx) {
3010 return;
3011 }
3012
3013 self.buffer.update(cx, |buffer, cx| {
3014 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3015 });
3016 }
3017
3018 pub fn edit_with_block_indent<I, S, T>(
3019 &mut self,
3020 edits: I,
3021 original_indent_columns: Vec<Option<u32>>,
3022 cx: &mut Context<Self>,
3023 ) where
3024 I: IntoIterator<Item = (Range<S>, T)>,
3025 S: ToOffset,
3026 T: Into<Arc<str>>,
3027 {
3028 if self.read_only(cx) {
3029 return;
3030 }
3031
3032 self.buffer.update(cx, |buffer, cx| {
3033 buffer.edit(
3034 edits,
3035 Some(AutoindentMode::Block {
3036 original_indent_columns,
3037 }),
3038 cx,
3039 )
3040 });
3041 }
3042
3043 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3044 self.hide_context_menu(window, cx);
3045
3046 match phase {
3047 SelectPhase::Begin {
3048 position,
3049 add,
3050 click_count,
3051 } => self.begin_selection(position, add, click_count, window, cx),
3052 SelectPhase::BeginColumnar {
3053 position,
3054 goal_column,
3055 reset,
3056 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3057 SelectPhase::Extend {
3058 position,
3059 click_count,
3060 } => self.extend_selection(position, click_count, window, cx),
3061 SelectPhase::Update {
3062 position,
3063 goal_column,
3064 scroll_delta,
3065 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3066 SelectPhase::End => self.end_selection(window, cx),
3067 }
3068 }
3069
3070 fn extend_selection(
3071 &mut self,
3072 position: DisplayPoint,
3073 click_count: usize,
3074 window: &mut Window,
3075 cx: &mut Context<Self>,
3076 ) {
3077 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3078 let tail = self.selections.newest::<usize>(cx).tail();
3079 self.begin_selection(position, false, click_count, window, cx);
3080
3081 let position = position.to_offset(&display_map, Bias::Left);
3082 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3083
3084 let mut pending_selection = self
3085 .selections
3086 .pending_anchor()
3087 .expect("extend_selection not called with pending selection");
3088 if position >= tail {
3089 pending_selection.start = tail_anchor;
3090 } else {
3091 pending_selection.end = tail_anchor;
3092 pending_selection.reversed = true;
3093 }
3094
3095 let mut pending_mode = self.selections.pending_mode().unwrap();
3096 match &mut pending_mode {
3097 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3098 _ => {}
3099 }
3100
3101 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3102
3103 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3104 s.set_pending(pending_selection, pending_mode)
3105 });
3106 }
3107
3108 fn begin_selection(
3109 &mut self,
3110 position: DisplayPoint,
3111 add: bool,
3112 click_count: usize,
3113 window: &mut Window,
3114 cx: &mut Context<Self>,
3115 ) {
3116 if !self.focus_handle.is_focused(window) {
3117 self.last_focused_descendant = None;
3118 window.focus(&self.focus_handle);
3119 }
3120
3121 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3122 let buffer = &display_map.buffer_snapshot;
3123 let position = display_map.clip_point(position, Bias::Left);
3124
3125 let start;
3126 let end;
3127 let mode;
3128 let mut auto_scroll;
3129 match click_count {
3130 1 => {
3131 start = buffer.anchor_before(position.to_point(&display_map));
3132 end = start;
3133 mode = SelectMode::Character;
3134 auto_scroll = true;
3135 }
3136 2 => {
3137 let range = movement::surrounding_word(&display_map, position);
3138 start = buffer.anchor_before(range.start.to_point(&display_map));
3139 end = buffer.anchor_before(range.end.to_point(&display_map));
3140 mode = SelectMode::Word(start..end);
3141 auto_scroll = true;
3142 }
3143 3 => {
3144 let position = display_map
3145 .clip_point(position, Bias::Left)
3146 .to_point(&display_map);
3147 let line_start = display_map.prev_line_boundary(position).0;
3148 let next_line_start = buffer.clip_point(
3149 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3150 Bias::Left,
3151 );
3152 start = buffer.anchor_before(line_start);
3153 end = buffer.anchor_before(next_line_start);
3154 mode = SelectMode::Line(start..end);
3155 auto_scroll = true;
3156 }
3157 _ => {
3158 start = buffer.anchor_before(0);
3159 end = buffer.anchor_before(buffer.len());
3160 mode = SelectMode::All;
3161 auto_scroll = false;
3162 }
3163 }
3164 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3165
3166 let point_to_delete: Option<usize> = {
3167 let selected_points: Vec<Selection<Point>> =
3168 self.selections.disjoint_in_range(start..end, cx);
3169
3170 if !add || click_count > 1 {
3171 None
3172 } else if !selected_points.is_empty() {
3173 Some(selected_points[0].id)
3174 } else {
3175 let clicked_point_already_selected =
3176 self.selections.disjoint.iter().find(|selection| {
3177 selection.start.to_point(buffer) == start.to_point(buffer)
3178 || selection.end.to_point(buffer) == end.to_point(buffer)
3179 });
3180
3181 clicked_point_already_selected.map(|selection| selection.id)
3182 }
3183 };
3184
3185 let selections_count = self.selections.count();
3186
3187 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3188 if let Some(point_to_delete) = point_to_delete {
3189 s.delete(point_to_delete);
3190
3191 if selections_count == 1 {
3192 s.set_pending_anchor_range(start..end, mode);
3193 }
3194 } else {
3195 if !add {
3196 s.clear_disjoint();
3197 }
3198
3199 s.set_pending_anchor_range(start..end, mode);
3200 }
3201 });
3202 }
3203
3204 fn begin_columnar_selection(
3205 &mut self,
3206 position: DisplayPoint,
3207 goal_column: u32,
3208 reset: bool,
3209 window: &mut Window,
3210 cx: &mut Context<Self>,
3211 ) {
3212 if !self.focus_handle.is_focused(window) {
3213 self.last_focused_descendant = None;
3214 window.focus(&self.focus_handle);
3215 }
3216
3217 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3218
3219 if reset {
3220 let pointer_position = display_map
3221 .buffer_snapshot
3222 .anchor_before(position.to_point(&display_map));
3223
3224 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3225 s.clear_disjoint();
3226 s.set_pending_anchor_range(
3227 pointer_position..pointer_position,
3228 SelectMode::Character,
3229 );
3230 });
3231 }
3232
3233 let tail = self.selections.newest::<Point>(cx).tail();
3234 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3235
3236 if !reset {
3237 self.select_columns(
3238 tail.to_display_point(&display_map),
3239 position,
3240 goal_column,
3241 &display_map,
3242 window,
3243 cx,
3244 );
3245 }
3246 }
3247
3248 fn update_selection(
3249 &mut self,
3250 position: DisplayPoint,
3251 goal_column: u32,
3252 scroll_delta: gpui::Point<f32>,
3253 window: &mut Window,
3254 cx: &mut Context<Self>,
3255 ) {
3256 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3257
3258 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3259 let tail = tail.to_display_point(&display_map);
3260 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3261 } else if let Some(mut pending) = self.selections.pending_anchor() {
3262 let buffer = self.buffer.read(cx).snapshot(cx);
3263 let head;
3264 let tail;
3265 let mode = self.selections.pending_mode().unwrap();
3266 match &mode {
3267 SelectMode::Character => {
3268 head = position.to_point(&display_map);
3269 tail = pending.tail().to_point(&buffer);
3270 }
3271 SelectMode::Word(original_range) => {
3272 let original_display_range = original_range.start.to_display_point(&display_map)
3273 ..original_range.end.to_display_point(&display_map);
3274 let original_buffer_range = original_display_range.start.to_point(&display_map)
3275 ..original_display_range.end.to_point(&display_map);
3276 if movement::is_inside_word(&display_map, position)
3277 || original_display_range.contains(&position)
3278 {
3279 let word_range = movement::surrounding_word(&display_map, position);
3280 if word_range.start < original_display_range.start {
3281 head = word_range.start.to_point(&display_map);
3282 } else {
3283 head = word_range.end.to_point(&display_map);
3284 }
3285 } else {
3286 head = position.to_point(&display_map);
3287 }
3288
3289 if head <= original_buffer_range.start {
3290 tail = original_buffer_range.end;
3291 } else {
3292 tail = original_buffer_range.start;
3293 }
3294 }
3295 SelectMode::Line(original_range) => {
3296 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3297
3298 let position = display_map
3299 .clip_point(position, Bias::Left)
3300 .to_point(&display_map);
3301 let line_start = display_map.prev_line_boundary(position).0;
3302 let next_line_start = buffer.clip_point(
3303 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3304 Bias::Left,
3305 );
3306
3307 if line_start < original_range.start {
3308 head = line_start
3309 } else {
3310 head = next_line_start
3311 }
3312
3313 if head <= original_range.start {
3314 tail = original_range.end;
3315 } else {
3316 tail = original_range.start;
3317 }
3318 }
3319 SelectMode::All => {
3320 return;
3321 }
3322 };
3323
3324 if head < tail {
3325 pending.start = buffer.anchor_before(head);
3326 pending.end = buffer.anchor_before(tail);
3327 pending.reversed = true;
3328 } else {
3329 pending.start = buffer.anchor_before(tail);
3330 pending.end = buffer.anchor_before(head);
3331 pending.reversed = false;
3332 }
3333
3334 self.change_selections(None, window, cx, |s| {
3335 s.set_pending(pending, mode);
3336 });
3337 } else {
3338 log::error!("update_selection dispatched with no pending selection");
3339 return;
3340 }
3341
3342 self.apply_scroll_delta(scroll_delta, window, cx);
3343 cx.notify();
3344 }
3345
3346 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3347 self.columnar_selection_tail.take();
3348 if self.selections.pending_anchor().is_some() {
3349 let selections = self.selections.all::<usize>(cx);
3350 self.change_selections(None, window, cx, |s| {
3351 s.select(selections);
3352 s.clear_pending();
3353 });
3354 }
3355 }
3356
3357 fn select_columns(
3358 &mut self,
3359 tail: DisplayPoint,
3360 head: DisplayPoint,
3361 goal_column: u32,
3362 display_map: &DisplaySnapshot,
3363 window: &mut Window,
3364 cx: &mut Context<Self>,
3365 ) {
3366 let start_row = cmp::min(tail.row(), head.row());
3367 let end_row = cmp::max(tail.row(), head.row());
3368 let start_column = cmp::min(tail.column(), goal_column);
3369 let end_column = cmp::max(tail.column(), goal_column);
3370 let reversed = start_column < tail.column();
3371
3372 let selection_ranges = (start_row.0..=end_row.0)
3373 .map(DisplayRow)
3374 .filter_map(|row| {
3375 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3376 let start = display_map
3377 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3378 .to_point(display_map);
3379 let end = display_map
3380 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3381 .to_point(display_map);
3382 if reversed {
3383 Some(end..start)
3384 } else {
3385 Some(start..end)
3386 }
3387 } else {
3388 None
3389 }
3390 })
3391 .collect::<Vec<_>>();
3392
3393 self.change_selections(None, window, cx, |s| {
3394 s.select_ranges(selection_ranges);
3395 });
3396 cx.notify();
3397 }
3398
3399 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3400 self.selections
3401 .all_adjusted(cx)
3402 .iter()
3403 .any(|selection| !selection.is_empty())
3404 }
3405
3406 pub fn has_pending_nonempty_selection(&self) -> bool {
3407 let pending_nonempty_selection = match self.selections.pending_anchor() {
3408 Some(Selection { start, end, .. }) => start != end,
3409 None => false,
3410 };
3411
3412 pending_nonempty_selection
3413 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3414 }
3415
3416 pub fn has_pending_selection(&self) -> bool {
3417 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3418 }
3419
3420 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3421 self.selection_mark_mode = false;
3422
3423 if self.clear_expanded_diff_hunks(cx) {
3424 cx.notify();
3425 return;
3426 }
3427 if self.dismiss_menus_and_popups(true, window, cx) {
3428 return;
3429 }
3430
3431 if self.mode.is_full()
3432 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3433 {
3434 return;
3435 }
3436
3437 cx.propagate();
3438 }
3439
3440 pub fn dismiss_menus_and_popups(
3441 &mut self,
3442 is_user_requested: bool,
3443 window: &mut Window,
3444 cx: &mut Context<Self>,
3445 ) -> bool {
3446 if self.take_rename(false, window, cx).is_some() {
3447 return true;
3448 }
3449
3450 if hide_hover(self, cx) {
3451 return true;
3452 }
3453
3454 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3455 return true;
3456 }
3457
3458 if self.hide_context_menu(window, cx).is_some() {
3459 return true;
3460 }
3461
3462 if self.mouse_context_menu.take().is_some() {
3463 return true;
3464 }
3465
3466 if is_user_requested && self.discard_inline_completion(true, cx) {
3467 return true;
3468 }
3469
3470 if self.snippet_stack.pop().is_some() {
3471 return true;
3472 }
3473
3474 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3475 self.dismiss_diagnostics(cx);
3476 return true;
3477 }
3478
3479 false
3480 }
3481
3482 fn linked_editing_ranges_for(
3483 &self,
3484 selection: Range<text::Anchor>,
3485 cx: &App,
3486 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3487 if self.linked_edit_ranges.is_empty() {
3488 return None;
3489 }
3490 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3491 selection.end.buffer_id.and_then(|end_buffer_id| {
3492 if selection.start.buffer_id != Some(end_buffer_id) {
3493 return None;
3494 }
3495 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3496 let snapshot = buffer.read(cx).snapshot();
3497 self.linked_edit_ranges
3498 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3499 .map(|ranges| (ranges, snapshot, buffer))
3500 })?;
3501 use text::ToOffset as TO;
3502 // find offset from the start of current range to current cursor position
3503 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3504
3505 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3506 let start_difference = start_offset - start_byte_offset;
3507 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3508 let end_difference = end_offset - start_byte_offset;
3509 // Current range has associated linked ranges.
3510 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3511 for range in linked_ranges.iter() {
3512 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3513 let end_offset = start_offset + end_difference;
3514 let start_offset = start_offset + start_difference;
3515 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3516 continue;
3517 }
3518 if self.selections.disjoint_anchor_ranges().any(|s| {
3519 if s.start.buffer_id != selection.start.buffer_id
3520 || s.end.buffer_id != selection.end.buffer_id
3521 {
3522 return false;
3523 }
3524 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3525 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3526 }) {
3527 continue;
3528 }
3529 let start = buffer_snapshot.anchor_after(start_offset);
3530 let end = buffer_snapshot.anchor_after(end_offset);
3531 linked_edits
3532 .entry(buffer.clone())
3533 .or_default()
3534 .push(start..end);
3535 }
3536 Some(linked_edits)
3537 }
3538
3539 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3540 let text: Arc<str> = text.into();
3541
3542 if self.read_only(cx) {
3543 return;
3544 }
3545
3546 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3547
3548 let selections = self.selections.all_adjusted(cx);
3549 let mut bracket_inserted = false;
3550 let mut edits = Vec::new();
3551 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3552 let mut new_selections = Vec::with_capacity(selections.len());
3553 let mut new_autoclose_regions = Vec::new();
3554 let snapshot = self.buffer.read(cx).read(cx);
3555 let mut clear_linked_edit_ranges = false;
3556
3557 for (selection, autoclose_region) in
3558 self.selections_with_autoclose_regions(selections, &snapshot)
3559 {
3560 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3561 // Determine if the inserted text matches the opening or closing
3562 // bracket of any of this language's bracket pairs.
3563 let mut bracket_pair = None;
3564 let mut is_bracket_pair_start = false;
3565 let mut is_bracket_pair_end = false;
3566 if !text.is_empty() {
3567 let mut bracket_pair_matching_end = None;
3568 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3569 // and they are removing the character that triggered IME popup.
3570 for (pair, enabled) in scope.brackets() {
3571 if !pair.close && !pair.surround {
3572 continue;
3573 }
3574
3575 if enabled && pair.start.ends_with(text.as_ref()) {
3576 let prefix_len = pair.start.len() - text.len();
3577 let preceding_text_matches_prefix = prefix_len == 0
3578 || (selection.start.column >= (prefix_len as u32)
3579 && snapshot.contains_str_at(
3580 Point::new(
3581 selection.start.row,
3582 selection.start.column - (prefix_len as u32),
3583 ),
3584 &pair.start[..prefix_len],
3585 ));
3586 if preceding_text_matches_prefix {
3587 bracket_pair = Some(pair.clone());
3588 is_bracket_pair_start = true;
3589 break;
3590 }
3591 }
3592 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3593 {
3594 // take first bracket pair matching end, but don't break in case a later bracket
3595 // pair matches start
3596 bracket_pair_matching_end = Some(pair.clone());
3597 }
3598 }
3599 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3600 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3601 is_bracket_pair_end = true;
3602 }
3603 }
3604
3605 if let Some(bracket_pair) = bracket_pair {
3606 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3607 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3608 let auto_surround =
3609 self.use_auto_surround && snapshot_settings.use_auto_surround;
3610 if selection.is_empty() {
3611 if is_bracket_pair_start {
3612 // If the inserted text is a suffix of an opening bracket and the
3613 // selection is preceded by the rest of the opening bracket, then
3614 // insert the closing bracket.
3615 let following_text_allows_autoclose = snapshot
3616 .chars_at(selection.start)
3617 .next()
3618 .map_or(true, |c| scope.should_autoclose_before(c));
3619
3620 let preceding_text_allows_autoclose = selection.start.column == 0
3621 || snapshot.reversed_chars_at(selection.start).next().map_or(
3622 true,
3623 |c| {
3624 bracket_pair.start != bracket_pair.end
3625 || !snapshot
3626 .char_classifier_at(selection.start)
3627 .is_word(c)
3628 },
3629 );
3630
3631 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3632 && bracket_pair.start.len() == 1
3633 {
3634 let target = bracket_pair.start.chars().next().unwrap();
3635 let current_line_count = snapshot
3636 .reversed_chars_at(selection.start)
3637 .take_while(|&c| c != '\n')
3638 .filter(|&c| c == target)
3639 .count();
3640 current_line_count % 2 == 1
3641 } else {
3642 false
3643 };
3644
3645 if autoclose
3646 && bracket_pair.close
3647 && following_text_allows_autoclose
3648 && preceding_text_allows_autoclose
3649 && !is_closing_quote
3650 {
3651 let anchor = snapshot.anchor_before(selection.end);
3652 new_selections.push((selection.map(|_| anchor), text.len()));
3653 new_autoclose_regions.push((
3654 anchor,
3655 text.len(),
3656 selection.id,
3657 bracket_pair.clone(),
3658 ));
3659 edits.push((
3660 selection.range(),
3661 format!("{}{}", text, bracket_pair.end).into(),
3662 ));
3663 bracket_inserted = true;
3664 continue;
3665 }
3666 }
3667
3668 if let Some(region) = autoclose_region {
3669 // If the selection is followed by an auto-inserted closing bracket,
3670 // then don't insert that closing bracket again; just move the selection
3671 // past the closing bracket.
3672 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3673 && text.as_ref() == region.pair.end.as_str();
3674 if should_skip {
3675 let anchor = snapshot.anchor_after(selection.end);
3676 new_selections
3677 .push((selection.map(|_| anchor), region.pair.end.len()));
3678 continue;
3679 }
3680 }
3681
3682 let always_treat_brackets_as_autoclosed = snapshot
3683 .language_settings_at(selection.start, cx)
3684 .always_treat_brackets_as_autoclosed;
3685 if always_treat_brackets_as_autoclosed
3686 && is_bracket_pair_end
3687 && snapshot.contains_str_at(selection.end, text.as_ref())
3688 {
3689 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3690 // and the inserted text is a closing bracket and the selection is followed
3691 // by the closing bracket then move the selection past the closing bracket.
3692 let anchor = snapshot.anchor_after(selection.end);
3693 new_selections.push((selection.map(|_| anchor), text.len()));
3694 continue;
3695 }
3696 }
3697 // If an opening bracket is 1 character long and is typed while
3698 // text is selected, then surround that text with the bracket pair.
3699 else if auto_surround
3700 && bracket_pair.surround
3701 && is_bracket_pair_start
3702 && bracket_pair.start.chars().count() == 1
3703 {
3704 edits.push((selection.start..selection.start, text.clone()));
3705 edits.push((
3706 selection.end..selection.end,
3707 bracket_pair.end.as_str().into(),
3708 ));
3709 bracket_inserted = true;
3710 new_selections.push((
3711 Selection {
3712 id: selection.id,
3713 start: snapshot.anchor_after(selection.start),
3714 end: snapshot.anchor_before(selection.end),
3715 reversed: selection.reversed,
3716 goal: selection.goal,
3717 },
3718 0,
3719 ));
3720 continue;
3721 }
3722 }
3723 }
3724
3725 if self.auto_replace_emoji_shortcode
3726 && selection.is_empty()
3727 && text.as_ref().ends_with(':')
3728 {
3729 if let Some(possible_emoji_short_code) =
3730 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3731 {
3732 if !possible_emoji_short_code.is_empty() {
3733 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3734 let emoji_shortcode_start = Point::new(
3735 selection.start.row,
3736 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3737 );
3738
3739 // Remove shortcode from buffer
3740 edits.push((
3741 emoji_shortcode_start..selection.start,
3742 "".to_string().into(),
3743 ));
3744 new_selections.push((
3745 Selection {
3746 id: selection.id,
3747 start: snapshot.anchor_after(emoji_shortcode_start),
3748 end: snapshot.anchor_before(selection.start),
3749 reversed: selection.reversed,
3750 goal: selection.goal,
3751 },
3752 0,
3753 ));
3754
3755 // Insert emoji
3756 let selection_start_anchor = snapshot.anchor_after(selection.start);
3757 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3758 edits.push((selection.start..selection.end, emoji.to_string().into()));
3759
3760 continue;
3761 }
3762 }
3763 }
3764 }
3765
3766 // If not handling any auto-close operation, then just replace the selected
3767 // text with the given input and move the selection to the end of the
3768 // newly inserted text.
3769 let anchor = snapshot.anchor_after(selection.end);
3770 if !self.linked_edit_ranges.is_empty() {
3771 let start_anchor = snapshot.anchor_before(selection.start);
3772
3773 let is_word_char = text.chars().next().map_or(true, |char| {
3774 let classifier = snapshot
3775 .char_classifier_at(start_anchor.to_offset(&snapshot))
3776 .ignore_punctuation(true);
3777 classifier.is_word(char)
3778 });
3779
3780 if is_word_char {
3781 if let Some(ranges) = self
3782 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3783 {
3784 for (buffer, edits) in ranges {
3785 linked_edits
3786 .entry(buffer.clone())
3787 .or_default()
3788 .extend(edits.into_iter().map(|range| (range, text.clone())));
3789 }
3790 }
3791 } else {
3792 clear_linked_edit_ranges = true;
3793 }
3794 }
3795
3796 new_selections.push((selection.map(|_| anchor), 0));
3797 edits.push((selection.start..selection.end, text.clone()));
3798 }
3799
3800 drop(snapshot);
3801
3802 self.transact(window, cx, |this, window, cx| {
3803 if clear_linked_edit_ranges {
3804 this.linked_edit_ranges.clear();
3805 }
3806 let initial_buffer_versions =
3807 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3808
3809 this.buffer.update(cx, |buffer, cx| {
3810 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3811 });
3812 for (buffer, edits) in linked_edits {
3813 buffer.update(cx, |buffer, cx| {
3814 let snapshot = buffer.snapshot();
3815 let edits = edits
3816 .into_iter()
3817 .map(|(range, text)| {
3818 use text::ToPoint as TP;
3819 let end_point = TP::to_point(&range.end, &snapshot);
3820 let start_point = TP::to_point(&range.start, &snapshot);
3821 (start_point..end_point, text)
3822 })
3823 .sorted_by_key(|(range, _)| range.start);
3824 buffer.edit(edits, None, cx);
3825 })
3826 }
3827 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3828 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3829 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3830 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3831 .zip(new_selection_deltas)
3832 .map(|(selection, delta)| Selection {
3833 id: selection.id,
3834 start: selection.start + delta,
3835 end: selection.end + delta,
3836 reversed: selection.reversed,
3837 goal: SelectionGoal::None,
3838 })
3839 .collect::<Vec<_>>();
3840
3841 let mut i = 0;
3842 for (position, delta, selection_id, pair) in new_autoclose_regions {
3843 let position = position.to_offset(&map.buffer_snapshot) + delta;
3844 let start = map.buffer_snapshot.anchor_before(position);
3845 let end = map.buffer_snapshot.anchor_after(position);
3846 while let Some(existing_state) = this.autoclose_regions.get(i) {
3847 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3848 Ordering::Less => i += 1,
3849 Ordering::Greater => break,
3850 Ordering::Equal => {
3851 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3852 Ordering::Less => i += 1,
3853 Ordering::Equal => break,
3854 Ordering::Greater => break,
3855 }
3856 }
3857 }
3858 }
3859 this.autoclose_regions.insert(
3860 i,
3861 AutocloseRegion {
3862 selection_id,
3863 range: start..end,
3864 pair,
3865 },
3866 );
3867 }
3868
3869 let had_active_inline_completion = this.has_active_inline_completion();
3870 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3871 s.select(new_selections)
3872 });
3873
3874 if !bracket_inserted {
3875 if let Some(on_type_format_task) =
3876 this.trigger_on_type_formatting(text.to_string(), window, cx)
3877 {
3878 on_type_format_task.detach_and_log_err(cx);
3879 }
3880 }
3881
3882 let editor_settings = EditorSettings::get_global(cx);
3883 if bracket_inserted
3884 && (editor_settings.auto_signature_help
3885 || editor_settings.show_signature_help_after_edits)
3886 {
3887 this.show_signature_help(&ShowSignatureHelp, window, cx);
3888 }
3889
3890 let trigger_in_words =
3891 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3892 if this.hard_wrap.is_some() {
3893 let latest: Range<Point> = this.selections.newest(cx).range();
3894 if latest.is_empty()
3895 && this
3896 .buffer()
3897 .read(cx)
3898 .snapshot(cx)
3899 .line_len(MultiBufferRow(latest.start.row))
3900 == latest.start.column
3901 {
3902 this.rewrap_impl(
3903 RewrapOptions {
3904 override_language_settings: true,
3905 preserve_existing_whitespace: true,
3906 },
3907 cx,
3908 )
3909 }
3910 }
3911 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3912 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3913 this.refresh_inline_completion(true, false, window, cx);
3914 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3915 });
3916 }
3917
3918 fn find_possible_emoji_shortcode_at_position(
3919 snapshot: &MultiBufferSnapshot,
3920 position: Point,
3921 ) -> Option<String> {
3922 let mut chars = Vec::new();
3923 let mut found_colon = false;
3924 for char in snapshot.reversed_chars_at(position).take(100) {
3925 // Found a possible emoji shortcode in the middle of the buffer
3926 if found_colon {
3927 if char.is_whitespace() {
3928 chars.reverse();
3929 return Some(chars.iter().collect());
3930 }
3931 // If the previous character is not a whitespace, we are in the middle of a word
3932 // and we only want to complete the shortcode if the word is made up of other emojis
3933 let mut containing_word = String::new();
3934 for ch in snapshot
3935 .reversed_chars_at(position)
3936 .skip(chars.len() + 1)
3937 .take(100)
3938 {
3939 if ch.is_whitespace() {
3940 break;
3941 }
3942 containing_word.push(ch);
3943 }
3944 let containing_word = containing_word.chars().rev().collect::<String>();
3945 if util::word_consists_of_emojis(containing_word.as_str()) {
3946 chars.reverse();
3947 return Some(chars.iter().collect());
3948 }
3949 }
3950
3951 if char.is_whitespace() || !char.is_ascii() {
3952 return None;
3953 }
3954 if char == ':' {
3955 found_colon = true;
3956 } else {
3957 chars.push(char);
3958 }
3959 }
3960 // Found a possible emoji shortcode at the beginning of the buffer
3961 chars.reverse();
3962 Some(chars.iter().collect())
3963 }
3964
3965 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3966 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3967 self.transact(window, cx, |this, window, cx| {
3968 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3969 let selections = this.selections.all::<usize>(cx);
3970 let multi_buffer = this.buffer.read(cx);
3971 let buffer = multi_buffer.snapshot(cx);
3972 selections
3973 .iter()
3974 .map(|selection| {
3975 let start_point = selection.start.to_point(&buffer);
3976 let mut existing_indent =
3977 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3978 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3979 let start = selection.start;
3980 let end = selection.end;
3981 let selection_is_empty = start == end;
3982 let language_scope = buffer.language_scope_at(start);
3983 let (
3984 comment_delimiter,
3985 doc_delimiter,
3986 insert_extra_newline,
3987 indent_on_newline,
3988 indent_on_extra_newline,
3989 ) = if let Some(language) = &language_scope {
3990 let mut insert_extra_newline =
3991 insert_extra_newline_brackets(&buffer, start..end, language)
3992 || insert_extra_newline_tree_sitter(&buffer, start..end);
3993
3994 // Comment extension on newline is allowed only for cursor selections
3995 let comment_delimiter = maybe!({
3996 if !selection_is_empty {
3997 return None;
3998 }
3999
4000 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4001 return None;
4002 }
4003
4004 let delimiters = language.line_comment_prefixes();
4005 let max_len_of_delimiter =
4006 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4007 let (snapshot, range) =
4008 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4009
4010 let num_of_whitespaces = snapshot
4011 .chars_for_range(range.clone())
4012 .take_while(|c| c.is_whitespace())
4013 .count();
4014 let comment_candidate = snapshot
4015 .chars_for_range(range)
4016 .skip(num_of_whitespaces)
4017 .take(max_len_of_delimiter)
4018 .collect::<String>();
4019 let (delimiter, trimmed_len) = delimiters
4020 .iter()
4021 .filter_map(|delimiter| {
4022 let prefix = delimiter.trim_end();
4023 if comment_candidate.starts_with(prefix) {
4024 Some((delimiter, prefix.len()))
4025 } else {
4026 None
4027 }
4028 })
4029 .max_by_key(|(_, len)| *len)?;
4030
4031 let cursor_is_placed_after_comment_marker =
4032 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4033 if cursor_is_placed_after_comment_marker {
4034 Some(delimiter.clone())
4035 } else {
4036 None
4037 }
4038 });
4039
4040 let mut indent_on_newline = IndentSize::spaces(0);
4041 let mut indent_on_extra_newline = IndentSize::spaces(0);
4042
4043 let doc_delimiter = maybe!({
4044 if !selection_is_empty {
4045 return None;
4046 }
4047
4048 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4049 return None;
4050 }
4051
4052 let DocumentationConfig {
4053 start: start_tag,
4054 end: end_tag,
4055 prefix: delimiter,
4056 tab_size: len,
4057 } = language.documentation()?;
4058
4059 let is_within_block_comment = buffer
4060 .language_scope_at(start_point)
4061 .is_some_and(|scope| scope.override_name() == Some("comment"));
4062 if !is_within_block_comment {
4063 return None;
4064 }
4065
4066 let (snapshot, range) =
4067 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4068
4069 let num_of_whitespaces = snapshot
4070 .chars_for_range(range.clone())
4071 .take_while(|c| c.is_whitespace())
4072 .count();
4073
4074 // 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.
4075 let column = start_point.column;
4076 let cursor_is_after_start_tag = {
4077 let start_tag_len = start_tag.len();
4078 let start_tag_line = snapshot
4079 .chars_for_range(range.clone())
4080 .skip(num_of_whitespaces)
4081 .take(start_tag_len)
4082 .collect::<String>();
4083 if start_tag_line.starts_with(start_tag.as_ref()) {
4084 num_of_whitespaces + start_tag_len <= column as usize
4085 } else {
4086 false
4087 }
4088 };
4089
4090 let cursor_is_after_delimiter = {
4091 let delimiter_trim = delimiter.trim_end();
4092 let delimiter_line = snapshot
4093 .chars_for_range(range.clone())
4094 .skip(num_of_whitespaces)
4095 .take(delimiter_trim.len())
4096 .collect::<String>();
4097 if delimiter_line.starts_with(delimiter_trim) {
4098 num_of_whitespaces + delimiter_trim.len() <= column as usize
4099 } else {
4100 false
4101 }
4102 };
4103
4104 let cursor_is_before_end_tag_if_exists = {
4105 let mut char_position = 0u32;
4106 let mut end_tag_offset = None;
4107
4108 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4109 if let Some(byte_pos) = chunk.find(&**end_tag) {
4110 let chars_before_match =
4111 chunk[..byte_pos].chars().count() as u32;
4112 end_tag_offset =
4113 Some(char_position + chars_before_match);
4114 break 'outer;
4115 }
4116 char_position += chunk.chars().count() as u32;
4117 }
4118
4119 if let Some(end_tag_offset) = end_tag_offset {
4120 let cursor_is_before_end_tag = column <= end_tag_offset;
4121 if cursor_is_after_start_tag {
4122 if cursor_is_before_end_tag {
4123 insert_extra_newline = true;
4124 }
4125 let cursor_is_at_start_of_end_tag =
4126 column == end_tag_offset;
4127 if cursor_is_at_start_of_end_tag {
4128 indent_on_extra_newline.len = (*len).into();
4129 }
4130 }
4131 cursor_is_before_end_tag
4132 } else {
4133 true
4134 }
4135 };
4136
4137 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4138 && cursor_is_before_end_tag_if_exists
4139 {
4140 if cursor_is_after_start_tag {
4141 indent_on_newline.len = (*len).into();
4142 }
4143 Some(delimiter.clone())
4144 } else {
4145 None
4146 }
4147 });
4148
4149 (
4150 comment_delimiter,
4151 doc_delimiter,
4152 insert_extra_newline,
4153 indent_on_newline,
4154 indent_on_extra_newline,
4155 )
4156 } else {
4157 (
4158 None,
4159 None,
4160 false,
4161 IndentSize::default(),
4162 IndentSize::default(),
4163 )
4164 };
4165
4166 let prevent_auto_indent = doc_delimiter.is_some();
4167 let delimiter = comment_delimiter.or(doc_delimiter);
4168
4169 let capacity_for_delimiter =
4170 delimiter.as_deref().map(str::len).unwrap_or_default();
4171 let mut new_text = String::with_capacity(
4172 1 + capacity_for_delimiter
4173 + existing_indent.len as usize
4174 + indent_on_newline.len as usize
4175 + indent_on_extra_newline.len as usize,
4176 );
4177 new_text.push('\n');
4178 new_text.extend(existing_indent.chars());
4179 new_text.extend(indent_on_newline.chars());
4180
4181 if let Some(delimiter) = &delimiter {
4182 new_text.push_str(delimiter);
4183 }
4184
4185 if insert_extra_newline {
4186 new_text.push('\n');
4187 new_text.extend(existing_indent.chars());
4188 new_text.extend(indent_on_extra_newline.chars());
4189 }
4190
4191 let anchor = buffer.anchor_after(end);
4192 let new_selection = selection.map(|_| anchor);
4193 (
4194 ((start..end, new_text), prevent_auto_indent),
4195 (insert_extra_newline, new_selection),
4196 )
4197 })
4198 .unzip()
4199 };
4200
4201 let mut auto_indent_edits = Vec::new();
4202 let mut edits = Vec::new();
4203 for (edit, prevent_auto_indent) in edits_with_flags {
4204 if prevent_auto_indent {
4205 edits.push(edit);
4206 } else {
4207 auto_indent_edits.push(edit);
4208 }
4209 }
4210 if !edits.is_empty() {
4211 this.edit(edits, cx);
4212 }
4213 if !auto_indent_edits.is_empty() {
4214 this.edit_with_autoindent(auto_indent_edits, cx);
4215 }
4216
4217 let buffer = this.buffer.read(cx).snapshot(cx);
4218 let new_selections = selection_info
4219 .into_iter()
4220 .map(|(extra_newline_inserted, new_selection)| {
4221 let mut cursor = new_selection.end.to_point(&buffer);
4222 if extra_newline_inserted {
4223 cursor.row -= 1;
4224 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4225 }
4226 new_selection.map(|_| cursor)
4227 })
4228 .collect();
4229
4230 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4231 s.select(new_selections)
4232 });
4233 this.refresh_inline_completion(true, false, window, cx);
4234 });
4235 }
4236
4237 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4238 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4239
4240 let buffer = self.buffer.read(cx);
4241 let snapshot = buffer.snapshot(cx);
4242
4243 let mut edits = Vec::new();
4244 let mut rows = Vec::new();
4245
4246 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4247 let cursor = selection.head();
4248 let row = cursor.row;
4249
4250 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4251
4252 let newline = "\n".to_string();
4253 edits.push((start_of_line..start_of_line, newline));
4254
4255 rows.push(row + rows_inserted as u32);
4256 }
4257
4258 self.transact(window, cx, |editor, window, cx| {
4259 editor.edit(edits, cx);
4260
4261 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4262 let mut index = 0;
4263 s.move_cursors_with(|map, _, _| {
4264 let row = rows[index];
4265 index += 1;
4266
4267 let point = Point::new(row, 0);
4268 let boundary = map.next_line_boundary(point).1;
4269 let clipped = map.clip_point(boundary, Bias::Left);
4270
4271 (clipped, SelectionGoal::None)
4272 });
4273 });
4274
4275 let mut indent_edits = Vec::new();
4276 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4277 for row in rows {
4278 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4279 for (row, indent) in indents {
4280 if indent.len == 0 {
4281 continue;
4282 }
4283
4284 let text = match indent.kind {
4285 IndentKind::Space => " ".repeat(indent.len as usize),
4286 IndentKind::Tab => "\t".repeat(indent.len as usize),
4287 };
4288 let point = Point::new(row.0, 0);
4289 indent_edits.push((point..point, text));
4290 }
4291 }
4292 editor.edit(indent_edits, cx);
4293 });
4294 }
4295
4296 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4297 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4298
4299 let buffer = self.buffer.read(cx);
4300 let snapshot = buffer.snapshot(cx);
4301
4302 let mut edits = Vec::new();
4303 let mut rows = Vec::new();
4304 let mut rows_inserted = 0;
4305
4306 for selection in self.selections.all_adjusted(cx) {
4307 let cursor = selection.head();
4308 let row = cursor.row;
4309
4310 let point = Point::new(row + 1, 0);
4311 let start_of_line = snapshot.clip_point(point, Bias::Left);
4312
4313 let newline = "\n".to_string();
4314 edits.push((start_of_line..start_of_line, newline));
4315
4316 rows_inserted += 1;
4317 rows.push(row + rows_inserted);
4318 }
4319
4320 self.transact(window, cx, |editor, window, cx| {
4321 editor.edit(edits, cx);
4322
4323 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4324 let mut index = 0;
4325 s.move_cursors_with(|map, _, _| {
4326 let row = rows[index];
4327 index += 1;
4328
4329 let point = Point::new(row, 0);
4330 let boundary = map.next_line_boundary(point).1;
4331 let clipped = map.clip_point(boundary, Bias::Left);
4332
4333 (clipped, SelectionGoal::None)
4334 });
4335 });
4336
4337 let mut indent_edits = Vec::new();
4338 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4339 for row in rows {
4340 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4341 for (row, indent) in indents {
4342 if indent.len == 0 {
4343 continue;
4344 }
4345
4346 let text = match indent.kind {
4347 IndentKind::Space => " ".repeat(indent.len as usize),
4348 IndentKind::Tab => "\t".repeat(indent.len as usize),
4349 };
4350 let point = Point::new(row.0, 0);
4351 indent_edits.push((point..point, text));
4352 }
4353 }
4354 editor.edit(indent_edits, cx);
4355 });
4356 }
4357
4358 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4359 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4360 original_indent_columns: Vec::new(),
4361 });
4362 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4363 }
4364
4365 fn insert_with_autoindent_mode(
4366 &mut self,
4367 text: &str,
4368 autoindent_mode: Option<AutoindentMode>,
4369 window: &mut Window,
4370 cx: &mut Context<Self>,
4371 ) {
4372 if self.read_only(cx) {
4373 return;
4374 }
4375
4376 let text: Arc<str> = text.into();
4377 self.transact(window, cx, |this, window, cx| {
4378 let old_selections = this.selections.all_adjusted(cx);
4379 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4380 let anchors = {
4381 let snapshot = buffer.read(cx);
4382 old_selections
4383 .iter()
4384 .map(|s| {
4385 let anchor = snapshot.anchor_after(s.head());
4386 s.map(|_| anchor)
4387 })
4388 .collect::<Vec<_>>()
4389 };
4390 buffer.edit(
4391 old_selections
4392 .iter()
4393 .map(|s| (s.start..s.end, text.clone())),
4394 autoindent_mode,
4395 cx,
4396 );
4397 anchors
4398 });
4399
4400 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4401 s.select_anchors(selection_anchors);
4402 });
4403
4404 cx.notify();
4405 });
4406 }
4407
4408 fn trigger_completion_on_input(
4409 &mut self,
4410 text: &str,
4411 trigger_in_words: bool,
4412 window: &mut Window,
4413 cx: &mut Context<Self>,
4414 ) {
4415 let ignore_completion_provider = self
4416 .context_menu
4417 .borrow()
4418 .as_ref()
4419 .map(|menu| match menu {
4420 CodeContextMenu::Completions(completions_menu) => {
4421 completions_menu.ignore_completion_provider
4422 }
4423 CodeContextMenu::CodeActions(_) => false,
4424 })
4425 .unwrap_or(false);
4426
4427 if ignore_completion_provider {
4428 self.show_word_completions(&ShowWordCompletions, window, cx);
4429 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4430 self.show_completions(
4431 &ShowCompletions {
4432 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4433 },
4434 window,
4435 cx,
4436 );
4437 } else {
4438 self.hide_context_menu(window, cx);
4439 }
4440 }
4441
4442 fn is_completion_trigger(
4443 &self,
4444 text: &str,
4445 trigger_in_words: bool,
4446 cx: &mut Context<Self>,
4447 ) -> bool {
4448 let position = self.selections.newest_anchor().head();
4449 let multibuffer = self.buffer.read(cx);
4450 let Some(buffer) = position
4451 .buffer_id
4452 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4453 else {
4454 return false;
4455 };
4456
4457 if let Some(completion_provider) = &self.completion_provider {
4458 completion_provider.is_completion_trigger(
4459 &buffer,
4460 position.text_anchor,
4461 text,
4462 trigger_in_words,
4463 cx,
4464 )
4465 } else {
4466 false
4467 }
4468 }
4469
4470 /// If any empty selections is touching the start of its innermost containing autoclose
4471 /// region, expand it to select the brackets.
4472 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4473 let selections = self.selections.all::<usize>(cx);
4474 let buffer = self.buffer.read(cx).read(cx);
4475 let new_selections = self
4476 .selections_with_autoclose_regions(selections, &buffer)
4477 .map(|(mut selection, region)| {
4478 if !selection.is_empty() {
4479 return selection;
4480 }
4481
4482 if let Some(region) = region {
4483 let mut range = region.range.to_offset(&buffer);
4484 if selection.start == range.start && range.start >= region.pair.start.len() {
4485 range.start -= region.pair.start.len();
4486 if buffer.contains_str_at(range.start, ®ion.pair.start)
4487 && buffer.contains_str_at(range.end, ®ion.pair.end)
4488 {
4489 range.end += region.pair.end.len();
4490 selection.start = range.start;
4491 selection.end = range.end;
4492
4493 return selection;
4494 }
4495 }
4496 }
4497
4498 let always_treat_brackets_as_autoclosed = buffer
4499 .language_settings_at(selection.start, cx)
4500 .always_treat_brackets_as_autoclosed;
4501
4502 if !always_treat_brackets_as_autoclosed {
4503 return selection;
4504 }
4505
4506 if let Some(scope) = buffer.language_scope_at(selection.start) {
4507 for (pair, enabled) in scope.brackets() {
4508 if !enabled || !pair.close {
4509 continue;
4510 }
4511
4512 if buffer.contains_str_at(selection.start, &pair.end) {
4513 let pair_start_len = pair.start.len();
4514 if buffer.contains_str_at(
4515 selection.start.saturating_sub(pair_start_len),
4516 &pair.start,
4517 ) {
4518 selection.start -= pair_start_len;
4519 selection.end += pair.end.len();
4520
4521 return selection;
4522 }
4523 }
4524 }
4525 }
4526
4527 selection
4528 })
4529 .collect();
4530
4531 drop(buffer);
4532 self.change_selections(None, window, cx, |selections| {
4533 selections.select(new_selections)
4534 });
4535 }
4536
4537 /// Iterate the given selections, and for each one, find the smallest surrounding
4538 /// autoclose region. This uses the ordering of the selections and the autoclose
4539 /// regions to avoid repeated comparisons.
4540 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4541 &'a self,
4542 selections: impl IntoIterator<Item = Selection<D>>,
4543 buffer: &'a MultiBufferSnapshot,
4544 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4545 let mut i = 0;
4546 let mut regions = self.autoclose_regions.as_slice();
4547 selections.into_iter().map(move |selection| {
4548 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4549
4550 let mut enclosing = None;
4551 while let Some(pair_state) = regions.get(i) {
4552 if pair_state.range.end.to_offset(buffer) < range.start {
4553 regions = ®ions[i + 1..];
4554 i = 0;
4555 } else if pair_state.range.start.to_offset(buffer) > range.end {
4556 break;
4557 } else {
4558 if pair_state.selection_id == selection.id {
4559 enclosing = Some(pair_state);
4560 }
4561 i += 1;
4562 }
4563 }
4564
4565 (selection, enclosing)
4566 })
4567 }
4568
4569 /// Remove any autoclose regions that no longer contain their selection.
4570 fn invalidate_autoclose_regions(
4571 &mut self,
4572 mut selections: &[Selection<Anchor>],
4573 buffer: &MultiBufferSnapshot,
4574 ) {
4575 self.autoclose_regions.retain(|state| {
4576 let mut i = 0;
4577 while let Some(selection) = selections.get(i) {
4578 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4579 selections = &selections[1..];
4580 continue;
4581 }
4582 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4583 break;
4584 }
4585 if selection.id == state.selection_id {
4586 return true;
4587 } else {
4588 i += 1;
4589 }
4590 }
4591 false
4592 });
4593 }
4594
4595 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4596 let offset = position.to_offset(buffer);
4597 let (word_range, kind) = buffer.surrounding_word(offset, true);
4598 if offset > word_range.start && kind == Some(CharKind::Word) {
4599 Some(
4600 buffer
4601 .text_for_range(word_range.start..offset)
4602 .collect::<String>(),
4603 )
4604 } else {
4605 None
4606 }
4607 }
4608
4609 pub fn toggle_inline_values(
4610 &mut self,
4611 _: &ToggleInlineValues,
4612 _: &mut Window,
4613 cx: &mut Context<Self>,
4614 ) {
4615 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4616
4617 self.refresh_inline_values(cx);
4618 }
4619
4620 pub fn toggle_inlay_hints(
4621 &mut self,
4622 _: &ToggleInlayHints,
4623 _: &mut Window,
4624 cx: &mut Context<Self>,
4625 ) {
4626 self.refresh_inlay_hints(
4627 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4628 cx,
4629 );
4630 }
4631
4632 pub fn inlay_hints_enabled(&self) -> bool {
4633 self.inlay_hint_cache.enabled
4634 }
4635
4636 pub fn inline_values_enabled(&self) -> bool {
4637 self.inline_value_cache.enabled
4638 }
4639
4640 #[cfg(any(test, feature = "test-support"))]
4641 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4642 self.display_map
4643 .read(cx)
4644 .current_inlays()
4645 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4646 .cloned()
4647 .collect()
4648 }
4649
4650 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4651 if self.semantics_provider.is_none() || !self.mode.is_full() {
4652 return;
4653 }
4654
4655 let reason_description = reason.description();
4656 let ignore_debounce = matches!(
4657 reason,
4658 InlayHintRefreshReason::SettingsChange(_)
4659 | InlayHintRefreshReason::Toggle(_)
4660 | InlayHintRefreshReason::ExcerptsRemoved(_)
4661 | InlayHintRefreshReason::ModifiersChanged(_)
4662 );
4663 let (invalidate_cache, required_languages) = match reason {
4664 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4665 match self.inlay_hint_cache.modifiers_override(enabled) {
4666 Some(enabled) => {
4667 if enabled {
4668 (InvalidationStrategy::RefreshRequested, None)
4669 } else {
4670 self.splice_inlays(
4671 &self
4672 .visible_inlay_hints(cx)
4673 .iter()
4674 .map(|inlay| inlay.id)
4675 .collect::<Vec<InlayId>>(),
4676 Vec::new(),
4677 cx,
4678 );
4679 return;
4680 }
4681 }
4682 None => return,
4683 }
4684 }
4685 InlayHintRefreshReason::Toggle(enabled) => {
4686 if self.inlay_hint_cache.toggle(enabled) {
4687 if enabled {
4688 (InvalidationStrategy::RefreshRequested, None)
4689 } else {
4690 self.splice_inlays(
4691 &self
4692 .visible_inlay_hints(cx)
4693 .iter()
4694 .map(|inlay| inlay.id)
4695 .collect::<Vec<InlayId>>(),
4696 Vec::new(),
4697 cx,
4698 );
4699 return;
4700 }
4701 } else {
4702 return;
4703 }
4704 }
4705 InlayHintRefreshReason::SettingsChange(new_settings) => {
4706 match self.inlay_hint_cache.update_settings(
4707 &self.buffer,
4708 new_settings,
4709 self.visible_inlay_hints(cx),
4710 cx,
4711 ) {
4712 ControlFlow::Break(Some(InlaySplice {
4713 to_remove,
4714 to_insert,
4715 })) => {
4716 self.splice_inlays(&to_remove, to_insert, cx);
4717 return;
4718 }
4719 ControlFlow::Break(None) => return,
4720 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4721 }
4722 }
4723 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4724 if let Some(InlaySplice {
4725 to_remove,
4726 to_insert,
4727 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4728 {
4729 self.splice_inlays(&to_remove, to_insert, cx);
4730 }
4731 self.display_map.update(cx, |display_map, _| {
4732 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4733 });
4734 return;
4735 }
4736 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4737 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4738 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4739 }
4740 InlayHintRefreshReason::RefreshRequested => {
4741 (InvalidationStrategy::RefreshRequested, None)
4742 }
4743 };
4744
4745 if let Some(InlaySplice {
4746 to_remove,
4747 to_insert,
4748 }) = self.inlay_hint_cache.spawn_hint_refresh(
4749 reason_description,
4750 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4751 invalidate_cache,
4752 ignore_debounce,
4753 cx,
4754 ) {
4755 self.splice_inlays(&to_remove, to_insert, cx);
4756 }
4757 }
4758
4759 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4760 self.display_map
4761 .read(cx)
4762 .current_inlays()
4763 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4764 .cloned()
4765 .collect()
4766 }
4767
4768 pub fn excerpts_for_inlay_hints_query(
4769 &self,
4770 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4771 cx: &mut Context<Editor>,
4772 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4773 let Some(project) = self.project.as_ref() else {
4774 return HashMap::default();
4775 };
4776 let project = project.read(cx);
4777 let multi_buffer = self.buffer().read(cx);
4778 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4779 let multi_buffer_visible_start = self
4780 .scroll_manager
4781 .anchor()
4782 .anchor
4783 .to_point(&multi_buffer_snapshot);
4784 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4785 multi_buffer_visible_start
4786 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4787 Bias::Left,
4788 );
4789 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4790 multi_buffer_snapshot
4791 .range_to_buffer_ranges(multi_buffer_visible_range)
4792 .into_iter()
4793 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4794 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4795 let buffer_file = project::File::from_dyn(buffer.file())?;
4796 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4797 let worktree_entry = buffer_worktree
4798 .read(cx)
4799 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4800 if worktree_entry.is_ignored {
4801 return None;
4802 }
4803
4804 let language = buffer.language()?;
4805 if let Some(restrict_to_languages) = restrict_to_languages {
4806 if !restrict_to_languages.contains(language) {
4807 return None;
4808 }
4809 }
4810 Some((
4811 excerpt_id,
4812 (
4813 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4814 buffer.version().clone(),
4815 excerpt_visible_range,
4816 ),
4817 ))
4818 })
4819 .collect()
4820 }
4821
4822 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4823 TextLayoutDetails {
4824 text_system: window.text_system().clone(),
4825 editor_style: self.style.clone().unwrap(),
4826 rem_size: window.rem_size(),
4827 scroll_anchor: self.scroll_manager.anchor(),
4828 visible_rows: self.visible_line_count(),
4829 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4830 }
4831 }
4832
4833 pub fn splice_inlays(
4834 &self,
4835 to_remove: &[InlayId],
4836 to_insert: Vec<Inlay>,
4837 cx: &mut Context<Self>,
4838 ) {
4839 self.display_map.update(cx, |display_map, cx| {
4840 display_map.splice_inlays(to_remove, to_insert, cx)
4841 });
4842 cx.notify();
4843 }
4844
4845 fn trigger_on_type_formatting(
4846 &self,
4847 input: String,
4848 window: &mut Window,
4849 cx: &mut Context<Self>,
4850 ) -> Option<Task<Result<()>>> {
4851 if input.len() != 1 {
4852 return None;
4853 }
4854
4855 let project = self.project.as_ref()?;
4856 let position = self.selections.newest_anchor().head();
4857 let (buffer, buffer_position) = self
4858 .buffer
4859 .read(cx)
4860 .text_anchor_for_position(position, cx)?;
4861
4862 let settings = language_settings::language_settings(
4863 buffer
4864 .read(cx)
4865 .language_at(buffer_position)
4866 .map(|l| l.name()),
4867 buffer.read(cx).file(),
4868 cx,
4869 );
4870 if !settings.use_on_type_format {
4871 return None;
4872 }
4873
4874 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4875 // hence we do LSP request & edit on host side only — add formats to host's history.
4876 let push_to_lsp_host_history = true;
4877 // If this is not the host, append its history with new edits.
4878 let push_to_client_history = project.read(cx).is_via_collab();
4879
4880 let on_type_formatting = project.update(cx, |project, cx| {
4881 project.on_type_format(
4882 buffer.clone(),
4883 buffer_position,
4884 input,
4885 push_to_lsp_host_history,
4886 cx,
4887 )
4888 });
4889 Some(cx.spawn_in(window, async move |editor, cx| {
4890 if let Some(transaction) = on_type_formatting.await? {
4891 if push_to_client_history {
4892 buffer
4893 .update(cx, |buffer, _| {
4894 buffer.push_transaction(transaction, Instant::now());
4895 buffer.finalize_last_transaction();
4896 })
4897 .ok();
4898 }
4899 editor.update(cx, |editor, cx| {
4900 editor.refresh_document_highlights(cx);
4901 })?;
4902 }
4903 Ok(())
4904 }))
4905 }
4906
4907 pub fn show_word_completions(
4908 &mut self,
4909 _: &ShowWordCompletions,
4910 window: &mut Window,
4911 cx: &mut Context<Self>,
4912 ) {
4913 self.open_completions_menu(true, None, window, cx);
4914 }
4915
4916 pub fn show_completions(
4917 &mut self,
4918 options: &ShowCompletions,
4919 window: &mut Window,
4920 cx: &mut Context<Self>,
4921 ) {
4922 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4923 }
4924
4925 fn open_completions_menu(
4926 &mut self,
4927 ignore_completion_provider: bool,
4928 trigger: Option<&str>,
4929 window: &mut Window,
4930 cx: &mut Context<Self>,
4931 ) {
4932 if self.pending_rename.is_some() {
4933 return;
4934 }
4935 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4936 return;
4937 }
4938
4939 let position = self.selections.newest_anchor().head();
4940 if position.diff_base_anchor.is_some() {
4941 return;
4942 }
4943 let (buffer, buffer_position) =
4944 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4945 output
4946 } else {
4947 return;
4948 };
4949 let buffer_snapshot = buffer.read(cx).snapshot();
4950 let show_completion_documentation = buffer_snapshot
4951 .settings_at(buffer_position, cx)
4952 .show_completion_documentation;
4953
4954 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4955
4956 let trigger_kind = match trigger {
4957 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4958 CompletionTriggerKind::TRIGGER_CHARACTER
4959 }
4960 _ => CompletionTriggerKind::INVOKED,
4961 };
4962 let completion_context = CompletionContext {
4963 trigger_character: trigger.and_then(|trigger| {
4964 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4965 Some(String::from(trigger))
4966 } else {
4967 None
4968 }
4969 }),
4970 trigger_kind,
4971 };
4972
4973 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4974 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4975 let word_to_exclude = buffer_snapshot
4976 .text_for_range(old_range.clone())
4977 .collect::<String>();
4978 (
4979 buffer_snapshot.anchor_before(old_range.start)
4980 ..buffer_snapshot.anchor_after(old_range.end),
4981 Some(word_to_exclude),
4982 )
4983 } else {
4984 (buffer_position..buffer_position, None)
4985 };
4986
4987 let completion_settings = language_settings(
4988 buffer_snapshot
4989 .language_at(buffer_position)
4990 .map(|language| language.name()),
4991 buffer_snapshot.file(),
4992 cx,
4993 )
4994 .completions;
4995
4996 // The document can be large, so stay in reasonable bounds when searching for words,
4997 // otherwise completion pop-up might be slow to appear.
4998 const WORD_LOOKUP_ROWS: u32 = 5_000;
4999 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5000 let min_word_search = buffer_snapshot.clip_point(
5001 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5002 Bias::Left,
5003 );
5004 let max_word_search = buffer_snapshot.clip_point(
5005 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5006 Bias::Right,
5007 );
5008 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5009 ..buffer_snapshot.point_to_offset(max_word_search);
5010
5011 let provider = if ignore_completion_provider {
5012 None
5013 } else {
5014 self.completion_provider.clone()
5015 };
5016 let skip_digits = query
5017 .as_ref()
5018 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5019
5020 let (mut words, provided_completions) = match &provider {
5021 Some(provider) => {
5022 let completions = provider.completions(
5023 position.excerpt_id,
5024 &buffer,
5025 buffer_position,
5026 completion_context,
5027 window,
5028 cx,
5029 );
5030
5031 let words = match completion_settings.words {
5032 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5033 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5034 .background_spawn(async move {
5035 buffer_snapshot.words_in_range(WordsQuery {
5036 fuzzy_contents: None,
5037 range: word_search_range,
5038 skip_digits,
5039 })
5040 }),
5041 };
5042
5043 (words, completions)
5044 }
5045 None => (
5046 cx.background_spawn(async move {
5047 buffer_snapshot.words_in_range(WordsQuery {
5048 fuzzy_contents: None,
5049 range: word_search_range,
5050 skip_digits,
5051 })
5052 }),
5053 Task::ready(Ok(None)),
5054 ),
5055 };
5056
5057 let sort_completions = provider
5058 .as_ref()
5059 .map_or(false, |provider| provider.sort_completions());
5060
5061 let filter_completions = provider
5062 .as_ref()
5063 .map_or(true, |provider| provider.filter_completions());
5064
5065 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5066
5067 let id = post_inc(&mut self.next_completion_id);
5068 let task = cx.spawn_in(window, async move |editor, cx| {
5069 async move {
5070 editor.update(cx, |this, _| {
5071 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5072 })?;
5073
5074 let mut completions = Vec::new();
5075 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5076 completions.extend(provided_completions);
5077 if completion_settings.words == WordsCompletionMode::Fallback {
5078 words = Task::ready(BTreeMap::default());
5079 }
5080 }
5081
5082 let mut words = words.await;
5083 if let Some(word_to_exclude) = &word_to_exclude {
5084 words.remove(word_to_exclude);
5085 }
5086 for lsp_completion in &completions {
5087 words.remove(&lsp_completion.new_text);
5088 }
5089 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5090 replace_range: old_range.clone(),
5091 new_text: word.clone(),
5092 label: CodeLabel::plain(word, None),
5093 icon_path: None,
5094 documentation: None,
5095 source: CompletionSource::BufferWord {
5096 word_range,
5097 resolved: false,
5098 },
5099 insert_text_mode: Some(InsertTextMode::AS_IS),
5100 confirm: None,
5101 }));
5102
5103 let menu = if completions.is_empty() {
5104 None
5105 } else {
5106 let mut menu = CompletionsMenu::new(
5107 id,
5108 sort_completions,
5109 show_completion_documentation,
5110 ignore_completion_provider,
5111 position,
5112 buffer.clone(),
5113 completions.into(),
5114 snippet_sort_order,
5115 );
5116
5117 menu.filter(
5118 if filter_completions {
5119 query.as_deref()
5120 } else {
5121 None
5122 },
5123 provider,
5124 editor.clone(),
5125 cx,
5126 )
5127 .await;
5128
5129 menu.visible().then_some(menu)
5130 };
5131
5132 editor.update_in(cx, |editor, window, cx| {
5133 match editor.context_menu.borrow().as_ref() {
5134 None => {}
5135 Some(CodeContextMenu::Completions(prev_menu)) => {
5136 if prev_menu.id > id {
5137 return;
5138 }
5139 }
5140 _ => return,
5141 }
5142
5143 if editor.focus_handle.is_focused(window) && menu.is_some() {
5144 let mut menu = menu.unwrap();
5145 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5146 crate::hover_popover::hide_hover(editor, cx);
5147 *editor.context_menu.borrow_mut() =
5148 Some(CodeContextMenu::Completions(menu));
5149
5150 if editor.show_edit_predictions_in_menu() {
5151 editor.update_visible_inline_completion(window, cx);
5152 } else {
5153 editor.discard_inline_completion(false, cx);
5154 }
5155
5156 cx.notify();
5157 } else if editor.completion_tasks.len() <= 1 {
5158 // If there are no more completion tasks and the last menu was
5159 // empty, we should hide it.
5160 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5161 // If it was already hidden and we don't show inline
5162 // completions in the menu, we should also show the
5163 // inline-completion when available.
5164 if was_hidden && editor.show_edit_predictions_in_menu() {
5165 editor.update_visible_inline_completion(window, cx);
5166 }
5167 }
5168 })?;
5169
5170 anyhow::Ok(())
5171 }
5172 .log_err()
5173 .await
5174 });
5175
5176 self.completion_tasks.push((id, task));
5177 }
5178
5179 #[cfg(feature = "test-support")]
5180 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5181 let menu = self.context_menu.borrow();
5182 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5183 let completions = menu.completions.borrow();
5184 Some(completions.to_vec())
5185 } else {
5186 None
5187 }
5188 }
5189
5190 pub fn confirm_completion(
5191 &mut self,
5192 action: &ConfirmCompletion,
5193 window: &mut Window,
5194 cx: &mut Context<Self>,
5195 ) -> Option<Task<Result<()>>> {
5196 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5197 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5198 }
5199
5200 pub fn confirm_completion_insert(
5201 &mut self,
5202 _: &ConfirmCompletionInsert,
5203 window: &mut Window,
5204 cx: &mut Context<Self>,
5205 ) -> Option<Task<Result<()>>> {
5206 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5207 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5208 }
5209
5210 pub fn confirm_completion_replace(
5211 &mut self,
5212 _: &ConfirmCompletionReplace,
5213 window: &mut Window,
5214 cx: &mut Context<Self>,
5215 ) -> Option<Task<Result<()>>> {
5216 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5217 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5218 }
5219
5220 pub fn compose_completion(
5221 &mut self,
5222 action: &ComposeCompletion,
5223 window: &mut Window,
5224 cx: &mut Context<Self>,
5225 ) -> Option<Task<Result<()>>> {
5226 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5227 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5228 }
5229
5230 fn do_completion(
5231 &mut self,
5232 item_ix: Option<usize>,
5233 intent: CompletionIntent,
5234 window: &mut Window,
5235 cx: &mut Context<Editor>,
5236 ) -> Option<Task<Result<()>>> {
5237 use language::ToOffset as _;
5238
5239 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5240 else {
5241 return None;
5242 };
5243
5244 let candidate_id = {
5245 let entries = completions_menu.entries.borrow();
5246 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5247 if self.show_edit_predictions_in_menu() {
5248 self.discard_inline_completion(true, cx);
5249 }
5250 mat.candidate_id
5251 };
5252
5253 let buffer_handle = completions_menu.buffer;
5254 let completion = completions_menu
5255 .completions
5256 .borrow()
5257 .get(candidate_id)?
5258 .clone();
5259 cx.stop_propagation();
5260
5261 let snapshot = self.buffer.read(cx).snapshot(cx);
5262 let newest_anchor = self.selections.newest_anchor();
5263
5264 let snippet;
5265 let new_text;
5266 if completion.is_snippet() {
5267 let mut snippet_source = completion.new_text.clone();
5268 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5269 if scope.prefers_label_for_snippet_in_completion() {
5270 if let Some(label) = completion.label() {
5271 if matches!(
5272 completion.kind(),
5273 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5274 ) {
5275 snippet_source = label;
5276 }
5277 }
5278 }
5279 }
5280 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5281 new_text = snippet.as_ref().unwrap().text.clone();
5282 } else {
5283 snippet = None;
5284 new_text = completion.new_text.clone();
5285 };
5286
5287 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5288 let buffer = buffer_handle.read(cx);
5289 let replace_range_multibuffer = {
5290 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5291 let multibuffer_anchor = snapshot
5292 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5293 .unwrap()
5294 ..snapshot
5295 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5296 .unwrap();
5297 multibuffer_anchor.start.to_offset(&snapshot)
5298 ..multibuffer_anchor.end.to_offset(&snapshot)
5299 };
5300 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5301 return None;
5302 }
5303
5304 let old_text = buffer
5305 .text_for_range(replace_range.clone())
5306 .collect::<String>();
5307 let lookbehind = newest_anchor
5308 .start
5309 .text_anchor
5310 .to_offset(buffer)
5311 .saturating_sub(replace_range.start);
5312 let lookahead = replace_range
5313 .end
5314 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5315 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5316 let suffix = &old_text[lookbehind.min(old_text.len())..];
5317
5318 let selections = self.selections.all::<usize>(cx);
5319 let mut ranges = Vec::new();
5320 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5321
5322 for selection in &selections {
5323 let range = if selection.id == newest_anchor.id {
5324 replace_range_multibuffer.clone()
5325 } else {
5326 let mut range = selection.range();
5327
5328 // if prefix is present, don't duplicate it
5329 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5330 range.start = range.start.saturating_sub(lookbehind);
5331
5332 // if suffix is also present, mimic the newest cursor and replace it
5333 if selection.id != newest_anchor.id
5334 && snapshot.contains_str_at(range.end, suffix)
5335 {
5336 range.end += lookahead;
5337 }
5338 }
5339 range
5340 };
5341
5342 ranges.push(range.clone());
5343
5344 if !self.linked_edit_ranges.is_empty() {
5345 let start_anchor = snapshot.anchor_before(range.start);
5346 let end_anchor = snapshot.anchor_after(range.end);
5347 if let Some(ranges) = self
5348 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5349 {
5350 for (buffer, edits) in ranges {
5351 linked_edits
5352 .entry(buffer.clone())
5353 .or_default()
5354 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5355 }
5356 }
5357 }
5358 }
5359
5360 cx.emit(EditorEvent::InputHandled {
5361 utf16_range_to_replace: None,
5362 text: new_text.clone().into(),
5363 });
5364
5365 self.transact(window, cx, |this, window, cx| {
5366 if let Some(mut snippet) = snippet {
5367 snippet.text = new_text.to_string();
5368 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5369 } else {
5370 this.buffer.update(cx, |buffer, cx| {
5371 let auto_indent = match completion.insert_text_mode {
5372 Some(InsertTextMode::AS_IS) => None,
5373 _ => this.autoindent_mode.clone(),
5374 };
5375 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5376 buffer.edit(edits, auto_indent, cx);
5377 });
5378 }
5379 for (buffer, edits) in linked_edits {
5380 buffer.update(cx, |buffer, cx| {
5381 let snapshot = buffer.snapshot();
5382 let edits = edits
5383 .into_iter()
5384 .map(|(range, text)| {
5385 use text::ToPoint as TP;
5386 let end_point = TP::to_point(&range.end, &snapshot);
5387 let start_point = TP::to_point(&range.start, &snapshot);
5388 (start_point..end_point, text)
5389 })
5390 .sorted_by_key(|(range, _)| range.start);
5391 buffer.edit(edits, None, cx);
5392 })
5393 }
5394
5395 this.refresh_inline_completion(true, false, window, cx);
5396 });
5397
5398 let show_new_completions_on_confirm = completion
5399 .confirm
5400 .as_ref()
5401 .map_or(false, |confirm| confirm(intent, window, cx));
5402 if show_new_completions_on_confirm {
5403 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5404 }
5405
5406 let provider = self.completion_provider.as_ref()?;
5407 drop(completion);
5408 let apply_edits = provider.apply_additional_edits_for_completion(
5409 buffer_handle,
5410 completions_menu.completions.clone(),
5411 candidate_id,
5412 true,
5413 cx,
5414 );
5415
5416 let editor_settings = EditorSettings::get_global(cx);
5417 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5418 // After the code completion is finished, users often want to know what signatures are needed.
5419 // so we should automatically call signature_help
5420 self.show_signature_help(&ShowSignatureHelp, window, cx);
5421 }
5422
5423 Some(cx.foreground_executor().spawn(async move {
5424 apply_edits.await?;
5425 Ok(())
5426 }))
5427 }
5428
5429 pub fn toggle_code_actions(
5430 &mut self,
5431 action: &ToggleCodeActions,
5432 window: &mut Window,
5433 cx: &mut Context<Self>,
5434 ) {
5435 let quick_launch = action.quick_launch;
5436 let mut context_menu = self.context_menu.borrow_mut();
5437 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5438 if code_actions.deployed_from == action.deployed_from {
5439 // Toggle if we're selecting the same one
5440 *context_menu = None;
5441 cx.notify();
5442 return;
5443 } else {
5444 // Otherwise, clear it and start a new one
5445 *context_menu = None;
5446 cx.notify();
5447 }
5448 }
5449 drop(context_menu);
5450 let snapshot = self.snapshot(window, cx);
5451 let deployed_from = action.deployed_from.clone();
5452 let mut task = self.code_actions_task.take();
5453 let action = action.clone();
5454 cx.spawn_in(window, async move |editor, cx| {
5455 while let Some(prev_task) = task {
5456 prev_task.await.log_err();
5457 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5458 }
5459
5460 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5461 if editor.focus_handle.is_focused(window) {
5462 let multibuffer_point = match &action.deployed_from {
5463 Some(CodeActionSource::Indicator(row)) => {
5464 DisplayPoint::new(*row, 0).to_point(&snapshot)
5465 }
5466 _ => editor.selections.newest::<Point>(cx).head(),
5467 };
5468 let (buffer, buffer_row) = snapshot
5469 .buffer_snapshot
5470 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5471 .and_then(|(buffer_snapshot, range)| {
5472 editor
5473 .buffer
5474 .read(cx)
5475 .buffer(buffer_snapshot.remote_id())
5476 .map(|buffer| (buffer, range.start.row))
5477 })?;
5478 let (_, code_actions) = editor
5479 .available_code_actions
5480 .clone()
5481 .and_then(|(location, code_actions)| {
5482 let snapshot = location.buffer.read(cx).snapshot();
5483 let point_range = location.range.to_point(&snapshot);
5484 let point_range = point_range.start.row..=point_range.end.row;
5485 if point_range.contains(&buffer_row) {
5486 Some((location, code_actions))
5487 } else {
5488 None
5489 }
5490 })
5491 .unzip();
5492 let buffer_id = buffer.read(cx).remote_id();
5493 let tasks = editor
5494 .tasks
5495 .get(&(buffer_id, buffer_row))
5496 .map(|t| Arc::new(t.to_owned()));
5497 if tasks.is_none() && code_actions.is_none() {
5498 return None;
5499 }
5500
5501 editor.completion_tasks.clear();
5502 editor.discard_inline_completion(false, cx);
5503 let task_context =
5504 tasks
5505 .as_ref()
5506 .zip(editor.project.clone())
5507 .map(|(tasks, project)| {
5508 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5509 });
5510
5511 Some(cx.spawn_in(window, async move |editor, cx| {
5512 let task_context = match task_context {
5513 Some(task_context) => task_context.await,
5514 None => None,
5515 };
5516 let resolved_tasks =
5517 tasks
5518 .zip(task_context.clone())
5519 .map(|(tasks, task_context)| ResolvedTasks {
5520 templates: tasks.resolve(&task_context).collect(),
5521 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5522 multibuffer_point.row,
5523 tasks.column,
5524 )),
5525 });
5526 let debug_scenarios = editor.update(cx, |editor, cx| {
5527 if cx.has_flag::<DebuggerFeatureFlag>() {
5528 maybe!({
5529 let project = editor.project.as_ref()?;
5530 let dap_store = project.read(cx).dap_store();
5531 let mut scenarios = vec![];
5532 let resolved_tasks = resolved_tasks.as_ref()?;
5533 let buffer = buffer.read(cx);
5534 let language = buffer.language()?;
5535 let file = buffer.file();
5536 let debug_adapter =
5537 language_settings(language.name().into(), file, cx)
5538 .debuggers
5539 .first()
5540 .map(SharedString::from)
5541 .or_else(|| {
5542 language
5543 .config()
5544 .debuggers
5545 .first()
5546 .map(SharedString::from)
5547 })?;
5548
5549 dap_store.update(cx, |dap_store, cx| {
5550 for (_, task) in &resolved_tasks.templates {
5551 if let Some(scenario) = dap_store
5552 .debug_scenario_for_build_task(
5553 task.original_task().clone(),
5554 debug_adapter.clone().into(),
5555 task.display_label().to_owned().into(),
5556 cx,
5557 )
5558 {
5559 scenarios.push(scenario);
5560 }
5561 }
5562 });
5563 Some(scenarios)
5564 })
5565 .unwrap_or_default()
5566 } else {
5567 vec![]
5568 }
5569 })?;
5570 let spawn_straight_away = quick_launch
5571 && resolved_tasks
5572 .as_ref()
5573 .map_or(false, |tasks| tasks.templates.len() == 1)
5574 && code_actions
5575 .as_ref()
5576 .map_or(true, |actions| actions.is_empty())
5577 && debug_scenarios.is_empty();
5578 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5579 crate::hover_popover::hide_hover(editor, cx);
5580 *editor.context_menu.borrow_mut() =
5581 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5582 buffer,
5583 actions: CodeActionContents::new(
5584 resolved_tasks,
5585 code_actions,
5586 debug_scenarios,
5587 task_context.unwrap_or_default(),
5588 ),
5589 selected_item: Default::default(),
5590 scroll_handle: UniformListScrollHandle::default(),
5591 deployed_from,
5592 }));
5593 if spawn_straight_away {
5594 if let Some(task) = editor.confirm_code_action(
5595 &ConfirmCodeAction { item_ix: Some(0) },
5596 window,
5597 cx,
5598 ) {
5599 cx.notify();
5600 return task;
5601 }
5602 }
5603 cx.notify();
5604 Task::ready(Ok(()))
5605 }) {
5606 task.await
5607 } else {
5608 Ok(())
5609 }
5610 }))
5611 } else {
5612 Some(Task::ready(Ok(())))
5613 }
5614 })?;
5615 if let Some(task) = spawned_test_task {
5616 task.await?;
5617 }
5618
5619 anyhow::Ok(())
5620 })
5621 .detach_and_log_err(cx);
5622 }
5623
5624 pub fn confirm_code_action(
5625 &mut self,
5626 action: &ConfirmCodeAction,
5627 window: &mut Window,
5628 cx: &mut Context<Self>,
5629 ) -> Option<Task<Result<()>>> {
5630 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5631
5632 let actions_menu =
5633 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5634 menu
5635 } else {
5636 return None;
5637 };
5638
5639 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5640 let action = actions_menu.actions.get(action_ix)?;
5641 let title = action.label();
5642 let buffer = actions_menu.buffer;
5643 let workspace = self.workspace()?;
5644
5645 match action {
5646 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5647 workspace.update(cx, |workspace, cx| {
5648 workspace.schedule_resolved_task(
5649 task_source_kind,
5650 resolved_task,
5651 false,
5652 window,
5653 cx,
5654 );
5655
5656 Some(Task::ready(Ok(())))
5657 })
5658 }
5659 CodeActionsItem::CodeAction {
5660 excerpt_id,
5661 action,
5662 provider,
5663 } => {
5664 let apply_code_action =
5665 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5666 let workspace = workspace.downgrade();
5667 Some(cx.spawn_in(window, async move |editor, cx| {
5668 let project_transaction = apply_code_action.await?;
5669 Self::open_project_transaction(
5670 &editor,
5671 workspace,
5672 project_transaction,
5673 title,
5674 cx,
5675 )
5676 .await
5677 }))
5678 }
5679 CodeActionsItem::DebugScenario(scenario) => {
5680 let context = actions_menu.actions.context.clone();
5681
5682 workspace.update(cx, |workspace, cx| {
5683 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5684 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5685 });
5686 Some(Task::ready(Ok(())))
5687 }
5688 }
5689 }
5690
5691 pub async fn open_project_transaction(
5692 this: &WeakEntity<Editor>,
5693 workspace: WeakEntity<Workspace>,
5694 transaction: ProjectTransaction,
5695 title: String,
5696 cx: &mut AsyncWindowContext,
5697 ) -> Result<()> {
5698 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5699 cx.update(|_, cx| {
5700 entries.sort_unstable_by_key(|(buffer, _)| {
5701 buffer.read(cx).file().map(|f| f.path().clone())
5702 });
5703 })?;
5704
5705 // If the project transaction's edits are all contained within this editor, then
5706 // avoid opening a new editor to display them.
5707
5708 if let Some((buffer, transaction)) = entries.first() {
5709 if entries.len() == 1 {
5710 let excerpt = this.update(cx, |editor, cx| {
5711 editor
5712 .buffer()
5713 .read(cx)
5714 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5715 })?;
5716 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5717 if excerpted_buffer == *buffer {
5718 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5719 let excerpt_range = excerpt_range.to_offset(buffer);
5720 buffer
5721 .edited_ranges_for_transaction::<usize>(transaction)
5722 .all(|range| {
5723 excerpt_range.start <= range.start
5724 && excerpt_range.end >= range.end
5725 })
5726 })?;
5727
5728 if all_edits_within_excerpt {
5729 return Ok(());
5730 }
5731 }
5732 }
5733 }
5734 } else {
5735 return Ok(());
5736 }
5737
5738 let mut ranges_to_highlight = Vec::new();
5739 let excerpt_buffer = cx.new(|cx| {
5740 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5741 for (buffer_handle, transaction) in &entries {
5742 let edited_ranges = buffer_handle
5743 .read(cx)
5744 .edited_ranges_for_transaction::<Point>(transaction)
5745 .collect::<Vec<_>>();
5746 let (ranges, _) = multibuffer.set_excerpts_for_path(
5747 PathKey::for_buffer(buffer_handle, cx),
5748 buffer_handle.clone(),
5749 edited_ranges,
5750 DEFAULT_MULTIBUFFER_CONTEXT,
5751 cx,
5752 );
5753
5754 ranges_to_highlight.extend(ranges);
5755 }
5756 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5757 multibuffer
5758 })?;
5759
5760 workspace.update_in(cx, |workspace, window, cx| {
5761 let project = workspace.project().clone();
5762 let editor =
5763 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5764 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5765 editor.update(cx, |editor, cx| {
5766 editor.highlight_background::<Self>(
5767 &ranges_to_highlight,
5768 |theme| theme.editor_highlighted_line_background,
5769 cx,
5770 );
5771 });
5772 })?;
5773
5774 Ok(())
5775 }
5776
5777 pub fn clear_code_action_providers(&mut self) {
5778 self.code_action_providers.clear();
5779 self.available_code_actions.take();
5780 }
5781
5782 pub fn add_code_action_provider(
5783 &mut self,
5784 provider: Rc<dyn CodeActionProvider>,
5785 window: &mut Window,
5786 cx: &mut Context<Self>,
5787 ) {
5788 if self
5789 .code_action_providers
5790 .iter()
5791 .any(|existing_provider| existing_provider.id() == provider.id())
5792 {
5793 return;
5794 }
5795
5796 self.code_action_providers.push(provider);
5797 self.refresh_code_actions(window, cx);
5798 }
5799
5800 pub fn remove_code_action_provider(
5801 &mut self,
5802 id: Arc<str>,
5803 window: &mut Window,
5804 cx: &mut Context<Self>,
5805 ) {
5806 self.code_action_providers
5807 .retain(|provider| provider.id() != id);
5808 self.refresh_code_actions(window, cx);
5809 }
5810
5811 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
5812 !self.code_action_providers.is_empty()
5813 && EditorSettings::get_global(cx).toolbar.code_actions
5814 }
5815
5816 pub fn has_available_code_actions(&self) -> bool {
5817 self.available_code_actions
5818 .as_ref()
5819 .is_some_and(|(_, actions)| !actions.is_empty())
5820 }
5821
5822 fn render_inline_code_actions(
5823 &self,
5824 icon_size: ui::IconSize,
5825 display_row: DisplayRow,
5826 is_active: bool,
5827 cx: &mut Context<Self>,
5828 ) -> AnyElement {
5829 let show_tooltip = !self.context_menu_visible();
5830 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
5831 .icon_size(icon_size)
5832 .shape(ui::IconButtonShape::Square)
5833 .style(ButtonStyle::Transparent)
5834 .icon_color(ui::Color::Hidden)
5835 .toggle_state(is_active)
5836 .when(show_tooltip, |this| {
5837 this.tooltip({
5838 let focus_handle = self.focus_handle.clone();
5839 move |window, cx| {
5840 Tooltip::for_action_in(
5841 "Toggle Code Actions",
5842 &ToggleCodeActions {
5843 deployed_from: None,
5844 quick_launch: false,
5845 },
5846 &focus_handle,
5847 window,
5848 cx,
5849 )
5850 }
5851 })
5852 })
5853 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
5854 window.focus(&editor.focus_handle(cx));
5855 editor.toggle_code_actions(
5856 &crate::actions::ToggleCodeActions {
5857 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
5858 display_row,
5859 )),
5860 quick_launch: false,
5861 },
5862 window,
5863 cx,
5864 );
5865 }))
5866 .into_any_element()
5867 }
5868
5869 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
5870 &self.context_menu
5871 }
5872
5873 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5874 let newest_selection = self.selections.newest_anchor().clone();
5875 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5876 let buffer = self.buffer.read(cx);
5877 if newest_selection.head().diff_base_anchor.is_some() {
5878 return None;
5879 }
5880 let (start_buffer, start) =
5881 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5882 let (end_buffer, end) =
5883 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5884 if start_buffer != end_buffer {
5885 return None;
5886 }
5887
5888 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5889 cx.background_executor()
5890 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5891 .await;
5892
5893 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5894 let providers = this.code_action_providers.clone();
5895 let tasks = this
5896 .code_action_providers
5897 .iter()
5898 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5899 .collect::<Vec<_>>();
5900 (providers, tasks)
5901 })?;
5902
5903 let mut actions = Vec::new();
5904 for (provider, provider_actions) in
5905 providers.into_iter().zip(future::join_all(tasks).await)
5906 {
5907 if let Some(provider_actions) = provider_actions.log_err() {
5908 actions.extend(provider_actions.into_iter().map(|action| {
5909 AvailableCodeAction {
5910 excerpt_id: newest_selection.start.excerpt_id,
5911 action,
5912 provider: provider.clone(),
5913 }
5914 }));
5915 }
5916 }
5917
5918 this.update(cx, |this, cx| {
5919 this.available_code_actions = if actions.is_empty() {
5920 None
5921 } else {
5922 Some((
5923 Location {
5924 buffer: start_buffer,
5925 range: start..end,
5926 },
5927 actions.into(),
5928 ))
5929 };
5930 cx.notify();
5931 })
5932 }));
5933 None
5934 }
5935
5936 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5937 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5938 self.show_git_blame_inline = false;
5939
5940 self.show_git_blame_inline_delay_task =
5941 Some(cx.spawn_in(window, async move |this, cx| {
5942 cx.background_executor().timer(delay).await;
5943
5944 this.update(cx, |this, cx| {
5945 this.show_git_blame_inline = true;
5946 cx.notify();
5947 })
5948 .log_err();
5949 }));
5950 }
5951 }
5952
5953 fn show_blame_popover(
5954 &mut self,
5955 blame_entry: &BlameEntry,
5956 position: gpui::Point<Pixels>,
5957 cx: &mut Context<Self>,
5958 ) {
5959 if let Some(state) = &mut self.inline_blame_popover {
5960 state.hide_task.take();
5961 cx.notify();
5962 } else {
5963 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5964 let show_task = cx.spawn(async move |editor, cx| {
5965 cx.background_executor()
5966 .timer(std::time::Duration::from_millis(delay))
5967 .await;
5968 editor
5969 .update(cx, |editor, cx| {
5970 if let Some(state) = &mut editor.inline_blame_popover {
5971 state.show_task = None;
5972 cx.notify();
5973 }
5974 })
5975 .ok();
5976 });
5977 let Some(blame) = self.blame.as_ref() else {
5978 return;
5979 };
5980 let blame = blame.read(cx);
5981 let details = blame.details_for_entry(&blame_entry);
5982 let markdown = cx.new(|cx| {
5983 Markdown::new(
5984 details
5985 .as_ref()
5986 .map(|message| message.message.clone())
5987 .unwrap_or_default(),
5988 None,
5989 None,
5990 cx,
5991 )
5992 });
5993 self.inline_blame_popover = Some(InlineBlamePopover {
5994 position,
5995 show_task: Some(show_task),
5996 hide_task: None,
5997 popover_bounds: None,
5998 popover_state: InlineBlamePopoverState {
5999 scroll_handle: ScrollHandle::new(),
6000 commit_message: details,
6001 markdown,
6002 },
6003 });
6004 }
6005 }
6006
6007 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6008 if let Some(state) = &mut self.inline_blame_popover {
6009 if state.show_task.is_some() {
6010 self.inline_blame_popover.take();
6011 cx.notify();
6012 } else {
6013 let hide_task = cx.spawn(async move |editor, cx| {
6014 cx.background_executor()
6015 .timer(std::time::Duration::from_millis(100))
6016 .await;
6017 editor
6018 .update(cx, |editor, cx| {
6019 editor.inline_blame_popover.take();
6020 cx.notify();
6021 })
6022 .ok();
6023 });
6024 state.hide_task = Some(hide_task);
6025 }
6026 }
6027 }
6028
6029 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6030 if self.pending_rename.is_some() {
6031 return None;
6032 }
6033
6034 let provider = self.semantics_provider.clone()?;
6035 let buffer = self.buffer.read(cx);
6036 let newest_selection = self.selections.newest_anchor().clone();
6037 let cursor_position = newest_selection.head();
6038 let (cursor_buffer, cursor_buffer_position) =
6039 buffer.text_anchor_for_position(cursor_position, cx)?;
6040 let (tail_buffer, tail_buffer_position) =
6041 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6042 if cursor_buffer != tail_buffer {
6043 return None;
6044 }
6045
6046 let snapshot = cursor_buffer.read(cx).snapshot();
6047 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6048 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6049 if start_word_range != end_word_range {
6050 self.document_highlights_task.take();
6051 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6052 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6053 return None;
6054 }
6055
6056 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6057 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6058 cx.background_executor()
6059 .timer(Duration::from_millis(debounce))
6060 .await;
6061
6062 let highlights = if let Some(highlights) = cx
6063 .update(|cx| {
6064 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6065 })
6066 .ok()
6067 .flatten()
6068 {
6069 highlights.await.log_err()
6070 } else {
6071 None
6072 };
6073
6074 if let Some(highlights) = highlights {
6075 this.update(cx, |this, cx| {
6076 if this.pending_rename.is_some() {
6077 return;
6078 }
6079
6080 let buffer_id = cursor_position.buffer_id;
6081 let buffer = this.buffer.read(cx);
6082 if !buffer
6083 .text_anchor_for_position(cursor_position, cx)
6084 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6085 {
6086 return;
6087 }
6088
6089 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6090 let mut write_ranges = Vec::new();
6091 let mut read_ranges = Vec::new();
6092 for highlight in highlights {
6093 for (excerpt_id, excerpt_range) in
6094 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6095 {
6096 let start = highlight
6097 .range
6098 .start
6099 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6100 let end = highlight
6101 .range
6102 .end
6103 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6104 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6105 continue;
6106 }
6107
6108 let range = Anchor {
6109 buffer_id,
6110 excerpt_id,
6111 text_anchor: start,
6112 diff_base_anchor: None,
6113 }..Anchor {
6114 buffer_id,
6115 excerpt_id,
6116 text_anchor: end,
6117 diff_base_anchor: None,
6118 };
6119 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6120 write_ranges.push(range);
6121 } else {
6122 read_ranges.push(range);
6123 }
6124 }
6125 }
6126
6127 this.highlight_background::<DocumentHighlightRead>(
6128 &read_ranges,
6129 |theme| theme.editor_document_highlight_read_background,
6130 cx,
6131 );
6132 this.highlight_background::<DocumentHighlightWrite>(
6133 &write_ranges,
6134 |theme| theme.editor_document_highlight_write_background,
6135 cx,
6136 );
6137 cx.notify();
6138 })
6139 .log_err();
6140 }
6141 }));
6142 None
6143 }
6144
6145 fn prepare_highlight_query_from_selection(
6146 &mut self,
6147 cx: &mut Context<Editor>,
6148 ) -> Option<(String, Range<Anchor>)> {
6149 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6150 return None;
6151 }
6152 if !EditorSettings::get_global(cx).selection_highlight {
6153 return None;
6154 }
6155 if self.selections.count() != 1 || self.selections.line_mode {
6156 return None;
6157 }
6158 let selection = self.selections.newest::<Point>(cx);
6159 if selection.is_empty() || selection.start.row != selection.end.row {
6160 return None;
6161 }
6162 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6163 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6164 let query = multi_buffer_snapshot
6165 .text_for_range(selection_anchor_range.clone())
6166 .collect::<String>();
6167 if query.trim().is_empty() {
6168 return None;
6169 }
6170 Some((query, selection_anchor_range))
6171 }
6172
6173 fn update_selection_occurrence_highlights(
6174 &mut self,
6175 query_text: String,
6176 query_range: Range<Anchor>,
6177 multi_buffer_range_to_query: Range<Point>,
6178 use_debounce: bool,
6179 window: &mut Window,
6180 cx: &mut Context<Editor>,
6181 ) -> Task<()> {
6182 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6183 cx.spawn_in(window, async move |editor, cx| {
6184 if use_debounce {
6185 cx.background_executor()
6186 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6187 .await;
6188 }
6189 let match_task = cx.background_spawn(async move {
6190 let buffer_ranges = multi_buffer_snapshot
6191 .range_to_buffer_ranges(multi_buffer_range_to_query)
6192 .into_iter()
6193 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6194 let mut match_ranges = Vec::new();
6195 let Ok(regex) = project::search::SearchQuery::text(
6196 query_text.clone(),
6197 false,
6198 false,
6199 false,
6200 Default::default(),
6201 Default::default(),
6202 false,
6203 None,
6204 ) else {
6205 return Vec::default();
6206 };
6207 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6208 match_ranges.extend(
6209 regex
6210 .search(&buffer_snapshot, Some(search_range.clone()))
6211 .await
6212 .into_iter()
6213 .filter_map(|match_range| {
6214 let match_start = buffer_snapshot
6215 .anchor_after(search_range.start + match_range.start);
6216 let match_end = buffer_snapshot
6217 .anchor_before(search_range.start + match_range.end);
6218 let match_anchor_range = Anchor::range_in_buffer(
6219 excerpt_id,
6220 buffer_snapshot.remote_id(),
6221 match_start..match_end,
6222 );
6223 (match_anchor_range != query_range).then_some(match_anchor_range)
6224 }),
6225 );
6226 }
6227 match_ranges
6228 });
6229 let match_ranges = match_task.await;
6230 editor
6231 .update_in(cx, |editor, _, cx| {
6232 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6233 if !match_ranges.is_empty() {
6234 editor.highlight_background::<SelectedTextHighlight>(
6235 &match_ranges,
6236 |theme| theme.editor_document_highlight_bracket_background,
6237 cx,
6238 )
6239 }
6240 })
6241 .log_err();
6242 })
6243 }
6244
6245 fn refresh_selected_text_highlights(
6246 &mut self,
6247 on_buffer_edit: bool,
6248 window: &mut Window,
6249 cx: &mut Context<Editor>,
6250 ) {
6251 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6252 else {
6253 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6254 self.quick_selection_highlight_task.take();
6255 self.debounced_selection_highlight_task.take();
6256 return;
6257 };
6258 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6259 if on_buffer_edit
6260 || self
6261 .quick_selection_highlight_task
6262 .as_ref()
6263 .map_or(true, |(prev_anchor_range, _)| {
6264 prev_anchor_range != &query_range
6265 })
6266 {
6267 let multi_buffer_visible_start = self
6268 .scroll_manager
6269 .anchor()
6270 .anchor
6271 .to_point(&multi_buffer_snapshot);
6272 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6273 multi_buffer_visible_start
6274 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6275 Bias::Left,
6276 );
6277 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6278 self.quick_selection_highlight_task = Some((
6279 query_range.clone(),
6280 self.update_selection_occurrence_highlights(
6281 query_text.clone(),
6282 query_range.clone(),
6283 multi_buffer_visible_range,
6284 false,
6285 window,
6286 cx,
6287 ),
6288 ));
6289 }
6290 if on_buffer_edit
6291 || self
6292 .debounced_selection_highlight_task
6293 .as_ref()
6294 .map_or(true, |(prev_anchor_range, _)| {
6295 prev_anchor_range != &query_range
6296 })
6297 {
6298 let multi_buffer_start = multi_buffer_snapshot
6299 .anchor_before(0)
6300 .to_point(&multi_buffer_snapshot);
6301 let multi_buffer_end = multi_buffer_snapshot
6302 .anchor_after(multi_buffer_snapshot.len())
6303 .to_point(&multi_buffer_snapshot);
6304 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6305 self.debounced_selection_highlight_task = Some((
6306 query_range.clone(),
6307 self.update_selection_occurrence_highlights(
6308 query_text,
6309 query_range,
6310 multi_buffer_full_range,
6311 true,
6312 window,
6313 cx,
6314 ),
6315 ));
6316 }
6317 }
6318
6319 pub fn refresh_inline_completion(
6320 &mut self,
6321 debounce: bool,
6322 user_requested: bool,
6323 window: &mut Window,
6324 cx: &mut Context<Self>,
6325 ) -> Option<()> {
6326 let provider = self.edit_prediction_provider()?;
6327 let cursor = self.selections.newest_anchor().head();
6328 let (buffer, cursor_buffer_position) =
6329 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6330
6331 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6332 self.discard_inline_completion(false, cx);
6333 return None;
6334 }
6335
6336 if !user_requested
6337 && (!self.should_show_edit_predictions()
6338 || !self.is_focused(window)
6339 || buffer.read(cx).is_empty())
6340 {
6341 self.discard_inline_completion(false, cx);
6342 return None;
6343 }
6344
6345 self.update_visible_inline_completion(window, cx);
6346 provider.refresh(
6347 self.project.clone(),
6348 buffer,
6349 cursor_buffer_position,
6350 debounce,
6351 cx,
6352 );
6353 Some(())
6354 }
6355
6356 fn show_edit_predictions_in_menu(&self) -> bool {
6357 match self.edit_prediction_settings {
6358 EditPredictionSettings::Disabled => false,
6359 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6360 }
6361 }
6362
6363 pub fn edit_predictions_enabled(&self) -> bool {
6364 match self.edit_prediction_settings {
6365 EditPredictionSettings::Disabled => false,
6366 EditPredictionSettings::Enabled { .. } => true,
6367 }
6368 }
6369
6370 fn edit_prediction_requires_modifier(&self) -> bool {
6371 match self.edit_prediction_settings {
6372 EditPredictionSettings::Disabled => false,
6373 EditPredictionSettings::Enabled {
6374 preview_requires_modifier,
6375 ..
6376 } => preview_requires_modifier,
6377 }
6378 }
6379
6380 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6381 if self.edit_prediction_provider.is_none() {
6382 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6383 } else {
6384 let selection = self.selections.newest_anchor();
6385 let cursor = selection.head();
6386
6387 if let Some((buffer, cursor_buffer_position)) =
6388 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6389 {
6390 self.edit_prediction_settings =
6391 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6392 }
6393 }
6394 }
6395
6396 fn edit_prediction_settings_at_position(
6397 &self,
6398 buffer: &Entity<Buffer>,
6399 buffer_position: language::Anchor,
6400 cx: &App,
6401 ) -> EditPredictionSettings {
6402 if !self.mode.is_full()
6403 || !self.show_inline_completions_override.unwrap_or(true)
6404 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6405 {
6406 return EditPredictionSettings::Disabled;
6407 }
6408
6409 let buffer = buffer.read(cx);
6410
6411 let file = buffer.file();
6412
6413 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6414 return EditPredictionSettings::Disabled;
6415 };
6416
6417 let by_provider = matches!(
6418 self.menu_inline_completions_policy,
6419 MenuInlineCompletionsPolicy::ByProvider
6420 );
6421
6422 let show_in_menu = by_provider
6423 && self
6424 .edit_prediction_provider
6425 .as_ref()
6426 .map_or(false, |provider| {
6427 provider.provider.show_completions_in_menu()
6428 });
6429
6430 let preview_requires_modifier =
6431 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6432
6433 EditPredictionSettings::Enabled {
6434 show_in_menu,
6435 preview_requires_modifier,
6436 }
6437 }
6438
6439 fn should_show_edit_predictions(&self) -> bool {
6440 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6441 }
6442
6443 pub fn edit_prediction_preview_is_active(&self) -> bool {
6444 matches!(
6445 self.edit_prediction_preview,
6446 EditPredictionPreview::Active { .. }
6447 )
6448 }
6449
6450 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6451 let cursor = self.selections.newest_anchor().head();
6452 if let Some((buffer, cursor_position)) =
6453 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6454 {
6455 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6456 } else {
6457 false
6458 }
6459 }
6460
6461 pub fn supports_minimap(&self, cx: &App) -> bool {
6462 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6463 }
6464
6465 fn edit_predictions_enabled_in_buffer(
6466 &self,
6467 buffer: &Entity<Buffer>,
6468 buffer_position: language::Anchor,
6469 cx: &App,
6470 ) -> bool {
6471 maybe!({
6472 if self.read_only(cx) {
6473 return Some(false);
6474 }
6475 let provider = self.edit_prediction_provider()?;
6476 if !provider.is_enabled(&buffer, buffer_position, cx) {
6477 return Some(false);
6478 }
6479 let buffer = buffer.read(cx);
6480 let Some(file) = buffer.file() else {
6481 return Some(true);
6482 };
6483 let settings = all_language_settings(Some(file), cx);
6484 Some(settings.edit_predictions_enabled_for_file(file, cx))
6485 })
6486 .unwrap_or(false)
6487 }
6488
6489 fn cycle_inline_completion(
6490 &mut self,
6491 direction: Direction,
6492 window: &mut Window,
6493 cx: &mut Context<Self>,
6494 ) -> Option<()> {
6495 let provider = self.edit_prediction_provider()?;
6496 let cursor = self.selections.newest_anchor().head();
6497 let (buffer, cursor_buffer_position) =
6498 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6499 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6500 return None;
6501 }
6502
6503 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6504 self.update_visible_inline_completion(window, cx);
6505
6506 Some(())
6507 }
6508
6509 pub fn show_inline_completion(
6510 &mut self,
6511 _: &ShowEditPrediction,
6512 window: &mut Window,
6513 cx: &mut Context<Self>,
6514 ) {
6515 if !self.has_active_inline_completion() {
6516 self.refresh_inline_completion(false, true, window, cx);
6517 return;
6518 }
6519
6520 self.update_visible_inline_completion(window, cx);
6521 }
6522
6523 pub fn display_cursor_names(
6524 &mut self,
6525 _: &DisplayCursorNames,
6526 window: &mut Window,
6527 cx: &mut Context<Self>,
6528 ) {
6529 self.show_cursor_names(window, cx);
6530 }
6531
6532 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6533 self.show_cursor_names = true;
6534 cx.notify();
6535 cx.spawn_in(window, async move |this, cx| {
6536 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6537 this.update(cx, |this, cx| {
6538 this.show_cursor_names = false;
6539 cx.notify()
6540 })
6541 .ok()
6542 })
6543 .detach();
6544 }
6545
6546 pub fn next_edit_prediction(
6547 &mut self,
6548 _: &NextEditPrediction,
6549 window: &mut Window,
6550 cx: &mut Context<Self>,
6551 ) {
6552 if self.has_active_inline_completion() {
6553 self.cycle_inline_completion(Direction::Next, window, cx);
6554 } else {
6555 let is_copilot_disabled = self
6556 .refresh_inline_completion(false, true, window, cx)
6557 .is_none();
6558 if is_copilot_disabled {
6559 cx.propagate();
6560 }
6561 }
6562 }
6563
6564 pub fn previous_edit_prediction(
6565 &mut self,
6566 _: &PreviousEditPrediction,
6567 window: &mut Window,
6568 cx: &mut Context<Self>,
6569 ) {
6570 if self.has_active_inline_completion() {
6571 self.cycle_inline_completion(Direction::Prev, window, cx);
6572 } else {
6573 let is_copilot_disabled = self
6574 .refresh_inline_completion(false, true, window, cx)
6575 .is_none();
6576 if is_copilot_disabled {
6577 cx.propagate();
6578 }
6579 }
6580 }
6581
6582 pub fn accept_edit_prediction(
6583 &mut self,
6584 _: &AcceptEditPrediction,
6585 window: &mut Window,
6586 cx: &mut Context<Self>,
6587 ) {
6588 if self.show_edit_predictions_in_menu() {
6589 self.hide_context_menu(window, cx);
6590 }
6591
6592 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6593 return;
6594 };
6595
6596 self.report_inline_completion_event(
6597 active_inline_completion.completion_id.clone(),
6598 true,
6599 cx,
6600 );
6601
6602 match &active_inline_completion.completion {
6603 InlineCompletion::Move { target, .. } => {
6604 let target = *target;
6605
6606 if let Some(position_map) = &self.last_position_map {
6607 if position_map
6608 .visible_row_range
6609 .contains(&target.to_display_point(&position_map.snapshot).row())
6610 || !self.edit_prediction_requires_modifier()
6611 {
6612 self.unfold_ranges(&[target..target], true, false, cx);
6613 // Note that this is also done in vim's handler of the Tab action.
6614 self.change_selections(
6615 Some(Autoscroll::newest()),
6616 window,
6617 cx,
6618 |selections| {
6619 selections.select_anchor_ranges([target..target]);
6620 },
6621 );
6622 self.clear_row_highlights::<EditPredictionPreview>();
6623
6624 self.edit_prediction_preview
6625 .set_previous_scroll_position(None);
6626 } else {
6627 self.edit_prediction_preview
6628 .set_previous_scroll_position(Some(
6629 position_map.snapshot.scroll_anchor,
6630 ));
6631
6632 self.highlight_rows::<EditPredictionPreview>(
6633 target..target,
6634 cx.theme().colors().editor_highlighted_line_background,
6635 RowHighlightOptions {
6636 autoscroll: true,
6637 ..Default::default()
6638 },
6639 cx,
6640 );
6641 self.request_autoscroll(Autoscroll::fit(), cx);
6642 }
6643 }
6644 }
6645 InlineCompletion::Edit { edits, .. } => {
6646 if let Some(provider) = self.edit_prediction_provider() {
6647 provider.accept(cx);
6648 }
6649
6650 // Store the transaction ID and selections before applying the edit
6651 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6652
6653 let snapshot = self.buffer.read(cx).snapshot(cx);
6654 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6655
6656 self.buffer.update(cx, |buffer, cx| {
6657 buffer.edit(edits.iter().cloned(), None, cx)
6658 });
6659
6660 self.change_selections(None, window, cx, |s| {
6661 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6662 });
6663
6664 let selections = self.selections.disjoint_anchors();
6665 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6666 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6667 if has_new_transaction {
6668 self.selection_history
6669 .insert_transaction(transaction_id_now, selections);
6670 }
6671 }
6672
6673 self.update_visible_inline_completion(window, cx);
6674 if self.active_inline_completion.is_none() {
6675 self.refresh_inline_completion(true, true, window, cx);
6676 }
6677
6678 cx.notify();
6679 }
6680 }
6681
6682 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6683 }
6684
6685 pub fn accept_partial_inline_completion(
6686 &mut self,
6687 _: &AcceptPartialEditPrediction,
6688 window: &mut Window,
6689 cx: &mut Context<Self>,
6690 ) {
6691 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6692 return;
6693 };
6694 if self.selections.count() != 1 {
6695 return;
6696 }
6697
6698 self.report_inline_completion_event(
6699 active_inline_completion.completion_id.clone(),
6700 true,
6701 cx,
6702 );
6703
6704 match &active_inline_completion.completion {
6705 InlineCompletion::Move { target, .. } => {
6706 let target = *target;
6707 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6708 selections.select_anchor_ranges([target..target]);
6709 });
6710 }
6711 InlineCompletion::Edit { edits, .. } => {
6712 // Find an insertion that starts at the cursor position.
6713 let snapshot = self.buffer.read(cx).snapshot(cx);
6714 let cursor_offset = self.selections.newest::<usize>(cx).head();
6715 let insertion = edits.iter().find_map(|(range, text)| {
6716 let range = range.to_offset(&snapshot);
6717 if range.is_empty() && range.start == cursor_offset {
6718 Some(text)
6719 } else {
6720 None
6721 }
6722 });
6723
6724 if let Some(text) = insertion {
6725 let mut partial_completion = text
6726 .chars()
6727 .by_ref()
6728 .take_while(|c| c.is_alphabetic())
6729 .collect::<String>();
6730 if partial_completion.is_empty() {
6731 partial_completion = text
6732 .chars()
6733 .by_ref()
6734 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6735 .collect::<String>();
6736 }
6737
6738 cx.emit(EditorEvent::InputHandled {
6739 utf16_range_to_replace: None,
6740 text: partial_completion.clone().into(),
6741 });
6742
6743 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6744
6745 self.refresh_inline_completion(true, true, window, cx);
6746 cx.notify();
6747 } else {
6748 self.accept_edit_prediction(&Default::default(), window, cx);
6749 }
6750 }
6751 }
6752 }
6753
6754 fn discard_inline_completion(
6755 &mut self,
6756 should_report_inline_completion_event: bool,
6757 cx: &mut Context<Self>,
6758 ) -> bool {
6759 if should_report_inline_completion_event {
6760 let completion_id = self
6761 .active_inline_completion
6762 .as_ref()
6763 .and_then(|active_completion| active_completion.completion_id.clone());
6764
6765 self.report_inline_completion_event(completion_id, false, cx);
6766 }
6767
6768 if let Some(provider) = self.edit_prediction_provider() {
6769 provider.discard(cx);
6770 }
6771
6772 self.take_active_inline_completion(cx)
6773 }
6774
6775 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6776 let Some(provider) = self.edit_prediction_provider() else {
6777 return;
6778 };
6779
6780 let Some((_, buffer, _)) = self
6781 .buffer
6782 .read(cx)
6783 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6784 else {
6785 return;
6786 };
6787
6788 let extension = buffer
6789 .read(cx)
6790 .file()
6791 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6792
6793 let event_type = match accepted {
6794 true => "Edit Prediction Accepted",
6795 false => "Edit Prediction Discarded",
6796 };
6797 telemetry::event!(
6798 event_type,
6799 provider = provider.name(),
6800 prediction_id = id,
6801 suggestion_accepted = accepted,
6802 file_extension = extension,
6803 );
6804 }
6805
6806 pub fn has_active_inline_completion(&self) -> bool {
6807 self.active_inline_completion.is_some()
6808 }
6809
6810 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6811 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6812 return false;
6813 };
6814
6815 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6816 self.clear_highlights::<InlineCompletionHighlight>(cx);
6817 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6818 true
6819 }
6820
6821 /// Returns true when we're displaying the edit prediction popover below the cursor
6822 /// like we are not previewing and the LSP autocomplete menu is visible
6823 /// or we are in `when_holding_modifier` mode.
6824 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6825 if self.edit_prediction_preview_is_active()
6826 || !self.show_edit_predictions_in_menu()
6827 || !self.edit_predictions_enabled()
6828 {
6829 return false;
6830 }
6831
6832 if self.has_visible_completions_menu() {
6833 return true;
6834 }
6835
6836 has_completion && self.edit_prediction_requires_modifier()
6837 }
6838
6839 fn handle_modifiers_changed(
6840 &mut self,
6841 modifiers: Modifiers,
6842 position_map: &PositionMap,
6843 window: &mut Window,
6844 cx: &mut Context<Self>,
6845 ) {
6846 if self.show_edit_predictions_in_menu() {
6847 self.update_edit_prediction_preview(&modifiers, window, cx);
6848 }
6849
6850 self.update_selection_mode(&modifiers, position_map, window, cx);
6851
6852 let mouse_position = window.mouse_position();
6853 if !position_map.text_hitbox.is_hovered(window) {
6854 return;
6855 }
6856
6857 self.update_hovered_link(
6858 position_map.point_for_position(mouse_position),
6859 &position_map.snapshot,
6860 modifiers,
6861 window,
6862 cx,
6863 )
6864 }
6865
6866 fn update_selection_mode(
6867 &mut self,
6868 modifiers: &Modifiers,
6869 position_map: &PositionMap,
6870 window: &mut Window,
6871 cx: &mut Context<Self>,
6872 ) {
6873 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6874 return;
6875 }
6876
6877 let mouse_position = window.mouse_position();
6878 let point_for_position = position_map.point_for_position(mouse_position);
6879 let position = point_for_position.previous_valid;
6880
6881 self.select(
6882 SelectPhase::BeginColumnar {
6883 position,
6884 reset: false,
6885 goal_column: point_for_position.exact_unclipped.column(),
6886 },
6887 window,
6888 cx,
6889 );
6890 }
6891
6892 fn update_edit_prediction_preview(
6893 &mut self,
6894 modifiers: &Modifiers,
6895 window: &mut Window,
6896 cx: &mut Context<Self>,
6897 ) {
6898 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6899 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6900 return;
6901 };
6902
6903 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6904 if matches!(
6905 self.edit_prediction_preview,
6906 EditPredictionPreview::Inactive { .. }
6907 ) {
6908 self.edit_prediction_preview = EditPredictionPreview::Active {
6909 previous_scroll_position: None,
6910 since: Instant::now(),
6911 };
6912
6913 self.update_visible_inline_completion(window, cx);
6914 cx.notify();
6915 }
6916 } else if let EditPredictionPreview::Active {
6917 previous_scroll_position,
6918 since,
6919 } = self.edit_prediction_preview
6920 {
6921 if let (Some(previous_scroll_position), Some(position_map)) =
6922 (previous_scroll_position, self.last_position_map.as_ref())
6923 {
6924 self.set_scroll_position(
6925 previous_scroll_position
6926 .scroll_position(&position_map.snapshot.display_snapshot),
6927 window,
6928 cx,
6929 );
6930 }
6931
6932 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6933 released_too_fast: since.elapsed() < Duration::from_millis(200),
6934 };
6935 self.clear_row_highlights::<EditPredictionPreview>();
6936 self.update_visible_inline_completion(window, cx);
6937 cx.notify();
6938 }
6939 }
6940
6941 fn update_visible_inline_completion(
6942 &mut self,
6943 _window: &mut Window,
6944 cx: &mut Context<Self>,
6945 ) -> Option<()> {
6946 let selection = self.selections.newest_anchor();
6947 let cursor = selection.head();
6948 let multibuffer = self.buffer.read(cx).snapshot(cx);
6949 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6950 let excerpt_id = cursor.excerpt_id;
6951
6952 let show_in_menu = self.show_edit_predictions_in_menu();
6953 let completions_menu_has_precedence = !show_in_menu
6954 && (self.context_menu.borrow().is_some()
6955 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6956
6957 if completions_menu_has_precedence
6958 || !offset_selection.is_empty()
6959 || self
6960 .active_inline_completion
6961 .as_ref()
6962 .map_or(false, |completion| {
6963 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6964 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6965 !invalidation_range.contains(&offset_selection.head())
6966 })
6967 {
6968 self.discard_inline_completion(false, cx);
6969 return None;
6970 }
6971
6972 self.take_active_inline_completion(cx);
6973 let Some(provider) = self.edit_prediction_provider() else {
6974 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6975 return None;
6976 };
6977
6978 let (buffer, cursor_buffer_position) =
6979 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6980
6981 self.edit_prediction_settings =
6982 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6983
6984 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
6985
6986 if self.edit_prediction_indent_conflict {
6987 let cursor_point = cursor.to_point(&multibuffer);
6988
6989 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
6990
6991 if let Some((_, indent)) = indents.iter().next() {
6992 if indent.len == cursor_point.column {
6993 self.edit_prediction_indent_conflict = false;
6994 }
6995 }
6996 }
6997
6998 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
6999 let edits = inline_completion
7000 .edits
7001 .into_iter()
7002 .flat_map(|(range, new_text)| {
7003 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7004 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7005 Some((start..end, new_text))
7006 })
7007 .collect::<Vec<_>>();
7008 if edits.is_empty() {
7009 return None;
7010 }
7011
7012 let first_edit_start = edits.first().unwrap().0.start;
7013 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7014 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7015
7016 let last_edit_end = edits.last().unwrap().0.end;
7017 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7018 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7019
7020 let cursor_row = cursor.to_point(&multibuffer).row;
7021
7022 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7023
7024 let mut inlay_ids = Vec::new();
7025 let invalidation_row_range;
7026 let move_invalidation_row_range = if cursor_row < edit_start_row {
7027 Some(cursor_row..edit_end_row)
7028 } else if cursor_row > edit_end_row {
7029 Some(edit_start_row..cursor_row)
7030 } else {
7031 None
7032 };
7033 let is_move =
7034 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7035 let completion = if is_move {
7036 invalidation_row_range =
7037 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7038 let target = first_edit_start;
7039 InlineCompletion::Move { target, snapshot }
7040 } else {
7041 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7042 && !self.inline_completions_hidden_for_vim_mode;
7043
7044 if show_completions_in_buffer {
7045 if edits
7046 .iter()
7047 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7048 {
7049 let mut inlays = Vec::new();
7050 for (range, new_text) in &edits {
7051 let inlay = Inlay::inline_completion(
7052 post_inc(&mut self.next_inlay_id),
7053 range.start,
7054 new_text.as_str(),
7055 );
7056 inlay_ids.push(inlay.id);
7057 inlays.push(inlay);
7058 }
7059
7060 self.splice_inlays(&[], inlays, cx);
7061 } else {
7062 let background_color = cx.theme().status().deleted_background;
7063 self.highlight_text::<InlineCompletionHighlight>(
7064 edits.iter().map(|(range, _)| range.clone()).collect(),
7065 HighlightStyle {
7066 background_color: Some(background_color),
7067 ..Default::default()
7068 },
7069 cx,
7070 );
7071 }
7072 }
7073
7074 invalidation_row_range = edit_start_row..edit_end_row;
7075
7076 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7077 if provider.show_tab_accept_marker() {
7078 EditDisplayMode::TabAccept
7079 } else {
7080 EditDisplayMode::Inline
7081 }
7082 } else {
7083 EditDisplayMode::DiffPopover
7084 };
7085
7086 InlineCompletion::Edit {
7087 edits,
7088 edit_preview: inline_completion.edit_preview,
7089 display_mode,
7090 snapshot,
7091 }
7092 };
7093
7094 let invalidation_range = multibuffer
7095 .anchor_before(Point::new(invalidation_row_range.start, 0))
7096 ..multibuffer.anchor_after(Point::new(
7097 invalidation_row_range.end,
7098 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7099 ));
7100
7101 self.stale_inline_completion_in_menu = None;
7102 self.active_inline_completion = Some(InlineCompletionState {
7103 inlay_ids,
7104 completion,
7105 completion_id: inline_completion.id,
7106 invalidation_range,
7107 });
7108
7109 cx.notify();
7110
7111 Some(())
7112 }
7113
7114 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7115 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7116 }
7117
7118 fn clear_tasks(&mut self) {
7119 self.tasks.clear()
7120 }
7121
7122 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7123 if self.tasks.insert(key, value).is_some() {
7124 // This case should hopefully be rare, but just in case...
7125 log::error!(
7126 "multiple different run targets found on a single line, only the last target will be rendered"
7127 )
7128 }
7129 }
7130
7131 /// Get all display points of breakpoints that will be rendered within editor
7132 ///
7133 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7134 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7135 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7136 fn active_breakpoints(
7137 &self,
7138 range: Range<DisplayRow>,
7139 window: &mut Window,
7140 cx: &mut Context<Self>,
7141 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7142 let mut breakpoint_display_points = HashMap::default();
7143
7144 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7145 return breakpoint_display_points;
7146 };
7147
7148 let snapshot = self.snapshot(window, cx);
7149
7150 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7151 let Some(project) = self.project.as_ref() else {
7152 return breakpoint_display_points;
7153 };
7154
7155 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7156 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7157
7158 for (buffer_snapshot, range, excerpt_id) in
7159 multi_buffer_snapshot.range_to_buffer_ranges(range)
7160 {
7161 let Some(buffer) = project
7162 .read(cx)
7163 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7164 else {
7165 continue;
7166 };
7167 let breakpoints = breakpoint_store.read(cx).breakpoints(
7168 &buffer,
7169 Some(
7170 buffer_snapshot.anchor_before(range.start)
7171 ..buffer_snapshot.anchor_after(range.end),
7172 ),
7173 buffer_snapshot,
7174 cx,
7175 );
7176 for (breakpoint, state) in breakpoints {
7177 let multi_buffer_anchor =
7178 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7179 let position = multi_buffer_anchor
7180 .to_point(&multi_buffer_snapshot)
7181 .to_display_point(&snapshot);
7182
7183 breakpoint_display_points.insert(
7184 position.row(),
7185 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7186 );
7187 }
7188 }
7189
7190 breakpoint_display_points
7191 }
7192
7193 fn breakpoint_context_menu(
7194 &self,
7195 anchor: Anchor,
7196 window: &mut Window,
7197 cx: &mut Context<Self>,
7198 ) -> Entity<ui::ContextMenu> {
7199 let weak_editor = cx.weak_entity();
7200 let focus_handle = self.focus_handle(cx);
7201
7202 let row = self
7203 .buffer
7204 .read(cx)
7205 .snapshot(cx)
7206 .summary_for_anchor::<Point>(&anchor)
7207 .row;
7208
7209 let breakpoint = self
7210 .breakpoint_at_row(row, window, cx)
7211 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7212
7213 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7214 "Edit Log Breakpoint"
7215 } else {
7216 "Set Log Breakpoint"
7217 };
7218
7219 let condition_breakpoint_msg = if breakpoint
7220 .as_ref()
7221 .is_some_and(|bp| bp.1.condition.is_some())
7222 {
7223 "Edit Condition Breakpoint"
7224 } else {
7225 "Set Condition Breakpoint"
7226 };
7227
7228 let hit_condition_breakpoint_msg = if breakpoint
7229 .as_ref()
7230 .is_some_and(|bp| bp.1.hit_condition.is_some())
7231 {
7232 "Edit Hit Condition Breakpoint"
7233 } else {
7234 "Set Hit Condition Breakpoint"
7235 };
7236
7237 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7238 "Unset Breakpoint"
7239 } else {
7240 "Set Breakpoint"
7241 };
7242
7243 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7244 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7245
7246 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7247 BreakpointState::Enabled => Some("Disable"),
7248 BreakpointState::Disabled => Some("Enable"),
7249 });
7250
7251 let (anchor, breakpoint) =
7252 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7253
7254 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7255 menu.on_blur_subscription(Subscription::new(|| {}))
7256 .context(focus_handle)
7257 .when(run_to_cursor, |this| {
7258 let weak_editor = weak_editor.clone();
7259 this.entry("Run to cursor", None, move |window, cx| {
7260 weak_editor
7261 .update(cx, |editor, cx| {
7262 editor.change_selections(None, window, cx, |s| {
7263 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7264 });
7265 })
7266 .ok();
7267
7268 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7269 })
7270 .separator()
7271 })
7272 .when_some(toggle_state_msg, |this, msg| {
7273 this.entry(msg, None, {
7274 let weak_editor = weak_editor.clone();
7275 let breakpoint = breakpoint.clone();
7276 move |_window, cx| {
7277 weak_editor
7278 .update(cx, |this, cx| {
7279 this.edit_breakpoint_at_anchor(
7280 anchor,
7281 breakpoint.as_ref().clone(),
7282 BreakpointEditAction::InvertState,
7283 cx,
7284 );
7285 })
7286 .log_err();
7287 }
7288 })
7289 })
7290 .entry(set_breakpoint_msg, None, {
7291 let weak_editor = weak_editor.clone();
7292 let breakpoint = breakpoint.clone();
7293 move |_window, cx| {
7294 weak_editor
7295 .update(cx, |this, cx| {
7296 this.edit_breakpoint_at_anchor(
7297 anchor,
7298 breakpoint.as_ref().clone(),
7299 BreakpointEditAction::Toggle,
7300 cx,
7301 );
7302 })
7303 .log_err();
7304 }
7305 })
7306 .entry(log_breakpoint_msg, None, {
7307 let breakpoint = breakpoint.clone();
7308 let weak_editor = weak_editor.clone();
7309 move |window, cx| {
7310 weak_editor
7311 .update(cx, |this, cx| {
7312 this.add_edit_breakpoint_block(
7313 anchor,
7314 breakpoint.as_ref(),
7315 BreakpointPromptEditAction::Log,
7316 window,
7317 cx,
7318 );
7319 })
7320 .log_err();
7321 }
7322 })
7323 .entry(condition_breakpoint_msg, None, {
7324 let breakpoint = breakpoint.clone();
7325 let weak_editor = weak_editor.clone();
7326 move |window, cx| {
7327 weak_editor
7328 .update(cx, |this, cx| {
7329 this.add_edit_breakpoint_block(
7330 anchor,
7331 breakpoint.as_ref(),
7332 BreakpointPromptEditAction::Condition,
7333 window,
7334 cx,
7335 );
7336 })
7337 .log_err();
7338 }
7339 })
7340 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7341 weak_editor
7342 .update(cx, |this, cx| {
7343 this.add_edit_breakpoint_block(
7344 anchor,
7345 breakpoint.as_ref(),
7346 BreakpointPromptEditAction::HitCondition,
7347 window,
7348 cx,
7349 );
7350 })
7351 .log_err();
7352 })
7353 })
7354 }
7355
7356 fn render_breakpoint(
7357 &self,
7358 position: Anchor,
7359 row: DisplayRow,
7360 breakpoint: &Breakpoint,
7361 state: Option<BreakpointSessionState>,
7362 cx: &mut Context<Self>,
7363 ) -> IconButton {
7364 let is_rejected = state.is_some_and(|s| !s.verified);
7365 // Is it a breakpoint that shows up when hovering over gutter?
7366 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7367 (false, false),
7368 |PhantomBreakpointIndicator {
7369 is_active,
7370 display_row,
7371 collides_with_existing_breakpoint,
7372 }| {
7373 (
7374 is_active && display_row == row,
7375 collides_with_existing_breakpoint,
7376 )
7377 },
7378 );
7379
7380 let (color, icon) = {
7381 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7382 (false, false) => ui::IconName::DebugBreakpoint,
7383 (true, false) => ui::IconName::DebugLogBreakpoint,
7384 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7385 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7386 };
7387
7388 let color = if is_phantom {
7389 Color::Hint
7390 } else if is_rejected {
7391 Color::Disabled
7392 } else {
7393 Color::Debugger
7394 };
7395
7396 (color, icon)
7397 };
7398
7399 let breakpoint = Arc::from(breakpoint.clone());
7400
7401 let alt_as_text = gpui::Keystroke {
7402 modifiers: Modifiers::secondary_key(),
7403 ..Default::default()
7404 };
7405 let primary_action_text = if breakpoint.is_disabled() {
7406 "Enable breakpoint"
7407 } else if is_phantom && !collides_with_existing {
7408 "Set breakpoint"
7409 } else {
7410 "Unset breakpoint"
7411 };
7412 let focus_handle = self.focus_handle.clone();
7413
7414 let meta = if is_rejected {
7415 SharedString::from("No executable code is associated with this line.")
7416 } else if collides_with_existing && !breakpoint.is_disabled() {
7417 SharedString::from(format!(
7418 "{alt_as_text}-click to disable,\nright-click for more options."
7419 ))
7420 } else {
7421 SharedString::from("Right-click for more options.")
7422 };
7423 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7424 .icon_size(IconSize::XSmall)
7425 .size(ui::ButtonSize::None)
7426 .when(is_rejected, |this| {
7427 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7428 })
7429 .icon_color(color)
7430 .style(ButtonStyle::Transparent)
7431 .on_click(cx.listener({
7432 let breakpoint = breakpoint.clone();
7433
7434 move |editor, event: &ClickEvent, window, cx| {
7435 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7436 BreakpointEditAction::InvertState
7437 } else {
7438 BreakpointEditAction::Toggle
7439 };
7440
7441 window.focus(&editor.focus_handle(cx));
7442 editor.edit_breakpoint_at_anchor(
7443 position,
7444 breakpoint.as_ref().clone(),
7445 edit_action,
7446 cx,
7447 );
7448 }
7449 }))
7450 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7451 editor.set_breakpoint_context_menu(
7452 row,
7453 Some(position),
7454 event.down.position,
7455 window,
7456 cx,
7457 );
7458 }))
7459 .tooltip(move |window, cx| {
7460 Tooltip::with_meta_in(
7461 primary_action_text,
7462 Some(&ToggleBreakpoint),
7463 meta.clone(),
7464 &focus_handle,
7465 window,
7466 cx,
7467 )
7468 })
7469 }
7470
7471 fn build_tasks_context(
7472 project: &Entity<Project>,
7473 buffer: &Entity<Buffer>,
7474 buffer_row: u32,
7475 tasks: &Arc<RunnableTasks>,
7476 cx: &mut Context<Self>,
7477 ) -> Task<Option<task::TaskContext>> {
7478 let position = Point::new(buffer_row, tasks.column);
7479 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7480 let location = Location {
7481 buffer: buffer.clone(),
7482 range: range_start..range_start,
7483 };
7484 // Fill in the environmental variables from the tree-sitter captures
7485 let mut captured_task_variables = TaskVariables::default();
7486 for (capture_name, value) in tasks.extra_variables.clone() {
7487 captured_task_variables.insert(
7488 task::VariableName::Custom(capture_name.into()),
7489 value.clone(),
7490 );
7491 }
7492 project.update(cx, |project, cx| {
7493 project.task_store().update(cx, |task_store, cx| {
7494 task_store.task_context_for_location(captured_task_variables, location, cx)
7495 })
7496 })
7497 }
7498
7499 pub fn spawn_nearest_task(
7500 &mut self,
7501 action: &SpawnNearestTask,
7502 window: &mut Window,
7503 cx: &mut Context<Self>,
7504 ) {
7505 let Some((workspace, _)) = self.workspace.clone() else {
7506 return;
7507 };
7508 let Some(project) = self.project.clone() else {
7509 return;
7510 };
7511
7512 // Try to find a closest, enclosing node using tree-sitter that has a
7513 // task
7514 let Some((buffer, buffer_row, tasks)) = self
7515 .find_enclosing_node_task(cx)
7516 // Or find the task that's closest in row-distance.
7517 .or_else(|| self.find_closest_task(cx))
7518 else {
7519 return;
7520 };
7521
7522 let reveal_strategy = action.reveal;
7523 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7524 cx.spawn_in(window, async move |_, cx| {
7525 let context = task_context.await?;
7526 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7527
7528 let resolved = &mut resolved_task.resolved;
7529 resolved.reveal = reveal_strategy;
7530
7531 workspace
7532 .update_in(cx, |workspace, window, cx| {
7533 workspace.schedule_resolved_task(
7534 task_source_kind,
7535 resolved_task,
7536 false,
7537 window,
7538 cx,
7539 );
7540 })
7541 .ok()
7542 })
7543 .detach();
7544 }
7545
7546 fn find_closest_task(
7547 &mut self,
7548 cx: &mut Context<Self>,
7549 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7550 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7551
7552 let ((buffer_id, row), tasks) = self
7553 .tasks
7554 .iter()
7555 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7556
7557 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7558 let tasks = Arc::new(tasks.to_owned());
7559 Some((buffer, *row, tasks))
7560 }
7561
7562 fn find_enclosing_node_task(
7563 &mut self,
7564 cx: &mut Context<Self>,
7565 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7566 let snapshot = self.buffer.read(cx).snapshot(cx);
7567 let offset = self.selections.newest::<usize>(cx).head();
7568 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7569 let buffer_id = excerpt.buffer().remote_id();
7570
7571 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7572 let mut cursor = layer.node().walk();
7573
7574 while cursor.goto_first_child_for_byte(offset).is_some() {
7575 if cursor.node().end_byte() == offset {
7576 cursor.goto_next_sibling();
7577 }
7578 }
7579
7580 // Ascend to the smallest ancestor that contains the range and has a task.
7581 loop {
7582 let node = cursor.node();
7583 let node_range = node.byte_range();
7584 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7585
7586 // Check if this node contains our offset
7587 if node_range.start <= offset && node_range.end >= offset {
7588 // If it contains offset, check for task
7589 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7590 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7591 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7592 }
7593 }
7594
7595 if !cursor.goto_parent() {
7596 break;
7597 }
7598 }
7599 None
7600 }
7601
7602 fn render_run_indicator(
7603 &self,
7604 _style: &EditorStyle,
7605 is_active: bool,
7606 row: DisplayRow,
7607 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7608 cx: &mut Context<Self>,
7609 ) -> IconButton {
7610 let color = Color::Muted;
7611 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7612
7613 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7614 .shape(ui::IconButtonShape::Square)
7615 .icon_size(IconSize::XSmall)
7616 .icon_color(color)
7617 .toggle_state(is_active)
7618 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7619 let quick_launch = e.down.button == MouseButton::Left;
7620 window.focus(&editor.focus_handle(cx));
7621 editor.toggle_code_actions(
7622 &ToggleCodeActions {
7623 deployed_from: Some(CodeActionSource::Indicator(row)),
7624 quick_launch,
7625 },
7626 window,
7627 cx,
7628 );
7629 }))
7630 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7631 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7632 }))
7633 }
7634
7635 pub fn context_menu_visible(&self) -> bool {
7636 !self.edit_prediction_preview_is_active()
7637 && self
7638 .context_menu
7639 .borrow()
7640 .as_ref()
7641 .map_or(false, |menu| menu.visible())
7642 }
7643
7644 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7645 self.context_menu
7646 .borrow()
7647 .as_ref()
7648 .map(|menu| menu.origin())
7649 }
7650
7651 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7652 self.context_menu_options = Some(options);
7653 }
7654
7655 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7656 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7657
7658 fn render_edit_prediction_popover(
7659 &mut self,
7660 text_bounds: &Bounds<Pixels>,
7661 content_origin: gpui::Point<Pixels>,
7662 right_margin: Pixels,
7663 editor_snapshot: &EditorSnapshot,
7664 visible_row_range: Range<DisplayRow>,
7665 scroll_top: f32,
7666 scroll_bottom: f32,
7667 line_layouts: &[LineWithInvisibles],
7668 line_height: Pixels,
7669 scroll_pixel_position: gpui::Point<Pixels>,
7670 newest_selection_head: Option<DisplayPoint>,
7671 editor_width: Pixels,
7672 style: &EditorStyle,
7673 window: &mut Window,
7674 cx: &mut App,
7675 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7676 if self.mode().is_minimap() {
7677 return None;
7678 }
7679 let active_inline_completion = self.active_inline_completion.as_ref()?;
7680
7681 if self.edit_prediction_visible_in_cursor_popover(true) {
7682 return None;
7683 }
7684
7685 match &active_inline_completion.completion {
7686 InlineCompletion::Move { target, .. } => {
7687 let target_display_point = target.to_display_point(editor_snapshot);
7688
7689 if self.edit_prediction_requires_modifier() {
7690 if !self.edit_prediction_preview_is_active() {
7691 return None;
7692 }
7693
7694 self.render_edit_prediction_modifier_jump_popover(
7695 text_bounds,
7696 content_origin,
7697 visible_row_range,
7698 line_layouts,
7699 line_height,
7700 scroll_pixel_position,
7701 newest_selection_head,
7702 target_display_point,
7703 window,
7704 cx,
7705 )
7706 } else {
7707 self.render_edit_prediction_eager_jump_popover(
7708 text_bounds,
7709 content_origin,
7710 editor_snapshot,
7711 visible_row_range,
7712 scroll_top,
7713 scroll_bottom,
7714 line_height,
7715 scroll_pixel_position,
7716 target_display_point,
7717 editor_width,
7718 window,
7719 cx,
7720 )
7721 }
7722 }
7723 InlineCompletion::Edit {
7724 display_mode: EditDisplayMode::Inline,
7725 ..
7726 } => None,
7727 InlineCompletion::Edit {
7728 display_mode: EditDisplayMode::TabAccept,
7729 edits,
7730 ..
7731 } => {
7732 let range = &edits.first()?.0;
7733 let target_display_point = range.end.to_display_point(editor_snapshot);
7734
7735 self.render_edit_prediction_end_of_line_popover(
7736 "Accept",
7737 editor_snapshot,
7738 visible_row_range,
7739 target_display_point,
7740 line_height,
7741 scroll_pixel_position,
7742 content_origin,
7743 editor_width,
7744 window,
7745 cx,
7746 )
7747 }
7748 InlineCompletion::Edit {
7749 edits,
7750 edit_preview,
7751 display_mode: EditDisplayMode::DiffPopover,
7752 snapshot,
7753 } => self.render_edit_prediction_diff_popover(
7754 text_bounds,
7755 content_origin,
7756 right_margin,
7757 editor_snapshot,
7758 visible_row_range,
7759 line_layouts,
7760 line_height,
7761 scroll_pixel_position,
7762 newest_selection_head,
7763 editor_width,
7764 style,
7765 edits,
7766 edit_preview,
7767 snapshot,
7768 window,
7769 cx,
7770 ),
7771 }
7772 }
7773
7774 fn render_edit_prediction_modifier_jump_popover(
7775 &mut self,
7776 text_bounds: &Bounds<Pixels>,
7777 content_origin: gpui::Point<Pixels>,
7778 visible_row_range: Range<DisplayRow>,
7779 line_layouts: &[LineWithInvisibles],
7780 line_height: Pixels,
7781 scroll_pixel_position: gpui::Point<Pixels>,
7782 newest_selection_head: Option<DisplayPoint>,
7783 target_display_point: DisplayPoint,
7784 window: &mut Window,
7785 cx: &mut App,
7786 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7787 let scrolled_content_origin =
7788 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7789
7790 const SCROLL_PADDING_Y: Pixels = px(12.);
7791
7792 if target_display_point.row() < visible_row_range.start {
7793 return self.render_edit_prediction_scroll_popover(
7794 |_| SCROLL_PADDING_Y,
7795 IconName::ArrowUp,
7796 visible_row_range,
7797 line_layouts,
7798 newest_selection_head,
7799 scrolled_content_origin,
7800 window,
7801 cx,
7802 );
7803 } else if target_display_point.row() >= visible_row_range.end {
7804 return self.render_edit_prediction_scroll_popover(
7805 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7806 IconName::ArrowDown,
7807 visible_row_range,
7808 line_layouts,
7809 newest_selection_head,
7810 scrolled_content_origin,
7811 window,
7812 cx,
7813 );
7814 }
7815
7816 const POLE_WIDTH: Pixels = px(2.);
7817
7818 let line_layout =
7819 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7820 let target_column = target_display_point.column() as usize;
7821
7822 let target_x = line_layout.x_for_index(target_column);
7823 let target_y =
7824 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7825
7826 let flag_on_right = target_x < text_bounds.size.width / 2.;
7827
7828 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7829 border_color.l += 0.001;
7830
7831 let mut element = v_flex()
7832 .items_end()
7833 .when(flag_on_right, |el| el.items_start())
7834 .child(if flag_on_right {
7835 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7836 .rounded_bl(px(0.))
7837 .rounded_tl(px(0.))
7838 .border_l_2()
7839 .border_color(border_color)
7840 } else {
7841 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7842 .rounded_br(px(0.))
7843 .rounded_tr(px(0.))
7844 .border_r_2()
7845 .border_color(border_color)
7846 })
7847 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7848 .into_any();
7849
7850 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7851
7852 let mut origin = scrolled_content_origin + point(target_x, target_y)
7853 - point(
7854 if flag_on_right {
7855 POLE_WIDTH
7856 } else {
7857 size.width - POLE_WIDTH
7858 },
7859 size.height - line_height,
7860 );
7861
7862 origin.x = origin.x.max(content_origin.x);
7863
7864 element.prepaint_at(origin, window, cx);
7865
7866 Some((element, origin))
7867 }
7868
7869 fn render_edit_prediction_scroll_popover(
7870 &mut self,
7871 to_y: impl Fn(Size<Pixels>) -> Pixels,
7872 scroll_icon: IconName,
7873 visible_row_range: Range<DisplayRow>,
7874 line_layouts: &[LineWithInvisibles],
7875 newest_selection_head: Option<DisplayPoint>,
7876 scrolled_content_origin: gpui::Point<Pixels>,
7877 window: &mut Window,
7878 cx: &mut App,
7879 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7880 let mut element = self
7881 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7882 .into_any();
7883
7884 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7885
7886 let cursor = newest_selection_head?;
7887 let cursor_row_layout =
7888 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7889 let cursor_column = cursor.column() as usize;
7890
7891 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7892
7893 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7894
7895 element.prepaint_at(origin, window, cx);
7896 Some((element, origin))
7897 }
7898
7899 fn render_edit_prediction_eager_jump_popover(
7900 &mut self,
7901 text_bounds: &Bounds<Pixels>,
7902 content_origin: gpui::Point<Pixels>,
7903 editor_snapshot: &EditorSnapshot,
7904 visible_row_range: Range<DisplayRow>,
7905 scroll_top: f32,
7906 scroll_bottom: f32,
7907 line_height: Pixels,
7908 scroll_pixel_position: gpui::Point<Pixels>,
7909 target_display_point: DisplayPoint,
7910 editor_width: Pixels,
7911 window: &mut Window,
7912 cx: &mut App,
7913 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7914 if target_display_point.row().as_f32() < scroll_top {
7915 let mut element = self
7916 .render_edit_prediction_line_popover(
7917 "Jump to Edit",
7918 Some(IconName::ArrowUp),
7919 window,
7920 cx,
7921 )?
7922 .into_any();
7923
7924 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7925 let offset = point(
7926 (text_bounds.size.width - size.width) / 2.,
7927 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7928 );
7929
7930 let origin = text_bounds.origin + offset;
7931 element.prepaint_at(origin, window, cx);
7932 Some((element, origin))
7933 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7934 let mut element = self
7935 .render_edit_prediction_line_popover(
7936 "Jump to Edit",
7937 Some(IconName::ArrowDown),
7938 window,
7939 cx,
7940 )?
7941 .into_any();
7942
7943 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7944 let offset = point(
7945 (text_bounds.size.width - size.width) / 2.,
7946 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7947 );
7948
7949 let origin = text_bounds.origin + offset;
7950 element.prepaint_at(origin, window, cx);
7951 Some((element, origin))
7952 } else {
7953 self.render_edit_prediction_end_of_line_popover(
7954 "Jump to Edit",
7955 editor_snapshot,
7956 visible_row_range,
7957 target_display_point,
7958 line_height,
7959 scroll_pixel_position,
7960 content_origin,
7961 editor_width,
7962 window,
7963 cx,
7964 )
7965 }
7966 }
7967
7968 fn render_edit_prediction_end_of_line_popover(
7969 self: &mut Editor,
7970 label: &'static str,
7971 editor_snapshot: &EditorSnapshot,
7972 visible_row_range: Range<DisplayRow>,
7973 target_display_point: DisplayPoint,
7974 line_height: Pixels,
7975 scroll_pixel_position: gpui::Point<Pixels>,
7976 content_origin: gpui::Point<Pixels>,
7977 editor_width: Pixels,
7978 window: &mut Window,
7979 cx: &mut App,
7980 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7981 let target_line_end = DisplayPoint::new(
7982 target_display_point.row(),
7983 editor_snapshot.line_len(target_display_point.row()),
7984 );
7985
7986 let mut element = self
7987 .render_edit_prediction_line_popover(label, None, window, cx)?
7988 .into_any();
7989
7990 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7991
7992 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
7993
7994 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
7995 let mut origin = start_point
7996 + line_origin
7997 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
7998 origin.x = origin.x.max(content_origin.x);
7999
8000 let max_x = content_origin.x + editor_width - size.width;
8001
8002 if origin.x > max_x {
8003 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8004
8005 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8006 origin.y += offset;
8007 IconName::ArrowUp
8008 } else {
8009 origin.y -= offset;
8010 IconName::ArrowDown
8011 };
8012
8013 element = self
8014 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8015 .into_any();
8016
8017 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8018
8019 origin.x = content_origin.x + editor_width - size.width - px(2.);
8020 }
8021
8022 element.prepaint_at(origin, window, cx);
8023 Some((element, origin))
8024 }
8025
8026 fn render_edit_prediction_diff_popover(
8027 self: &Editor,
8028 text_bounds: &Bounds<Pixels>,
8029 content_origin: gpui::Point<Pixels>,
8030 right_margin: Pixels,
8031 editor_snapshot: &EditorSnapshot,
8032 visible_row_range: Range<DisplayRow>,
8033 line_layouts: &[LineWithInvisibles],
8034 line_height: Pixels,
8035 scroll_pixel_position: gpui::Point<Pixels>,
8036 newest_selection_head: Option<DisplayPoint>,
8037 editor_width: Pixels,
8038 style: &EditorStyle,
8039 edits: &Vec<(Range<Anchor>, String)>,
8040 edit_preview: &Option<language::EditPreview>,
8041 snapshot: &language::BufferSnapshot,
8042 window: &mut Window,
8043 cx: &mut App,
8044 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8045 let edit_start = edits
8046 .first()
8047 .unwrap()
8048 .0
8049 .start
8050 .to_display_point(editor_snapshot);
8051 let edit_end = edits
8052 .last()
8053 .unwrap()
8054 .0
8055 .end
8056 .to_display_point(editor_snapshot);
8057
8058 let is_visible = visible_row_range.contains(&edit_start.row())
8059 || visible_row_range.contains(&edit_end.row());
8060 if !is_visible {
8061 return None;
8062 }
8063
8064 let highlighted_edits =
8065 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8066
8067 let styled_text = highlighted_edits.to_styled_text(&style.text);
8068 let line_count = highlighted_edits.text.lines().count();
8069
8070 const BORDER_WIDTH: Pixels = px(1.);
8071
8072 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8073 let has_keybind = keybind.is_some();
8074
8075 let mut element = h_flex()
8076 .items_start()
8077 .child(
8078 h_flex()
8079 .bg(cx.theme().colors().editor_background)
8080 .border(BORDER_WIDTH)
8081 .shadow_sm()
8082 .border_color(cx.theme().colors().border)
8083 .rounded_l_lg()
8084 .when(line_count > 1, |el| el.rounded_br_lg())
8085 .pr_1()
8086 .child(styled_text),
8087 )
8088 .child(
8089 h_flex()
8090 .h(line_height + BORDER_WIDTH * 2.)
8091 .px_1p5()
8092 .gap_1()
8093 // Workaround: For some reason, there's a gap if we don't do this
8094 .ml(-BORDER_WIDTH)
8095 .shadow(vec![gpui::BoxShadow {
8096 color: gpui::black().opacity(0.05),
8097 offset: point(px(1.), px(1.)),
8098 blur_radius: px(2.),
8099 spread_radius: px(0.),
8100 }])
8101 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8102 .border(BORDER_WIDTH)
8103 .border_color(cx.theme().colors().border)
8104 .rounded_r_lg()
8105 .id("edit_prediction_diff_popover_keybind")
8106 .when(!has_keybind, |el| {
8107 let status_colors = cx.theme().status();
8108
8109 el.bg(status_colors.error_background)
8110 .border_color(status_colors.error.opacity(0.6))
8111 .child(Icon::new(IconName::Info).color(Color::Error))
8112 .cursor_default()
8113 .hoverable_tooltip(move |_window, cx| {
8114 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8115 })
8116 })
8117 .children(keybind),
8118 )
8119 .into_any();
8120
8121 let longest_row =
8122 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8123 let longest_line_width = if visible_row_range.contains(&longest_row) {
8124 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8125 } else {
8126 layout_line(
8127 longest_row,
8128 editor_snapshot,
8129 style,
8130 editor_width,
8131 |_| false,
8132 window,
8133 cx,
8134 )
8135 .width
8136 };
8137
8138 let viewport_bounds =
8139 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8140 right: -right_margin,
8141 ..Default::default()
8142 });
8143
8144 let x_after_longest =
8145 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8146 - scroll_pixel_position.x;
8147
8148 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8149
8150 // Fully visible if it can be displayed within the window (allow overlapping other
8151 // panes). However, this is only allowed if the popover starts within text_bounds.
8152 let can_position_to_the_right = x_after_longest < text_bounds.right()
8153 && x_after_longest + element_bounds.width < viewport_bounds.right();
8154
8155 let mut origin = if can_position_to_the_right {
8156 point(
8157 x_after_longest,
8158 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8159 - scroll_pixel_position.y,
8160 )
8161 } else {
8162 let cursor_row = newest_selection_head.map(|head| head.row());
8163 let above_edit = edit_start
8164 .row()
8165 .0
8166 .checked_sub(line_count as u32)
8167 .map(DisplayRow);
8168 let below_edit = Some(edit_end.row() + 1);
8169 let above_cursor =
8170 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8171 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8172
8173 // Place the edit popover adjacent to the edit if there is a location
8174 // available that is onscreen and does not obscure the cursor. Otherwise,
8175 // place it adjacent to the cursor.
8176 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8177 .into_iter()
8178 .flatten()
8179 .find(|&start_row| {
8180 let end_row = start_row + line_count as u32;
8181 visible_row_range.contains(&start_row)
8182 && visible_row_range.contains(&end_row)
8183 && cursor_row.map_or(true, |cursor_row| {
8184 !((start_row..end_row).contains(&cursor_row))
8185 })
8186 })?;
8187
8188 content_origin
8189 + point(
8190 -scroll_pixel_position.x,
8191 row_target.as_f32() * line_height - scroll_pixel_position.y,
8192 )
8193 };
8194
8195 origin.x -= BORDER_WIDTH;
8196
8197 window.defer_draw(element, origin, 1);
8198
8199 // Do not return an element, since it will already be drawn due to defer_draw.
8200 None
8201 }
8202
8203 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8204 px(30.)
8205 }
8206
8207 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8208 if self.read_only(cx) {
8209 cx.theme().players().read_only()
8210 } else {
8211 self.style.as_ref().unwrap().local_player
8212 }
8213 }
8214
8215 fn render_edit_prediction_accept_keybind(
8216 &self,
8217 window: &mut Window,
8218 cx: &App,
8219 ) -> Option<AnyElement> {
8220 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8221 let accept_keystroke = accept_binding.keystroke()?;
8222
8223 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8224
8225 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8226 Color::Accent
8227 } else {
8228 Color::Muted
8229 };
8230
8231 h_flex()
8232 .px_0p5()
8233 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8234 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8235 .text_size(TextSize::XSmall.rems(cx))
8236 .child(h_flex().children(ui::render_modifiers(
8237 &accept_keystroke.modifiers,
8238 PlatformStyle::platform(),
8239 Some(modifiers_color),
8240 Some(IconSize::XSmall.rems().into()),
8241 true,
8242 )))
8243 .when(is_platform_style_mac, |parent| {
8244 parent.child(accept_keystroke.key.clone())
8245 })
8246 .when(!is_platform_style_mac, |parent| {
8247 parent.child(
8248 Key::new(
8249 util::capitalize(&accept_keystroke.key),
8250 Some(Color::Default),
8251 )
8252 .size(Some(IconSize::XSmall.rems().into())),
8253 )
8254 })
8255 .into_any()
8256 .into()
8257 }
8258
8259 fn render_edit_prediction_line_popover(
8260 &self,
8261 label: impl Into<SharedString>,
8262 icon: Option<IconName>,
8263 window: &mut Window,
8264 cx: &App,
8265 ) -> Option<Stateful<Div>> {
8266 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8267
8268 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8269 let has_keybind = keybind.is_some();
8270
8271 let result = h_flex()
8272 .id("ep-line-popover")
8273 .py_0p5()
8274 .pl_1()
8275 .pr(padding_right)
8276 .gap_1()
8277 .rounded_md()
8278 .border_1()
8279 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8280 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8281 .shadow_sm()
8282 .when(!has_keybind, |el| {
8283 let status_colors = cx.theme().status();
8284
8285 el.bg(status_colors.error_background)
8286 .border_color(status_colors.error.opacity(0.6))
8287 .pl_2()
8288 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8289 .cursor_default()
8290 .hoverable_tooltip(move |_window, cx| {
8291 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8292 })
8293 })
8294 .children(keybind)
8295 .child(
8296 Label::new(label)
8297 .size(LabelSize::Small)
8298 .when(!has_keybind, |el| {
8299 el.color(cx.theme().status().error.into()).strikethrough()
8300 }),
8301 )
8302 .when(!has_keybind, |el| {
8303 el.child(
8304 h_flex().ml_1().child(
8305 Icon::new(IconName::Info)
8306 .size(IconSize::Small)
8307 .color(cx.theme().status().error.into()),
8308 ),
8309 )
8310 })
8311 .when_some(icon, |element, icon| {
8312 element.child(
8313 div()
8314 .mt(px(1.5))
8315 .child(Icon::new(icon).size(IconSize::Small)),
8316 )
8317 });
8318
8319 Some(result)
8320 }
8321
8322 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8323 let accent_color = cx.theme().colors().text_accent;
8324 let editor_bg_color = cx.theme().colors().editor_background;
8325 editor_bg_color.blend(accent_color.opacity(0.1))
8326 }
8327
8328 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8329 let accent_color = cx.theme().colors().text_accent;
8330 let editor_bg_color = cx.theme().colors().editor_background;
8331 editor_bg_color.blend(accent_color.opacity(0.6))
8332 }
8333
8334 fn render_edit_prediction_cursor_popover(
8335 &self,
8336 min_width: Pixels,
8337 max_width: Pixels,
8338 cursor_point: Point,
8339 style: &EditorStyle,
8340 accept_keystroke: Option<&gpui::Keystroke>,
8341 _window: &Window,
8342 cx: &mut Context<Editor>,
8343 ) -> Option<AnyElement> {
8344 let provider = self.edit_prediction_provider.as_ref()?;
8345
8346 if provider.provider.needs_terms_acceptance(cx) {
8347 return Some(
8348 h_flex()
8349 .min_w(min_width)
8350 .flex_1()
8351 .px_2()
8352 .py_1()
8353 .gap_3()
8354 .elevation_2(cx)
8355 .hover(|style| style.bg(cx.theme().colors().element_hover))
8356 .id("accept-terms")
8357 .cursor_pointer()
8358 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8359 .on_click(cx.listener(|this, _event, window, cx| {
8360 cx.stop_propagation();
8361 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8362 window.dispatch_action(
8363 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8364 cx,
8365 );
8366 }))
8367 .child(
8368 h_flex()
8369 .flex_1()
8370 .gap_2()
8371 .child(Icon::new(IconName::ZedPredict))
8372 .child(Label::new("Accept Terms of Service"))
8373 .child(div().w_full())
8374 .child(
8375 Icon::new(IconName::ArrowUpRight)
8376 .color(Color::Muted)
8377 .size(IconSize::Small),
8378 )
8379 .into_any_element(),
8380 )
8381 .into_any(),
8382 );
8383 }
8384
8385 let is_refreshing = provider.provider.is_refreshing(cx);
8386
8387 fn pending_completion_container() -> Div {
8388 h_flex()
8389 .h_full()
8390 .flex_1()
8391 .gap_2()
8392 .child(Icon::new(IconName::ZedPredict))
8393 }
8394
8395 let completion = match &self.active_inline_completion {
8396 Some(prediction) => {
8397 if !self.has_visible_completions_menu() {
8398 const RADIUS: Pixels = px(6.);
8399 const BORDER_WIDTH: Pixels = px(1.);
8400
8401 return Some(
8402 h_flex()
8403 .elevation_2(cx)
8404 .border(BORDER_WIDTH)
8405 .border_color(cx.theme().colors().border)
8406 .when(accept_keystroke.is_none(), |el| {
8407 el.border_color(cx.theme().status().error)
8408 })
8409 .rounded(RADIUS)
8410 .rounded_tl(px(0.))
8411 .overflow_hidden()
8412 .child(div().px_1p5().child(match &prediction.completion {
8413 InlineCompletion::Move { target, snapshot } => {
8414 use text::ToPoint as _;
8415 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8416 {
8417 Icon::new(IconName::ZedPredictDown)
8418 } else {
8419 Icon::new(IconName::ZedPredictUp)
8420 }
8421 }
8422 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8423 }))
8424 .child(
8425 h_flex()
8426 .gap_1()
8427 .py_1()
8428 .px_2()
8429 .rounded_r(RADIUS - BORDER_WIDTH)
8430 .border_l_1()
8431 .border_color(cx.theme().colors().border)
8432 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8433 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8434 el.child(
8435 Label::new("Hold")
8436 .size(LabelSize::Small)
8437 .when(accept_keystroke.is_none(), |el| {
8438 el.strikethrough()
8439 })
8440 .line_height_style(LineHeightStyle::UiLabel),
8441 )
8442 })
8443 .id("edit_prediction_cursor_popover_keybind")
8444 .when(accept_keystroke.is_none(), |el| {
8445 let status_colors = cx.theme().status();
8446
8447 el.bg(status_colors.error_background)
8448 .border_color(status_colors.error.opacity(0.6))
8449 .child(Icon::new(IconName::Info).color(Color::Error))
8450 .cursor_default()
8451 .hoverable_tooltip(move |_window, cx| {
8452 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8453 .into()
8454 })
8455 })
8456 .when_some(
8457 accept_keystroke.as_ref(),
8458 |el, accept_keystroke| {
8459 el.child(h_flex().children(ui::render_modifiers(
8460 &accept_keystroke.modifiers,
8461 PlatformStyle::platform(),
8462 Some(Color::Default),
8463 Some(IconSize::XSmall.rems().into()),
8464 false,
8465 )))
8466 },
8467 ),
8468 )
8469 .into_any(),
8470 );
8471 }
8472
8473 self.render_edit_prediction_cursor_popover_preview(
8474 prediction,
8475 cursor_point,
8476 style,
8477 cx,
8478 )?
8479 }
8480
8481 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8482 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8483 stale_completion,
8484 cursor_point,
8485 style,
8486 cx,
8487 )?,
8488
8489 None => {
8490 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8491 }
8492 },
8493
8494 None => pending_completion_container().child(Label::new("No Prediction")),
8495 };
8496
8497 let completion = if is_refreshing {
8498 completion
8499 .with_animation(
8500 "loading-completion",
8501 Animation::new(Duration::from_secs(2))
8502 .repeat()
8503 .with_easing(pulsating_between(0.4, 0.8)),
8504 |label, delta| label.opacity(delta),
8505 )
8506 .into_any_element()
8507 } else {
8508 completion.into_any_element()
8509 };
8510
8511 let has_completion = self.active_inline_completion.is_some();
8512
8513 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8514 Some(
8515 h_flex()
8516 .min_w(min_width)
8517 .max_w(max_width)
8518 .flex_1()
8519 .elevation_2(cx)
8520 .border_color(cx.theme().colors().border)
8521 .child(
8522 div()
8523 .flex_1()
8524 .py_1()
8525 .px_2()
8526 .overflow_hidden()
8527 .child(completion),
8528 )
8529 .when_some(accept_keystroke, |el, accept_keystroke| {
8530 if !accept_keystroke.modifiers.modified() {
8531 return el;
8532 }
8533
8534 el.child(
8535 h_flex()
8536 .h_full()
8537 .border_l_1()
8538 .rounded_r_lg()
8539 .border_color(cx.theme().colors().border)
8540 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8541 .gap_1()
8542 .py_1()
8543 .px_2()
8544 .child(
8545 h_flex()
8546 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8547 .when(is_platform_style_mac, |parent| parent.gap_1())
8548 .child(h_flex().children(ui::render_modifiers(
8549 &accept_keystroke.modifiers,
8550 PlatformStyle::platform(),
8551 Some(if !has_completion {
8552 Color::Muted
8553 } else {
8554 Color::Default
8555 }),
8556 None,
8557 false,
8558 ))),
8559 )
8560 .child(Label::new("Preview").into_any_element())
8561 .opacity(if has_completion { 1.0 } else { 0.4 }),
8562 )
8563 })
8564 .into_any(),
8565 )
8566 }
8567
8568 fn render_edit_prediction_cursor_popover_preview(
8569 &self,
8570 completion: &InlineCompletionState,
8571 cursor_point: Point,
8572 style: &EditorStyle,
8573 cx: &mut Context<Editor>,
8574 ) -> Option<Div> {
8575 use text::ToPoint as _;
8576
8577 fn render_relative_row_jump(
8578 prefix: impl Into<String>,
8579 current_row: u32,
8580 target_row: u32,
8581 ) -> Div {
8582 let (row_diff, arrow) = if target_row < current_row {
8583 (current_row - target_row, IconName::ArrowUp)
8584 } else {
8585 (target_row - current_row, IconName::ArrowDown)
8586 };
8587
8588 h_flex()
8589 .child(
8590 Label::new(format!("{}{}", prefix.into(), row_diff))
8591 .color(Color::Muted)
8592 .size(LabelSize::Small),
8593 )
8594 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8595 }
8596
8597 match &completion.completion {
8598 InlineCompletion::Move {
8599 target, snapshot, ..
8600 } => Some(
8601 h_flex()
8602 .px_2()
8603 .gap_2()
8604 .flex_1()
8605 .child(
8606 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8607 Icon::new(IconName::ZedPredictDown)
8608 } else {
8609 Icon::new(IconName::ZedPredictUp)
8610 },
8611 )
8612 .child(Label::new("Jump to Edit")),
8613 ),
8614
8615 InlineCompletion::Edit {
8616 edits,
8617 edit_preview,
8618 snapshot,
8619 display_mode: _,
8620 } => {
8621 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8622
8623 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8624 &snapshot,
8625 &edits,
8626 edit_preview.as_ref()?,
8627 true,
8628 cx,
8629 )
8630 .first_line_preview();
8631
8632 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8633 .with_default_highlights(&style.text, highlighted_edits.highlights);
8634
8635 let preview = h_flex()
8636 .gap_1()
8637 .min_w_16()
8638 .child(styled_text)
8639 .when(has_more_lines, |parent| parent.child("…"));
8640
8641 let left = if first_edit_row != cursor_point.row {
8642 render_relative_row_jump("", cursor_point.row, first_edit_row)
8643 .into_any_element()
8644 } else {
8645 Icon::new(IconName::ZedPredict).into_any_element()
8646 };
8647
8648 Some(
8649 h_flex()
8650 .h_full()
8651 .flex_1()
8652 .gap_2()
8653 .pr_1()
8654 .overflow_x_hidden()
8655 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8656 .child(left)
8657 .child(preview),
8658 )
8659 }
8660 }
8661 }
8662
8663 pub fn render_context_menu(
8664 &self,
8665 style: &EditorStyle,
8666 max_height_in_lines: u32,
8667 window: &mut Window,
8668 cx: &mut Context<Editor>,
8669 ) -> Option<AnyElement> {
8670 let menu = self.context_menu.borrow();
8671 let menu = menu.as_ref()?;
8672 if !menu.visible() {
8673 return None;
8674 };
8675 Some(menu.render(style, max_height_in_lines, window, cx))
8676 }
8677
8678 fn render_context_menu_aside(
8679 &mut self,
8680 max_size: Size<Pixels>,
8681 window: &mut Window,
8682 cx: &mut Context<Editor>,
8683 ) -> Option<AnyElement> {
8684 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8685 if menu.visible() {
8686 menu.render_aside(self, max_size, window, cx)
8687 } else {
8688 None
8689 }
8690 })
8691 }
8692
8693 fn hide_context_menu(
8694 &mut self,
8695 window: &mut Window,
8696 cx: &mut Context<Self>,
8697 ) -> Option<CodeContextMenu> {
8698 cx.notify();
8699 self.completion_tasks.clear();
8700 let context_menu = self.context_menu.borrow_mut().take();
8701 self.stale_inline_completion_in_menu.take();
8702 self.update_visible_inline_completion(window, cx);
8703 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8704 if let Some(completion_provider) = &self.completion_provider {
8705 completion_provider.selection_changed(None, window, cx);
8706 }
8707 }
8708 context_menu
8709 }
8710
8711 fn show_snippet_choices(
8712 &mut self,
8713 choices: &Vec<String>,
8714 selection: Range<Anchor>,
8715 cx: &mut Context<Self>,
8716 ) {
8717 if selection.start.buffer_id.is_none() {
8718 return;
8719 }
8720 let buffer_id = selection.start.buffer_id.unwrap();
8721 let buffer = self.buffer().read(cx).buffer(buffer_id);
8722 let id = post_inc(&mut self.next_completion_id);
8723 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8724
8725 if let Some(buffer) = buffer {
8726 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8727 CompletionsMenu::new_snippet_choices(
8728 id,
8729 true,
8730 choices,
8731 selection,
8732 buffer,
8733 snippet_sort_order,
8734 ),
8735 ));
8736 }
8737 }
8738
8739 pub fn insert_snippet(
8740 &mut self,
8741 insertion_ranges: &[Range<usize>],
8742 snippet: Snippet,
8743 window: &mut Window,
8744 cx: &mut Context<Self>,
8745 ) -> Result<()> {
8746 struct Tabstop<T> {
8747 is_end_tabstop: bool,
8748 ranges: Vec<Range<T>>,
8749 choices: Option<Vec<String>>,
8750 }
8751
8752 let tabstops = self.buffer.update(cx, |buffer, cx| {
8753 let snippet_text: Arc<str> = snippet.text.clone().into();
8754 let edits = insertion_ranges
8755 .iter()
8756 .cloned()
8757 .map(|range| (range, snippet_text.clone()));
8758 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8759
8760 let snapshot = &*buffer.read(cx);
8761 let snippet = &snippet;
8762 snippet
8763 .tabstops
8764 .iter()
8765 .map(|tabstop| {
8766 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8767 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8768 });
8769 let mut tabstop_ranges = tabstop
8770 .ranges
8771 .iter()
8772 .flat_map(|tabstop_range| {
8773 let mut delta = 0_isize;
8774 insertion_ranges.iter().map(move |insertion_range| {
8775 let insertion_start = insertion_range.start as isize + delta;
8776 delta +=
8777 snippet.text.len() as isize - insertion_range.len() as isize;
8778
8779 let start = ((insertion_start + tabstop_range.start) as usize)
8780 .min(snapshot.len());
8781 let end = ((insertion_start + tabstop_range.end) as usize)
8782 .min(snapshot.len());
8783 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8784 })
8785 })
8786 .collect::<Vec<_>>();
8787 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8788
8789 Tabstop {
8790 is_end_tabstop,
8791 ranges: tabstop_ranges,
8792 choices: tabstop.choices.clone(),
8793 }
8794 })
8795 .collect::<Vec<_>>()
8796 });
8797 if let Some(tabstop) = tabstops.first() {
8798 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8799 s.select_ranges(tabstop.ranges.iter().cloned());
8800 });
8801
8802 if let Some(choices) = &tabstop.choices {
8803 if let Some(selection) = tabstop.ranges.first() {
8804 self.show_snippet_choices(choices, selection.clone(), cx)
8805 }
8806 }
8807
8808 // If we're already at the last tabstop and it's at the end of the snippet,
8809 // we're done, we don't need to keep the state around.
8810 if !tabstop.is_end_tabstop {
8811 let choices = tabstops
8812 .iter()
8813 .map(|tabstop| tabstop.choices.clone())
8814 .collect();
8815
8816 let ranges = tabstops
8817 .into_iter()
8818 .map(|tabstop| tabstop.ranges)
8819 .collect::<Vec<_>>();
8820
8821 self.snippet_stack.push(SnippetState {
8822 active_index: 0,
8823 ranges,
8824 choices,
8825 });
8826 }
8827
8828 // Check whether the just-entered snippet ends with an auto-closable bracket.
8829 if self.autoclose_regions.is_empty() {
8830 let snapshot = self.buffer.read(cx).snapshot(cx);
8831 for selection in &mut self.selections.all::<Point>(cx) {
8832 let selection_head = selection.head();
8833 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8834 continue;
8835 };
8836
8837 let mut bracket_pair = None;
8838 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8839 let prev_chars = snapshot
8840 .reversed_chars_at(selection_head)
8841 .collect::<String>();
8842 for (pair, enabled) in scope.brackets() {
8843 if enabled
8844 && pair.close
8845 && prev_chars.starts_with(pair.start.as_str())
8846 && next_chars.starts_with(pair.end.as_str())
8847 {
8848 bracket_pair = Some(pair.clone());
8849 break;
8850 }
8851 }
8852 if let Some(pair) = bracket_pair {
8853 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8854 let autoclose_enabled =
8855 self.use_autoclose && snapshot_settings.use_autoclose;
8856 if autoclose_enabled {
8857 let start = snapshot.anchor_after(selection_head);
8858 let end = snapshot.anchor_after(selection_head);
8859 self.autoclose_regions.push(AutocloseRegion {
8860 selection_id: selection.id,
8861 range: start..end,
8862 pair,
8863 });
8864 }
8865 }
8866 }
8867 }
8868 }
8869 Ok(())
8870 }
8871
8872 pub fn move_to_next_snippet_tabstop(
8873 &mut self,
8874 window: &mut Window,
8875 cx: &mut Context<Self>,
8876 ) -> bool {
8877 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8878 }
8879
8880 pub fn move_to_prev_snippet_tabstop(
8881 &mut self,
8882 window: &mut Window,
8883 cx: &mut Context<Self>,
8884 ) -> bool {
8885 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8886 }
8887
8888 pub fn move_to_snippet_tabstop(
8889 &mut self,
8890 bias: Bias,
8891 window: &mut Window,
8892 cx: &mut Context<Self>,
8893 ) -> bool {
8894 if let Some(mut snippet) = self.snippet_stack.pop() {
8895 match bias {
8896 Bias::Left => {
8897 if snippet.active_index > 0 {
8898 snippet.active_index -= 1;
8899 } else {
8900 self.snippet_stack.push(snippet);
8901 return false;
8902 }
8903 }
8904 Bias::Right => {
8905 if snippet.active_index + 1 < snippet.ranges.len() {
8906 snippet.active_index += 1;
8907 } else {
8908 self.snippet_stack.push(snippet);
8909 return false;
8910 }
8911 }
8912 }
8913 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8914 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8915 s.select_anchor_ranges(current_ranges.iter().cloned())
8916 });
8917
8918 if let Some(choices) = &snippet.choices[snippet.active_index] {
8919 if let Some(selection) = current_ranges.first() {
8920 self.show_snippet_choices(&choices, selection.clone(), cx);
8921 }
8922 }
8923
8924 // If snippet state is not at the last tabstop, push it back on the stack
8925 if snippet.active_index + 1 < snippet.ranges.len() {
8926 self.snippet_stack.push(snippet);
8927 }
8928 return true;
8929 }
8930 }
8931
8932 false
8933 }
8934
8935 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8936 self.transact(window, cx, |this, window, cx| {
8937 this.select_all(&SelectAll, window, cx);
8938 this.insert("", window, cx);
8939 });
8940 }
8941
8942 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8943 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8944 self.transact(window, cx, |this, window, cx| {
8945 this.select_autoclose_pair(window, cx);
8946 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8947 if !this.linked_edit_ranges.is_empty() {
8948 let selections = this.selections.all::<MultiBufferPoint>(cx);
8949 let snapshot = this.buffer.read(cx).snapshot(cx);
8950
8951 for selection in selections.iter() {
8952 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8953 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8954 if selection_start.buffer_id != selection_end.buffer_id {
8955 continue;
8956 }
8957 if let Some(ranges) =
8958 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8959 {
8960 for (buffer, entries) in ranges {
8961 linked_ranges.entry(buffer).or_default().extend(entries);
8962 }
8963 }
8964 }
8965 }
8966
8967 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8968 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8969 for selection in &mut selections {
8970 if selection.is_empty() {
8971 let old_head = selection.head();
8972 let mut new_head =
8973 movement::left(&display_map, old_head.to_display_point(&display_map))
8974 .to_point(&display_map);
8975 if let Some((buffer, line_buffer_range)) = display_map
8976 .buffer_snapshot
8977 .buffer_line_for_row(MultiBufferRow(old_head.row))
8978 {
8979 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
8980 let indent_len = match indent_size.kind {
8981 IndentKind::Space => {
8982 buffer.settings_at(line_buffer_range.start, cx).tab_size
8983 }
8984 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
8985 };
8986 if old_head.column <= indent_size.len && old_head.column > 0 {
8987 let indent_len = indent_len.get();
8988 new_head = cmp::min(
8989 new_head,
8990 MultiBufferPoint::new(
8991 old_head.row,
8992 ((old_head.column - 1) / indent_len) * indent_len,
8993 ),
8994 );
8995 }
8996 }
8997
8998 selection.set_head(new_head, SelectionGoal::None);
8999 }
9000 }
9001
9002 this.signature_help_state.set_backspace_pressed(true);
9003 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9004 s.select(selections)
9005 });
9006 this.insert("", window, cx);
9007 let empty_str: Arc<str> = Arc::from("");
9008 for (buffer, edits) in linked_ranges {
9009 let snapshot = buffer.read(cx).snapshot();
9010 use text::ToPoint as TP;
9011
9012 let edits = edits
9013 .into_iter()
9014 .map(|range| {
9015 let end_point = TP::to_point(&range.end, &snapshot);
9016 let mut start_point = TP::to_point(&range.start, &snapshot);
9017
9018 if end_point == start_point {
9019 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9020 .saturating_sub(1);
9021 start_point =
9022 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9023 };
9024
9025 (start_point..end_point, empty_str.clone())
9026 })
9027 .sorted_by_key(|(range, _)| range.start)
9028 .collect::<Vec<_>>();
9029 buffer.update(cx, |this, cx| {
9030 this.edit(edits, None, cx);
9031 })
9032 }
9033 this.refresh_inline_completion(true, false, window, cx);
9034 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9035 });
9036 }
9037
9038 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9039 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9040 self.transact(window, cx, |this, window, cx| {
9041 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9042 s.move_with(|map, selection| {
9043 if selection.is_empty() {
9044 let cursor = movement::right(map, selection.head());
9045 selection.end = cursor;
9046 selection.reversed = true;
9047 selection.goal = SelectionGoal::None;
9048 }
9049 })
9050 });
9051 this.insert("", window, cx);
9052 this.refresh_inline_completion(true, false, window, cx);
9053 });
9054 }
9055
9056 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9057 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9058 if self.move_to_prev_snippet_tabstop(window, cx) {
9059 return;
9060 }
9061 self.outdent(&Outdent, window, cx);
9062 }
9063
9064 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9065 if self.move_to_next_snippet_tabstop(window, cx) {
9066 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9067 return;
9068 }
9069 if self.read_only(cx) {
9070 return;
9071 }
9072 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9073 let mut selections = self.selections.all_adjusted(cx);
9074 let buffer = self.buffer.read(cx);
9075 let snapshot = buffer.snapshot(cx);
9076 let rows_iter = selections.iter().map(|s| s.head().row);
9077 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9078
9079 let has_some_cursor_in_whitespace = selections
9080 .iter()
9081 .filter(|selection| selection.is_empty())
9082 .any(|selection| {
9083 let cursor = selection.head();
9084 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9085 cursor.column < current_indent.len
9086 });
9087
9088 let mut edits = Vec::new();
9089 let mut prev_edited_row = 0;
9090 let mut row_delta = 0;
9091 for selection in &mut selections {
9092 if selection.start.row != prev_edited_row {
9093 row_delta = 0;
9094 }
9095 prev_edited_row = selection.end.row;
9096
9097 // If the selection is non-empty, then increase the indentation of the selected lines.
9098 if !selection.is_empty() {
9099 row_delta =
9100 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9101 continue;
9102 }
9103
9104 let cursor = selection.head();
9105 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9106 if let Some(suggested_indent) =
9107 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9108 {
9109 // Don't do anything if already at suggested indent
9110 // and there is any other cursor which is not
9111 if has_some_cursor_in_whitespace
9112 && cursor.column == current_indent.len
9113 && current_indent.len == suggested_indent.len
9114 {
9115 continue;
9116 }
9117
9118 // Adjust line and move cursor to suggested indent
9119 // if cursor is not at suggested indent
9120 if cursor.column < suggested_indent.len
9121 && cursor.column <= current_indent.len
9122 && current_indent.len <= suggested_indent.len
9123 {
9124 selection.start = Point::new(cursor.row, suggested_indent.len);
9125 selection.end = selection.start;
9126 if row_delta == 0 {
9127 edits.extend(Buffer::edit_for_indent_size_adjustment(
9128 cursor.row,
9129 current_indent,
9130 suggested_indent,
9131 ));
9132 row_delta = suggested_indent.len - current_indent.len;
9133 }
9134 continue;
9135 }
9136
9137 // If current indent is more than suggested indent
9138 // only move cursor to current indent and skip indent
9139 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9140 selection.start = Point::new(cursor.row, current_indent.len);
9141 selection.end = selection.start;
9142 continue;
9143 }
9144 }
9145
9146 // Otherwise, insert a hard or soft tab.
9147 let settings = buffer.language_settings_at(cursor, cx);
9148 let tab_size = if settings.hard_tabs {
9149 IndentSize::tab()
9150 } else {
9151 let tab_size = settings.tab_size.get();
9152 let indent_remainder = snapshot
9153 .text_for_range(Point::new(cursor.row, 0)..cursor)
9154 .flat_map(str::chars)
9155 .fold(row_delta % tab_size, |counter: u32, c| {
9156 if c == '\t' {
9157 0
9158 } else {
9159 (counter + 1) % tab_size
9160 }
9161 });
9162
9163 let chars_to_next_tab_stop = tab_size - indent_remainder;
9164 IndentSize::spaces(chars_to_next_tab_stop)
9165 };
9166 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9167 selection.end = selection.start;
9168 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9169 row_delta += tab_size.len;
9170 }
9171
9172 self.transact(window, cx, |this, window, cx| {
9173 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9174 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9175 s.select(selections)
9176 });
9177 this.refresh_inline_completion(true, false, window, cx);
9178 });
9179 }
9180
9181 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9182 if self.read_only(cx) {
9183 return;
9184 }
9185 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9186 let mut selections = self.selections.all::<Point>(cx);
9187 let mut prev_edited_row = 0;
9188 let mut row_delta = 0;
9189 let mut edits = Vec::new();
9190 let buffer = self.buffer.read(cx);
9191 let snapshot = buffer.snapshot(cx);
9192 for selection in &mut selections {
9193 if selection.start.row != prev_edited_row {
9194 row_delta = 0;
9195 }
9196 prev_edited_row = selection.end.row;
9197
9198 row_delta =
9199 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9200 }
9201
9202 self.transact(window, cx, |this, window, cx| {
9203 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9204 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9205 s.select(selections)
9206 });
9207 });
9208 }
9209
9210 fn indent_selection(
9211 buffer: &MultiBuffer,
9212 snapshot: &MultiBufferSnapshot,
9213 selection: &mut Selection<Point>,
9214 edits: &mut Vec<(Range<Point>, String)>,
9215 delta_for_start_row: u32,
9216 cx: &App,
9217 ) -> u32 {
9218 let settings = buffer.language_settings_at(selection.start, cx);
9219 let tab_size = settings.tab_size.get();
9220 let indent_kind = if settings.hard_tabs {
9221 IndentKind::Tab
9222 } else {
9223 IndentKind::Space
9224 };
9225 let mut start_row = selection.start.row;
9226 let mut end_row = selection.end.row + 1;
9227
9228 // If a selection ends at the beginning of a line, don't indent
9229 // that last line.
9230 if selection.end.column == 0 && selection.end.row > selection.start.row {
9231 end_row -= 1;
9232 }
9233
9234 // Avoid re-indenting a row that has already been indented by a
9235 // previous selection, but still update this selection's column
9236 // to reflect that indentation.
9237 if delta_for_start_row > 0 {
9238 start_row += 1;
9239 selection.start.column += delta_for_start_row;
9240 if selection.end.row == selection.start.row {
9241 selection.end.column += delta_for_start_row;
9242 }
9243 }
9244
9245 let mut delta_for_end_row = 0;
9246 let has_multiple_rows = start_row + 1 != end_row;
9247 for row in start_row..end_row {
9248 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9249 let indent_delta = match (current_indent.kind, indent_kind) {
9250 (IndentKind::Space, IndentKind::Space) => {
9251 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9252 IndentSize::spaces(columns_to_next_tab_stop)
9253 }
9254 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9255 (_, IndentKind::Tab) => IndentSize::tab(),
9256 };
9257
9258 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9259 0
9260 } else {
9261 selection.start.column
9262 };
9263 let row_start = Point::new(row, start);
9264 edits.push((
9265 row_start..row_start,
9266 indent_delta.chars().collect::<String>(),
9267 ));
9268
9269 // Update this selection's endpoints to reflect the indentation.
9270 if row == selection.start.row {
9271 selection.start.column += indent_delta.len;
9272 }
9273 if row == selection.end.row {
9274 selection.end.column += indent_delta.len;
9275 delta_for_end_row = indent_delta.len;
9276 }
9277 }
9278
9279 if selection.start.row == selection.end.row {
9280 delta_for_start_row + delta_for_end_row
9281 } else {
9282 delta_for_end_row
9283 }
9284 }
9285
9286 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9287 if self.read_only(cx) {
9288 return;
9289 }
9290 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9291 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9292 let selections = self.selections.all::<Point>(cx);
9293 let mut deletion_ranges = Vec::new();
9294 let mut last_outdent = None;
9295 {
9296 let buffer = self.buffer.read(cx);
9297 let snapshot = buffer.snapshot(cx);
9298 for selection in &selections {
9299 let settings = buffer.language_settings_at(selection.start, cx);
9300 let tab_size = settings.tab_size.get();
9301 let mut rows = selection.spanned_rows(false, &display_map);
9302
9303 // Avoid re-outdenting a row that has already been outdented by a
9304 // previous selection.
9305 if let Some(last_row) = last_outdent {
9306 if last_row == rows.start {
9307 rows.start = rows.start.next_row();
9308 }
9309 }
9310 let has_multiple_rows = rows.len() > 1;
9311 for row in rows.iter_rows() {
9312 let indent_size = snapshot.indent_size_for_line(row);
9313 if indent_size.len > 0 {
9314 let deletion_len = match indent_size.kind {
9315 IndentKind::Space => {
9316 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9317 if columns_to_prev_tab_stop == 0 {
9318 tab_size
9319 } else {
9320 columns_to_prev_tab_stop
9321 }
9322 }
9323 IndentKind::Tab => 1,
9324 };
9325 let start = if has_multiple_rows
9326 || deletion_len > selection.start.column
9327 || indent_size.len < selection.start.column
9328 {
9329 0
9330 } else {
9331 selection.start.column - deletion_len
9332 };
9333 deletion_ranges.push(
9334 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9335 );
9336 last_outdent = Some(row);
9337 }
9338 }
9339 }
9340 }
9341
9342 self.transact(window, cx, |this, window, cx| {
9343 this.buffer.update(cx, |buffer, cx| {
9344 let empty_str: Arc<str> = Arc::default();
9345 buffer.edit(
9346 deletion_ranges
9347 .into_iter()
9348 .map(|range| (range, empty_str.clone())),
9349 None,
9350 cx,
9351 );
9352 });
9353 let selections = this.selections.all::<usize>(cx);
9354 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9355 s.select(selections)
9356 });
9357 });
9358 }
9359
9360 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9361 if self.read_only(cx) {
9362 return;
9363 }
9364 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9365 let selections = self
9366 .selections
9367 .all::<usize>(cx)
9368 .into_iter()
9369 .map(|s| s.range());
9370
9371 self.transact(window, cx, |this, window, cx| {
9372 this.buffer.update(cx, |buffer, cx| {
9373 buffer.autoindent_ranges(selections, cx);
9374 });
9375 let selections = this.selections.all::<usize>(cx);
9376 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9377 s.select(selections)
9378 });
9379 });
9380 }
9381
9382 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9383 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9384 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9385 let selections = self.selections.all::<Point>(cx);
9386
9387 let mut new_cursors = Vec::new();
9388 let mut edit_ranges = Vec::new();
9389 let mut selections = selections.iter().peekable();
9390 while let Some(selection) = selections.next() {
9391 let mut rows = selection.spanned_rows(false, &display_map);
9392 let goal_display_column = selection.head().to_display_point(&display_map).column();
9393
9394 // Accumulate contiguous regions of rows that we want to delete.
9395 while let Some(next_selection) = selections.peek() {
9396 let next_rows = next_selection.spanned_rows(false, &display_map);
9397 if next_rows.start <= rows.end {
9398 rows.end = next_rows.end;
9399 selections.next().unwrap();
9400 } else {
9401 break;
9402 }
9403 }
9404
9405 let buffer = &display_map.buffer_snapshot;
9406 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9407 let edit_end;
9408 let cursor_buffer_row;
9409 if buffer.max_point().row >= rows.end.0 {
9410 // If there's a line after the range, delete the \n from the end of the row range
9411 // and position the cursor on the next line.
9412 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9413 cursor_buffer_row = rows.end;
9414 } else {
9415 // If there isn't a line after the range, delete the \n from the line before the
9416 // start of the row range and position the cursor there.
9417 edit_start = edit_start.saturating_sub(1);
9418 edit_end = buffer.len();
9419 cursor_buffer_row = rows.start.previous_row();
9420 }
9421
9422 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9423 *cursor.column_mut() =
9424 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9425
9426 new_cursors.push((
9427 selection.id,
9428 buffer.anchor_after(cursor.to_point(&display_map)),
9429 ));
9430 edit_ranges.push(edit_start..edit_end);
9431 }
9432
9433 self.transact(window, cx, |this, window, cx| {
9434 let buffer = this.buffer.update(cx, |buffer, cx| {
9435 let empty_str: Arc<str> = Arc::default();
9436 buffer.edit(
9437 edit_ranges
9438 .into_iter()
9439 .map(|range| (range, empty_str.clone())),
9440 None,
9441 cx,
9442 );
9443 buffer.snapshot(cx)
9444 });
9445 let new_selections = new_cursors
9446 .into_iter()
9447 .map(|(id, cursor)| {
9448 let cursor = cursor.to_point(&buffer);
9449 Selection {
9450 id,
9451 start: cursor,
9452 end: cursor,
9453 reversed: false,
9454 goal: SelectionGoal::None,
9455 }
9456 })
9457 .collect();
9458
9459 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9460 s.select(new_selections);
9461 });
9462 });
9463 }
9464
9465 pub fn join_lines_impl(
9466 &mut self,
9467 insert_whitespace: bool,
9468 window: &mut Window,
9469 cx: &mut Context<Self>,
9470 ) {
9471 if self.read_only(cx) {
9472 return;
9473 }
9474 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9475 for selection in self.selections.all::<Point>(cx) {
9476 let start = MultiBufferRow(selection.start.row);
9477 // Treat single line selections as if they include the next line. Otherwise this action
9478 // would do nothing for single line selections individual cursors.
9479 let end = if selection.start.row == selection.end.row {
9480 MultiBufferRow(selection.start.row + 1)
9481 } else {
9482 MultiBufferRow(selection.end.row)
9483 };
9484
9485 if let Some(last_row_range) = row_ranges.last_mut() {
9486 if start <= last_row_range.end {
9487 last_row_range.end = end;
9488 continue;
9489 }
9490 }
9491 row_ranges.push(start..end);
9492 }
9493
9494 let snapshot = self.buffer.read(cx).snapshot(cx);
9495 let mut cursor_positions = Vec::new();
9496 for row_range in &row_ranges {
9497 let anchor = snapshot.anchor_before(Point::new(
9498 row_range.end.previous_row().0,
9499 snapshot.line_len(row_range.end.previous_row()),
9500 ));
9501 cursor_positions.push(anchor..anchor);
9502 }
9503
9504 self.transact(window, cx, |this, window, cx| {
9505 for row_range in row_ranges.into_iter().rev() {
9506 for row in row_range.iter_rows().rev() {
9507 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9508 let next_line_row = row.next_row();
9509 let indent = snapshot.indent_size_for_line(next_line_row);
9510 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9511
9512 let replace =
9513 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9514 " "
9515 } else {
9516 ""
9517 };
9518
9519 this.buffer.update(cx, |buffer, cx| {
9520 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9521 });
9522 }
9523 }
9524
9525 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9526 s.select_anchor_ranges(cursor_positions)
9527 });
9528 });
9529 }
9530
9531 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9532 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9533 self.join_lines_impl(true, window, cx);
9534 }
9535
9536 pub fn sort_lines_case_sensitive(
9537 &mut self,
9538 _: &SortLinesCaseSensitive,
9539 window: &mut Window,
9540 cx: &mut Context<Self>,
9541 ) {
9542 self.manipulate_lines(window, cx, |lines| lines.sort())
9543 }
9544
9545 pub fn sort_lines_case_insensitive(
9546 &mut self,
9547 _: &SortLinesCaseInsensitive,
9548 window: &mut Window,
9549 cx: &mut Context<Self>,
9550 ) {
9551 self.manipulate_lines(window, cx, |lines| {
9552 lines.sort_by_key(|line| line.to_lowercase())
9553 })
9554 }
9555
9556 pub fn unique_lines_case_insensitive(
9557 &mut self,
9558 _: &UniqueLinesCaseInsensitive,
9559 window: &mut Window,
9560 cx: &mut Context<Self>,
9561 ) {
9562 self.manipulate_lines(window, cx, |lines| {
9563 let mut seen = HashSet::default();
9564 lines.retain(|line| seen.insert(line.to_lowercase()));
9565 })
9566 }
9567
9568 pub fn unique_lines_case_sensitive(
9569 &mut self,
9570 _: &UniqueLinesCaseSensitive,
9571 window: &mut Window,
9572 cx: &mut Context<Self>,
9573 ) {
9574 self.manipulate_lines(window, cx, |lines| {
9575 let mut seen = HashSet::default();
9576 lines.retain(|line| seen.insert(*line));
9577 })
9578 }
9579
9580 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9581 let Some(project) = self.project.clone() else {
9582 return;
9583 };
9584 self.reload(project, window, cx)
9585 .detach_and_notify_err(window, cx);
9586 }
9587
9588 pub fn restore_file(
9589 &mut self,
9590 _: &::git::RestoreFile,
9591 window: &mut Window,
9592 cx: &mut Context<Self>,
9593 ) {
9594 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9595 let mut buffer_ids = HashSet::default();
9596 let snapshot = self.buffer().read(cx).snapshot(cx);
9597 for selection in self.selections.all::<usize>(cx) {
9598 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9599 }
9600
9601 let buffer = self.buffer().read(cx);
9602 let ranges = buffer_ids
9603 .into_iter()
9604 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9605 .collect::<Vec<_>>();
9606
9607 self.restore_hunks_in_ranges(ranges, window, cx);
9608 }
9609
9610 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9611 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9612 let selections = self
9613 .selections
9614 .all(cx)
9615 .into_iter()
9616 .map(|s| s.range())
9617 .collect();
9618 self.restore_hunks_in_ranges(selections, window, cx);
9619 }
9620
9621 pub fn restore_hunks_in_ranges(
9622 &mut self,
9623 ranges: Vec<Range<Point>>,
9624 window: &mut Window,
9625 cx: &mut Context<Editor>,
9626 ) {
9627 let mut revert_changes = HashMap::default();
9628 let chunk_by = self
9629 .snapshot(window, cx)
9630 .hunks_for_ranges(ranges)
9631 .into_iter()
9632 .chunk_by(|hunk| hunk.buffer_id);
9633 for (buffer_id, hunks) in &chunk_by {
9634 let hunks = hunks.collect::<Vec<_>>();
9635 for hunk in &hunks {
9636 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9637 }
9638 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9639 }
9640 drop(chunk_by);
9641 if !revert_changes.is_empty() {
9642 self.transact(window, cx, |editor, window, cx| {
9643 editor.restore(revert_changes, window, cx);
9644 });
9645 }
9646 }
9647
9648 pub fn open_active_item_in_terminal(
9649 &mut self,
9650 _: &OpenInTerminal,
9651 window: &mut Window,
9652 cx: &mut Context<Self>,
9653 ) {
9654 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9655 let project_path = buffer.read(cx).project_path(cx)?;
9656 let project = self.project.as_ref()?.read(cx);
9657 let entry = project.entry_for_path(&project_path, cx)?;
9658 let parent = match &entry.canonical_path {
9659 Some(canonical_path) => canonical_path.to_path_buf(),
9660 None => project.absolute_path(&project_path, cx)?,
9661 }
9662 .parent()?
9663 .to_path_buf();
9664 Some(parent)
9665 }) {
9666 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9667 }
9668 }
9669
9670 fn set_breakpoint_context_menu(
9671 &mut self,
9672 display_row: DisplayRow,
9673 position: Option<Anchor>,
9674 clicked_point: gpui::Point<Pixels>,
9675 window: &mut Window,
9676 cx: &mut Context<Self>,
9677 ) {
9678 if !cx.has_flag::<DebuggerFeatureFlag>() {
9679 return;
9680 }
9681 let source = self
9682 .buffer
9683 .read(cx)
9684 .snapshot(cx)
9685 .anchor_before(Point::new(display_row.0, 0u32));
9686
9687 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9688
9689 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9690 self,
9691 source,
9692 clicked_point,
9693 context_menu,
9694 window,
9695 cx,
9696 );
9697 }
9698
9699 fn add_edit_breakpoint_block(
9700 &mut self,
9701 anchor: Anchor,
9702 breakpoint: &Breakpoint,
9703 edit_action: BreakpointPromptEditAction,
9704 window: &mut Window,
9705 cx: &mut Context<Self>,
9706 ) {
9707 let weak_editor = cx.weak_entity();
9708 let bp_prompt = cx.new(|cx| {
9709 BreakpointPromptEditor::new(
9710 weak_editor,
9711 anchor,
9712 breakpoint.clone(),
9713 edit_action,
9714 window,
9715 cx,
9716 )
9717 });
9718
9719 let height = bp_prompt.update(cx, |this, cx| {
9720 this.prompt
9721 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9722 });
9723 let cloned_prompt = bp_prompt.clone();
9724 let blocks = vec![BlockProperties {
9725 style: BlockStyle::Sticky,
9726 placement: BlockPlacement::Above(anchor),
9727 height: Some(height),
9728 render: Arc::new(move |cx| {
9729 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9730 cloned_prompt.clone().into_any_element()
9731 }),
9732 priority: 0,
9733 render_in_minimap: true,
9734 }];
9735
9736 let focus_handle = bp_prompt.focus_handle(cx);
9737 window.focus(&focus_handle);
9738
9739 let block_ids = self.insert_blocks(blocks, None, cx);
9740 bp_prompt.update(cx, |prompt, _| {
9741 prompt.add_block_ids(block_ids);
9742 });
9743 }
9744
9745 pub(crate) fn breakpoint_at_row(
9746 &self,
9747 row: u32,
9748 window: &mut Window,
9749 cx: &mut Context<Self>,
9750 ) -> Option<(Anchor, Breakpoint)> {
9751 let snapshot = self.snapshot(window, cx);
9752 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9753
9754 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9755 }
9756
9757 pub(crate) fn breakpoint_at_anchor(
9758 &self,
9759 breakpoint_position: Anchor,
9760 snapshot: &EditorSnapshot,
9761 cx: &mut Context<Self>,
9762 ) -> Option<(Anchor, Breakpoint)> {
9763 let project = self.project.clone()?;
9764
9765 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9766 snapshot
9767 .buffer_snapshot
9768 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9769 })?;
9770
9771 let enclosing_excerpt = breakpoint_position.excerpt_id;
9772 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
9773 let buffer_snapshot = buffer.read(cx).snapshot();
9774
9775 let row = buffer_snapshot
9776 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9777 .row;
9778
9779 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9780 let anchor_end = snapshot
9781 .buffer_snapshot
9782 .anchor_after(Point::new(row, line_len));
9783
9784 let bp = self
9785 .breakpoint_store
9786 .as_ref()?
9787 .read_with(cx, |breakpoint_store, cx| {
9788 breakpoint_store
9789 .breakpoints(
9790 &buffer,
9791 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9792 &buffer_snapshot,
9793 cx,
9794 )
9795 .next()
9796 .and_then(|(bp, _)| {
9797 let breakpoint_row = buffer_snapshot
9798 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9799 .row;
9800
9801 if breakpoint_row == row {
9802 snapshot
9803 .buffer_snapshot
9804 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9805 .map(|position| (position, bp.bp.clone()))
9806 } else {
9807 None
9808 }
9809 })
9810 });
9811 bp
9812 }
9813
9814 pub fn edit_log_breakpoint(
9815 &mut self,
9816 _: &EditLogBreakpoint,
9817 window: &mut Window,
9818 cx: &mut Context<Self>,
9819 ) {
9820 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9821 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9822 message: None,
9823 state: BreakpointState::Enabled,
9824 condition: None,
9825 hit_condition: None,
9826 });
9827
9828 self.add_edit_breakpoint_block(
9829 anchor,
9830 &breakpoint,
9831 BreakpointPromptEditAction::Log,
9832 window,
9833 cx,
9834 );
9835 }
9836 }
9837
9838 fn breakpoints_at_cursors(
9839 &self,
9840 window: &mut Window,
9841 cx: &mut Context<Self>,
9842 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9843 let snapshot = self.snapshot(window, cx);
9844 let cursors = self
9845 .selections
9846 .disjoint_anchors()
9847 .into_iter()
9848 .map(|selection| {
9849 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9850
9851 let breakpoint_position = self
9852 .breakpoint_at_row(cursor_position.row, window, cx)
9853 .map(|bp| bp.0)
9854 .unwrap_or_else(|| {
9855 snapshot
9856 .display_snapshot
9857 .buffer_snapshot
9858 .anchor_after(Point::new(cursor_position.row, 0))
9859 });
9860
9861 let breakpoint = self
9862 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9863 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9864
9865 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9866 })
9867 // 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.
9868 .collect::<HashMap<Anchor, _>>();
9869
9870 cursors.into_iter().collect()
9871 }
9872
9873 pub fn enable_breakpoint(
9874 &mut self,
9875 _: &crate::actions::EnableBreakpoint,
9876 window: &mut Window,
9877 cx: &mut Context<Self>,
9878 ) {
9879 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9880 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9881 continue;
9882 };
9883 self.edit_breakpoint_at_anchor(
9884 anchor,
9885 breakpoint,
9886 BreakpointEditAction::InvertState,
9887 cx,
9888 );
9889 }
9890 }
9891
9892 pub fn disable_breakpoint(
9893 &mut self,
9894 _: &crate::actions::DisableBreakpoint,
9895 window: &mut Window,
9896 cx: &mut Context<Self>,
9897 ) {
9898 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9899 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9900 continue;
9901 };
9902 self.edit_breakpoint_at_anchor(
9903 anchor,
9904 breakpoint,
9905 BreakpointEditAction::InvertState,
9906 cx,
9907 );
9908 }
9909 }
9910
9911 pub fn toggle_breakpoint(
9912 &mut self,
9913 _: &crate::actions::ToggleBreakpoint,
9914 window: &mut Window,
9915 cx: &mut Context<Self>,
9916 ) {
9917 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9918 if let Some(breakpoint) = breakpoint {
9919 self.edit_breakpoint_at_anchor(
9920 anchor,
9921 breakpoint,
9922 BreakpointEditAction::Toggle,
9923 cx,
9924 );
9925 } else {
9926 self.edit_breakpoint_at_anchor(
9927 anchor,
9928 Breakpoint::new_standard(),
9929 BreakpointEditAction::Toggle,
9930 cx,
9931 );
9932 }
9933 }
9934 }
9935
9936 pub fn edit_breakpoint_at_anchor(
9937 &mut self,
9938 breakpoint_position: Anchor,
9939 breakpoint: Breakpoint,
9940 edit_action: BreakpointEditAction,
9941 cx: &mut Context<Self>,
9942 ) {
9943 let Some(breakpoint_store) = &self.breakpoint_store else {
9944 return;
9945 };
9946
9947 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9948 if breakpoint_position == Anchor::min() {
9949 self.buffer()
9950 .read(cx)
9951 .excerpt_buffer_ids()
9952 .into_iter()
9953 .next()
9954 } else {
9955 None
9956 }
9957 }) else {
9958 return;
9959 };
9960
9961 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9962 return;
9963 };
9964
9965 breakpoint_store.update(cx, |breakpoint_store, cx| {
9966 breakpoint_store.toggle_breakpoint(
9967 buffer,
9968 BreakpointWithPosition {
9969 position: breakpoint_position.text_anchor,
9970 bp: breakpoint,
9971 },
9972 edit_action,
9973 cx,
9974 );
9975 });
9976
9977 cx.notify();
9978 }
9979
9980 #[cfg(any(test, feature = "test-support"))]
9981 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
9982 self.breakpoint_store.clone()
9983 }
9984
9985 pub fn prepare_restore_change(
9986 &self,
9987 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
9988 hunk: &MultiBufferDiffHunk,
9989 cx: &mut App,
9990 ) -> Option<()> {
9991 if hunk.is_created_file() {
9992 return None;
9993 }
9994 let buffer = self.buffer.read(cx);
9995 let diff = buffer.diff_for(hunk.buffer_id)?;
9996 let buffer = buffer.buffer(hunk.buffer_id)?;
9997 let buffer = buffer.read(cx);
9998 let original_text = diff
9999 .read(cx)
10000 .base_text()
10001 .as_rope()
10002 .slice(hunk.diff_base_byte_range.clone());
10003 let buffer_snapshot = buffer.snapshot();
10004 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10005 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10006 probe
10007 .0
10008 .start
10009 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10010 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10011 }) {
10012 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10013 Some(())
10014 } else {
10015 None
10016 }
10017 }
10018
10019 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10020 self.manipulate_lines(window, cx, |lines| lines.reverse())
10021 }
10022
10023 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10024 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10025 }
10026
10027 fn manipulate_lines<Fn>(
10028 &mut self,
10029 window: &mut Window,
10030 cx: &mut Context<Self>,
10031 mut callback: Fn,
10032 ) where
10033 Fn: FnMut(&mut Vec<&str>),
10034 {
10035 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10036
10037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10038 let buffer = self.buffer.read(cx).snapshot(cx);
10039
10040 let mut edits = Vec::new();
10041
10042 let selections = self.selections.all::<Point>(cx);
10043 let mut selections = selections.iter().peekable();
10044 let mut contiguous_row_selections = Vec::new();
10045 let mut new_selections = Vec::new();
10046 let mut added_lines = 0;
10047 let mut removed_lines = 0;
10048
10049 while let Some(selection) = selections.next() {
10050 let (start_row, end_row) = consume_contiguous_rows(
10051 &mut contiguous_row_selections,
10052 selection,
10053 &display_map,
10054 &mut selections,
10055 );
10056
10057 let start_point = Point::new(start_row.0, 0);
10058 let end_point = Point::new(
10059 end_row.previous_row().0,
10060 buffer.line_len(end_row.previous_row()),
10061 );
10062 let text = buffer
10063 .text_for_range(start_point..end_point)
10064 .collect::<String>();
10065
10066 let mut lines = text.split('\n').collect_vec();
10067
10068 let lines_before = lines.len();
10069 callback(&mut lines);
10070 let lines_after = lines.len();
10071
10072 edits.push((start_point..end_point, lines.join("\n")));
10073
10074 // Selections must change based on added and removed line count
10075 let start_row =
10076 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10077 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10078 new_selections.push(Selection {
10079 id: selection.id,
10080 start: start_row,
10081 end: end_row,
10082 goal: SelectionGoal::None,
10083 reversed: selection.reversed,
10084 });
10085
10086 if lines_after > lines_before {
10087 added_lines += lines_after - lines_before;
10088 } else if lines_before > lines_after {
10089 removed_lines += lines_before - lines_after;
10090 }
10091 }
10092
10093 self.transact(window, cx, |this, window, cx| {
10094 let buffer = this.buffer.update(cx, |buffer, cx| {
10095 buffer.edit(edits, None, cx);
10096 buffer.snapshot(cx)
10097 });
10098
10099 // Recalculate offsets on newly edited buffer
10100 let new_selections = new_selections
10101 .iter()
10102 .map(|s| {
10103 let start_point = Point::new(s.start.0, 0);
10104 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10105 Selection {
10106 id: s.id,
10107 start: buffer.point_to_offset(start_point),
10108 end: buffer.point_to_offset(end_point),
10109 goal: s.goal,
10110 reversed: s.reversed,
10111 }
10112 })
10113 .collect();
10114
10115 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10116 s.select(new_selections);
10117 });
10118
10119 this.request_autoscroll(Autoscroll::fit(), cx);
10120 });
10121 }
10122
10123 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10124 self.manipulate_text(window, cx, |text| {
10125 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10126 if has_upper_case_characters {
10127 text.to_lowercase()
10128 } else {
10129 text.to_uppercase()
10130 }
10131 })
10132 }
10133
10134 pub fn convert_to_upper_case(
10135 &mut self,
10136 _: &ConvertToUpperCase,
10137 window: &mut Window,
10138 cx: &mut Context<Self>,
10139 ) {
10140 self.manipulate_text(window, cx, |text| text.to_uppercase())
10141 }
10142
10143 pub fn convert_to_lower_case(
10144 &mut self,
10145 _: &ConvertToLowerCase,
10146 window: &mut Window,
10147 cx: &mut Context<Self>,
10148 ) {
10149 self.manipulate_text(window, cx, |text| text.to_lowercase())
10150 }
10151
10152 pub fn convert_to_title_case(
10153 &mut self,
10154 _: &ConvertToTitleCase,
10155 window: &mut Window,
10156 cx: &mut Context<Self>,
10157 ) {
10158 self.manipulate_text(window, cx, |text| {
10159 text.split('\n')
10160 .map(|line| line.to_case(Case::Title))
10161 .join("\n")
10162 })
10163 }
10164
10165 pub fn convert_to_snake_case(
10166 &mut self,
10167 _: &ConvertToSnakeCase,
10168 window: &mut Window,
10169 cx: &mut Context<Self>,
10170 ) {
10171 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10172 }
10173
10174 pub fn convert_to_kebab_case(
10175 &mut self,
10176 _: &ConvertToKebabCase,
10177 window: &mut Window,
10178 cx: &mut Context<Self>,
10179 ) {
10180 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10181 }
10182
10183 pub fn convert_to_upper_camel_case(
10184 &mut self,
10185 _: &ConvertToUpperCamelCase,
10186 window: &mut Window,
10187 cx: &mut Context<Self>,
10188 ) {
10189 self.manipulate_text(window, cx, |text| {
10190 text.split('\n')
10191 .map(|line| line.to_case(Case::UpperCamel))
10192 .join("\n")
10193 })
10194 }
10195
10196 pub fn convert_to_lower_camel_case(
10197 &mut self,
10198 _: &ConvertToLowerCamelCase,
10199 window: &mut Window,
10200 cx: &mut Context<Self>,
10201 ) {
10202 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10203 }
10204
10205 pub fn convert_to_opposite_case(
10206 &mut self,
10207 _: &ConvertToOppositeCase,
10208 window: &mut Window,
10209 cx: &mut Context<Self>,
10210 ) {
10211 self.manipulate_text(window, cx, |text| {
10212 text.chars()
10213 .fold(String::with_capacity(text.len()), |mut t, c| {
10214 if c.is_uppercase() {
10215 t.extend(c.to_lowercase());
10216 } else {
10217 t.extend(c.to_uppercase());
10218 }
10219 t
10220 })
10221 })
10222 }
10223
10224 pub fn convert_to_rot13(
10225 &mut self,
10226 _: &ConvertToRot13,
10227 window: &mut Window,
10228 cx: &mut Context<Self>,
10229 ) {
10230 self.manipulate_text(window, cx, |text| {
10231 text.chars()
10232 .map(|c| match c {
10233 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10234 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10235 _ => c,
10236 })
10237 .collect()
10238 })
10239 }
10240
10241 pub fn convert_to_rot47(
10242 &mut self,
10243 _: &ConvertToRot47,
10244 window: &mut Window,
10245 cx: &mut Context<Self>,
10246 ) {
10247 self.manipulate_text(window, cx, |text| {
10248 text.chars()
10249 .map(|c| {
10250 let code_point = c as u32;
10251 if code_point >= 33 && code_point <= 126 {
10252 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10253 }
10254 c
10255 })
10256 .collect()
10257 })
10258 }
10259
10260 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10261 where
10262 Fn: FnMut(&str) -> String,
10263 {
10264 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10265 let buffer = self.buffer.read(cx).snapshot(cx);
10266
10267 let mut new_selections = Vec::new();
10268 let mut edits = Vec::new();
10269 let mut selection_adjustment = 0i32;
10270
10271 for selection in self.selections.all::<usize>(cx) {
10272 let selection_is_empty = selection.is_empty();
10273
10274 let (start, end) = if selection_is_empty {
10275 let word_range = movement::surrounding_word(
10276 &display_map,
10277 selection.start.to_display_point(&display_map),
10278 );
10279 let start = word_range.start.to_offset(&display_map, Bias::Left);
10280 let end = word_range.end.to_offset(&display_map, Bias::Left);
10281 (start, end)
10282 } else {
10283 (selection.start, selection.end)
10284 };
10285
10286 let text = buffer.text_for_range(start..end).collect::<String>();
10287 let old_length = text.len() as i32;
10288 let text = callback(&text);
10289
10290 new_selections.push(Selection {
10291 start: (start as i32 - selection_adjustment) as usize,
10292 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10293 goal: SelectionGoal::None,
10294 ..selection
10295 });
10296
10297 selection_adjustment += old_length - text.len() as i32;
10298
10299 edits.push((start..end, text));
10300 }
10301
10302 self.transact(window, cx, |this, window, cx| {
10303 this.buffer.update(cx, |buffer, cx| {
10304 buffer.edit(edits, None, cx);
10305 });
10306
10307 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10308 s.select(new_selections);
10309 });
10310
10311 this.request_autoscroll(Autoscroll::fit(), cx);
10312 });
10313 }
10314
10315 pub fn duplicate(
10316 &mut self,
10317 upwards: bool,
10318 whole_lines: bool,
10319 window: &mut Window,
10320 cx: &mut Context<Self>,
10321 ) {
10322 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10323
10324 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10325 let buffer = &display_map.buffer_snapshot;
10326 let selections = self.selections.all::<Point>(cx);
10327
10328 let mut edits = Vec::new();
10329 let mut selections_iter = selections.iter().peekable();
10330 while let Some(selection) = selections_iter.next() {
10331 let mut rows = selection.spanned_rows(false, &display_map);
10332 // duplicate line-wise
10333 if whole_lines || selection.start == selection.end {
10334 // Avoid duplicating the same lines twice.
10335 while let Some(next_selection) = selections_iter.peek() {
10336 let next_rows = next_selection.spanned_rows(false, &display_map);
10337 if next_rows.start < rows.end {
10338 rows.end = next_rows.end;
10339 selections_iter.next().unwrap();
10340 } else {
10341 break;
10342 }
10343 }
10344
10345 // Copy the text from the selected row region and splice it either at the start
10346 // or end of the region.
10347 let start = Point::new(rows.start.0, 0);
10348 let end = Point::new(
10349 rows.end.previous_row().0,
10350 buffer.line_len(rows.end.previous_row()),
10351 );
10352 let text = buffer
10353 .text_for_range(start..end)
10354 .chain(Some("\n"))
10355 .collect::<String>();
10356 let insert_location = if upwards {
10357 Point::new(rows.end.0, 0)
10358 } else {
10359 start
10360 };
10361 edits.push((insert_location..insert_location, text));
10362 } else {
10363 // duplicate character-wise
10364 let start = selection.start;
10365 let end = selection.end;
10366 let text = buffer.text_for_range(start..end).collect::<String>();
10367 edits.push((selection.end..selection.end, text));
10368 }
10369 }
10370
10371 self.transact(window, cx, |this, _, cx| {
10372 this.buffer.update(cx, |buffer, cx| {
10373 buffer.edit(edits, None, cx);
10374 });
10375
10376 this.request_autoscroll(Autoscroll::fit(), cx);
10377 });
10378 }
10379
10380 pub fn duplicate_line_up(
10381 &mut self,
10382 _: &DuplicateLineUp,
10383 window: &mut Window,
10384 cx: &mut Context<Self>,
10385 ) {
10386 self.duplicate(true, true, window, cx);
10387 }
10388
10389 pub fn duplicate_line_down(
10390 &mut self,
10391 _: &DuplicateLineDown,
10392 window: &mut Window,
10393 cx: &mut Context<Self>,
10394 ) {
10395 self.duplicate(false, true, window, cx);
10396 }
10397
10398 pub fn duplicate_selection(
10399 &mut self,
10400 _: &DuplicateSelection,
10401 window: &mut Window,
10402 cx: &mut Context<Self>,
10403 ) {
10404 self.duplicate(false, false, window, cx);
10405 }
10406
10407 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10408 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10409
10410 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10411 let buffer = self.buffer.read(cx).snapshot(cx);
10412
10413 let mut edits = Vec::new();
10414 let mut unfold_ranges = Vec::new();
10415 let mut refold_creases = Vec::new();
10416
10417 let selections = self.selections.all::<Point>(cx);
10418 let mut selections = selections.iter().peekable();
10419 let mut contiguous_row_selections = Vec::new();
10420 let mut new_selections = Vec::new();
10421
10422 while let Some(selection) = selections.next() {
10423 // Find all the selections that span a contiguous row range
10424 let (start_row, end_row) = consume_contiguous_rows(
10425 &mut contiguous_row_selections,
10426 selection,
10427 &display_map,
10428 &mut selections,
10429 );
10430
10431 // Move the text spanned by the row range to be before the line preceding the row range
10432 if start_row.0 > 0 {
10433 let range_to_move = Point::new(
10434 start_row.previous_row().0,
10435 buffer.line_len(start_row.previous_row()),
10436 )
10437 ..Point::new(
10438 end_row.previous_row().0,
10439 buffer.line_len(end_row.previous_row()),
10440 );
10441 let insertion_point = display_map
10442 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10443 .0;
10444
10445 // Don't move lines across excerpts
10446 if buffer
10447 .excerpt_containing(insertion_point..range_to_move.end)
10448 .is_some()
10449 {
10450 let text = buffer
10451 .text_for_range(range_to_move.clone())
10452 .flat_map(|s| s.chars())
10453 .skip(1)
10454 .chain(['\n'])
10455 .collect::<String>();
10456
10457 edits.push((
10458 buffer.anchor_after(range_to_move.start)
10459 ..buffer.anchor_before(range_to_move.end),
10460 String::new(),
10461 ));
10462 let insertion_anchor = buffer.anchor_after(insertion_point);
10463 edits.push((insertion_anchor..insertion_anchor, text));
10464
10465 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10466
10467 // Move selections up
10468 new_selections.extend(contiguous_row_selections.drain(..).map(
10469 |mut selection| {
10470 selection.start.row -= row_delta;
10471 selection.end.row -= row_delta;
10472 selection
10473 },
10474 ));
10475
10476 // Move folds up
10477 unfold_ranges.push(range_to_move.clone());
10478 for fold in display_map.folds_in_range(
10479 buffer.anchor_before(range_to_move.start)
10480 ..buffer.anchor_after(range_to_move.end),
10481 ) {
10482 let mut start = fold.range.start.to_point(&buffer);
10483 let mut end = fold.range.end.to_point(&buffer);
10484 start.row -= row_delta;
10485 end.row -= row_delta;
10486 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10487 }
10488 }
10489 }
10490
10491 // If we didn't move line(s), preserve the existing selections
10492 new_selections.append(&mut contiguous_row_selections);
10493 }
10494
10495 self.transact(window, cx, |this, window, cx| {
10496 this.unfold_ranges(&unfold_ranges, true, true, cx);
10497 this.buffer.update(cx, |buffer, cx| {
10498 for (range, text) in edits {
10499 buffer.edit([(range, text)], None, cx);
10500 }
10501 });
10502 this.fold_creases(refold_creases, true, window, cx);
10503 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10504 s.select(new_selections);
10505 })
10506 });
10507 }
10508
10509 pub fn move_line_down(
10510 &mut self,
10511 _: &MoveLineDown,
10512 window: &mut Window,
10513 cx: &mut Context<Self>,
10514 ) {
10515 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10516
10517 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10518 let buffer = self.buffer.read(cx).snapshot(cx);
10519
10520 let mut edits = Vec::new();
10521 let mut unfold_ranges = Vec::new();
10522 let mut refold_creases = Vec::new();
10523
10524 let selections = self.selections.all::<Point>(cx);
10525 let mut selections = selections.iter().peekable();
10526 let mut contiguous_row_selections = Vec::new();
10527 let mut new_selections = Vec::new();
10528
10529 while let Some(selection) = selections.next() {
10530 // Find all the selections that span a contiguous row range
10531 let (start_row, end_row) = consume_contiguous_rows(
10532 &mut contiguous_row_selections,
10533 selection,
10534 &display_map,
10535 &mut selections,
10536 );
10537
10538 // Move the text spanned by the row range to be after the last line of the row range
10539 if end_row.0 <= buffer.max_point().row {
10540 let range_to_move =
10541 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10542 let insertion_point = display_map
10543 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10544 .0;
10545
10546 // Don't move lines across excerpt boundaries
10547 if buffer
10548 .excerpt_containing(range_to_move.start..insertion_point)
10549 .is_some()
10550 {
10551 let mut text = String::from("\n");
10552 text.extend(buffer.text_for_range(range_to_move.clone()));
10553 text.pop(); // Drop trailing newline
10554 edits.push((
10555 buffer.anchor_after(range_to_move.start)
10556 ..buffer.anchor_before(range_to_move.end),
10557 String::new(),
10558 ));
10559 let insertion_anchor = buffer.anchor_after(insertion_point);
10560 edits.push((insertion_anchor..insertion_anchor, text));
10561
10562 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10563
10564 // Move selections down
10565 new_selections.extend(contiguous_row_selections.drain(..).map(
10566 |mut selection| {
10567 selection.start.row += row_delta;
10568 selection.end.row += row_delta;
10569 selection
10570 },
10571 ));
10572
10573 // Move folds down
10574 unfold_ranges.push(range_to_move.clone());
10575 for fold in display_map.folds_in_range(
10576 buffer.anchor_before(range_to_move.start)
10577 ..buffer.anchor_after(range_to_move.end),
10578 ) {
10579 let mut start = fold.range.start.to_point(&buffer);
10580 let mut end = fold.range.end.to_point(&buffer);
10581 start.row += row_delta;
10582 end.row += row_delta;
10583 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10584 }
10585 }
10586 }
10587
10588 // If we didn't move line(s), preserve the existing selections
10589 new_selections.append(&mut contiguous_row_selections);
10590 }
10591
10592 self.transact(window, cx, |this, window, cx| {
10593 this.unfold_ranges(&unfold_ranges, true, true, cx);
10594 this.buffer.update(cx, |buffer, cx| {
10595 for (range, text) in edits {
10596 buffer.edit([(range, text)], None, cx);
10597 }
10598 });
10599 this.fold_creases(refold_creases, true, window, cx);
10600 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10601 s.select(new_selections)
10602 });
10603 });
10604 }
10605
10606 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10607 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10608 let text_layout_details = &self.text_layout_details(window);
10609 self.transact(window, cx, |this, window, cx| {
10610 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10611 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10612 s.move_with(|display_map, selection| {
10613 if !selection.is_empty() {
10614 return;
10615 }
10616
10617 let mut head = selection.head();
10618 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10619 if head.column() == display_map.line_len(head.row()) {
10620 transpose_offset = display_map
10621 .buffer_snapshot
10622 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10623 }
10624
10625 if transpose_offset == 0 {
10626 return;
10627 }
10628
10629 *head.column_mut() += 1;
10630 head = display_map.clip_point(head, Bias::Right);
10631 let goal = SelectionGoal::HorizontalPosition(
10632 display_map
10633 .x_for_display_point(head, text_layout_details)
10634 .into(),
10635 );
10636 selection.collapse_to(head, goal);
10637
10638 let transpose_start = display_map
10639 .buffer_snapshot
10640 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10641 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10642 let transpose_end = display_map
10643 .buffer_snapshot
10644 .clip_offset(transpose_offset + 1, Bias::Right);
10645 if let Some(ch) =
10646 display_map.buffer_snapshot.chars_at(transpose_start).next()
10647 {
10648 edits.push((transpose_start..transpose_offset, String::new()));
10649 edits.push((transpose_end..transpose_end, ch.to_string()));
10650 }
10651 }
10652 });
10653 edits
10654 });
10655 this.buffer
10656 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10657 let selections = this.selections.all::<usize>(cx);
10658 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10659 s.select(selections);
10660 });
10661 });
10662 }
10663
10664 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10665 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10666 self.rewrap_impl(RewrapOptions::default(), cx)
10667 }
10668
10669 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10670 let buffer = self.buffer.read(cx).snapshot(cx);
10671 let selections = self.selections.all::<Point>(cx);
10672 let mut selections = selections.iter().peekable();
10673
10674 let mut edits = Vec::new();
10675 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10676
10677 while let Some(selection) = selections.next() {
10678 let mut start_row = selection.start.row;
10679 let mut end_row = selection.end.row;
10680
10681 // Skip selections that overlap with a range that has already been rewrapped.
10682 let selection_range = start_row..end_row;
10683 if rewrapped_row_ranges
10684 .iter()
10685 .any(|range| range.overlaps(&selection_range))
10686 {
10687 continue;
10688 }
10689
10690 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10691
10692 // Since not all lines in the selection may be at the same indent
10693 // level, choose the indent size that is the most common between all
10694 // of the lines.
10695 //
10696 // If there is a tie, we use the deepest indent.
10697 let (indent_size, indent_end) = {
10698 let mut indent_size_occurrences = HashMap::default();
10699 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10700
10701 for row in start_row..=end_row {
10702 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10703 rows_by_indent_size.entry(indent).or_default().push(row);
10704 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10705 }
10706
10707 let indent_size = indent_size_occurrences
10708 .into_iter()
10709 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10710 .map(|(indent, _)| indent)
10711 .unwrap_or_default();
10712 let row = rows_by_indent_size[&indent_size][0];
10713 let indent_end = Point::new(row, indent_size.len);
10714
10715 (indent_size, indent_end)
10716 };
10717
10718 let mut line_prefix = indent_size.chars().collect::<String>();
10719
10720 let mut inside_comment = false;
10721 if let Some(comment_prefix) =
10722 buffer
10723 .language_scope_at(selection.head())
10724 .and_then(|language| {
10725 language
10726 .line_comment_prefixes()
10727 .iter()
10728 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10729 .cloned()
10730 })
10731 {
10732 line_prefix.push_str(&comment_prefix);
10733 inside_comment = true;
10734 }
10735
10736 let language_settings = buffer.language_settings_at(selection.head(), cx);
10737 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10738 RewrapBehavior::InComments => inside_comment,
10739 RewrapBehavior::InSelections => !selection.is_empty(),
10740 RewrapBehavior::Anywhere => true,
10741 };
10742
10743 let should_rewrap = options.override_language_settings
10744 || allow_rewrap_based_on_language
10745 || self.hard_wrap.is_some();
10746 if !should_rewrap {
10747 continue;
10748 }
10749
10750 if selection.is_empty() {
10751 'expand_upwards: while start_row > 0 {
10752 let prev_row = start_row - 1;
10753 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10754 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10755 {
10756 start_row = prev_row;
10757 } else {
10758 break 'expand_upwards;
10759 }
10760 }
10761
10762 'expand_downwards: while end_row < buffer.max_point().row {
10763 let next_row = end_row + 1;
10764 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10765 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10766 {
10767 end_row = next_row;
10768 } else {
10769 break 'expand_downwards;
10770 }
10771 }
10772 }
10773
10774 let start = Point::new(start_row, 0);
10775 let start_offset = start.to_offset(&buffer);
10776 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10777 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10778 let Some(lines_without_prefixes) = selection_text
10779 .lines()
10780 .map(|line| {
10781 line.strip_prefix(&line_prefix)
10782 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10783 .with_context(|| {
10784 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10785 })
10786 })
10787 .collect::<Result<Vec<_>, _>>()
10788 .log_err()
10789 else {
10790 continue;
10791 };
10792
10793 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10794 buffer
10795 .language_settings_at(Point::new(start_row, 0), cx)
10796 .preferred_line_length as usize
10797 });
10798 let wrapped_text = wrap_with_prefix(
10799 line_prefix,
10800 lines_without_prefixes.join("\n"),
10801 wrap_column,
10802 tab_size,
10803 options.preserve_existing_whitespace,
10804 );
10805
10806 // TODO: should always use char-based diff while still supporting cursor behavior that
10807 // matches vim.
10808 let mut diff_options = DiffOptions::default();
10809 if options.override_language_settings {
10810 diff_options.max_word_diff_len = 0;
10811 diff_options.max_word_diff_line_count = 0;
10812 } else {
10813 diff_options.max_word_diff_len = usize::MAX;
10814 diff_options.max_word_diff_line_count = usize::MAX;
10815 }
10816
10817 for (old_range, new_text) in
10818 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10819 {
10820 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10821 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10822 edits.push((edit_start..edit_end, new_text));
10823 }
10824
10825 rewrapped_row_ranges.push(start_row..=end_row);
10826 }
10827
10828 self.buffer
10829 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10830 }
10831
10832 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10833 let mut text = String::new();
10834 let buffer = self.buffer.read(cx).snapshot(cx);
10835 let mut selections = self.selections.all::<Point>(cx);
10836 let mut clipboard_selections = Vec::with_capacity(selections.len());
10837 {
10838 let max_point = buffer.max_point();
10839 let mut is_first = true;
10840 for selection in &mut selections {
10841 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10842 if is_entire_line {
10843 selection.start = Point::new(selection.start.row, 0);
10844 if !selection.is_empty() && selection.end.column == 0 {
10845 selection.end = cmp::min(max_point, selection.end);
10846 } else {
10847 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10848 }
10849 selection.goal = SelectionGoal::None;
10850 }
10851 if is_first {
10852 is_first = false;
10853 } else {
10854 text += "\n";
10855 }
10856 let mut len = 0;
10857 for chunk in buffer.text_for_range(selection.start..selection.end) {
10858 text.push_str(chunk);
10859 len += chunk.len();
10860 }
10861 clipboard_selections.push(ClipboardSelection {
10862 len,
10863 is_entire_line,
10864 first_line_indent: buffer
10865 .indent_size_for_line(MultiBufferRow(selection.start.row))
10866 .len,
10867 });
10868 }
10869 }
10870
10871 self.transact(window, cx, |this, window, cx| {
10872 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10873 s.select(selections);
10874 });
10875 this.insert("", window, cx);
10876 });
10877 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10878 }
10879
10880 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10881 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10882 let item = self.cut_common(window, cx);
10883 cx.write_to_clipboard(item);
10884 }
10885
10886 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10887 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10888 self.change_selections(None, window, cx, |s| {
10889 s.move_with(|snapshot, sel| {
10890 if sel.is_empty() {
10891 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10892 }
10893 });
10894 });
10895 let item = self.cut_common(window, cx);
10896 cx.set_global(KillRing(item))
10897 }
10898
10899 pub fn kill_ring_yank(
10900 &mut self,
10901 _: &KillRingYank,
10902 window: &mut Window,
10903 cx: &mut Context<Self>,
10904 ) {
10905 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10906 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10907 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10908 (kill_ring.text().to_string(), kill_ring.metadata_json())
10909 } else {
10910 return;
10911 }
10912 } else {
10913 return;
10914 };
10915 self.do_paste(&text, metadata, false, window, cx);
10916 }
10917
10918 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10919 self.do_copy(true, cx);
10920 }
10921
10922 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10923 self.do_copy(false, cx);
10924 }
10925
10926 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10927 let selections = self.selections.all::<Point>(cx);
10928 let buffer = self.buffer.read(cx).read(cx);
10929 let mut text = String::new();
10930
10931 let mut clipboard_selections = Vec::with_capacity(selections.len());
10932 {
10933 let max_point = buffer.max_point();
10934 let mut is_first = true;
10935 for selection in &selections {
10936 let mut start = selection.start;
10937 let mut end = selection.end;
10938 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10939 if is_entire_line {
10940 start = Point::new(start.row, 0);
10941 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10942 }
10943
10944 let mut trimmed_selections = Vec::new();
10945 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10946 let row = MultiBufferRow(start.row);
10947 let first_indent = buffer.indent_size_for_line(row);
10948 if first_indent.len == 0 || start.column > first_indent.len {
10949 trimmed_selections.push(start..end);
10950 } else {
10951 trimmed_selections.push(
10952 Point::new(row.0, first_indent.len)
10953 ..Point::new(row.0, buffer.line_len(row)),
10954 );
10955 for row in start.row + 1..=end.row {
10956 let mut line_len = buffer.line_len(MultiBufferRow(row));
10957 if row == end.row {
10958 line_len = end.column;
10959 }
10960 if line_len == 0 {
10961 trimmed_selections
10962 .push(Point::new(row, 0)..Point::new(row, line_len));
10963 continue;
10964 }
10965 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10966 if row_indent_size.len >= first_indent.len {
10967 trimmed_selections.push(
10968 Point::new(row, first_indent.len)..Point::new(row, line_len),
10969 );
10970 } else {
10971 trimmed_selections.clear();
10972 trimmed_selections.push(start..end);
10973 break;
10974 }
10975 }
10976 }
10977 } else {
10978 trimmed_selections.push(start..end);
10979 }
10980
10981 for trimmed_range in trimmed_selections {
10982 if is_first {
10983 is_first = false;
10984 } else {
10985 text += "\n";
10986 }
10987 let mut len = 0;
10988 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
10989 text.push_str(chunk);
10990 len += chunk.len();
10991 }
10992 clipboard_selections.push(ClipboardSelection {
10993 len,
10994 is_entire_line,
10995 first_line_indent: buffer
10996 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
10997 .len,
10998 });
10999 }
11000 }
11001 }
11002
11003 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11004 text,
11005 clipboard_selections,
11006 ));
11007 }
11008
11009 pub fn do_paste(
11010 &mut self,
11011 text: &String,
11012 clipboard_selections: Option<Vec<ClipboardSelection>>,
11013 handle_entire_lines: bool,
11014 window: &mut Window,
11015 cx: &mut Context<Self>,
11016 ) {
11017 if self.read_only(cx) {
11018 return;
11019 }
11020
11021 let clipboard_text = Cow::Borrowed(text);
11022
11023 self.transact(window, cx, |this, window, cx| {
11024 if let Some(mut clipboard_selections) = clipboard_selections {
11025 let old_selections = this.selections.all::<usize>(cx);
11026 let all_selections_were_entire_line =
11027 clipboard_selections.iter().all(|s| s.is_entire_line);
11028 let first_selection_indent_column =
11029 clipboard_selections.first().map(|s| s.first_line_indent);
11030 if clipboard_selections.len() != old_selections.len() {
11031 clipboard_selections.drain(..);
11032 }
11033 let cursor_offset = this.selections.last::<usize>(cx).head();
11034 let mut auto_indent_on_paste = true;
11035
11036 this.buffer.update(cx, |buffer, cx| {
11037 let snapshot = buffer.read(cx);
11038 auto_indent_on_paste = snapshot
11039 .language_settings_at(cursor_offset, cx)
11040 .auto_indent_on_paste;
11041
11042 let mut start_offset = 0;
11043 let mut edits = Vec::new();
11044 let mut original_indent_columns = Vec::new();
11045 for (ix, selection) in old_selections.iter().enumerate() {
11046 let to_insert;
11047 let entire_line;
11048 let original_indent_column;
11049 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11050 let end_offset = start_offset + clipboard_selection.len;
11051 to_insert = &clipboard_text[start_offset..end_offset];
11052 entire_line = clipboard_selection.is_entire_line;
11053 start_offset = end_offset + 1;
11054 original_indent_column = Some(clipboard_selection.first_line_indent);
11055 } else {
11056 to_insert = clipboard_text.as_str();
11057 entire_line = all_selections_were_entire_line;
11058 original_indent_column = first_selection_indent_column
11059 }
11060
11061 // If the corresponding selection was empty when this slice of the
11062 // clipboard text was written, then the entire line containing the
11063 // selection was copied. If this selection is also currently empty,
11064 // then paste the line before the current line of the buffer.
11065 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11066 let column = selection.start.to_point(&snapshot).column as usize;
11067 let line_start = selection.start - column;
11068 line_start..line_start
11069 } else {
11070 selection.range()
11071 };
11072
11073 edits.push((range, to_insert));
11074 original_indent_columns.push(original_indent_column);
11075 }
11076 drop(snapshot);
11077
11078 buffer.edit(
11079 edits,
11080 if auto_indent_on_paste {
11081 Some(AutoindentMode::Block {
11082 original_indent_columns,
11083 })
11084 } else {
11085 None
11086 },
11087 cx,
11088 );
11089 });
11090
11091 let selections = this.selections.all::<usize>(cx);
11092 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11093 s.select(selections)
11094 });
11095 } else {
11096 this.insert(&clipboard_text, window, cx);
11097 }
11098 });
11099 }
11100
11101 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11102 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11103 if let Some(item) = cx.read_from_clipboard() {
11104 let entries = item.entries();
11105
11106 match entries.first() {
11107 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11108 // of all the pasted entries.
11109 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11110 .do_paste(
11111 clipboard_string.text(),
11112 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11113 true,
11114 window,
11115 cx,
11116 ),
11117 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11118 }
11119 }
11120 }
11121
11122 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11123 if self.read_only(cx) {
11124 return;
11125 }
11126
11127 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11128
11129 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11130 if let Some((selections, _)) =
11131 self.selection_history.transaction(transaction_id).cloned()
11132 {
11133 self.change_selections(None, window, cx, |s| {
11134 s.select_anchors(selections.to_vec());
11135 });
11136 } else {
11137 log::error!(
11138 "No entry in selection_history found for undo. \
11139 This may correspond to a bug where undo does not update the selection. \
11140 If this is occurring, please add details to \
11141 https://github.com/zed-industries/zed/issues/22692"
11142 );
11143 }
11144 self.request_autoscroll(Autoscroll::fit(), cx);
11145 self.unmark_text(window, cx);
11146 self.refresh_inline_completion(true, false, window, cx);
11147 cx.emit(EditorEvent::Edited { transaction_id });
11148 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11149 }
11150 }
11151
11152 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11153 if self.read_only(cx) {
11154 return;
11155 }
11156
11157 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11158
11159 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11160 if let Some((_, Some(selections))) =
11161 self.selection_history.transaction(transaction_id).cloned()
11162 {
11163 self.change_selections(None, window, cx, |s| {
11164 s.select_anchors(selections.to_vec());
11165 });
11166 } else {
11167 log::error!(
11168 "No entry in selection_history found for redo. \
11169 This may correspond to a bug where undo does not update the selection. \
11170 If this is occurring, please add details to \
11171 https://github.com/zed-industries/zed/issues/22692"
11172 );
11173 }
11174 self.request_autoscroll(Autoscroll::fit(), cx);
11175 self.unmark_text(window, cx);
11176 self.refresh_inline_completion(true, false, window, cx);
11177 cx.emit(EditorEvent::Edited { transaction_id });
11178 }
11179 }
11180
11181 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11182 self.buffer
11183 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11184 }
11185
11186 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11187 self.buffer
11188 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11189 }
11190
11191 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11192 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11193 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11194 s.move_with(|map, selection| {
11195 let cursor = if selection.is_empty() {
11196 movement::left(map, selection.start)
11197 } else {
11198 selection.start
11199 };
11200 selection.collapse_to(cursor, SelectionGoal::None);
11201 });
11202 })
11203 }
11204
11205 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11206 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11207 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11208 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11209 })
11210 }
11211
11212 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11213 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11214 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11215 s.move_with(|map, selection| {
11216 let cursor = if selection.is_empty() {
11217 movement::right(map, selection.end)
11218 } else {
11219 selection.end
11220 };
11221 selection.collapse_to(cursor, SelectionGoal::None)
11222 });
11223 })
11224 }
11225
11226 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11227 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11228 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11229 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11230 })
11231 }
11232
11233 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11234 if self.take_rename(true, window, cx).is_some() {
11235 return;
11236 }
11237
11238 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11239 cx.propagate();
11240 return;
11241 }
11242
11243 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11244
11245 let text_layout_details = &self.text_layout_details(window);
11246 let selection_count = self.selections.count();
11247 let first_selection = self.selections.first_anchor();
11248
11249 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11250 s.move_with(|map, selection| {
11251 if !selection.is_empty() {
11252 selection.goal = SelectionGoal::None;
11253 }
11254 let (cursor, goal) = movement::up(
11255 map,
11256 selection.start,
11257 selection.goal,
11258 false,
11259 text_layout_details,
11260 );
11261 selection.collapse_to(cursor, goal);
11262 });
11263 });
11264
11265 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11266 {
11267 cx.propagate();
11268 }
11269 }
11270
11271 pub fn move_up_by_lines(
11272 &mut self,
11273 action: &MoveUpByLines,
11274 window: &mut Window,
11275 cx: &mut Context<Self>,
11276 ) {
11277 if self.take_rename(true, window, cx).is_some() {
11278 return;
11279 }
11280
11281 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11282 cx.propagate();
11283 return;
11284 }
11285
11286 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11287
11288 let text_layout_details = &self.text_layout_details(window);
11289
11290 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11291 s.move_with(|map, selection| {
11292 if !selection.is_empty() {
11293 selection.goal = SelectionGoal::None;
11294 }
11295 let (cursor, goal) = movement::up_by_rows(
11296 map,
11297 selection.start,
11298 action.lines,
11299 selection.goal,
11300 false,
11301 text_layout_details,
11302 );
11303 selection.collapse_to(cursor, goal);
11304 });
11305 })
11306 }
11307
11308 pub fn move_down_by_lines(
11309 &mut self,
11310 action: &MoveDownByLines,
11311 window: &mut Window,
11312 cx: &mut Context<Self>,
11313 ) {
11314 if self.take_rename(true, window, cx).is_some() {
11315 return;
11316 }
11317
11318 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11319 cx.propagate();
11320 return;
11321 }
11322
11323 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11324
11325 let text_layout_details = &self.text_layout_details(window);
11326
11327 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11328 s.move_with(|map, selection| {
11329 if !selection.is_empty() {
11330 selection.goal = SelectionGoal::None;
11331 }
11332 let (cursor, goal) = movement::down_by_rows(
11333 map,
11334 selection.start,
11335 action.lines,
11336 selection.goal,
11337 false,
11338 text_layout_details,
11339 );
11340 selection.collapse_to(cursor, goal);
11341 });
11342 })
11343 }
11344
11345 pub fn select_down_by_lines(
11346 &mut self,
11347 action: &SelectDownByLines,
11348 window: &mut Window,
11349 cx: &mut Context<Self>,
11350 ) {
11351 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11352 let text_layout_details = &self.text_layout_details(window);
11353 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11354 s.move_heads_with(|map, head, goal| {
11355 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11356 })
11357 })
11358 }
11359
11360 pub fn select_up_by_lines(
11361 &mut self,
11362 action: &SelectUpByLines,
11363 window: &mut Window,
11364 cx: &mut Context<Self>,
11365 ) {
11366 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11367 let text_layout_details = &self.text_layout_details(window);
11368 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11369 s.move_heads_with(|map, head, goal| {
11370 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11371 })
11372 })
11373 }
11374
11375 pub fn select_page_up(
11376 &mut self,
11377 _: &SelectPageUp,
11378 window: &mut Window,
11379 cx: &mut Context<Self>,
11380 ) {
11381 let Some(row_count) = self.visible_row_count() else {
11382 return;
11383 };
11384
11385 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11386
11387 let text_layout_details = &self.text_layout_details(window);
11388
11389 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11390 s.move_heads_with(|map, head, goal| {
11391 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11392 })
11393 })
11394 }
11395
11396 pub fn move_page_up(
11397 &mut self,
11398 action: &MovePageUp,
11399 window: &mut Window,
11400 cx: &mut Context<Self>,
11401 ) {
11402 if self.take_rename(true, window, cx).is_some() {
11403 return;
11404 }
11405
11406 if self
11407 .context_menu
11408 .borrow_mut()
11409 .as_mut()
11410 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11411 .unwrap_or(false)
11412 {
11413 return;
11414 }
11415
11416 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11417 cx.propagate();
11418 return;
11419 }
11420
11421 let Some(row_count) = self.visible_row_count() else {
11422 return;
11423 };
11424
11425 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11426
11427 let autoscroll = if action.center_cursor {
11428 Autoscroll::center()
11429 } else {
11430 Autoscroll::fit()
11431 };
11432
11433 let text_layout_details = &self.text_layout_details(window);
11434
11435 self.change_selections(Some(autoscroll), window, cx, |s| {
11436 s.move_with(|map, selection| {
11437 if !selection.is_empty() {
11438 selection.goal = SelectionGoal::None;
11439 }
11440 let (cursor, goal) = movement::up_by_rows(
11441 map,
11442 selection.end,
11443 row_count,
11444 selection.goal,
11445 false,
11446 text_layout_details,
11447 );
11448 selection.collapse_to(cursor, goal);
11449 });
11450 });
11451 }
11452
11453 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11454 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11455 let text_layout_details = &self.text_layout_details(window);
11456 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11457 s.move_heads_with(|map, head, goal| {
11458 movement::up(map, head, goal, false, text_layout_details)
11459 })
11460 })
11461 }
11462
11463 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11464 self.take_rename(true, window, cx);
11465
11466 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11467 cx.propagate();
11468 return;
11469 }
11470
11471 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11472
11473 let text_layout_details = &self.text_layout_details(window);
11474 let selection_count = self.selections.count();
11475 let first_selection = self.selections.first_anchor();
11476
11477 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11478 s.move_with(|map, selection| {
11479 if !selection.is_empty() {
11480 selection.goal = SelectionGoal::None;
11481 }
11482 let (cursor, goal) = movement::down(
11483 map,
11484 selection.end,
11485 selection.goal,
11486 false,
11487 text_layout_details,
11488 );
11489 selection.collapse_to(cursor, goal);
11490 });
11491 });
11492
11493 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11494 {
11495 cx.propagate();
11496 }
11497 }
11498
11499 pub fn select_page_down(
11500 &mut self,
11501 _: &SelectPageDown,
11502 window: &mut Window,
11503 cx: &mut Context<Self>,
11504 ) {
11505 let Some(row_count) = self.visible_row_count() else {
11506 return;
11507 };
11508
11509 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11510
11511 let text_layout_details = &self.text_layout_details(window);
11512
11513 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11514 s.move_heads_with(|map, head, goal| {
11515 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11516 })
11517 })
11518 }
11519
11520 pub fn move_page_down(
11521 &mut self,
11522 action: &MovePageDown,
11523 window: &mut Window,
11524 cx: &mut Context<Self>,
11525 ) {
11526 if self.take_rename(true, window, cx).is_some() {
11527 return;
11528 }
11529
11530 if self
11531 .context_menu
11532 .borrow_mut()
11533 .as_mut()
11534 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11535 .unwrap_or(false)
11536 {
11537 return;
11538 }
11539
11540 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11541 cx.propagate();
11542 return;
11543 }
11544
11545 let Some(row_count) = self.visible_row_count() else {
11546 return;
11547 };
11548
11549 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11550
11551 let autoscroll = if action.center_cursor {
11552 Autoscroll::center()
11553 } else {
11554 Autoscroll::fit()
11555 };
11556
11557 let text_layout_details = &self.text_layout_details(window);
11558 self.change_selections(Some(autoscroll), window, cx, |s| {
11559 s.move_with(|map, selection| {
11560 if !selection.is_empty() {
11561 selection.goal = SelectionGoal::None;
11562 }
11563 let (cursor, goal) = movement::down_by_rows(
11564 map,
11565 selection.end,
11566 row_count,
11567 selection.goal,
11568 false,
11569 text_layout_details,
11570 );
11571 selection.collapse_to(cursor, goal);
11572 });
11573 });
11574 }
11575
11576 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11577 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11578 let text_layout_details = &self.text_layout_details(window);
11579 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11580 s.move_heads_with(|map, head, goal| {
11581 movement::down(map, head, goal, false, text_layout_details)
11582 })
11583 });
11584 }
11585
11586 pub fn context_menu_first(
11587 &mut self,
11588 _: &ContextMenuFirst,
11589 window: &mut Window,
11590 cx: &mut Context<Self>,
11591 ) {
11592 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11593 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11594 }
11595 }
11596
11597 pub fn context_menu_prev(
11598 &mut self,
11599 _: &ContextMenuPrevious,
11600 window: &mut Window,
11601 cx: &mut Context<Self>,
11602 ) {
11603 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11604 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11605 }
11606 }
11607
11608 pub fn context_menu_next(
11609 &mut self,
11610 _: &ContextMenuNext,
11611 window: &mut Window,
11612 cx: &mut Context<Self>,
11613 ) {
11614 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11615 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11616 }
11617 }
11618
11619 pub fn context_menu_last(
11620 &mut self,
11621 _: &ContextMenuLast,
11622 window: &mut Window,
11623 cx: &mut Context<Self>,
11624 ) {
11625 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11626 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11627 }
11628 }
11629
11630 pub fn move_to_previous_word_start(
11631 &mut self,
11632 _: &MoveToPreviousWordStart,
11633 window: &mut Window,
11634 cx: &mut Context<Self>,
11635 ) {
11636 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11637 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11638 s.move_cursors_with(|map, head, _| {
11639 (
11640 movement::previous_word_start(map, head),
11641 SelectionGoal::None,
11642 )
11643 });
11644 })
11645 }
11646
11647 pub fn move_to_previous_subword_start(
11648 &mut self,
11649 _: &MoveToPreviousSubwordStart,
11650 window: &mut Window,
11651 cx: &mut Context<Self>,
11652 ) {
11653 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11654 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11655 s.move_cursors_with(|map, head, _| {
11656 (
11657 movement::previous_subword_start(map, head),
11658 SelectionGoal::None,
11659 )
11660 });
11661 })
11662 }
11663
11664 pub fn select_to_previous_word_start(
11665 &mut self,
11666 _: &SelectToPreviousWordStart,
11667 window: &mut Window,
11668 cx: &mut Context<Self>,
11669 ) {
11670 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11671 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11672 s.move_heads_with(|map, head, _| {
11673 (
11674 movement::previous_word_start(map, head),
11675 SelectionGoal::None,
11676 )
11677 });
11678 })
11679 }
11680
11681 pub fn select_to_previous_subword_start(
11682 &mut self,
11683 _: &SelectToPreviousSubwordStart,
11684 window: &mut Window,
11685 cx: &mut Context<Self>,
11686 ) {
11687 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11688 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11689 s.move_heads_with(|map, head, _| {
11690 (
11691 movement::previous_subword_start(map, head),
11692 SelectionGoal::None,
11693 )
11694 });
11695 })
11696 }
11697
11698 pub fn delete_to_previous_word_start(
11699 &mut self,
11700 action: &DeleteToPreviousWordStart,
11701 window: &mut Window,
11702 cx: &mut Context<Self>,
11703 ) {
11704 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11705 self.transact(window, cx, |this, window, cx| {
11706 this.select_autoclose_pair(window, cx);
11707 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11708 s.move_with(|map, selection| {
11709 if selection.is_empty() {
11710 let cursor = if action.ignore_newlines {
11711 movement::previous_word_start(map, selection.head())
11712 } else {
11713 movement::previous_word_start_or_newline(map, selection.head())
11714 };
11715 selection.set_head(cursor, SelectionGoal::None);
11716 }
11717 });
11718 });
11719 this.insert("", window, cx);
11720 });
11721 }
11722
11723 pub fn delete_to_previous_subword_start(
11724 &mut self,
11725 _: &DeleteToPreviousSubwordStart,
11726 window: &mut Window,
11727 cx: &mut Context<Self>,
11728 ) {
11729 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11730 self.transact(window, cx, |this, window, cx| {
11731 this.select_autoclose_pair(window, cx);
11732 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11733 s.move_with(|map, selection| {
11734 if selection.is_empty() {
11735 let cursor = movement::previous_subword_start(map, selection.head());
11736 selection.set_head(cursor, SelectionGoal::None);
11737 }
11738 });
11739 });
11740 this.insert("", window, cx);
11741 });
11742 }
11743
11744 pub fn move_to_next_word_end(
11745 &mut self,
11746 _: &MoveToNextWordEnd,
11747 window: &mut Window,
11748 cx: &mut Context<Self>,
11749 ) {
11750 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11751 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11752 s.move_cursors_with(|map, head, _| {
11753 (movement::next_word_end(map, head), SelectionGoal::None)
11754 });
11755 })
11756 }
11757
11758 pub fn move_to_next_subword_end(
11759 &mut self,
11760 _: &MoveToNextSubwordEnd,
11761 window: &mut Window,
11762 cx: &mut Context<Self>,
11763 ) {
11764 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11765 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11766 s.move_cursors_with(|map, head, _| {
11767 (movement::next_subword_end(map, head), SelectionGoal::None)
11768 });
11769 })
11770 }
11771
11772 pub fn select_to_next_word_end(
11773 &mut self,
11774 _: &SelectToNextWordEnd,
11775 window: &mut Window,
11776 cx: &mut Context<Self>,
11777 ) {
11778 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11779 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11780 s.move_heads_with(|map, head, _| {
11781 (movement::next_word_end(map, head), SelectionGoal::None)
11782 });
11783 })
11784 }
11785
11786 pub fn select_to_next_subword_end(
11787 &mut self,
11788 _: &SelectToNextSubwordEnd,
11789 window: &mut Window,
11790 cx: &mut Context<Self>,
11791 ) {
11792 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11793 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11794 s.move_heads_with(|map, head, _| {
11795 (movement::next_subword_end(map, head), SelectionGoal::None)
11796 });
11797 })
11798 }
11799
11800 pub fn delete_to_next_word_end(
11801 &mut self,
11802 action: &DeleteToNextWordEnd,
11803 window: &mut Window,
11804 cx: &mut Context<Self>,
11805 ) {
11806 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11807 self.transact(window, cx, |this, window, cx| {
11808 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11809 s.move_with(|map, selection| {
11810 if selection.is_empty() {
11811 let cursor = if action.ignore_newlines {
11812 movement::next_word_end(map, selection.head())
11813 } else {
11814 movement::next_word_end_or_newline(map, selection.head())
11815 };
11816 selection.set_head(cursor, SelectionGoal::None);
11817 }
11818 });
11819 });
11820 this.insert("", window, cx);
11821 });
11822 }
11823
11824 pub fn delete_to_next_subword_end(
11825 &mut self,
11826 _: &DeleteToNextSubwordEnd,
11827 window: &mut Window,
11828 cx: &mut Context<Self>,
11829 ) {
11830 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11831 self.transact(window, cx, |this, window, cx| {
11832 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11833 s.move_with(|map, selection| {
11834 if selection.is_empty() {
11835 let cursor = movement::next_subword_end(map, selection.head());
11836 selection.set_head(cursor, SelectionGoal::None);
11837 }
11838 });
11839 });
11840 this.insert("", window, cx);
11841 });
11842 }
11843
11844 pub fn move_to_beginning_of_line(
11845 &mut self,
11846 action: &MoveToBeginningOfLine,
11847 window: &mut Window,
11848 cx: &mut Context<Self>,
11849 ) {
11850 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11851 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11852 s.move_cursors_with(|map, head, _| {
11853 (
11854 movement::indented_line_beginning(
11855 map,
11856 head,
11857 action.stop_at_soft_wraps,
11858 action.stop_at_indent,
11859 ),
11860 SelectionGoal::None,
11861 )
11862 });
11863 })
11864 }
11865
11866 pub fn select_to_beginning_of_line(
11867 &mut self,
11868 action: &SelectToBeginningOfLine,
11869 window: &mut Window,
11870 cx: &mut Context<Self>,
11871 ) {
11872 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11873 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11874 s.move_heads_with(|map, head, _| {
11875 (
11876 movement::indented_line_beginning(
11877 map,
11878 head,
11879 action.stop_at_soft_wraps,
11880 action.stop_at_indent,
11881 ),
11882 SelectionGoal::None,
11883 )
11884 });
11885 });
11886 }
11887
11888 pub fn delete_to_beginning_of_line(
11889 &mut self,
11890 action: &DeleteToBeginningOfLine,
11891 window: &mut Window,
11892 cx: &mut Context<Self>,
11893 ) {
11894 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11895 self.transact(window, cx, |this, window, cx| {
11896 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11897 s.move_with(|_, selection| {
11898 selection.reversed = true;
11899 });
11900 });
11901
11902 this.select_to_beginning_of_line(
11903 &SelectToBeginningOfLine {
11904 stop_at_soft_wraps: false,
11905 stop_at_indent: action.stop_at_indent,
11906 },
11907 window,
11908 cx,
11909 );
11910 this.backspace(&Backspace, window, cx);
11911 });
11912 }
11913
11914 pub fn move_to_end_of_line(
11915 &mut self,
11916 action: &MoveToEndOfLine,
11917 window: &mut Window,
11918 cx: &mut Context<Self>,
11919 ) {
11920 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11921 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11922 s.move_cursors_with(|map, head, _| {
11923 (
11924 movement::line_end(map, head, action.stop_at_soft_wraps),
11925 SelectionGoal::None,
11926 )
11927 });
11928 })
11929 }
11930
11931 pub fn select_to_end_of_line(
11932 &mut self,
11933 action: &SelectToEndOfLine,
11934 window: &mut Window,
11935 cx: &mut Context<Self>,
11936 ) {
11937 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11938 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11939 s.move_heads_with(|map, head, _| {
11940 (
11941 movement::line_end(map, head, action.stop_at_soft_wraps),
11942 SelectionGoal::None,
11943 )
11944 });
11945 })
11946 }
11947
11948 pub fn delete_to_end_of_line(
11949 &mut self,
11950 _: &DeleteToEndOfLine,
11951 window: &mut Window,
11952 cx: &mut Context<Self>,
11953 ) {
11954 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11955 self.transact(window, cx, |this, window, cx| {
11956 this.select_to_end_of_line(
11957 &SelectToEndOfLine {
11958 stop_at_soft_wraps: false,
11959 },
11960 window,
11961 cx,
11962 );
11963 this.delete(&Delete, window, cx);
11964 });
11965 }
11966
11967 pub fn cut_to_end_of_line(
11968 &mut self,
11969 _: &CutToEndOfLine,
11970 window: &mut Window,
11971 cx: &mut Context<Self>,
11972 ) {
11973 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11974 self.transact(window, cx, |this, window, cx| {
11975 this.select_to_end_of_line(
11976 &SelectToEndOfLine {
11977 stop_at_soft_wraps: false,
11978 },
11979 window,
11980 cx,
11981 );
11982 this.cut(&Cut, window, cx);
11983 });
11984 }
11985
11986 pub fn move_to_start_of_paragraph(
11987 &mut self,
11988 _: &MoveToStartOfParagraph,
11989 window: &mut Window,
11990 cx: &mut Context<Self>,
11991 ) {
11992 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11993 cx.propagate();
11994 return;
11995 }
11996 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11997 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11998 s.move_with(|map, selection| {
11999 selection.collapse_to(
12000 movement::start_of_paragraph(map, selection.head(), 1),
12001 SelectionGoal::None,
12002 )
12003 });
12004 })
12005 }
12006
12007 pub fn move_to_end_of_paragraph(
12008 &mut self,
12009 _: &MoveToEndOfParagraph,
12010 window: &mut Window,
12011 cx: &mut Context<Self>,
12012 ) {
12013 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12014 cx.propagate();
12015 return;
12016 }
12017 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12018 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12019 s.move_with(|map, selection| {
12020 selection.collapse_to(
12021 movement::end_of_paragraph(map, selection.head(), 1),
12022 SelectionGoal::None,
12023 )
12024 });
12025 })
12026 }
12027
12028 pub fn select_to_start_of_paragraph(
12029 &mut self,
12030 _: &SelectToStartOfParagraph,
12031 window: &mut Window,
12032 cx: &mut Context<Self>,
12033 ) {
12034 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12035 cx.propagate();
12036 return;
12037 }
12038 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12039 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12040 s.move_heads_with(|map, head, _| {
12041 (
12042 movement::start_of_paragraph(map, head, 1),
12043 SelectionGoal::None,
12044 )
12045 });
12046 })
12047 }
12048
12049 pub fn select_to_end_of_paragraph(
12050 &mut self,
12051 _: &SelectToEndOfParagraph,
12052 window: &mut Window,
12053 cx: &mut Context<Self>,
12054 ) {
12055 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12056 cx.propagate();
12057 return;
12058 }
12059 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12060 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12061 s.move_heads_with(|map, head, _| {
12062 (
12063 movement::end_of_paragraph(map, head, 1),
12064 SelectionGoal::None,
12065 )
12066 });
12067 })
12068 }
12069
12070 pub fn move_to_start_of_excerpt(
12071 &mut self,
12072 _: &MoveToStartOfExcerpt,
12073 window: &mut Window,
12074 cx: &mut Context<Self>,
12075 ) {
12076 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12077 cx.propagate();
12078 return;
12079 }
12080 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12081 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12082 s.move_with(|map, selection| {
12083 selection.collapse_to(
12084 movement::start_of_excerpt(
12085 map,
12086 selection.head(),
12087 workspace::searchable::Direction::Prev,
12088 ),
12089 SelectionGoal::None,
12090 )
12091 });
12092 })
12093 }
12094
12095 pub fn move_to_start_of_next_excerpt(
12096 &mut self,
12097 _: &MoveToStartOfNextExcerpt,
12098 window: &mut Window,
12099 cx: &mut Context<Self>,
12100 ) {
12101 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12102 cx.propagate();
12103 return;
12104 }
12105
12106 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12107 s.move_with(|map, selection| {
12108 selection.collapse_to(
12109 movement::start_of_excerpt(
12110 map,
12111 selection.head(),
12112 workspace::searchable::Direction::Next,
12113 ),
12114 SelectionGoal::None,
12115 )
12116 });
12117 })
12118 }
12119
12120 pub fn move_to_end_of_excerpt(
12121 &mut self,
12122 _: &MoveToEndOfExcerpt,
12123 window: &mut Window,
12124 cx: &mut Context<Self>,
12125 ) {
12126 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12127 cx.propagate();
12128 return;
12129 }
12130 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12131 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12132 s.move_with(|map, selection| {
12133 selection.collapse_to(
12134 movement::end_of_excerpt(
12135 map,
12136 selection.head(),
12137 workspace::searchable::Direction::Next,
12138 ),
12139 SelectionGoal::None,
12140 )
12141 });
12142 })
12143 }
12144
12145 pub fn move_to_end_of_previous_excerpt(
12146 &mut self,
12147 _: &MoveToEndOfPreviousExcerpt,
12148 window: &mut Window,
12149 cx: &mut Context<Self>,
12150 ) {
12151 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12152 cx.propagate();
12153 return;
12154 }
12155 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12156 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12157 s.move_with(|map, selection| {
12158 selection.collapse_to(
12159 movement::end_of_excerpt(
12160 map,
12161 selection.head(),
12162 workspace::searchable::Direction::Prev,
12163 ),
12164 SelectionGoal::None,
12165 )
12166 });
12167 })
12168 }
12169
12170 pub fn select_to_start_of_excerpt(
12171 &mut self,
12172 _: &SelectToStartOfExcerpt,
12173 window: &mut Window,
12174 cx: &mut Context<Self>,
12175 ) {
12176 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12177 cx.propagate();
12178 return;
12179 }
12180 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12181 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12182 s.move_heads_with(|map, head, _| {
12183 (
12184 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12185 SelectionGoal::None,
12186 )
12187 });
12188 })
12189 }
12190
12191 pub fn select_to_start_of_next_excerpt(
12192 &mut self,
12193 _: &SelectToStartOfNextExcerpt,
12194 window: &mut Window,
12195 cx: &mut Context<Self>,
12196 ) {
12197 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12198 cx.propagate();
12199 return;
12200 }
12201 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12202 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12203 s.move_heads_with(|map, head, _| {
12204 (
12205 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12206 SelectionGoal::None,
12207 )
12208 });
12209 })
12210 }
12211
12212 pub fn select_to_end_of_excerpt(
12213 &mut self,
12214 _: &SelectToEndOfExcerpt,
12215 window: &mut Window,
12216 cx: &mut Context<Self>,
12217 ) {
12218 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12219 cx.propagate();
12220 return;
12221 }
12222 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12223 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12224 s.move_heads_with(|map, head, _| {
12225 (
12226 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12227 SelectionGoal::None,
12228 )
12229 });
12230 })
12231 }
12232
12233 pub fn select_to_end_of_previous_excerpt(
12234 &mut self,
12235 _: &SelectToEndOfPreviousExcerpt,
12236 window: &mut Window,
12237 cx: &mut Context<Self>,
12238 ) {
12239 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12240 cx.propagate();
12241 return;
12242 }
12243 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12244 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12245 s.move_heads_with(|map, head, _| {
12246 (
12247 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12248 SelectionGoal::None,
12249 )
12250 });
12251 })
12252 }
12253
12254 pub fn move_to_beginning(
12255 &mut self,
12256 _: &MoveToBeginning,
12257 window: &mut Window,
12258 cx: &mut Context<Self>,
12259 ) {
12260 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12261 cx.propagate();
12262 return;
12263 }
12264 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12265 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12266 s.select_ranges(vec![0..0]);
12267 });
12268 }
12269
12270 pub fn select_to_beginning(
12271 &mut self,
12272 _: &SelectToBeginning,
12273 window: &mut Window,
12274 cx: &mut Context<Self>,
12275 ) {
12276 let mut selection = self.selections.last::<Point>(cx);
12277 selection.set_head(Point::zero(), SelectionGoal::None);
12278 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12279 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12280 s.select(vec![selection]);
12281 });
12282 }
12283
12284 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12285 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12286 cx.propagate();
12287 return;
12288 }
12289 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12290 let cursor = self.buffer.read(cx).read(cx).len();
12291 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12292 s.select_ranges(vec![cursor..cursor])
12293 });
12294 }
12295
12296 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12297 self.nav_history = nav_history;
12298 }
12299
12300 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12301 self.nav_history.as_ref()
12302 }
12303
12304 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12305 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12306 }
12307
12308 fn push_to_nav_history(
12309 &mut self,
12310 cursor_anchor: Anchor,
12311 new_position: Option<Point>,
12312 is_deactivate: bool,
12313 cx: &mut Context<Self>,
12314 ) {
12315 if let Some(nav_history) = self.nav_history.as_mut() {
12316 let buffer = self.buffer.read(cx).read(cx);
12317 let cursor_position = cursor_anchor.to_point(&buffer);
12318 let scroll_state = self.scroll_manager.anchor();
12319 let scroll_top_row = scroll_state.top_row(&buffer);
12320 drop(buffer);
12321
12322 if let Some(new_position) = new_position {
12323 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12324 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12325 return;
12326 }
12327 }
12328
12329 nav_history.push(
12330 Some(NavigationData {
12331 cursor_anchor,
12332 cursor_position,
12333 scroll_anchor: scroll_state,
12334 scroll_top_row,
12335 }),
12336 cx,
12337 );
12338 cx.emit(EditorEvent::PushedToNavHistory {
12339 anchor: cursor_anchor,
12340 is_deactivate,
12341 })
12342 }
12343 }
12344
12345 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12346 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12347 let buffer = self.buffer.read(cx).snapshot(cx);
12348 let mut selection = self.selections.first::<usize>(cx);
12349 selection.set_head(buffer.len(), SelectionGoal::None);
12350 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12351 s.select(vec![selection]);
12352 });
12353 }
12354
12355 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12356 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12357 let end = self.buffer.read(cx).read(cx).len();
12358 self.change_selections(None, window, cx, |s| {
12359 s.select_ranges(vec![0..end]);
12360 });
12361 }
12362
12363 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12364 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12365 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12366 let mut selections = self.selections.all::<Point>(cx);
12367 let max_point = display_map.buffer_snapshot.max_point();
12368 for selection in &mut selections {
12369 let rows = selection.spanned_rows(true, &display_map);
12370 selection.start = Point::new(rows.start.0, 0);
12371 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12372 selection.reversed = false;
12373 }
12374 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12375 s.select(selections);
12376 });
12377 }
12378
12379 pub fn split_selection_into_lines(
12380 &mut self,
12381 _: &SplitSelectionIntoLines,
12382 window: &mut Window,
12383 cx: &mut Context<Self>,
12384 ) {
12385 let selections = self
12386 .selections
12387 .all::<Point>(cx)
12388 .into_iter()
12389 .map(|selection| selection.start..selection.end)
12390 .collect::<Vec<_>>();
12391 self.unfold_ranges(&selections, true, true, cx);
12392
12393 let mut new_selection_ranges = Vec::new();
12394 {
12395 let buffer = self.buffer.read(cx).read(cx);
12396 for selection in selections {
12397 for row in selection.start.row..selection.end.row {
12398 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12399 new_selection_ranges.push(cursor..cursor);
12400 }
12401
12402 let is_multiline_selection = selection.start.row != selection.end.row;
12403 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12404 // so this action feels more ergonomic when paired with other selection operations
12405 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12406 if !should_skip_last {
12407 new_selection_ranges.push(selection.end..selection.end);
12408 }
12409 }
12410 }
12411 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12412 s.select_ranges(new_selection_ranges);
12413 });
12414 }
12415
12416 pub fn add_selection_above(
12417 &mut self,
12418 _: &AddSelectionAbove,
12419 window: &mut Window,
12420 cx: &mut Context<Self>,
12421 ) {
12422 self.add_selection(true, window, cx);
12423 }
12424
12425 pub fn add_selection_below(
12426 &mut self,
12427 _: &AddSelectionBelow,
12428 window: &mut Window,
12429 cx: &mut Context<Self>,
12430 ) {
12431 self.add_selection(false, window, cx);
12432 }
12433
12434 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12435 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12436
12437 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12438 let mut selections = self.selections.all::<Point>(cx);
12439 let text_layout_details = self.text_layout_details(window);
12440 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12441 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12442 let range = oldest_selection.display_range(&display_map).sorted();
12443
12444 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12445 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12446 let positions = start_x.min(end_x)..start_x.max(end_x);
12447
12448 selections.clear();
12449 let mut stack = Vec::new();
12450 for row in range.start.row().0..=range.end.row().0 {
12451 if let Some(selection) = self.selections.build_columnar_selection(
12452 &display_map,
12453 DisplayRow(row),
12454 &positions,
12455 oldest_selection.reversed,
12456 &text_layout_details,
12457 ) {
12458 stack.push(selection.id);
12459 selections.push(selection);
12460 }
12461 }
12462
12463 if above {
12464 stack.reverse();
12465 }
12466
12467 AddSelectionsState { above, stack }
12468 });
12469
12470 let last_added_selection = *state.stack.last().unwrap();
12471 let mut new_selections = Vec::new();
12472 if above == state.above {
12473 let end_row = if above {
12474 DisplayRow(0)
12475 } else {
12476 display_map.max_point().row()
12477 };
12478
12479 'outer: for selection in selections {
12480 if selection.id == last_added_selection {
12481 let range = selection.display_range(&display_map).sorted();
12482 debug_assert_eq!(range.start.row(), range.end.row());
12483 let mut row = range.start.row();
12484 let positions =
12485 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12486 px(start)..px(end)
12487 } else {
12488 let start_x =
12489 display_map.x_for_display_point(range.start, &text_layout_details);
12490 let end_x =
12491 display_map.x_for_display_point(range.end, &text_layout_details);
12492 start_x.min(end_x)..start_x.max(end_x)
12493 };
12494
12495 while row != end_row {
12496 if above {
12497 row.0 -= 1;
12498 } else {
12499 row.0 += 1;
12500 }
12501
12502 if let Some(new_selection) = self.selections.build_columnar_selection(
12503 &display_map,
12504 row,
12505 &positions,
12506 selection.reversed,
12507 &text_layout_details,
12508 ) {
12509 state.stack.push(new_selection.id);
12510 if above {
12511 new_selections.push(new_selection);
12512 new_selections.push(selection);
12513 } else {
12514 new_selections.push(selection);
12515 new_selections.push(new_selection);
12516 }
12517
12518 continue 'outer;
12519 }
12520 }
12521 }
12522
12523 new_selections.push(selection);
12524 }
12525 } else {
12526 new_selections = selections;
12527 new_selections.retain(|s| s.id != last_added_selection);
12528 state.stack.pop();
12529 }
12530
12531 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12532 s.select(new_selections);
12533 });
12534 if state.stack.len() > 1 {
12535 self.add_selections_state = Some(state);
12536 }
12537 }
12538
12539 fn select_match_ranges(
12540 &mut self,
12541 range: Range<usize>,
12542 reversed: bool,
12543 replace_newest: bool,
12544 auto_scroll: Option<Autoscroll>,
12545 window: &mut Window,
12546 cx: &mut Context<Editor>,
12547 ) {
12548 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12549 self.change_selections(auto_scroll, window, cx, |s| {
12550 if replace_newest {
12551 s.delete(s.newest_anchor().id);
12552 }
12553 if reversed {
12554 s.insert_range(range.end..range.start);
12555 } else {
12556 s.insert_range(range);
12557 }
12558 });
12559 }
12560
12561 pub fn select_next_match_internal(
12562 &mut self,
12563 display_map: &DisplaySnapshot,
12564 replace_newest: bool,
12565 autoscroll: Option<Autoscroll>,
12566 window: &mut Window,
12567 cx: &mut Context<Self>,
12568 ) -> Result<()> {
12569 let buffer = &display_map.buffer_snapshot;
12570 let mut selections = self.selections.all::<usize>(cx);
12571 if let Some(mut select_next_state) = self.select_next_state.take() {
12572 let query = &select_next_state.query;
12573 if !select_next_state.done {
12574 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12575 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12576 let mut next_selected_range = None;
12577
12578 let bytes_after_last_selection =
12579 buffer.bytes_in_range(last_selection.end..buffer.len());
12580 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12581 let query_matches = query
12582 .stream_find_iter(bytes_after_last_selection)
12583 .map(|result| (last_selection.end, result))
12584 .chain(
12585 query
12586 .stream_find_iter(bytes_before_first_selection)
12587 .map(|result| (0, result)),
12588 );
12589
12590 for (start_offset, query_match) in query_matches {
12591 let query_match = query_match.unwrap(); // can only fail due to I/O
12592 let offset_range =
12593 start_offset + query_match.start()..start_offset + query_match.end();
12594 let display_range = offset_range.start.to_display_point(display_map)
12595 ..offset_range.end.to_display_point(display_map);
12596
12597 if !select_next_state.wordwise
12598 || (!movement::is_inside_word(display_map, display_range.start)
12599 && !movement::is_inside_word(display_map, display_range.end))
12600 {
12601 // TODO: This is n^2, because we might check all the selections
12602 if !selections
12603 .iter()
12604 .any(|selection| selection.range().overlaps(&offset_range))
12605 {
12606 next_selected_range = Some(offset_range);
12607 break;
12608 }
12609 }
12610 }
12611
12612 if let Some(next_selected_range) = next_selected_range {
12613 self.select_match_ranges(
12614 next_selected_range,
12615 last_selection.reversed,
12616 replace_newest,
12617 autoscroll,
12618 window,
12619 cx,
12620 );
12621 } else {
12622 select_next_state.done = true;
12623 }
12624 }
12625
12626 self.select_next_state = Some(select_next_state);
12627 } else {
12628 let mut only_carets = true;
12629 let mut same_text_selected = true;
12630 let mut selected_text = None;
12631
12632 let mut selections_iter = selections.iter().peekable();
12633 while let Some(selection) = selections_iter.next() {
12634 if selection.start != selection.end {
12635 only_carets = false;
12636 }
12637
12638 if same_text_selected {
12639 if selected_text.is_none() {
12640 selected_text =
12641 Some(buffer.text_for_range(selection.range()).collect::<String>());
12642 }
12643
12644 if let Some(next_selection) = selections_iter.peek() {
12645 if next_selection.range().len() == selection.range().len() {
12646 let next_selected_text = buffer
12647 .text_for_range(next_selection.range())
12648 .collect::<String>();
12649 if Some(next_selected_text) != selected_text {
12650 same_text_selected = false;
12651 selected_text = None;
12652 }
12653 } else {
12654 same_text_selected = false;
12655 selected_text = None;
12656 }
12657 }
12658 }
12659 }
12660
12661 if only_carets {
12662 for selection in &mut selections {
12663 let word_range = movement::surrounding_word(
12664 display_map,
12665 selection.start.to_display_point(display_map),
12666 );
12667 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12668 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12669 selection.goal = SelectionGoal::None;
12670 selection.reversed = false;
12671 self.select_match_ranges(
12672 selection.start..selection.end,
12673 selection.reversed,
12674 replace_newest,
12675 autoscroll,
12676 window,
12677 cx,
12678 );
12679 }
12680
12681 if selections.len() == 1 {
12682 let selection = selections
12683 .last()
12684 .expect("ensured that there's only one selection");
12685 let query = buffer
12686 .text_for_range(selection.start..selection.end)
12687 .collect::<String>();
12688 let is_empty = query.is_empty();
12689 let select_state = SelectNextState {
12690 query: AhoCorasick::new(&[query])?,
12691 wordwise: true,
12692 done: is_empty,
12693 };
12694 self.select_next_state = Some(select_state);
12695 } else {
12696 self.select_next_state = None;
12697 }
12698 } else if let Some(selected_text) = selected_text {
12699 self.select_next_state = Some(SelectNextState {
12700 query: AhoCorasick::new(&[selected_text])?,
12701 wordwise: false,
12702 done: false,
12703 });
12704 self.select_next_match_internal(
12705 display_map,
12706 replace_newest,
12707 autoscroll,
12708 window,
12709 cx,
12710 )?;
12711 }
12712 }
12713 Ok(())
12714 }
12715
12716 pub fn select_all_matches(
12717 &mut self,
12718 _action: &SelectAllMatches,
12719 window: &mut Window,
12720 cx: &mut Context<Self>,
12721 ) -> Result<()> {
12722 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12723
12724 self.push_to_selection_history();
12725 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12726
12727 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12728 let Some(select_next_state) = self.select_next_state.as_mut() else {
12729 return Ok(());
12730 };
12731 if select_next_state.done {
12732 return Ok(());
12733 }
12734
12735 let mut new_selections = Vec::new();
12736
12737 let reversed = self.selections.oldest::<usize>(cx).reversed;
12738 let buffer = &display_map.buffer_snapshot;
12739 let query_matches = select_next_state
12740 .query
12741 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12742
12743 for query_match in query_matches.into_iter() {
12744 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12745 let offset_range = if reversed {
12746 query_match.end()..query_match.start()
12747 } else {
12748 query_match.start()..query_match.end()
12749 };
12750 let display_range = offset_range.start.to_display_point(&display_map)
12751 ..offset_range.end.to_display_point(&display_map);
12752
12753 if !select_next_state.wordwise
12754 || (!movement::is_inside_word(&display_map, display_range.start)
12755 && !movement::is_inside_word(&display_map, display_range.end))
12756 {
12757 new_selections.push(offset_range.start..offset_range.end);
12758 }
12759 }
12760
12761 select_next_state.done = true;
12762 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12763 self.change_selections(None, window, cx, |selections| {
12764 selections.select_ranges(new_selections)
12765 });
12766
12767 Ok(())
12768 }
12769
12770 pub fn select_next(
12771 &mut self,
12772 action: &SelectNext,
12773 window: &mut Window,
12774 cx: &mut Context<Self>,
12775 ) -> Result<()> {
12776 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12777 self.push_to_selection_history();
12778 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12779 self.select_next_match_internal(
12780 &display_map,
12781 action.replace_newest,
12782 Some(Autoscroll::newest()),
12783 window,
12784 cx,
12785 )?;
12786 Ok(())
12787 }
12788
12789 pub fn select_previous(
12790 &mut self,
12791 action: &SelectPrevious,
12792 window: &mut Window,
12793 cx: &mut Context<Self>,
12794 ) -> Result<()> {
12795 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12796 self.push_to_selection_history();
12797 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12798 let buffer = &display_map.buffer_snapshot;
12799 let mut selections = self.selections.all::<usize>(cx);
12800 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12801 let query = &select_prev_state.query;
12802 if !select_prev_state.done {
12803 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12804 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12805 let mut next_selected_range = None;
12806 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12807 let bytes_before_last_selection =
12808 buffer.reversed_bytes_in_range(0..last_selection.start);
12809 let bytes_after_first_selection =
12810 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12811 let query_matches = query
12812 .stream_find_iter(bytes_before_last_selection)
12813 .map(|result| (last_selection.start, result))
12814 .chain(
12815 query
12816 .stream_find_iter(bytes_after_first_selection)
12817 .map(|result| (buffer.len(), result)),
12818 );
12819 for (end_offset, query_match) in query_matches {
12820 let query_match = query_match.unwrap(); // can only fail due to I/O
12821 let offset_range =
12822 end_offset - query_match.end()..end_offset - query_match.start();
12823 let display_range = offset_range.start.to_display_point(&display_map)
12824 ..offset_range.end.to_display_point(&display_map);
12825
12826 if !select_prev_state.wordwise
12827 || (!movement::is_inside_word(&display_map, display_range.start)
12828 && !movement::is_inside_word(&display_map, display_range.end))
12829 {
12830 next_selected_range = Some(offset_range);
12831 break;
12832 }
12833 }
12834
12835 if let Some(next_selected_range) = next_selected_range {
12836 self.select_match_ranges(
12837 next_selected_range,
12838 last_selection.reversed,
12839 action.replace_newest,
12840 Some(Autoscroll::newest()),
12841 window,
12842 cx,
12843 );
12844 } else {
12845 select_prev_state.done = true;
12846 }
12847 }
12848
12849 self.select_prev_state = Some(select_prev_state);
12850 } else {
12851 let mut only_carets = true;
12852 let mut same_text_selected = true;
12853 let mut selected_text = None;
12854
12855 let mut selections_iter = selections.iter().peekable();
12856 while let Some(selection) = selections_iter.next() {
12857 if selection.start != selection.end {
12858 only_carets = false;
12859 }
12860
12861 if same_text_selected {
12862 if selected_text.is_none() {
12863 selected_text =
12864 Some(buffer.text_for_range(selection.range()).collect::<String>());
12865 }
12866
12867 if let Some(next_selection) = selections_iter.peek() {
12868 if next_selection.range().len() == selection.range().len() {
12869 let next_selected_text = buffer
12870 .text_for_range(next_selection.range())
12871 .collect::<String>();
12872 if Some(next_selected_text) != selected_text {
12873 same_text_selected = false;
12874 selected_text = None;
12875 }
12876 } else {
12877 same_text_selected = false;
12878 selected_text = None;
12879 }
12880 }
12881 }
12882 }
12883
12884 if only_carets {
12885 for selection in &mut selections {
12886 let word_range = movement::surrounding_word(
12887 &display_map,
12888 selection.start.to_display_point(&display_map),
12889 );
12890 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12891 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12892 selection.goal = SelectionGoal::None;
12893 selection.reversed = false;
12894 self.select_match_ranges(
12895 selection.start..selection.end,
12896 selection.reversed,
12897 action.replace_newest,
12898 Some(Autoscroll::newest()),
12899 window,
12900 cx,
12901 );
12902 }
12903 if selections.len() == 1 {
12904 let selection = selections
12905 .last()
12906 .expect("ensured that there's only one selection");
12907 let query = buffer
12908 .text_for_range(selection.start..selection.end)
12909 .collect::<String>();
12910 let is_empty = query.is_empty();
12911 let select_state = SelectNextState {
12912 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12913 wordwise: true,
12914 done: is_empty,
12915 };
12916 self.select_prev_state = Some(select_state);
12917 } else {
12918 self.select_prev_state = None;
12919 }
12920 } else if let Some(selected_text) = selected_text {
12921 self.select_prev_state = Some(SelectNextState {
12922 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12923 wordwise: false,
12924 done: false,
12925 });
12926 self.select_previous(action, window, cx)?;
12927 }
12928 }
12929 Ok(())
12930 }
12931
12932 pub fn find_next_match(
12933 &mut self,
12934 _: &FindNextMatch,
12935 window: &mut Window,
12936 cx: &mut Context<Self>,
12937 ) -> Result<()> {
12938 let selections = self.selections.disjoint_anchors();
12939 match selections.first() {
12940 Some(first) if selections.len() >= 2 => {
12941 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12942 s.select_ranges([first.range()]);
12943 });
12944 }
12945 _ => self.select_next(
12946 &SelectNext {
12947 replace_newest: true,
12948 },
12949 window,
12950 cx,
12951 )?,
12952 }
12953 Ok(())
12954 }
12955
12956 pub fn find_previous_match(
12957 &mut self,
12958 _: &FindPreviousMatch,
12959 window: &mut Window,
12960 cx: &mut Context<Self>,
12961 ) -> Result<()> {
12962 let selections = self.selections.disjoint_anchors();
12963 match selections.last() {
12964 Some(last) if selections.len() >= 2 => {
12965 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12966 s.select_ranges([last.range()]);
12967 });
12968 }
12969 _ => self.select_previous(
12970 &SelectPrevious {
12971 replace_newest: true,
12972 },
12973 window,
12974 cx,
12975 )?,
12976 }
12977 Ok(())
12978 }
12979
12980 pub fn toggle_comments(
12981 &mut self,
12982 action: &ToggleComments,
12983 window: &mut Window,
12984 cx: &mut Context<Self>,
12985 ) {
12986 if self.read_only(cx) {
12987 return;
12988 }
12989 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12990 let text_layout_details = &self.text_layout_details(window);
12991 self.transact(window, cx, |this, window, cx| {
12992 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
12993 let mut edits = Vec::new();
12994 let mut selection_edit_ranges = Vec::new();
12995 let mut last_toggled_row = None;
12996 let snapshot = this.buffer.read(cx).read(cx);
12997 let empty_str: Arc<str> = Arc::default();
12998 let mut suffixes_inserted = Vec::new();
12999 let ignore_indent = action.ignore_indent;
13000
13001 fn comment_prefix_range(
13002 snapshot: &MultiBufferSnapshot,
13003 row: MultiBufferRow,
13004 comment_prefix: &str,
13005 comment_prefix_whitespace: &str,
13006 ignore_indent: bool,
13007 ) -> Range<Point> {
13008 let indent_size = if ignore_indent {
13009 0
13010 } else {
13011 snapshot.indent_size_for_line(row).len
13012 };
13013
13014 let start = Point::new(row.0, indent_size);
13015
13016 let mut line_bytes = snapshot
13017 .bytes_in_range(start..snapshot.max_point())
13018 .flatten()
13019 .copied();
13020
13021 // If this line currently begins with the line comment prefix, then record
13022 // the range containing the prefix.
13023 if line_bytes
13024 .by_ref()
13025 .take(comment_prefix.len())
13026 .eq(comment_prefix.bytes())
13027 {
13028 // Include any whitespace that matches the comment prefix.
13029 let matching_whitespace_len = line_bytes
13030 .zip(comment_prefix_whitespace.bytes())
13031 .take_while(|(a, b)| a == b)
13032 .count() as u32;
13033 let end = Point::new(
13034 start.row,
13035 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13036 );
13037 start..end
13038 } else {
13039 start..start
13040 }
13041 }
13042
13043 fn comment_suffix_range(
13044 snapshot: &MultiBufferSnapshot,
13045 row: MultiBufferRow,
13046 comment_suffix: &str,
13047 comment_suffix_has_leading_space: bool,
13048 ) -> Range<Point> {
13049 let end = Point::new(row.0, snapshot.line_len(row));
13050 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13051
13052 let mut line_end_bytes = snapshot
13053 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13054 .flatten()
13055 .copied();
13056
13057 let leading_space_len = if suffix_start_column > 0
13058 && line_end_bytes.next() == Some(b' ')
13059 && comment_suffix_has_leading_space
13060 {
13061 1
13062 } else {
13063 0
13064 };
13065
13066 // If this line currently begins with the line comment prefix, then record
13067 // the range containing the prefix.
13068 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13069 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13070 start..end
13071 } else {
13072 end..end
13073 }
13074 }
13075
13076 // TODO: Handle selections that cross excerpts
13077 for selection in &mut selections {
13078 let start_column = snapshot
13079 .indent_size_for_line(MultiBufferRow(selection.start.row))
13080 .len;
13081 let language = if let Some(language) =
13082 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13083 {
13084 language
13085 } else {
13086 continue;
13087 };
13088
13089 selection_edit_ranges.clear();
13090
13091 // If multiple selections contain a given row, avoid processing that
13092 // row more than once.
13093 let mut start_row = MultiBufferRow(selection.start.row);
13094 if last_toggled_row == Some(start_row) {
13095 start_row = start_row.next_row();
13096 }
13097 let end_row =
13098 if selection.end.row > selection.start.row && selection.end.column == 0 {
13099 MultiBufferRow(selection.end.row - 1)
13100 } else {
13101 MultiBufferRow(selection.end.row)
13102 };
13103 last_toggled_row = Some(end_row);
13104
13105 if start_row > end_row {
13106 continue;
13107 }
13108
13109 // If the language has line comments, toggle those.
13110 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13111
13112 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13113 if ignore_indent {
13114 full_comment_prefixes = full_comment_prefixes
13115 .into_iter()
13116 .map(|s| Arc::from(s.trim_end()))
13117 .collect();
13118 }
13119
13120 if !full_comment_prefixes.is_empty() {
13121 let first_prefix = full_comment_prefixes
13122 .first()
13123 .expect("prefixes is non-empty");
13124 let prefix_trimmed_lengths = full_comment_prefixes
13125 .iter()
13126 .map(|p| p.trim_end_matches(' ').len())
13127 .collect::<SmallVec<[usize; 4]>>();
13128
13129 let mut all_selection_lines_are_comments = true;
13130
13131 for row in start_row.0..=end_row.0 {
13132 let row = MultiBufferRow(row);
13133 if start_row < end_row && snapshot.is_line_blank(row) {
13134 continue;
13135 }
13136
13137 let prefix_range = full_comment_prefixes
13138 .iter()
13139 .zip(prefix_trimmed_lengths.iter().copied())
13140 .map(|(prefix, trimmed_prefix_len)| {
13141 comment_prefix_range(
13142 snapshot.deref(),
13143 row,
13144 &prefix[..trimmed_prefix_len],
13145 &prefix[trimmed_prefix_len..],
13146 ignore_indent,
13147 )
13148 })
13149 .max_by_key(|range| range.end.column - range.start.column)
13150 .expect("prefixes is non-empty");
13151
13152 if prefix_range.is_empty() {
13153 all_selection_lines_are_comments = false;
13154 }
13155
13156 selection_edit_ranges.push(prefix_range);
13157 }
13158
13159 if all_selection_lines_are_comments {
13160 edits.extend(
13161 selection_edit_ranges
13162 .iter()
13163 .cloned()
13164 .map(|range| (range, empty_str.clone())),
13165 );
13166 } else {
13167 let min_column = selection_edit_ranges
13168 .iter()
13169 .map(|range| range.start.column)
13170 .min()
13171 .unwrap_or(0);
13172 edits.extend(selection_edit_ranges.iter().map(|range| {
13173 let position = Point::new(range.start.row, min_column);
13174 (position..position, first_prefix.clone())
13175 }));
13176 }
13177 } else if let Some((full_comment_prefix, comment_suffix)) =
13178 language.block_comment_delimiters()
13179 {
13180 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13181 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13182 let prefix_range = comment_prefix_range(
13183 snapshot.deref(),
13184 start_row,
13185 comment_prefix,
13186 comment_prefix_whitespace,
13187 ignore_indent,
13188 );
13189 let suffix_range = comment_suffix_range(
13190 snapshot.deref(),
13191 end_row,
13192 comment_suffix.trim_start_matches(' '),
13193 comment_suffix.starts_with(' '),
13194 );
13195
13196 if prefix_range.is_empty() || suffix_range.is_empty() {
13197 edits.push((
13198 prefix_range.start..prefix_range.start,
13199 full_comment_prefix.clone(),
13200 ));
13201 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13202 suffixes_inserted.push((end_row, comment_suffix.len()));
13203 } else {
13204 edits.push((prefix_range, empty_str.clone()));
13205 edits.push((suffix_range, empty_str.clone()));
13206 }
13207 } else {
13208 continue;
13209 }
13210 }
13211
13212 drop(snapshot);
13213 this.buffer.update(cx, |buffer, cx| {
13214 buffer.edit(edits, None, cx);
13215 });
13216
13217 // Adjust selections so that they end before any comment suffixes that
13218 // were inserted.
13219 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13220 let mut selections = this.selections.all::<Point>(cx);
13221 let snapshot = this.buffer.read(cx).read(cx);
13222 for selection in &mut selections {
13223 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13224 match row.cmp(&MultiBufferRow(selection.end.row)) {
13225 Ordering::Less => {
13226 suffixes_inserted.next();
13227 continue;
13228 }
13229 Ordering::Greater => break,
13230 Ordering::Equal => {
13231 if selection.end.column == snapshot.line_len(row) {
13232 if selection.is_empty() {
13233 selection.start.column -= suffix_len as u32;
13234 }
13235 selection.end.column -= suffix_len as u32;
13236 }
13237 break;
13238 }
13239 }
13240 }
13241 }
13242
13243 drop(snapshot);
13244 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13245 s.select(selections)
13246 });
13247
13248 let selections = this.selections.all::<Point>(cx);
13249 let selections_on_single_row = selections.windows(2).all(|selections| {
13250 selections[0].start.row == selections[1].start.row
13251 && selections[0].end.row == selections[1].end.row
13252 && selections[0].start.row == selections[0].end.row
13253 });
13254 let selections_selecting = selections
13255 .iter()
13256 .any(|selection| selection.start != selection.end);
13257 let advance_downwards = action.advance_downwards
13258 && selections_on_single_row
13259 && !selections_selecting
13260 && !matches!(this.mode, EditorMode::SingleLine { .. });
13261
13262 if advance_downwards {
13263 let snapshot = this.buffer.read(cx).snapshot(cx);
13264
13265 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13266 s.move_cursors_with(|display_snapshot, display_point, _| {
13267 let mut point = display_point.to_point(display_snapshot);
13268 point.row += 1;
13269 point = snapshot.clip_point(point, Bias::Left);
13270 let display_point = point.to_display_point(display_snapshot);
13271 let goal = SelectionGoal::HorizontalPosition(
13272 display_snapshot
13273 .x_for_display_point(display_point, text_layout_details)
13274 .into(),
13275 );
13276 (display_point, goal)
13277 })
13278 });
13279 }
13280 });
13281 }
13282
13283 pub fn select_enclosing_symbol(
13284 &mut self,
13285 _: &SelectEnclosingSymbol,
13286 window: &mut Window,
13287 cx: &mut Context<Self>,
13288 ) {
13289 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13290
13291 let buffer = self.buffer.read(cx).snapshot(cx);
13292 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13293
13294 fn update_selection(
13295 selection: &Selection<usize>,
13296 buffer_snap: &MultiBufferSnapshot,
13297 ) -> Option<Selection<usize>> {
13298 let cursor = selection.head();
13299 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13300 for symbol in symbols.iter().rev() {
13301 let start = symbol.range.start.to_offset(buffer_snap);
13302 let end = symbol.range.end.to_offset(buffer_snap);
13303 let new_range = start..end;
13304 if start < selection.start || end > selection.end {
13305 return Some(Selection {
13306 id: selection.id,
13307 start: new_range.start,
13308 end: new_range.end,
13309 goal: SelectionGoal::None,
13310 reversed: selection.reversed,
13311 });
13312 }
13313 }
13314 None
13315 }
13316
13317 let mut selected_larger_symbol = false;
13318 let new_selections = old_selections
13319 .iter()
13320 .map(|selection| match update_selection(selection, &buffer) {
13321 Some(new_selection) => {
13322 if new_selection.range() != selection.range() {
13323 selected_larger_symbol = true;
13324 }
13325 new_selection
13326 }
13327 None => selection.clone(),
13328 })
13329 .collect::<Vec<_>>();
13330
13331 if selected_larger_symbol {
13332 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13333 s.select(new_selections);
13334 });
13335 }
13336 }
13337
13338 pub fn select_larger_syntax_node(
13339 &mut self,
13340 _: &SelectLargerSyntaxNode,
13341 window: &mut Window,
13342 cx: &mut Context<Self>,
13343 ) {
13344 let Some(visible_row_count) = self.visible_row_count() else {
13345 return;
13346 };
13347 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13348 if old_selections.is_empty() {
13349 return;
13350 }
13351
13352 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13353
13354 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13355 let buffer = self.buffer.read(cx).snapshot(cx);
13356
13357 let mut selected_larger_node = false;
13358 let mut new_selections = old_selections
13359 .iter()
13360 .map(|selection| {
13361 let old_range = selection.start..selection.end;
13362
13363 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13364 // manually select word at selection
13365 if ["string_content", "inline"].contains(&node.kind()) {
13366 let word_range = {
13367 let display_point = buffer
13368 .offset_to_point(old_range.start)
13369 .to_display_point(&display_map);
13370 let Range { start, end } =
13371 movement::surrounding_word(&display_map, display_point);
13372 start.to_point(&display_map).to_offset(&buffer)
13373 ..end.to_point(&display_map).to_offset(&buffer)
13374 };
13375 // ignore if word is already selected
13376 if !word_range.is_empty() && old_range != word_range {
13377 let last_word_range = {
13378 let display_point = buffer
13379 .offset_to_point(old_range.end)
13380 .to_display_point(&display_map);
13381 let Range { start, end } =
13382 movement::surrounding_word(&display_map, display_point);
13383 start.to_point(&display_map).to_offset(&buffer)
13384 ..end.to_point(&display_map).to_offset(&buffer)
13385 };
13386 // only select word if start and end point belongs to same word
13387 if word_range == last_word_range {
13388 selected_larger_node = true;
13389 return Selection {
13390 id: selection.id,
13391 start: word_range.start,
13392 end: word_range.end,
13393 goal: SelectionGoal::None,
13394 reversed: selection.reversed,
13395 };
13396 }
13397 }
13398 }
13399 }
13400
13401 let mut new_range = old_range.clone();
13402 while let Some((_node, containing_range)) =
13403 buffer.syntax_ancestor(new_range.clone())
13404 {
13405 new_range = match containing_range {
13406 MultiOrSingleBufferOffsetRange::Single(_) => break,
13407 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13408 };
13409 if !display_map.intersects_fold(new_range.start)
13410 && !display_map.intersects_fold(new_range.end)
13411 {
13412 break;
13413 }
13414 }
13415
13416 selected_larger_node |= new_range != old_range;
13417 Selection {
13418 id: selection.id,
13419 start: new_range.start,
13420 end: new_range.end,
13421 goal: SelectionGoal::None,
13422 reversed: selection.reversed,
13423 }
13424 })
13425 .collect::<Vec<_>>();
13426
13427 if !selected_larger_node {
13428 return; // don't put this call in the history
13429 }
13430
13431 // scroll based on transformation done to the last selection created by the user
13432 let (last_old, last_new) = old_selections
13433 .last()
13434 .zip(new_selections.last().cloned())
13435 .expect("old_selections isn't empty");
13436
13437 // revert selection
13438 let is_selection_reversed = {
13439 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13440 new_selections.last_mut().expect("checked above").reversed =
13441 should_newest_selection_be_reversed;
13442 should_newest_selection_be_reversed
13443 };
13444
13445 if selected_larger_node {
13446 self.select_syntax_node_history.disable_clearing = true;
13447 self.change_selections(None, window, cx, |s| {
13448 s.select(new_selections.clone());
13449 });
13450 self.select_syntax_node_history.disable_clearing = false;
13451 }
13452
13453 let start_row = last_new.start.to_display_point(&display_map).row().0;
13454 let end_row = last_new.end.to_display_point(&display_map).row().0;
13455 let selection_height = end_row - start_row + 1;
13456 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13457
13458 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13459 let scroll_behavior = if fits_on_the_screen {
13460 self.request_autoscroll(Autoscroll::fit(), cx);
13461 SelectSyntaxNodeScrollBehavior::FitSelection
13462 } else if is_selection_reversed {
13463 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13464 SelectSyntaxNodeScrollBehavior::CursorTop
13465 } else {
13466 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13467 SelectSyntaxNodeScrollBehavior::CursorBottom
13468 };
13469
13470 self.select_syntax_node_history.push((
13471 old_selections,
13472 scroll_behavior,
13473 is_selection_reversed,
13474 ));
13475 }
13476
13477 pub fn select_smaller_syntax_node(
13478 &mut self,
13479 _: &SelectSmallerSyntaxNode,
13480 window: &mut Window,
13481 cx: &mut Context<Self>,
13482 ) {
13483 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13484
13485 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13486 self.select_syntax_node_history.pop()
13487 {
13488 if let Some(selection) = selections.last_mut() {
13489 selection.reversed = is_selection_reversed;
13490 }
13491
13492 self.select_syntax_node_history.disable_clearing = true;
13493 self.change_selections(None, window, cx, |s| {
13494 s.select(selections.to_vec());
13495 });
13496 self.select_syntax_node_history.disable_clearing = false;
13497
13498 match scroll_behavior {
13499 SelectSyntaxNodeScrollBehavior::CursorTop => {
13500 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13501 }
13502 SelectSyntaxNodeScrollBehavior::FitSelection => {
13503 self.request_autoscroll(Autoscroll::fit(), cx);
13504 }
13505 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13506 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13507 }
13508 }
13509 }
13510 }
13511
13512 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13513 if !EditorSettings::get_global(cx).gutter.runnables {
13514 self.clear_tasks();
13515 return Task::ready(());
13516 }
13517 let project = self.project.as_ref().map(Entity::downgrade);
13518 let task_sources = self.lsp_task_sources(cx);
13519 cx.spawn_in(window, async move |editor, cx| {
13520 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13521 let Some(project) = project.and_then(|p| p.upgrade()) else {
13522 return;
13523 };
13524 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13525 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13526 }) else {
13527 return;
13528 };
13529
13530 let hide_runnables = project
13531 .update(cx, |project, cx| {
13532 // Do not display any test indicators in non-dev server remote projects.
13533 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13534 })
13535 .unwrap_or(true);
13536 if hide_runnables {
13537 return;
13538 }
13539 let new_rows =
13540 cx.background_spawn({
13541 let snapshot = display_snapshot.clone();
13542 async move {
13543 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13544 }
13545 })
13546 .await;
13547 let Ok(lsp_tasks) =
13548 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13549 else {
13550 return;
13551 };
13552 let lsp_tasks = lsp_tasks.await;
13553
13554 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13555 lsp_tasks
13556 .into_iter()
13557 .flat_map(|(kind, tasks)| {
13558 tasks.into_iter().filter_map(move |(location, task)| {
13559 Some((kind.clone(), location?, task))
13560 })
13561 })
13562 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13563 let buffer = location.target.buffer;
13564 let buffer_snapshot = buffer.read(cx).snapshot();
13565 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13566 |(excerpt_id, snapshot, _)| {
13567 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13568 display_snapshot
13569 .buffer_snapshot
13570 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13571 } else {
13572 None
13573 }
13574 },
13575 );
13576 if let Some(offset) = offset {
13577 let task_buffer_range =
13578 location.target.range.to_point(&buffer_snapshot);
13579 let context_buffer_range =
13580 task_buffer_range.to_offset(&buffer_snapshot);
13581 let context_range = BufferOffset(context_buffer_range.start)
13582 ..BufferOffset(context_buffer_range.end);
13583
13584 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13585 .or_insert_with(|| RunnableTasks {
13586 templates: Vec::new(),
13587 offset,
13588 column: task_buffer_range.start.column,
13589 extra_variables: HashMap::default(),
13590 context_range,
13591 })
13592 .templates
13593 .push((kind, task.original_task().clone()));
13594 }
13595
13596 acc
13597 })
13598 }) else {
13599 return;
13600 };
13601
13602 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13603 editor
13604 .update(cx, |editor, _| {
13605 editor.clear_tasks();
13606 for (key, mut value) in rows {
13607 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13608 value.templates.extend(lsp_tasks.templates);
13609 }
13610
13611 editor.insert_tasks(key, value);
13612 }
13613 for (key, value) in lsp_tasks_by_rows {
13614 editor.insert_tasks(key, value);
13615 }
13616 })
13617 .ok();
13618 })
13619 }
13620 fn fetch_runnable_ranges(
13621 snapshot: &DisplaySnapshot,
13622 range: Range<Anchor>,
13623 ) -> Vec<language::RunnableRange> {
13624 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13625 }
13626
13627 fn runnable_rows(
13628 project: Entity<Project>,
13629 snapshot: DisplaySnapshot,
13630 runnable_ranges: Vec<RunnableRange>,
13631 mut cx: AsyncWindowContext,
13632 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13633 runnable_ranges
13634 .into_iter()
13635 .filter_map(|mut runnable| {
13636 let tasks = cx
13637 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13638 .ok()?;
13639 if tasks.is_empty() {
13640 return None;
13641 }
13642
13643 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13644
13645 let row = snapshot
13646 .buffer_snapshot
13647 .buffer_line_for_row(MultiBufferRow(point.row))?
13648 .1
13649 .start
13650 .row;
13651
13652 let context_range =
13653 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13654 Some((
13655 (runnable.buffer_id, row),
13656 RunnableTasks {
13657 templates: tasks,
13658 offset: snapshot
13659 .buffer_snapshot
13660 .anchor_before(runnable.run_range.start),
13661 context_range,
13662 column: point.column,
13663 extra_variables: runnable.extra_captures,
13664 },
13665 ))
13666 })
13667 .collect()
13668 }
13669
13670 fn templates_with_tags(
13671 project: &Entity<Project>,
13672 runnable: &mut Runnable,
13673 cx: &mut App,
13674 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13675 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13676 let (worktree_id, file) = project
13677 .buffer_for_id(runnable.buffer, cx)
13678 .and_then(|buffer| buffer.read(cx).file())
13679 .map(|file| (file.worktree_id(cx), file.clone()))
13680 .unzip();
13681
13682 (
13683 project.task_store().read(cx).task_inventory().cloned(),
13684 worktree_id,
13685 file,
13686 )
13687 });
13688
13689 let mut templates_with_tags = mem::take(&mut runnable.tags)
13690 .into_iter()
13691 .flat_map(|RunnableTag(tag)| {
13692 inventory
13693 .as_ref()
13694 .into_iter()
13695 .flat_map(|inventory| {
13696 inventory.read(cx).list_tasks(
13697 file.clone(),
13698 Some(runnable.language.clone()),
13699 worktree_id,
13700 cx,
13701 )
13702 })
13703 .filter(move |(_, template)| {
13704 template.tags.iter().any(|source_tag| source_tag == &tag)
13705 })
13706 })
13707 .sorted_by_key(|(kind, _)| kind.to_owned())
13708 .collect::<Vec<_>>();
13709 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13710 // Strongest source wins; if we have worktree tag binding, prefer that to
13711 // global and language bindings;
13712 // if we have a global binding, prefer that to language binding.
13713 let first_mismatch = templates_with_tags
13714 .iter()
13715 .position(|(tag_source, _)| tag_source != leading_tag_source);
13716 if let Some(index) = first_mismatch {
13717 templates_with_tags.truncate(index);
13718 }
13719 }
13720
13721 templates_with_tags
13722 }
13723
13724 pub fn move_to_enclosing_bracket(
13725 &mut self,
13726 _: &MoveToEnclosingBracket,
13727 window: &mut Window,
13728 cx: &mut Context<Self>,
13729 ) {
13730 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13731 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13732 s.move_offsets_with(|snapshot, selection| {
13733 let Some(enclosing_bracket_ranges) =
13734 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13735 else {
13736 return;
13737 };
13738
13739 let mut best_length = usize::MAX;
13740 let mut best_inside = false;
13741 let mut best_in_bracket_range = false;
13742 let mut best_destination = None;
13743 for (open, close) in enclosing_bracket_ranges {
13744 let close = close.to_inclusive();
13745 let length = close.end() - open.start;
13746 let inside = selection.start >= open.end && selection.end <= *close.start();
13747 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13748 || close.contains(&selection.head());
13749
13750 // If best is next to a bracket and current isn't, skip
13751 if !in_bracket_range && best_in_bracket_range {
13752 continue;
13753 }
13754
13755 // Prefer smaller lengths unless best is inside and current isn't
13756 if length > best_length && (best_inside || !inside) {
13757 continue;
13758 }
13759
13760 best_length = length;
13761 best_inside = inside;
13762 best_in_bracket_range = in_bracket_range;
13763 best_destination = Some(
13764 if close.contains(&selection.start) && close.contains(&selection.end) {
13765 if inside { open.end } else { open.start }
13766 } else if inside {
13767 *close.start()
13768 } else {
13769 *close.end()
13770 },
13771 );
13772 }
13773
13774 if let Some(destination) = best_destination {
13775 selection.collapse_to(destination, SelectionGoal::None);
13776 }
13777 })
13778 });
13779 }
13780
13781 pub fn undo_selection(
13782 &mut self,
13783 _: &UndoSelection,
13784 window: &mut Window,
13785 cx: &mut Context<Self>,
13786 ) {
13787 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13788 self.end_selection(window, cx);
13789 self.selection_history.mode = SelectionHistoryMode::Undoing;
13790 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13791 self.change_selections(None, window, cx, |s| {
13792 s.select_anchors(entry.selections.to_vec())
13793 });
13794 self.select_next_state = entry.select_next_state;
13795 self.select_prev_state = entry.select_prev_state;
13796 self.add_selections_state = entry.add_selections_state;
13797 self.request_autoscroll(Autoscroll::newest(), cx);
13798 }
13799 self.selection_history.mode = SelectionHistoryMode::Normal;
13800 }
13801
13802 pub fn redo_selection(
13803 &mut self,
13804 _: &RedoSelection,
13805 window: &mut Window,
13806 cx: &mut Context<Self>,
13807 ) {
13808 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13809 self.end_selection(window, cx);
13810 self.selection_history.mode = SelectionHistoryMode::Redoing;
13811 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13812 self.change_selections(None, window, cx, |s| {
13813 s.select_anchors(entry.selections.to_vec())
13814 });
13815 self.select_next_state = entry.select_next_state;
13816 self.select_prev_state = entry.select_prev_state;
13817 self.add_selections_state = entry.add_selections_state;
13818 self.request_autoscroll(Autoscroll::newest(), cx);
13819 }
13820 self.selection_history.mode = SelectionHistoryMode::Normal;
13821 }
13822
13823 pub fn expand_excerpts(
13824 &mut self,
13825 action: &ExpandExcerpts,
13826 _: &mut Window,
13827 cx: &mut Context<Self>,
13828 ) {
13829 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13830 }
13831
13832 pub fn expand_excerpts_down(
13833 &mut self,
13834 action: &ExpandExcerptsDown,
13835 _: &mut Window,
13836 cx: &mut Context<Self>,
13837 ) {
13838 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13839 }
13840
13841 pub fn expand_excerpts_up(
13842 &mut self,
13843 action: &ExpandExcerptsUp,
13844 _: &mut Window,
13845 cx: &mut Context<Self>,
13846 ) {
13847 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13848 }
13849
13850 pub fn expand_excerpts_for_direction(
13851 &mut self,
13852 lines: u32,
13853 direction: ExpandExcerptDirection,
13854
13855 cx: &mut Context<Self>,
13856 ) {
13857 let selections = self.selections.disjoint_anchors();
13858
13859 let lines = if lines == 0 {
13860 EditorSettings::get_global(cx).expand_excerpt_lines
13861 } else {
13862 lines
13863 };
13864
13865 self.buffer.update(cx, |buffer, cx| {
13866 let snapshot = buffer.snapshot(cx);
13867 let mut excerpt_ids = selections
13868 .iter()
13869 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13870 .collect::<Vec<_>>();
13871 excerpt_ids.sort();
13872 excerpt_ids.dedup();
13873 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13874 })
13875 }
13876
13877 pub fn expand_excerpt(
13878 &mut self,
13879 excerpt: ExcerptId,
13880 direction: ExpandExcerptDirection,
13881 window: &mut Window,
13882 cx: &mut Context<Self>,
13883 ) {
13884 let current_scroll_position = self.scroll_position(cx);
13885 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13886 let mut should_scroll_up = false;
13887
13888 if direction == ExpandExcerptDirection::Down {
13889 let multi_buffer = self.buffer.read(cx);
13890 let snapshot = multi_buffer.snapshot(cx);
13891 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13892 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13893 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13894 let buffer_snapshot = buffer.read(cx).snapshot();
13895 let excerpt_end_row =
13896 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13897 let last_row = buffer_snapshot.max_point().row;
13898 let lines_below = last_row.saturating_sub(excerpt_end_row);
13899 should_scroll_up = lines_below >= lines_to_expand;
13900 }
13901 }
13902 }
13903 }
13904
13905 self.buffer.update(cx, |buffer, cx| {
13906 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13907 });
13908
13909 if should_scroll_up {
13910 let new_scroll_position =
13911 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13912 self.set_scroll_position(new_scroll_position, window, cx);
13913 }
13914 }
13915
13916 pub fn go_to_singleton_buffer_point(
13917 &mut self,
13918 point: Point,
13919 window: &mut Window,
13920 cx: &mut Context<Self>,
13921 ) {
13922 self.go_to_singleton_buffer_range(point..point, window, cx);
13923 }
13924
13925 pub fn go_to_singleton_buffer_range(
13926 &mut self,
13927 range: Range<Point>,
13928 window: &mut Window,
13929 cx: &mut Context<Self>,
13930 ) {
13931 let multibuffer = self.buffer().read(cx);
13932 let Some(buffer) = multibuffer.as_singleton() else {
13933 return;
13934 };
13935 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13936 return;
13937 };
13938 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13939 return;
13940 };
13941 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13942 s.select_anchor_ranges([start..end])
13943 });
13944 }
13945
13946 pub fn go_to_diagnostic(
13947 &mut self,
13948 _: &GoToDiagnostic,
13949 window: &mut Window,
13950 cx: &mut Context<Self>,
13951 ) {
13952 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13953 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13954 }
13955
13956 pub fn go_to_prev_diagnostic(
13957 &mut self,
13958 _: &GoToPreviousDiagnostic,
13959 window: &mut Window,
13960 cx: &mut Context<Self>,
13961 ) {
13962 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13963 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13964 }
13965
13966 pub fn go_to_diagnostic_impl(
13967 &mut self,
13968 direction: Direction,
13969 window: &mut Window,
13970 cx: &mut Context<Self>,
13971 ) {
13972 let buffer = self.buffer.read(cx).snapshot(cx);
13973 let selection = self.selections.newest::<usize>(cx);
13974
13975 let mut active_group_id = None;
13976 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
13977 if active_group.active_range.start.to_offset(&buffer) == selection.start {
13978 active_group_id = Some(active_group.group_id);
13979 }
13980 }
13981
13982 fn filtered(
13983 snapshot: EditorSnapshot,
13984 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
13985 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
13986 diagnostics
13987 .filter(|entry| entry.range.start != entry.range.end)
13988 .filter(|entry| !entry.diagnostic.is_unnecessary)
13989 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
13990 }
13991
13992 let snapshot = self.snapshot(window, cx);
13993 let before = filtered(
13994 snapshot.clone(),
13995 buffer
13996 .diagnostics_in_range(0..selection.start)
13997 .filter(|entry| entry.range.start <= selection.start),
13998 );
13999 let after = filtered(
14000 snapshot,
14001 buffer
14002 .diagnostics_in_range(selection.start..buffer.len())
14003 .filter(|entry| entry.range.start >= selection.start),
14004 );
14005
14006 let mut found: Option<DiagnosticEntry<usize>> = None;
14007 if direction == Direction::Prev {
14008 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14009 {
14010 for diagnostic in prev_diagnostics.into_iter().rev() {
14011 if diagnostic.range.start != selection.start
14012 || active_group_id
14013 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14014 {
14015 found = Some(diagnostic);
14016 break 'outer;
14017 }
14018 }
14019 }
14020 } else {
14021 for diagnostic in after.chain(before) {
14022 if diagnostic.range.start != selection.start
14023 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14024 {
14025 found = Some(diagnostic);
14026 break;
14027 }
14028 }
14029 }
14030 let Some(next_diagnostic) = found else {
14031 return;
14032 };
14033
14034 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14035 return;
14036 };
14037 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14038 s.select_ranges(vec![
14039 next_diagnostic.range.start..next_diagnostic.range.start,
14040 ])
14041 });
14042 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14043 self.refresh_inline_completion(false, true, window, cx);
14044 }
14045
14046 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14047 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14048 let snapshot = self.snapshot(window, cx);
14049 let selection = self.selections.newest::<Point>(cx);
14050 self.go_to_hunk_before_or_after_position(
14051 &snapshot,
14052 selection.head(),
14053 Direction::Next,
14054 window,
14055 cx,
14056 );
14057 }
14058
14059 pub fn go_to_hunk_before_or_after_position(
14060 &mut self,
14061 snapshot: &EditorSnapshot,
14062 position: Point,
14063 direction: Direction,
14064 window: &mut Window,
14065 cx: &mut Context<Editor>,
14066 ) {
14067 let row = if direction == Direction::Next {
14068 self.hunk_after_position(snapshot, position)
14069 .map(|hunk| hunk.row_range.start)
14070 } else {
14071 self.hunk_before_position(snapshot, position)
14072 };
14073
14074 if let Some(row) = row {
14075 let destination = Point::new(row.0, 0);
14076 let autoscroll = Autoscroll::center();
14077
14078 self.unfold_ranges(&[destination..destination], false, false, cx);
14079 self.change_selections(Some(autoscroll), window, cx, |s| {
14080 s.select_ranges([destination..destination]);
14081 });
14082 }
14083 }
14084
14085 fn hunk_after_position(
14086 &mut self,
14087 snapshot: &EditorSnapshot,
14088 position: Point,
14089 ) -> Option<MultiBufferDiffHunk> {
14090 snapshot
14091 .buffer_snapshot
14092 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14093 .find(|hunk| hunk.row_range.start.0 > position.row)
14094 .or_else(|| {
14095 snapshot
14096 .buffer_snapshot
14097 .diff_hunks_in_range(Point::zero()..position)
14098 .find(|hunk| hunk.row_range.end.0 < position.row)
14099 })
14100 }
14101
14102 fn go_to_prev_hunk(
14103 &mut self,
14104 _: &GoToPreviousHunk,
14105 window: &mut Window,
14106 cx: &mut Context<Self>,
14107 ) {
14108 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14109 let snapshot = self.snapshot(window, cx);
14110 let selection = self.selections.newest::<Point>(cx);
14111 self.go_to_hunk_before_or_after_position(
14112 &snapshot,
14113 selection.head(),
14114 Direction::Prev,
14115 window,
14116 cx,
14117 );
14118 }
14119
14120 fn hunk_before_position(
14121 &mut self,
14122 snapshot: &EditorSnapshot,
14123 position: Point,
14124 ) -> Option<MultiBufferRow> {
14125 snapshot
14126 .buffer_snapshot
14127 .diff_hunk_before(position)
14128 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14129 }
14130
14131 fn go_to_next_change(
14132 &mut self,
14133 _: &GoToNextChange,
14134 window: &mut Window,
14135 cx: &mut Context<Self>,
14136 ) {
14137 if let Some(selections) = self
14138 .change_list
14139 .next_change(1, Direction::Next)
14140 .map(|s| s.to_vec())
14141 {
14142 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14143 let map = s.display_map();
14144 s.select_display_ranges(selections.iter().map(|a| {
14145 let point = a.to_display_point(&map);
14146 point..point
14147 }))
14148 })
14149 }
14150 }
14151
14152 fn go_to_previous_change(
14153 &mut self,
14154 _: &GoToPreviousChange,
14155 window: &mut Window,
14156 cx: &mut Context<Self>,
14157 ) {
14158 if let Some(selections) = self
14159 .change_list
14160 .next_change(1, Direction::Prev)
14161 .map(|s| s.to_vec())
14162 {
14163 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14164 let map = s.display_map();
14165 s.select_display_ranges(selections.iter().map(|a| {
14166 let point = a.to_display_point(&map);
14167 point..point
14168 }))
14169 })
14170 }
14171 }
14172
14173 fn go_to_line<T: 'static>(
14174 &mut self,
14175 position: Anchor,
14176 highlight_color: Option<Hsla>,
14177 window: &mut Window,
14178 cx: &mut Context<Self>,
14179 ) {
14180 let snapshot = self.snapshot(window, cx).display_snapshot;
14181 let position = position.to_point(&snapshot.buffer_snapshot);
14182 let start = snapshot
14183 .buffer_snapshot
14184 .clip_point(Point::new(position.row, 0), Bias::Left);
14185 let end = start + Point::new(1, 0);
14186 let start = snapshot.buffer_snapshot.anchor_before(start);
14187 let end = snapshot.buffer_snapshot.anchor_before(end);
14188
14189 self.highlight_rows::<T>(
14190 start..end,
14191 highlight_color
14192 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14193 Default::default(),
14194 cx,
14195 );
14196
14197 if self.buffer.read(cx).is_singleton() {
14198 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14199 }
14200 }
14201
14202 pub fn go_to_definition(
14203 &mut self,
14204 _: &GoToDefinition,
14205 window: &mut Window,
14206 cx: &mut Context<Self>,
14207 ) -> Task<Result<Navigated>> {
14208 let definition =
14209 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14210 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14211 cx.spawn_in(window, async move |editor, cx| {
14212 if definition.await? == Navigated::Yes {
14213 return Ok(Navigated::Yes);
14214 }
14215 match fallback_strategy {
14216 GoToDefinitionFallback::None => Ok(Navigated::No),
14217 GoToDefinitionFallback::FindAllReferences => {
14218 match editor.update_in(cx, |editor, window, cx| {
14219 editor.find_all_references(&FindAllReferences, window, cx)
14220 })? {
14221 Some(references) => references.await,
14222 None => Ok(Navigated::No),
14223 }
14224 }
14225 }
14226 })
14227 }
14228
14229 pub fn go_to_declaration(
14230 &mut self,
14231 _: &GoToDeclaration,
14232 window: &mut Window,
14233 cx: &mut Context<Self>,
14234 ) -> Task<Result<Navigated>> {
14235 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14236 }
14237
14238 pub fn go_to_declaration_split(
14239 &mut self,
14240 _: &GoToDeclaration,
14241 window: &mut Window,
14242 cx: &mut Context<Self>,
14243 ) -> Task<Result<Navigated>> {
14244 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14245 }
14246
14247 pub fn go_to_implementation(
14248 &mut self,
14249 _: &GoToImplementation,
14250 window: &mut Window,
14251 cx: &mut Context<Self>,
14252 ) -> Task<Result<Navigated>> {
14253 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14254 }
14255
14256 pub fn go_to_implementation_split(
14257 &mut self,
14258 _: &GoToImplementationSplit,
14259 window: &mut Window,
14260 cx: &mut Context<Self>,
14261 ) -> Task<Result<Navigated>> {
14262 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14263 }
14264
14265 pub fn go_to_type_definition(
14266 &mut self,
14267 _: &GoToTypeDefinition,
14268 window: &mut Window,
14269 cx: &mut Context<Self>,
14270 ) -> Task<Result<Navigated>> {
14271 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14272 }
14273
14274 pub fn go_to_definition_split(
14275 &mut self,
14276 _: &GoToDefinitionSplit,
14277 window: &mut Window,
14278 cx: &mut Context<Self>,
14279 ) -> Task<Result<Navigated>> {
14280 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14281 }
14282
14283 pub fn go_to_type_definition_split(
14284 &mut self,
14285 _: &GoToTypeDefinitionSplit,
14286 window: &mut Window,
14287 cx: &mut Context<Self>,
14288 ) -> Task<Result<Navigated>> {
14289 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14290 }
14291
14292 fn go_to_definition_of_kind(
14293 &mut self,
14294 kind: GotoDefinitionKind,
14295 split: bool,
14296 window: &mut Window,
14297 cx: &mut Context<Self>,
14298 ) -> Task<Result<Navigated>> {
14299 let Some(provider) = self.semantics_provider.clone() else {
14300 return Task::ready(Ok(Navigated::No));
14301 };
14302 let head = self.selections.newest::<usize>(cx).head();
14303 let buffer = self.buffer.read(cx);
14304 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14305 text_anchor
14306 } else {
14307 return Task::ready(Ok(Navigated::No));
14308 };
14309
14310 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14311 return Task::ready(Ok(Navigated::No));
14312 };
14313
14314 cx.spawn_in(window, async move |editor, cx| {
14315 let definitions = definitions.await?;
14316 let navigated = editor
14317 .update_in(cx, |editor, window, cx| {
14318 editor.navigate_to_hover_links(
14319 Some(kind),
14320 definitions
14321 .into_iter()
14322 .filter(|location| {
14323 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14324 })
14325 .map(HoverLink::Text)
14326 .collect::<Vec<_>>(),
14327 split,
14328 window,
14329 cx,
14330 )
14331 })?
14332 .await?;
14333 anyhow::Ok(navigated)
14334 })
14335 }
14336
14337 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14338 let selection = self.selections.newest_anchor();
14339 let head = selection.head();
14340 let tail = selection.tail();
14341
14342 let Some((buffer, start_position)) =
14343 self.buffer.read(cx).text_anchor_for_position(head, cx)
14344 else {
14345 return;
14346 };
14347
14348 let end_position = if head != tail {
14349 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14350 return;
14351 };
14352 Some(pos)
14353 } else {
14354 None
14355 };
14356
14357 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14358 let url = if let Some(end_pos) = end_position {
14359 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14360 } else {
14361 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14362 };
14363
14364 if let Some(url) = url {
14365 editor.update(cx, |_, cx| {
14366 cx.open_url(&url);
14367 })
14368 } else {
14369 Ok(())
14370 }
14371 });
14372
14373 url_finder.detach();
14374 }
14375
14376 pub fn open_selected_filename(
14377 &mut self,
14378 _: &OpenSelectedFilename,
14379 window: &mut Window,
14380 cx: &mut Context<Self>,
14381 ) {
14382 let Some(workspace) = self.workspace() else {
14383 return;
14384 };
14385
14386 let position = self.selections.newest_anchor().head();
14387
14388 let Some((buffer, buffer_position)) =
14389 self.buffer.read(cx).text_anchor_for_position(position, cx)
14390 else {
14391 return;
14392 };
14393
14394 let project = self.project.clone();
14395
14396 cx.spawn_in(window, async move |_, cx| {
14397 let result = find_file(&buffer, project, buffer_position, cx).await;
14398
14399 if let Some((_, path)) = result {
14400 workspace
14401 .update_in(cx, |workspace, window, cx| {
14402 workspace.open_resolved_path(path, window, cx)
14403 })?
14404 .await?;
14405 }
14406 anyhow::Ok(())
14407 })
14408 .detach();
14409 }
14410
14411 pub(crate) fn navigate_to_hover_links(
14412 &mut self,
14413 kind: Option<GotoDefinitionKind>,
14414 mut definitions: Vec<HoverLink>,
14415 split: bool,
14416 window: &mut Window,
14417 cx: &mut Context<Editor>,
14418 ) -> Task<Result<Navigated>> {
14419 // If there is one definition, just open it directly
14420 if definitions.len() == 1 {
14421 let definition = definitions.pop().unwrap();
14422
14423 enum TargetTaskResult {
14424 Location(Option<Location>),
14425 AlreadyNavigated,
14426 }
14427
14428 let target_task = match definition {
14429 HoverLink::Text(link) => {
14430 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14431 }
14432 HoverLink::InlayHint(lsp_location, server_id) => {
14433 let computation =
14434 self.compute_target_location(lsp_location, server_id, window, cx);
14435 cx.background_spawn(async move {
14436 let location = computation.await?;
14437 Ok(TargetTaskResult::Location(location))
14438 })
14439 }
14440 HoverLink::Url(url) => {
14441 cx.open_url(&url);
14442 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14443 }
14444 HoverLink::File(path) => {
14445 if let Some(workspace) = self.workspace() {
14446 cx.spawn_in(window, async move |_, cx| {
14447 workspace
14448 .update_in(cx, |workspace, window, cx| {
14449 workspace.open_resolved_path(path, window, cx)
14450 })?
14451 .await
14452 .map(|_| TargetTaskResult::AlreadyNavigated)
14453 })
14454 } else {
14455 Task::ready(Ok(TargetTaskResult::Location(None)))
14456 }
14457 }
14458 };
14459 cx.spawn_in(window, async move |editor, cx| {
14460 let target = match target_task.await.context("target resolution task")? {
14461 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14462 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14463 TargetTaskResult::Location(Some(target)) => target,
14464 };
14465
14466 editor.update_in(cx, |editor, window, cx| {
14467 let Some(workspace) = editor.workspace() else {
14468 return Navigated::No;
14469 };
14470 let pane = workspace.read(cx).active_pane().clone();
14471
14472 let range = target.range.to_point(target.buffer.read(cx));
14473 let range = editor.range_for_match(&range);
14474 let range = collapse_multiline_range(range);
14475
14476 if !split
14477 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14478 {
14479 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14480 } else {
14481 window.defer(cx, move |window, cx| {
14482 let target_editor: Entity<Self> =
14483 workspace.update(cx, |workspace, cx| {
14484 let pane = if split {
14485 workspace.adjacent_pane(window, cx)
14486 } else {
14487 workspace.active_pane().clone()
14488 };
14489
14490 workspace.open_project_item(
14491 pane,
14492 target.buffer.clone(),
14493 true,
14494 true,
14495 window,
14496 cx,
14497 )
14498 });
14499 target_editor.update(cx, |target_editor, cx| {
14500 // When selecting a definition in a different buffer, disable the nav history
14501 // to avoid creating a history entry at the previous cursor location.
14502 pane.update(cx, |pane, _| pane.disable_history());
14503 target_editor.go_to_singleton_buffer_range(range, window, cx);
14504 pane.update(cx, |pane, _| pane.enable_history());
14505 });
14506 });
14507 }
14508 Navigated::Yes
14509 })
14510 })
14511 } else if !definitions.is_empty() {
14512 cx.spawn_in(window, async move |editor, cx| {
14513 let (title, location_tasks, workspace) = editor
14514 .update_in(cx, |editor, window, cx| {
14515 let tab_kind = match kind {
14516 Some(GotoDefinitionKind::Implementation) => "Implementations",
14517 _ => "Definitions",
14518 };
14519 let title = definitions
14520 .iter()
14521 .find_map(|definition| match definition {
14522 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14523 let buffer = origin.buffer.read(cx);
14524 format!(
14525 "{} for {}",
14526 tab_kind,
14527 buffer
14528 .text_for_range(origin.range.clone())
14529 .collect::<String>()
14530 )
14531 }),
14532 HoverLink::InlayHint(_, _) => None,
14533 HoverLink::Url(_) => None,
14534 HoverLink::File(_) => None,
14535 })
14536 .unwrap_or(tab_kind.to_string());
14537 let location_tasks = definitions
14538 .into_iter()
14539 .map(|definition| match definition {
14540 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14541 HoverLink::InlayHint(lsp_location, server_id) => editor
14542 .compute_target_location(lsp_location, server_id, window, cx),
14543 HoverLink::Url(_) => Task::ready(Ok(None)),
14544 HoverLink::File(_) => Task::ready(Ok(None)),
14545 })
14546 .collect::<Vec<_>>();
14547 (title, location_tasks, editor.workspace().clone())
14548 })
14549 .context("location tasks preparation")?;
14550
14551 let locations = future::join_all(location_tasks)
14552 .await
14553 .into_iter()
14554 .filter_map(|location| location.transpose())
14555 .collect::<Result<_>>()
14556 .context("location tasks")?;
14557
14558 let Some(workspace) = workspace else {
14559 return Ok(Navigated::No);
14560 };
14561 let opened = workspace
14562 .update_in(cx, |workspace, window, cx| {
14563 Self::open_locations_in_multibuffer(
14564 workspace,
14565 locations,
14566 title,
14567 split,
14568 MultibufferSelectionMode::First,
14569 window,
14570 cx,
14571 )
14572 })
14573 .ok();
14574
14575 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14576 })
14577 } else {
14578 Task::ready(Ok(Navigated::No))
14579 }
14580 }
14581
14582 fn compute_target_location(
14583 &self,
14584 lsp_location: lsp::Location,
14585 server_id: LanguageServerId,
14586 window: &mut Window,
14587 cx: &mut Context<Self>,
14588 ) -> Task<anyhow::Result<Option<Location>>> {
14589 let Some(project) = self.project.clone() else {
14590 return Task::ready(Ok(None));
14591 };
14592
14593 cx.spawn_in(window, async move |editor, cx| {
14594 let location_task = editor.update(cx, |_, cx| {
14595 project.update(cx, |project, cx| {
14596 let language_server_name = project
14597 .language_server_statuses(cx)
14598 .find(|(id, _)| server_id == *id)
14599 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14600 language_server_name.map(|language_server_name| {
14601 project.open_local_buffer_via_lsp(
14602 lsp_location.uri.clone(),
14603 server_id,
14604 language_server_name,
14605 cx,
14606 )
14607 })
14608 })
14609 })?;
14610 let location = match location_task {
14611 Some(task) => Some({
14612 let target_buffer_handle = task.await.context("open local buffer")?;
14613 let range = target_buffer_handle.update(cx, |target_buffer, _| {
14614 let target_start = target_buffer
14615 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14616 let target_end = target_buffer
14617 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14618 target_buffer.anchor_after(target_start)
14619 ..target_buffer.anchor_before(target_end)
14620 })?;
14621 Location {
14622 buffer: target_buffer_handle,
14623 range,
14624 }
14625 }),
14626 None => None,
14627 };
14628 Ok(location)
14629 })
14630 }
14631
14632 pub fn find_all_references(
14633 &mut self,
14634 _: &FindAllReferences,
14635 window: &mut Window,
14636 cx: &mut Context<Self>,
14637 ) -> Option<Task<Result<Navigated>>> {
14638 let selection = self.selections.newest::<usize>(cx);
14639 let multi_buffer = self.buffer.read(cx);
14640 let head = selection.head();
14641
14642 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14643 let head_anchor = multi_buffer_snapshot.anchor_at(
14644 head,
14645 if head < selection.tail() {
14646 Bias::Right
14647 } else {
14648 Bias::Left
14649 },
14650 );
14651
14652 match self
14653 .find_all_references_task_sources
14654 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14655 {
14656 Ok(_) => {
14657 log::info!(
14658 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14659 );
14660 return None;
14661 }
14662 Err(i) => {
14663 self.find_all_references_task_sources.insert(i, head_anchor);
14664 }
14665 }
14666
14667 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14668 let workspace = self.workspace()?;
14669 let project = workspace.read(cx).project().clone();
14670 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14671 Some(cx.spawn_in(window, async move |editor, cx| {
14672 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14673 if let Ok(i) = editor
14674 .find_all_references_task_sources
14675 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14676 {
14677 editor.find_all_references_task_sources.remove(i);
14678 }
14679 });
14680
14681 let locations = references.await?;
14682 if locations.is_empty() {
14683 return anyhow::Ok(Navigated::No);
14684 }
14685
14686 workspace.update_in(cx, |workspace, window, cx| {
14687 let title = locations
14688 .first()
14689 .as_ref()
14690 .map(|location| {
14691 let buffer = location.buffer.read(cx);
14692 format!(
14693 "References to `{}`",
14694 buffer
14695 .text_for_range(location.range.clone())
14696 .collect::<String>()
14697 )
14698 })
14699 .unwrap();
14700 Self::open_locations_in_multibuffer(
14701 workspace,
14702 locations,
14703 title,
14704 false,
14705 MultibufferSelectionMode::First,
14706 window,
14707 cx,
14708 );
14709 Navigated::Yes
14710 })
14711 }))
14712 }
14713
14714 /// Opens a multibuffer with the given project locations in it
14715 pub fn open_locations_in_multibuffer(
14716 workspace: &mut Workspace,
14717 mut locations: Vec<Location>,
14718 title: String,
14719 split: bool,
14720 multibuffer_selection_mode: MultibufferSelectionMode,
14721 window: &mut Window,
14722 cx: &mut Context<Workspace>,
14723 ) {
14724 // If there are multiple definitions, open them in a multibuffer
14725 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14726 let mut locations = locations.into_iter().peekable();
14727 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14728 let capability = workspace.project().read(cx).capability();
14729
14730 let excerpt_buffer = cx.new(|cx| {
14731 let mut multibuffer = MultiBuffer::new(capability);
14732 while let Some(location) = locations.next() {
14733 let buffer = location.buffer.read(cx);
14734 let mut ranges_for_buffer = Vec::new();
14735 let range = location.range.to_point(buffer);
14736 ranges_for_buffer.push(range.clone());
14737
14738 while let Some(next_location) = locations.peek() {
14739 if next_location.buffer == location.buffer {
14740 ranges_for_buffer.push(next_location.range.to_point(buffer));
14741 locations.next();
14742 } else {
14743 break;
14744 }
14745 }
14746
14747 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14748 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14749 PathKey::for_buffer(&location.buffer, cx),
14750 location.buffer.clone(),
14751 ranges_for_buffer,
14752 DEFAULT_MULTIBUFFER_CONTEXT,
14753 cx,
14754 );
14755 ranges.extend(new_ranges)
14756 }
14757
14758 multibuffer.with_title(title)
14759 });
14760
14761 let editor = cx.new(|cx| {
14762 Editor::for_multibuffer(
14763 excerpt_buffer,
14764 Some(workspace.project().clone()),
14765 window,
14766 cx,
14767 )
14768 });
14769 editor.update(cx, |editor, cx| {
14770 match multibuffer_selection_mode {
14771 MultibufferSelectionMode::First => {
14772 if let Some(first_range) = ranges.first() {
14773 editor.change_selections(None, window, cx, |selections| {
14774 selections.clear_disjoint();
14775 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14776 });
14777 }
14778 editor.highlight_background::<Self>(
14779 &ranges,
14780 |theme| theme.editor_highlighted_line_background,
14781 cx,
14782 );
14783 }
14784 MultibufferSelectionMode::All => {
14785 editor.change_selections(None, window, cx, |selections| {
14786 selections.clear_disjoint();
14787 selections.select_anchor_ranges(ranges);
14788 });
14789 }
14790 }
14791 editor.register_buffers_with_language_servers(cx);
14792 });
14793
14794 let item = Box::new(editor);
14795 let item_id = item.item_id();
14796
14797 if split {
14798 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14799 } else {
14800 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14801 let (preview_item_id, preview_item_idx) =
14802 workspace.active_pane().update(cx, |pane, _| {
14803 (pane.preview_item_id(), pane.preview_item_idx())
14804 });
14805
14806 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14807
14808 if let Some(preview_item_id) = preview_item_id {
14809 workspace.active_pane().update(cx, |pane, cx| {
14810 pane.remove_item(preview_item_id, false, false, window, cx);
14811 });
14812 }
14813 } else {
14814 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14815 }
14816 }
14817 workspace.active_pane().update(cx, |pane, cx| {
14818 pane.set_preview_item_id(Some(item_id), cx);
14819 });
14820 }
14821
14822 pub fn rename(
14823 &mut self,
14824 _: &Rename,
14825 window: &mut Window,
14826 cx: &mut Context<Self>,
14827 ) -> Option<Task<Result<()>>> {
14828 use language::ToOffset as _;
14829
14830 let provider = self.semantics_provider.clone()?;
14831 let selection = self.selections.newest_anchor().clone();
14832 let (cursor_buffer, cursor_buffer_position) = self
14833 .buffer
14834 .read(cx)
14835 .text_anchor_for_position(selection.head(), cx)?;
14836 let (tail_buffer, cursor_buffer_position_end) = self
14837 .buffer
14838 .read(cx)
14839 .text_anchor_for_position(selection.tail(), cx)?;
14840 if tail_buffer != cursor_buffer {
14841 return None;
14842 }
14843
14844 let snapshot = cursor_buffer.read(cx).snapshot();
14845 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14846 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14847 let prepare_rename = provider
14848 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14849 .unwrap_or_else(|| Task::ready(Ok(None)));
14850 drop(snapshot);
14851
14852 Some(cx.spawn_in(window, async move |this, cx| {
14853 let rename_range = if let Some(range) = prepare_rename.await? {
14854 Some(range)
14855 } else {
14856 this.update(cx, |this, cx| {
14857 let buffer = this.buffer.read(cx).snapshot(cx);
14858 let mut buffer_highlights = this
14859 .document_highlights_for_position(selection.head(), &buffer)
14860 .filter(|highlight| {
14861 highlight.start.excerpt_id == selection.head().excerpt_id
14862 && highlight.end.excerpt_id == selection.head().excerpt_id
14863 });
14864 buffer_highlights
14865 .next()
14866 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14867 })?
14868 };
14869 if let Some(rename_range) = rename_range {
14870 this.update_in(cx, |this, window, cx| {
14871 let snapshot = cursor_buffer.read(cx).snapshot();
14872 let rename_buffer_range = rename_range.to_offset(&snapshot);
14873 let cursor_offset_in_rename_range =
14874 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14875 let cursor_offset_in_rename_range_end =
14876 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14877
14878 this.take_rename(false, window, cx);
14879 let buffer = this.buffer.read(cx).read(cx);
14880 let cursor_offset = selection.head().to_offset(&buffer);
14881 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14882 let rename_end = rename_start + rename_buffer_range.len();
14883 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14884 let mut old_highlight_id = None;
14885 let old_name: Arc<str> = buffer
14886 .chunks(rename_start..rename_end, true)
14887 .map(|chunk| {
14888 if old_highlight_id.is_none() {
14889 old_highlight_id = chunk.syntax_highlight_id;
14890 }
14891 chunk.text
14892 })
14893 .collect::<String>()
14894 .into();
14895
14896 drop(buffer);
14897
14898 // Position the selection in the rename editor so that it matches the current selection.
14899 this.show_local_selections = false;
14900 let rename_editor = cx.new(|cx| {
14901 let mut editor = Editor::single_line(window, cx);
14902 editor.buffer.update(cx, |buffer, cx| {
14903 buffer.edit([(0..0, old_name.clone())], None, cx)
14904 });
14905 let rename_selection_range = match cursor_offset_in_rename_range
14906 .cmp(&cursor_offset_in_rename_range_end)
14907 {
14908 Ordering::Equal => {
14909 editor.select_all(&SelectAll, window, cx);
14910 return editor;
14911 }
14912 Ordering::Less => {
14913 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14914 }
14915 Ordering::Greater => {
14916 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14917 }
14918 };
14919 if rename_selection_range.end > old_name.len() {
14920 editor.select_all(&SelectAll, window, cx);
14921 } else {
14922 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14923 s.select_ranges([rename_selection_range]);
14924 });
14925 }
14926 editor
14927 });
14928 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14929 if e == &EditorEvent::Focused {
14930 cx.emit(EditorEvent::FocusedIn)
14931 }
14932 })
14933 .detach();
14934
14935 let write_highlights =
14936 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14937 let read_highlights =
14938 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14939 let ranges = write_highlights
14940 .iter()
14941 .flat_map(|(_, ranges)| ranges.iter())
14942 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14943 .cloned()
14944 .collect();
14945
14946 this.highlight_text::<Rename>(
14947 ranges,
14948 HighlightStyle {
14949 fade_out: Some(0.6),
14950 ..Default::default()
14951 },
14952 cx,
14953 );
14954 let rename_focus_handle = rename_editor.focus_handle(cx);
14955 window.focus(&rename_focus_handle);
14956 let block_id = this.insert_blocks(
14957 [BlockProperties {
14958 style: BlockStyle::Flex,
14959 placement: BlockPlacement::Below(range.start),
14960 height: Some(1),
14961 render: Arc::new({
14962 let rename_editor = rename_editor.clone();
14963 move |cx: &mut BlockContext| {
14964 let mut text_style = cx.editor_style.text.clone();
14965 if let Some(highlight_style) = old_highlight_id
14966 .and_then(|h| h.style(&cx.editor_style.syntax))
14967 {
14968 text_style = text_style.highlight(highlight_style);
14969 }
14970 div()
14971 .block_mouse_down()
14972 .pl(cx.anchor_x)
14973 .child(EditorElement::new(
14974 &rename_editor,
14975 EditorStyle {
14976 background: cx.theme().system().transparent,
14977 local_player: cx.editor_style.local_player,
14978 text: text_style,
14979 scrollbar_width: cx.editor_style.scrollbar_width,
14980 syntax: cx.editor_style.syntax.clone(),
14981 status: cx.editor_style.status.clone(),
14982 inlay_hints_style: HighlightStyle {
14983 font_weight: Some(FontWeight::BOLD),
14984 ..make_inlay_hints_style(cx.app)
14985 },
14986 inline_completion_styles: make_suggestion_styles(
14987 cx.app,
14988 ),
14989 ..EditorStyle::default()
14990 },
14991 ))
14992 .into_any_element()
14993 }
14994 }),
14995 priority: 0,
14996 render_in_minimap: true,
14997 }],
14998 Some(Autoscroll::fit()),
14999 cx,
15000 )[0];
15001 this.pending_rename = Some(RenameState {
15002 range,
15003 old_name,
15004 editor: rename_editor,
15005 block_id,
15006 });
15007 })?;
15008 }
15009
15010 Ok(())
15011 }))
15012 }
15013
15014 pub fn confirm_rename(
15015 &mut self,
15016 _: &ConfirmRename,
15017 window: &mut Window,
15018 cx: &mut Context<Self>,
15019 ) -> Option<Task<Result<()>>> {
15020 let rename = self.take_rename(false, window, cx)?;
15021 let workspace = self.workspace()?.downgrade();
15022 let (buffer, start) = self
15023 .buffer
15024 .read(cx)
15025 .text_anchor_for_position(rename.range.start, cx)?;
15026 let (end_buffer, _) = self
15027 .buffer
15028 .read(cx)
15029 .text_anchor_for_position(rename.range.end, cx)?;
15030 if buffer != end_buffer {
15031 return None;
15032 }
15033
15034 let old_name = rename.old_name;
15035 let new_name = rename.editor.read(cx).text(cx);
15036
15037 let rename = self.semantics_provider.as_ref()?.perform_rename(
15038 &buffer,
15039 start,
15040 new_name.clone(),
15041 cx,
15042 )?;
15043
15044 Some(cx.spawn_in(window, async move |editor, cx| {
15045 let project_transaction = rename.await?;
15046 Self::open_project_transaction(
15047 &editor,
15048 workspace,
15049 project_transaction,
15050 format!("Rename: {} → {}", old_name, new_name),
15051 cx,
15052 )
15053 .await?;
15054
15055 editor.update(cx, |editor, cx| {
15056 editor.refresh_document_highlights(cx);
15057 })?;
15058 Ok(())
15059 }))
15060 }
15061
15062 fn take_rename(
15063 &mut self,
15064 moving_cursor: bool,
15065 window: &mut Window,
15066 cx: &mut Context<Self>,
15067 ) -> Option<RenameState> {
15068 let rename = self.pending_rename.take()?;
15069 if rename.editor.focus_handle(cx).is_focused(window) {
15070 window.focus(&self.focus_handle);
15071 }
15072
15073 self.remove_blocks(
15074 [rename.block_id].into_iter().collect(),
15075 Some(Autoscroll::fit()),
15076 cx,
15077 );
15078 self.clear_highlights::<Rename>(cx);
15079 self.show_local_selections = true;
15080
15081 if moving_cursor {
15082 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15083 editor.selections.newest::<usize>(cx).head()
15084 });
15085
15086 // Update the selection to match the position of the selection inside
15087 // the rename editor.
15088 let snapshot = self.buffer.read(cx).read(cx);
15089 let rename_range = rename.range.to_offset(&snapshot);
15090 let cursor_in_editor = snapshot
15091 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15092 .min(rename_range.end);
15093 drop(snapshot);
15094
15095 self.change_selections(None, window, cx, |s| {
15096 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15097 });
15098 } else {
15099 self.refresh_document_highlights(cx);
15100 }
15101
15102 Some(rename)
15103 }
15104
15105 pub fn pending_rename(&self) -> Option<&RenameState> {
15106 self.pending_rename.as_ref()
15107 }
15108
15109 fn format(
15110 &mut self,
15111 _: &Format,
15112 window: &mut Window,
15113 cx: &mut Context<Self>,
15114 ) -> Option<Task<Result<()>>> {
15115 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15116
15117 let project = match &self.project {
15118 Some(project) => project.clone(),
15119 None => return None,
15120 };
15121
15122 Some(self.perform_format(
15123 project,
15124 FormatTrigger::Manual,
15125 FormatTarget::Buffers,
15126 window,
15127 cx,
15128 ))
15129 }
15130
15131 fn format_selections(
15132 &mut self,
15133 _: &FormatSelections,
15134 window: &mut Window,
15135 cx: &mut Context<Self>,
15136 ) -> Option<Task<Result<()>>> {
15137 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15138
15139 let project = match &self.project {
15140 Some(project) => project.clone(),
15141 None => return None,
15142 };
15143
15144 let ranges = self
15145 .selections
15146 .all_adjusted(cx)
15147 .into_iter()
15148 .map(|selection| selection.range())
15149 .collect_vec();
15150
15151 Some(self.perform_format(
15152 project,
15153 FormatTrigger::Manual,
15154 FormatTarget::Ranges(ranges),
15155 window,
15156 cx,
15157 ))
15158 }
15159
15160 fn perform_format(
15161 &mut self,
15162 project: Entity<Project>,
15163 trigger: FormatTrigger,
15164 target: FormatTarget,
15165 window: &mut Window,
15166 cx: &mut Context<Self>,
15167 ) -> Task<Result<()>> {
15168 let buffer = self.buffer.clone();
15169 let (buffers, target) = match target {
15170 FormatTarget::Buffers => {
15171 let mut buffers = buffer.read(cx).all_buffers();
15172 if trigger == FormatTrigger::Save {
15173 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15174 }
15175 (buffers, LspFormatTarget::Buffers)
15176 }
15177 FormatTarget::Ranges(selection_ranges) => {
15178 let multi_buffer = buffer.read(cx);
15179 let snapshot = multi_buffer.read(cx);
15180 let mut buffers = HashSet::default();
15181 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15182 BTreeMap::new();
15183 for selection_range in selection_ranges {
15184 for (buffer, buffer_range, _) in
15185 snapshot.range_to_buffer_ranges(selection_range)
15186 {
15187 let buffer_id = buffer.remote_id();
15188 let start = buffer.anchor_before(buffer_range.start);
15189 let end = buffer.anchor_after(buffer_range.end);
15190 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15191 buffer_id_to_ranges
15192 .entry(buffer_id)
15193 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15194 .or_insert_with(|| vec![start..end]);
15195 }
15196 }
15197 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15198 }
15199 };
15200
15201 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15202 let selections_prev = transaction_id_prev
15203 .and_then(|transaction_id_prev| {
15204 // default to selections as they were after the last edit, if we have them,
15205 // instead of how they are now.
15206 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15207 // will take you back to where you made the last edit, instead of staying where you scrolled
15208 self.selection_history
15209 .transaction(transaction_id_prev)
15210 .map(|t| t.0.clone())
15211 })
15212 .unwrap_or_else(|| {
15213 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15214 self.selections.disjoint_anchors()
15215 });
15216
15217 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15218 let format = project.update(cx, |project, cx| {
15219 project.format(buffers, target, true, trigger, cx)
15220 });
15221
15222 cx.spawn_in(window, async move |editor, cx| {
15223 let transaction = futures::select_biased! {
15224 transaction = format.log_err().fuse() => transaction,
15225 () = timeout => {
15226 log::warn!("timed out waiting for formatting");
15227 None
15228 }
15229 };
15230
15231 buffer
15232 .update(cx, |buffer, cx| {
15233 if let Some(transaction) = transaction {
15234 if !buffer.is_singleton() {
15235 buffer.push_transaction(&transaction.0, cx);
15236 }
15237 }
15238 cx.notify();
15239 })
15240 .ok();
15241
15242 if let Some(transaction_id_now) =
15243 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15244 {
15245 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15246 if has_new_transaction {
15247 _ = editor.update(cx, |editor, _| {
15248 editor
15249 .selection_history
15250 .insert_transaction(transaction_id_now, selections_prev);
15251 });
15252 }
15253 }
15254
15255 Ok(())
15256 })
15257 }
15258
15259 fn organize_imports(
15260 &mut self,
15261 _: &OrganizeImports,
15262 window: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) -> Option<Task<Result<()>>> {
15265 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15266 let project = match &self.project {
15267 Some(project) => project.clone(),
15268 None => return None,
15269 };
15270 Some(self.perform_code_action_kind(
15271 project,
15272 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15273 window,
15274 cx,
15275 ))
15276 }
15277
15278 fn perform_code_action_kind(
15279 &mut self,
15280 project: Entity<Project>,
15281 kind: CodeActionKind,
15282 window: &mut Window,
15283 cx: &mut Context<Self>,
15284 ) -> Task<Result<()>> {
15285 let buffer = self.buffer.clone();
15286 let buffers = buffer.read(cx).all_buffers();
15287 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15288 let apply_action = project.update(cx, |project, cx| {
15289 project.apply_code_action_kind(buffers, kind, true, cx)
15290 });
15291 cx.spawn_in(window, async move |_, cx| {
15292 let transaction = futures::select_biased! {
15293 () = timeout => {
15294 log::warn!("timed out waiting for executing code action");
15295 None
15296 }
15297 transaction = apply_action.log_err().fuse() => transaction,
15298 };
15299 buffer
15300 .update(cx, |buffer, cx| {
15301 // check if we need this
15302 if let Some(transaction) = transaction {
15303 if !buffer.is_singleton() {
15304 buffer.push_transaction(&transaction.0, cx);
15305 }
15306 }
15307 cx.notify();
15308 })
15309 .ok();
15310 Ok(())
15311 })
15312 }
15313
15314 fn restart_language_server(
15315 &mut self,
15316 _: &RestartLanguageServer,
15317 _: &mut Window,
15318 cx: &mut Context<Self>,
15319 ) {
15320 if let Some(project) = self.project.clone() {
15321 self.buffer.update(cx, |multi_buffer, cx| {
15322 project.update(cx, |project, cx| {
15323 project.restart_language_servers_for_buffers(
15324 multi_buffer.all_buffers().into_iter().collect(),
15325 cx,
15326 );
15327 });
15328 })
15329 }
15330 }
15331
15332 fn stop_language_server(
15333 &mut self,
15334 _: &StopLanguageServer,
15335 _: &mut Window,
15336 cx: &mut Context<Self>,
15337 ) {
15338 if let Some(project) = self.project.clone() {
15339 self.buffer.update(cx, |multi_buffer, cx| {
15340 project.update(cx, |project, cx| {
15341 project.stop_language_servers_for_buffers(
15342 multi_buffer.all_buffers().into_iter().collect(),
15343 cx,
15344 );
15345 cx.emit(project::Event::RefreshInlayHints);
15346 });
15347 });
15348 }
15349 }
15350
15351 fn cancel_language_server_work(
15352 workspace: &mut Workspace,
15353 _: &actions::CancelLanguageServerWork,
15354 _: &mut Window,
15355 cx: &mut Context<Workspace>,
15356 ) {
15357 let project = workspace.project();
15358 let buffers = workspace
15359 .active_item(cx)
15360 .and_then(|item| item.act_as::<Editor>(cx))
15361 .map_or(HashSet::default(), |editor| {
15362 editor.read(cx).buffer.read(cx).all_buffers()
15363 });
15364 project.update(cx, |project, cx| {
15365 project.cancel_language_server_work_for_buffers(buffers, cx);
15366 });
15367 }
15368
15369 fn show_character_palette(
15370 &mut self,
15371 _: &ShowCharacterPalette,
15372 window: &mut Window,
15373 _: &mut Context<Self>,
15374 ) {
15375 window.show_character_palette();
15376 }
15377
15378 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15379 if self.mode.is_minimap() {
15380 return;
15381 }
15382
15383 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15384 let buffer = self.buffer.read(cx).snapshot(cx);
15385 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15386 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15387 let is_valid = buffer
15388 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15389 .any(|entry| {
15390 entry.diagnostic.is_primary
15391 && !entry.range.is_empty()
15392 && entry.range.start == primary_range_start
15393 && entry.diagnostic.message == active_diagnostics.active_message
15394 });
15395
15396 if !is_valid {
15397 self.dismiss_diagnostics(cx);
15398 }
15399 }
15400 }
15401
15402 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15403 match &self.active_diagnostics {
15404 ActiveDiagnostic::Group(group) => Some(group),
15405 _ => None,
15406 }
15407 }
15408
15409 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15410 self.dismiss_diagnostics(cx);
15411 self.active_diagnostics = ActiveDiagnostic::All;
15412 }
15413
15414 fn activate_diagnostics(
15415 &mut self,
15416 buffer_id: BufferId,
15417 diagnostic: DiagnosticEntry<usize>,
15418 window: &mut Window,
15419 cx: &mut Context<Self>,
15420 ) {
15421 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15422 return;
15423 }
15424 self.dismiss_diagnostics(cx);
15425 let snapshot = self.snapshot(window, cx);
15426 let buffer = self.buffer.read(cx).snapshot(cx);
15427 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15428 return;
15429 };
15430
15431 let diagnostic_group = buffer
15432 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15433 .collect::<Vec<_>>();
15434
15435 let blocks =
15436 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15437
15438 let blocks = self.display_map.update(cx, |display_map, cx| {
15439 display_map.insert_blocks(blocks, cx).into_iter().collect()
15440 });
15441 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15442 active_range: buffer.anchor_before(diagnostic.range.start)
15443 ..buffer.anchor_after(diagnostic.range.end),
15444 active_message: diagnostic.diagnostic.message.clone(),
15445 group_id: diagnostic.diagnostic.group_id,
15446 blocks,
15447 });
15448 cx.notify();
15449 }
15450
15451 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15452 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15453 return;
15454 };
15455
15456 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15457 if let ActiveDiagnostic::Group(group) = prev {
15458 self.display_map.update(cx, |display_map, cx| {
15459 display_map.remove_blocks(group.blocks, cx);
15460 });
15461 cx.notify();
15462 }
15463 }
15464
15465 /// Disable inline diagnostics rendering for this editor.
15466 pub fn disable_inline_diagnostics(&mut self) {
15467 self.inline_diagnostics_enabled = false;
15468 self.inline_diagnostics_update = Task::ready(());
15469 self.inline_diagnostics.clear();
15470 }
15471
15472 pub fn diagnostics_enabled(&self) -> bool {
15473 self.mode.is_full()
15474 }
15475
15476 pub fn inline_diagnostics_enabled(&self) -> bool {
15477 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15478 }
15479
15480 pub fn show_inline_diagnostics(&self) -> bool {
15481 self.show_inline_diagnostics
15482 }
15483
15484 pub fn toggle_inline_diagnostics(
15485 &mut self,
15486 _: &ToggleInlineDiagnostics,
15487 window: &mut Window,
15488 cx: &mut Context<Editor>,
15489 ) {
15490 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15491 self.refresh_inline_diagnostics(false, window, cx);
15492 }
15493
15494 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15495 self.diagnostics_max_severity = severity;
15496 self.display_map.update(cx, |display_map, _| {
15497 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15498 });
15499 }
15500
15501 pub fn toggle_diagnostics(
15502 &mut self,
15503 _: &ToggleDiagnostics,
15504 window: &mut Window,
15505 cx: &mut Context<Editor>,
15506 ) {
15507 if !self.diagnostics_enabled() {
15508 return;
15509 }
15510
15511 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15512 EditorSettings::get_global(cx)
15513 .diagnostics_max_severity
15514 .filter(|severity| severity != &DiagnosticSeverity::Off)
15515 .unwrap_or(DiagnosticSeverity::Hint)
15516 } else {
15517 DiagnosticSeverity::Off
15518 };
15519 self.set_max_diagnostics_severity(new_severity, cx);
15520 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15521 self.active_diagnostics = ActiveDiagnostic::None;
15522 self.inline_diagnostics_update = Task::ready(());
15523 self.inline_diagnostics.clear();
15524 } else {
15525 self.refresh_inline_diagnostics(false, window, cx);
15526 }
15527
15528 cx.notify();
15529 }
15530
15531 pub fn toggle_minimap(
15532 &mut self,
15533 _: &ToggleMinimap,
15534 window: &mut Window,
15535 cx: &mut Context<Editor>,
15536 ) {
15537 if self.supports_minimap(cx) {
15538 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15539 }
15540 }
15541
15542 fn refresh_inline_diagnostics(
15543 &mut self,
15544 debounce: bool,
15545 window: &mut Window,
15546 cx: &mut Context<Self>,
15547 ) {
15548 let max_severity = ProjectSettings::get_global(cx)
15549 .diagnostics
15550 .inline
15551 .max_severity
15552 .unwrap_or(self.diagnostics_max_severity);
15553
15554 if !self.inline_diagnostics_enabled()
15555 || !self.show_inline_diagnostics
15556 || max_severity == DiagnosticSeverity::Off
15557 {
15558 self.inline_diagnostics_update = Task::ready(());
15559 self.inline_diagnostics.clear();
15560 return;
15561 }
15562
15563 let debounce_ms = ProjectSettings::get_global(cx)
15564 .diagnostics
15565 .inline
15566 .update_debounce_ms;
15567 let debounce = if debounce && debounce_ms > 0 {
15568 Some(Duration::from_millis(debounce_ms))
15569 } else {
15570 None
15571 };
15572 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15573 let editor = editor.upgrade().unwrap();
15574
15575 if let Some(debounce) = debounce {
15576 cx.background_executor().timer(debounce).await;
15577 }
15578 let Some(snapshot) = editor
15579 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15580 .ok()
15581 else {
15582 return;
15583 };
15584
15585 let new_inline_diagnostics = cx
15586 .background_spawn(async move {
15587 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15588 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15589 let message = diagnostic_entry
15590 .diagnostic
15591 .message
15592 .split_once('\n')
15593 .map(|(line, _)| line)
15594 .map(SharedString::new)
15595 .unwrap_or_else(|| {
15596 SharedString::from(diagnostic_entry.diagnostic.message)
15597 });
15598 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15599 let (Ok(i) | Err(i)) = inline_diagnostics
15600 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15601 inline_diagnostics.insert(
15602 i,
15603 (
15604 start_anchor,
15605 InlineDiagnostic {
15606 message,
15607 group_id: diagnostic_entry.diagnostic.group_id,
15608 start: diagnostic_entry.range.start.to_point(&snapshot),
15609 is_primary: diagnostic_entry.diagnostic.is_primary,
15610 severity: diagnostic_entry.diagnostic.severity,
15611 },
15612 ),
15613 );
15614 }
15615 inline_diagnostics
15616 })
15617 .await;
15618
15619 editor
15620 .update(cx, |editor, cx| {
15621 editor.inline_diagnostics = new_inline_diagnostics;
15622 cx.notify();
15623 })
15624 .ok();
15625 });
15626 }
15627
15628 pub fn set_selections_from_remote(
15629 &mut self,
15630 selections: Vec<Selection<Anchor>>,
15631 pending_selection: Option<Selection<Anchor>>,
15632 window: &mut Window,
15633 cx: &mut Context<Self>,
15634 ) {
15635 let old_cursor_position = self.selections.newest_anchor().head();
15636 self.selections.change_with(cx, |s| {
15637 s.select_anchors(selections);
15638 if let Some(pending_selection) = pending_selection {
15639 s.set_pending(pending_selection, SelectMode::Character);
15640 } else {
15641 s.clear_pending();
15642 }
15643 });
15644 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15645 }
15646
15647 fn push_to_selection_history(&mut self) {
15648 self.selection_history.push(SelectionHistoryEntry {
15649 selections: self.selections.disjoint_anchors(),
15650 select_next_state: self.select_next_state.clone(),
15651 select_prev_state: self.select_prev_state.clone(),
15652 add_selections_state: self.add_selections_state.clone(),
15653 });
15654 }
15655
15656 pub fn transact(
15657 &mut self,
15658 window: &mut Window,
15659 cx: &mut Context<Self>,
15660 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15661 ) -> Option<TransactionId> {
15662 self.start_transaction_at(Instant::now(), window, cx);
15663 update(self, window, cx);
15664 self.end_transaction_at(Instant::now(), cx)
15665 }
15666
15667 pub fn start_transaction_at(
15668 &mut self,
15669 now: Instant,
15670 window: &mut Window,
15671 cx: &mut Context<Self>,
15672 ) {
15673 self.end_selection(window, cx);
15674 if let Some(tx_id) = self
15675 .buffer
15676 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15677 {
15678 self.selection_history
15679 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15680 cx.emit(EditorEvent::TransactionBegun {
15681 transaction_id: tx_id,
15682 })
15683 }
15684 }
15685
15686 pub fn end_transaction_at(
15687 &mut self,
15688 now: Instant,
15689 cx: &mut Context<Self>,
15690 ) -> Option<TransactionId> {
15691 if let Some(transaction_id) = self
15692 .buffer
15693 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15694 {
15695 if let Some((_, end_selections)) =
15696 self.selection_history.transaction_mut(transaction_id)
15697 {
15698 *end_selections = Some(self.selections.disjoint_anchors());
15699 } else {
15700 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15701 }
15702
15703 cx.emit(EditorEvent::Edited { transaction_id });
15704 Some(transaction_id)
15705 } else {
15706 None
15707 }
15708 }
15709
15710 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15711 if self.selection_mark_mode {
15712 self.change_selections(None, window, cx, |s| {
15713 s.move_with(|_, sel| {
15714 sel.collapse_to(sel.head(), SelectionGoal::None);
15715 });
15716 })
15717 }
15718 self.selection_mark_mode = true;
15719 cx.notify();
15720 }
15721
15722 pub fn swap_selection_ends(
15723 &mut self,
15724 _: &actions::SwapSelectionEnds,
15725 window: &mut Window,
15726 cx: &mut Context<Self>,
15727 ) {
15728 self.change_selections(None, window, cx, |s| {
15729 s.move_with(|_, sel| {
15730 if sel.start != sel.end {
15731 sel.reversed = !sel.reversed
15732 }
15733 });
15734 });
15735 self.request_autoscroll(Autoscroll::newest(), cx);
15736 cx.notify();
15737 }
15738
15739 pub fn toggle_fold(
15740 &mut self,
15741 _: &actions::ToggleFold,
15742 window: &mut Window,
15743 cx: &mut Context<Self>,
15744 ) {
15745 if self.is_singleton(cx) {
15746 let selection = self.selections.newest::<Point>(cx);
15747
15748 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15749 let range = if selection.is_empty() {
15750 let point = selection.head().to_display_point(&display_map);
15751 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15752 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15753 .to_point(&display_map);
15754 start..end
15755 } else {
15756 selection.range()
15757 };
15758 if display_map.folds_in_range(range).next().is_some() {
15759 self.unfold_lines(&Default::default(), window, cx)
15760 } else {
15761 self.fold(&Default::default(), window, cx)
15762 }
15763 } else {
15764 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15765 let buffer_ids: HashSet<_> = self
15766 .selections
15767 .disjoint_anchor_ranges()
15768 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15769 .collect();
15770
15771 let should_unfold = buffer_ids
15772 .iter()
15773 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15774
15775 for buffer_id in buffer_ids {
15776 if should_unfold {
15777 self.unfold_buffer(buffer_id, cx);
15778 } else {
15779 self.fold_buffer(buffer_id, cx);
15780 }
15781 }
15782 }
15783 }
15784
15785 pub fn toggle_fold_recursive(
15786 &mut self,
15787 _: &actions::ToggleFoldRecursive,
15788 window: &mut Window,
15789 cx: &mut Context<Self>,
15790 ) {
15791 let selection = self.selections.newest::<Point>(cx);
15792
15793 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15794 let range = if selection.is_empty() {
15795 let point = selection.head().to_display_point(&display_map);
15796 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15797 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15798 .to_point(&display_map);
15799 start..end
15800 } else {
15801 selection.range()
15802 };
15803 if display_map.folds_in_range(range).next().is_some() {
15804 self.unfold_recursive(&Default::default(), window, cx)
15805 } else {
15806 self.fold_recursive(&Default::default(), window, cx)
15807 }
15808 }
15809
15810 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15811 if self.is_singleton(cx) {
15812 let mut to_fold = Vec::new();
15813 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15814 let selections = self.selections.all_adjusted(cx);
15815
15816 for selection in selections {
15817 let range = selection.range().sorted();
15818 let buffer_start_row = range.start.row;
15819
15820 if range.start.row != range.end.row {
15821 let mut found = false;
15822 let mut row = range.start.row;
15823 while row <= range.end.row {
15824 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15825 {
15826 found = true;
15827 row = crease.range().end.row + 1;
15828 to_fold.push(crease);
15829 } else {
15830 row += 1
15831 }
15832 }
15833 if found {
15834 continue;
15835 }
15836 }
15837
15838 for row in (0..=range.start.row).rev() {
15839 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15840 if crease.range().end.row >= buffer_start_row {
15841 to_fold.push(crease);
15842 if row <= range.start.row {
15843 break;
15844 }
15845 }
15846 }
15847 }
15848 }
15849
15850 self.fold_creases(to_fold, true, window, cx);
15851 } else {
15852 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15853 let buffer_ids = self
15854 .selections
15855 .disjoint_anchor_ranges()
15856 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15857 .collect::<HashSet<_>>();
15858 for buffer_id in buffer_ids {
15859 self.fold_buffer(buffer_id, cx);
15860 }
15861 }
15862 }
15863
15864 fn fold_at_level(
15865 &mut self,
15866 fold_at: &FoldAtLevel,
15867 window: &mut Window,
15868 cx: &mut Context<Self>,
15869 ) {
15870 if !self.buffer.read(cx).is_singleton() {
15871 return;
15872 }
15873
15874 let fold_at_level = fold_at.0;
15875 let snapshot = self.buffer.read(cx).snapshot(cx);
15876 let mut to_fold = Vec::new();
15877 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15878
15879 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15880 while start_row < end_row {
15881 match self
15882 .snapshot(window, cx)
15883 .crease_for_buffer_row(MultiBufferRow(start_row))
15884 {
15885 Some(crease) => {
15886 let nested_start_row = crease.range().start.row + 1;
15887 let nested_end_row = crease.range().end.row;
15888
15889 if current_level < fold_at_level {
15890 stack.push((nested_start_row, nested_end_row, current_level + 1));
15891 } else if current_level == fold_at_level {
15892 to_fold.push(crease);
15893 }
15894
15895 start_row = nested_end_row + 1;
15896 }
15897 None => start_row += 1,
15898 }
15899 }
15900 }
15901
15902 self.fold_creases(to_fold, true, window, cx);
15903 }
15904
15905 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15906 if self.buffer.read(cx).is_singleton() {
15907 let mut fold_ranges = Vec::new();
15908 let snapshot = self.buffer.read(cx).snapshot(cx);
15909
15910 for row in 0..snapshot.max_row().0 {
15911 if let Some(foldable_range) = self
15912 .snapshot(window, cx)
15913 .crease_for_buffer_row(MultiBufferRow(row))
15914 {
15915 fold_ranges.push(foldable_range);
15916 }
15917 }
15918
15919 self.fold_creases(fold_ranges, true, window, cx);
15920 } else {
15921 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15922 editor
15923 .update_in(cx, |editor, _, cx| {
15924 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15925 editor.fold_buffer(buffer_id, cx);
15926 }
15927 })
15928 .ok();
15929 });
15930 }
15931 }
15932
15933 pub fn fold_function_bodies(
15934 &mut self,
15935 _: &actions::FoldFunctionBodies,
15936 window: &mut Window,
15937 cx: &mut Context<Self>,
15938 ) {
15939 let snapshot = self.buffer.read(cx).snapshot(cx);
15940
15941 let ranges = snapshot
15942 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15943 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15944 .collect::<Vec<_>>();
15945
15946 let creases = ranges
15947 .into_iter()
15948 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15949 .collect();
15950
15951 self.fold_creases(creases, true, window, cx);
15952 }
15953
15954 pub fn fold_recursive(
15955 &mut self,
15956 _: &actions::FoldRecursive,
15957 window: &mut Window,
15958 cx: &mut Context<Self>,
15959 ) {
15960 let mut to_fold = Vec::new();
15961 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15962 let selections = self.selections.all_adjusted(cx);
15963
15964 for selection in selections {
15965 let range = selection.range().sorted();
15966 let buffer_start_row = range.start.row;
15967
15968 if range.start.row != range.end.row {
15969 let mut found = false;
15970 for row in range.start.row..=range.end.row {
15971 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15972 found = true;
15973 to_fold.push(crease);
15974 }
15975 }
15976 if found {
15977 continue;
15978 }
15979 }
15980
15981 for row in (0..=range.start.row).rev() {
15982 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15983 if crease.range().end.row >= buffer_start_row {
15984 to_fold.push(crease);
15985 } else {
15986 break;
15987 }
15988 }
15989 }
15990 }
15991
15992 self.fold_creases(to_fold, true, window, cx);
15993 }
15994
15995 pub fn fold_at(
15996 &mut self,
15997 buffer_row: MultiBufferRow,
15998 window: &mut Window,
15999 cx: &mut Context<Self>,
16000 ) {
16001 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16002
16003 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16004 let autoscroll = self
16005 .selections
16006 .all::<Point>(cx)
16007 .iter()
16008 .any(|selection| crease.range().overlaps(&selection.range()));
16009
16010 self.fold_creases(vec![crease], autoscroll, window, cx);
16011 }
16012 }
16013
16014 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16015 if self.is_singleton(cx) {
16016 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16017 let buffer = &display_map.buffer_snapshot;
16018 let selections = self.selections.all::<Point>(cx);
16019 let ranges = selections
16020 .iter()
16021 .map(|s| {
16022 let range = s.display_range(&display_map).sorted();
16023 let mut start = range.start.to_point(&display_map);
16024 let mut end = range.end.to_point(&display_map);
16025 start.column = 0;
16026 end.column = buffer.line_len(MultiBufferRow(end.row));
16027 start..end
16028 })
16029 .collect::<Vec<_>>();
16030
16031 self.unfold_ranges(&ranges, true, true, cx);
16032 } else {
16033 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16034 let buffer_ids = self
16035 .selections
16036 .disjoint_anchor_ranges()
16037 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16038 .collect::<HashSet<_>>();
16039 for buffer_id in buffer_ids {
16040 self.unfold_buffer(buffer_id, cx);
16041 }
16042 }
16043 }
16044
16045 pub fn unfold_recursive(
16046 &mut self,
16047 _: &UnfoldRecursive,
16048 _window: &mut Window,
16049 cx: &mut Context<Self>,
16050 ) {
16051 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16052 let selections = self.selections.all::<Point>(cx);
16053 let ranges = selections
16054 .iter()
16055 .map(|s| {
16056 let mut range = s.display_range(&display_map).sorted();
16057 *range.start.column_mut() = 0;
16058 *range.end.column_mut() = display_map.line_len(range.end.row());
16059 let start = range.start.to_point(&display_map);
16060 let end = range.end.to_point(&display_map);
16061 start..end
16062 })
16063 .collect::<Vec<_>>();
16064
16065 self.unfold_ranges(&ranges, true, true, cx);
16066 }
16067
16068 pub fn unfold_at(
16069 &mut self,
16070 buffer_row: MultiBufferRow,
16071 _window: &mut Window,
16072 cx: &mut Context<Self>,
16073 ) {
16074 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16075
16076 let intersection_range = Point::new(buffer_row.0, 0)
16077 ..Point::new(
16078 buffer_row.0,
16079 display_map.buffer_snapshot.line_len(buffer_row),
16080 );
16081
16082 let autoscroll = self
16083 .selections
16084 .all::<Point>(cx)
16085 .iter()
16086 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16087
16088 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16089 }
16090
16091 pub fn unfold_all(
16092 &mut self,
16093 _: &actions::UnfoldAll,
16094 _window: &mut Window,
16095 cx: &mut Context<Self>,
16096 ) {
16097 if self.buffer.read(cx).is_singleton() {
16098 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16099 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16100 } else {
16101 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16102 editor
16103 .update(cx, |editor, cx| {
16104 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16105 editor.unfold_buffer(buffer_id, cx);
16106 }
16107 })
16108 .ok();
16109 });
16110 }
16111 }
16112
16113 pub fn fold_selected_ranges(
16114 &mut self,
16115 _: &FoldSelectedRanges,
16116 window: &mut Window,
16117 cx: &mut Context<Self>,
16118 ) {
16119 let selections = self.selections.all_adjusted(cx);
16120 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16121 let ranges = selections
16122 .into_iter()
16123 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16124 .collect::<Vec<_>>();
16125 self.fold_creases(ranges, true, window, cx);
16126 }
16127
16128 pub fn fold_ranges<T: ToOffset + Clone>(
16129 &mut self,
16130 ranges: Vec<Range<T>>,
16131 auto_scroll: bool,
16132 window: &mut Window,
16133 cx: &mut Context<Self>,
16134 ) {
16135 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16136 let ranges = ranges
16137 .into_iter()
16138 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16139 .collect::<Vec<_>>();
16140 self.fold_creases(ranges, auto_scroll, window, cx);
16141 }
16142
16143 pub fn fold_creases<T: ToOffset + Clone>(
16144 &mut self,
16145 creases: Vec<Crease<T>>,
16146 auto_scroll: bool,
16147 _window: &mut Window,
16148 cx: &mut Context<Self>,
16149 ) {
16150 if creases.is_empty() {
16151 return;
16152 }
16153
16154 let mut buffers_affected = HashSet::default();
16155 let multi_buffer = self.buffer().read(cx);
16156 for crease in &creases {
16157 if let Some((_, buffer, _)) =
16158 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16159 {
16160 buffers_affected.insert(buffer.read(cx).remote_id());
16161 };
16162 }
16163
16164 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16165
16166 if auto_scroll {
16167 self.request_autoscroll(Autoscroll::fit(), cx);
16168 }
16169
16170 cx.notify();
16171
16172 self.scrollbar_marker_state.dirty = true;
16173 self.folds_did_change(cx);
16174 }
16175
16176 /// Removes any folds whose ranges intersect any of the given ranges.
16177 pub fn unfold_ranges<T: ToOffset + Clone>(
16178 &mut self,
16179 ranges: &[Range<T>],
16180 inclusive: bool,
16181 auto_scroll: bool,
16182 cx: &mut Context<Self>,
16183 ) {
16184 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16185 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16186 });
16187 self.folds_did_change(cx);
16188 }
16189
16190 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16191 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16192 return;
16193 }
16194 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16195 self.display_map.update(cx, |display_map, cx| {
16196 display_map.fold_buffers([buffer_id], cx)
16197 });
16198 cx.emit(EditorEvent::BufferFoldToggled {
16199 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16200 folded: true,
16201 });
16202 cx.notify();
16203 }
16204
16205 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16206 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16207 return;
16208 }
16209 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16210 self.display_map.update(cx, |display_map, cx| {
16211 display_map.unfold_buffers([buffer_id], cx);
16212 });
16213 cx.emit(EditorEvent::BufferFoldToggled {
16214 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16215 folded: false,
16216 });
16217 cx.notify();
16218 }
16219
16220 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16221 self.display_map.read(cx).is_buffer_folded(buffer)
16222 }
16223
16224 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16225 self.display_map.read(cx).folded_buffers()
16226 }
16227
16228 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16229 self.display_map.update(cx, |display_map, cx| {
16230 display_map.disable_header_for_buffer(buffer_id, cx);
16231 });
16232 cx.notify();
16233 }
16234
16235 /// Removes any folds with the given ranges.
16236 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16237 &mut self,
16238 ranges: &[Range<T>],
16239 type_id: TypeId,
16240 auto_scroll: bool,
16241 cx: &mut Context<Self>,
16242 ) {
16243 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16244 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16245 });
16246 self.folds_did_change(cx);
16247 }
16248
16249 fn remove_folds_with<T: ToOffset + Clone>(
16250 &mut self,
16251 ranges: &[Range<T>],
16252 auto_scroll: bool,
16253 cx: &mut Context<Self>,
16254 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16255 ) {
16256 if ranges.is_empty() {
16257 return;
16258 }
16259
16260 let mut buffers_affected = HashSet::default();
16261 let multi_buffer = self.buffer().read(cx);
16262 for range in ranges {
16263 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16264 buffers_affected.insert(buffer.read(cx).remote_id());
16265 };
16266 }
16267
16268 self.display_map.update(cx, update);
16269
16270 if auto_scroll {
16271 self.request_autoscroll(Autoscroll::fit(), cx);
16272 }
16273
16274 cx.notify();
16275 self.scrollbar_marker_state.dirty = true;
16276 self.active_indent_guides_state.dirty = true;
16277 }
16278
16279 pub fn update_fold_widths(
16280 &mut self,
16281 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16282 cx: &mut Context<Self>,
16283 ) -> bool {
16284 self.display_map
16285 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16286 }
16287
16288 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16289 self.display_map.read(cx).fold_placeholder.clone()
16290 }
16291
16292 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16293 self.buffer.update(cx, |buffer, cx| {
16294 buffer.set_all_diff_hunks_expanded(cx);
16295 });
16296 }
16297
16298 pub fn expand_all_diff_hunks(
16299 &mut self,
16300 _: &ExpandAllDiffHunks,
16301 _window: &mut Window,
16302 cx: &mut Context<Self>,
16303 ) {
16304 self.buffer.update(cx, |buffer, cx| {
16305 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16306 });
16307 }
16308
16309 pub fn toggle_selected_diff_hunks(
16310 &mut self,
16311 _: &ToggleSelectedDiffHunks,
16312 _window: &mut Window,
16313 cx: &mut Context<Self>,
16314 ) {
16315 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16316 self.toggle_diff_hunks_in_ranges(ranges, cx);
16317 }
16318
16319 pub fn diff_hunks_in_ranges<'a>(
16320 &'a self,
16321 ranges: &'a [Range<Anchor>],
16322 buffer: &'a MultiBufferSnapshot,
16323 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16324 ranges.iter().flat_map(move |range| {
16325 let end_excerpt_id = range.end.excerpt_id;
16326 let range = range.to_point(buffer);
16327 let mut peek_end = range.end;
16328 if range.end.row < buffer.max_row().0 {
16329 peek_end = Point::new(range.end.row + 1, 0);
16330 }
16331 buffer
16332 .diff_hunks_in_range(range.start..peek_end)
16333 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16334 })
16335 }
16336
16337 pub fn has_stageable_diff_hunks_in_ranges(
16338 &self,
16339 ranges: &[Range<Anchor>],
16340 snapshot: &MultiBufferSnapshot,
16341 ) -> bool {
16342 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16343 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16344 }
16345
16346 pub fn toggle_staged_selected_diff_hunks(
16347 &mut self,
16348 _: &::git::ToggleStaged,
16349 _: &mut Window,
16350 cx: &mut Context<Self>,
16351 ) {
16352 let snapshot = self.buffer.read(cx).snapshot(cx);
16353 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16354 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16355 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16356 }
16357
16358 pub fn set_render_diff_hunk_controls(
16359 &mut self,
16360 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16361 cx: &mut Context<Self>,
16362 ) {
16363 self.render_diff_hunk_controls = render_diff_hunk_controls;
16364 cx.notify();
16365 }
16366
16367 pub fn stage_and_next(
16368 &mut self,
16369 _: &::git::StageAndNext,
16370 window: &mut Window,
16371 cx: &mut Context<Self>,
16372 ) {
16373 self.do_stage_or_unstage_and_next(true, window, cx);
16374 }
16375
16376 pub fn unstage_and_next(
16377 &mut self,
16378 _: &::git::UnstageAndNext,
16379 window: &mut Window,
16380 cx: &mut Context<Self>,
16381 ) {
16382 self.do_stage_or_unstage_and_next(false, window, cx);
16383 }
16384
16385 pub fn stage_or_unstage_diff_hunks(
16386 &mut self,
16387 stage: bool,
16388 ranges: Vec<Range<Anchor>>,
16389 cx: &mut Context<Self>,
16390 ) {
16391 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16392 cx.spawn(async move |this, cx| {
16393 task.await?;
16394 this.update(cx, |this, cx| {
16395 let snapshot = this.buffer.read(cx).snapshot(cx);
16396 let chunk_by = this
16397 .diff_hunks_in_ranges(&ranges, &snapshot)
16398 .chunk_by(|hunk| hunk.buffer_id);
16399 for (buffer_id, hunks) in &chunk_by {
16400 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16401 }
16402 })
16403 })
16404 .detach_and_log_err(cx);
16405 }
16406
16407 fn save_buffers_for_ranges_if_needed(
16408 &mut self,
16409 ranges: &[Range<Anchor>],
16410 cx: &mut Context<Editor>,
16411 ) -> Task<Result<()>> {
16412 let multibuffer = self.buffer.read(cx);
16413 let snapshot = multibuffer.read(cx);
16414 let buffer_ids: HashSet<_> = ranges
16415 .iter()
16416 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16417 .collect();
16418 drop(snapshot);
16419
16420 let mut buffers = HashSet::default();
16421 for buffer_id in buffer_ids {
16422 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16423 let buffer = buffer_entity.read(cx);
16424 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16425 {
16426 buffers.insert(buffer_entity);
16427 }
16428 }
16429 }
16430
16431 if let Some(project) = &self.project {
16432 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16433 } else {
16434 Task::ready(Ok(()))
16435 }
16436 }
16437
16438 fn do_stage_or_unstage_and_next(
16439 &mut self,
16440 stage: bool,
16441 window: &mut Window,
16442 cx: &mut Context<Self>,
16443 ) {
16444 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16445
16446 if ranges.iter().any(|range| range.start != range.end) {
16447 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16448 return;
16449 }
16450
16451 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16452 let snapshot = self.snapshot(window, cx);
16453 let position = self.selections.newest::<Point>(cx).head();
16454 let mut row = snapshot
16455 .buffer_snapshot
16456 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16457 .find(|hunk| hunk.row_range.start.0 > position.row)
16458 .map(|hunk| hunk.row_range.start);
16459
16460 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16461 // Outside of the project diff editor, wrap around to the beginning.
16462 if !all_diff_hunks_expanded {
16463 row = row.or_else(|| {
16464 snapshot
16465 .buffer_snapshot
16466 .diff_hunks_in_range(Point::zero()..position)
16467 .find(|hunk| hunk.row_range.end.0 < position.row)
16468 .map(|hunk| hunk.row_range.start)
16469 });
16470 }
16471
16472 if let Some(row) = row {
16473 let destination = Point::new(row.0, 0);
16474 let autoscroll = Autoscroll::center();
16475
16476 self.unfold_ranges(&[destination..destination], false, false, cx);
16477 self.change_selections(Some(autoscroll), window, cx, |s| {
16478 s.select_ranges([destination..destination]);
16479 });
16480 }
16481 }
16482
16483 fn do_stage_or_unstage(
16484 &self,
16485 stage: bool,
16486 buffer_id: BufferId,
16487 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16488 cx: &mut App,
16489 ) -> Option<()> {
16490 let project = self.project.as_ref()?;
16491 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16492 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16493 let buffer_snapshot = buffer.read(cx).snapshot();
16494 let file_exists = buffer_snapshot
16495 .file()
16496 .is_some_and(|file| file.disk_state().exists());
16497 diff.update(cx, |diff, cx| {
16498 diff.stage_or_unstage_hunks(
16499 stage,
16500 &hunks
16501 .map(|hunk| buffer_diff::DiffHunk {
16502 buffer_range: hunk.buffer_range,
16503 diff_base_byte_range: hunk.diff_base_byte_range,
16504 secondary_status: hunk.secondary_status,
16505 range: Point::zero()..Point::zero(), // unused
16506 })
16507 .collect::<Vec<_>>(),
16508 &buffer_snapshot,
16509 file_exists,
16510 cx,
16511 )
16512 });
16513 None
16514 }
16515
16516 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16517 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16518 self.buffer
16519 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16520 }
16521
16522 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16523 self.buffer.update(cx, |buffer, cx| {
16524 let ranges = vec![Anchor::min()..Anchor::max()];
16525 if !buffer.all_diff_hunks_expanded()
16526 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16527 {
16528 buffer.collapse_diff_hunks(ranges, cx);
16529 true
16530 } else {
16531 false
16532 }
16533 })
16534 }
16535
16536 fn toggle_diff_hunks_in_ranges(
16537 &mut self,
16538 ranges: Vec<Range<Anchor>>,
16539 cx: &mut Context<Editor>,
16540 ) {
16541 self.buffer.update(cx, |buffer, cx| {
16542 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16543 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16544 })
16545 }
16546
16547 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16548 self.buffer.update(cx, |buffer, cx| {
16549 let snapshot = buffer.snapshot(cx);
16550 let excerpt_id = range.end.excerpt_id;
16551 let point_range = range.to_point(&snapshot);
16552 let expand = !buffer.single_hunk_is_expanded(range, cx);
16553 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16554 })
16555 }
16556
16557 pub(crate) fn apply_all_diff_hunks(
16558 &mut self,
16559 _: &ApplyAllDiffHunks,
16560 window: &mut Window,
16561 cx: &mut Context<Self>,
16562 ) {
16563 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16564
16565 let buffers = self.buffer.read(cx).all_buffers();
16566 for branch_buffer in buffers {
16567 branch_buffer.update(cx, |branch_buffer, cx| {
16568 branch_buffer.merge_into_base(Vec::new(), cx);
16569 });
16570 }
16571
16572 if let Some(project) = self.project.clone() {
16573 self.save(true, project, window, cx).detach_and_log_err(cx);
16574 }
16575 }
16576
16577 pub(crate) fn apply_selected_diff_hunks(
16578 &mut self,
16579 _: &ApplyDiffHunk,
16580 window: &mut Window,
16581 cx: &mut Context<Self>,
16582 ) {
16583 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16584 let snapshot = self.snapshot(window, cx);
16585 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16586 let mut ranges_by_buffer = HashMap::default();
16587 self.transact(window, cx, |editor, _window, cx| {
16588 for hunk in hunks {
16589 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16590 ranges_by_buffer
16591 .entry(buffer.clone())
16592 .or_insert_with(Vec::new)
16593 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16594 }
16595 }
16596
16597 for (buffer, ranges) in ranges_by_buffer {
16598 buffer.update(cx, |buffer, cx| {
16599 buffer.merge_into_base(ranges, cx);
16600 });
16601 }
16602 });
16603
16604 if let Some(project) = self.project.clone() {
16605 self.save(true, project, window, cx).detach_and_log_err(cx);
16606 }
16607 }
16608
16609 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16610 if hovered != self.gutter_hovered {
16611 self.gutter_hovered = hovered;
16612 cx.notify();
16613 }
16614 }
16615
16616 pub fn insert_blocks(
16617 &mut self,
16618 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16619 autoscroll: Option<Autoscroll>,
16620 cx: &mut Context<Self>,
16621 ) -> Vec<CustomBlockId> {
16622 let blocks = self
16623 .display_map
16624 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16625 if let Some(autoscroll) = autoscroll {
16626 self.request_autoscroll(autoscroll, cx);
16627 }
16628 cx.notify();
16629 blocks
16630 }
16631
16632 pub fn resize_blocks(
16633 &mut self,
16634 heights: HashMap<CustomBlockId, u32>,
16635 autoscroll: Option<Autoscroll>,
16636 cx: &mut Context<Self>,
16637 ) {
16638 self.display_map
16639 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16640 if let Some(autoscroll) = autoscroll {
16641 self.request_autoscroll(autoscroll, cx);
16642 }
16643 cx.notify();
16644 }
16645
16646 pub fn replace_blocks(
16647 &mut self,
16648 renderers: HashMap<CustomBlockId, RenderBlock>,
16649 autoscroll: Option<Autoscroll>,
16650 cx: &mut Context<Self>,
16651 ) {
16652 self.display_map
16653 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16654 if let Some(autoscroll) = autoscroll {
16655 self.request_autoscroll(autoscroll, cx);
16656 }
16657 cx.notify();
16658 }
16659
16660 pub fn remove_blocks(
16661 &mut self,
16662 block_ids: HashSet<CustomBlockId>,
16663 autoscroll: Option<Autoscroll>,
16664 cx: &mut Context<Self>,
16665 ) {
16666 self.display_map.update(cx, |display_map, cx| {
16667 display_map.remove_blocks(block_ids, cx)
16668 });
16669 if let Some(autoscroll) = autoscroll {
16670 self.request_autoscroll(autoscroll, cx);
16671 }
16672 cx.notify();
16673 }
16674
16675 pub fn row_for_block(
16676 &self,
16677 block_id: CustomBlockId,
16678 cx: &mut Context<Self>,
16679 ) -> Option<DisplayRow> {
16680 self.display_map
16681 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16682 }
16683
16684 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16685 self.focused_block = Some(focused_block);
16686 }
16687
16688 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16689 self.focused_block.take()
16690 }
16691
16692 pub fn insert_creases(
16693 &mut self,
16694 creases: impl IntoIterator<Item = Crease<Anchor>>,
16695 cx: &mut Context<Self>,
16696 ) -> Vec<CreaseId> {
16697 self.display_map
16698 .update(cx, |map, cx| map.insert_creases(creases, cx))
16699 }
16700
16701 pub fn remove_creases(
16702 &mut self,
16703 ids: impl IntoIterator<Item = CreaseId>,
16704 cx: &mut Context<Self>,
16705 ) -> Vec<(CreaseId, Range<Anchor>)> {
16706 self.display_map
16707 .update(cx, |map, cx| map.remove_creases(ids, cx))
16708 }
16709
16710 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16711 self.display_map
16712 .update(cx, |map, cx| map.snapshot(cx))
16713 .longest_row()
16714 }
16715
16716 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16717 self.display_map
16718 .update(cx, |map, cx| map.snapshot(cx))
16719 .max_point()
16720 }
16721
16722 pub fn text(&self, cx: &App) -> String {
16723 self.buffer.read(cx).read(cx).text()
16724 }
16725
16726 pub fn is_empty(&self, cx: &App) -> bool {
16727 self.buffer.read(cx).read(cx).is_empty()
16728 }
16729
16730 pub fn text_option(&self, cx: &App) -> Option<String> {
16731 let text = self.text(cx);
16732 let text = text.trim();
16733
16734 if text.is_empty() {
16735 return None;
16736 }
16737
16738 Some(text.to_string())
16739 }
16740
16741 pub fn set_text(
16742 &mut self,
16743 text: impl Into<Arc<str>>,
16744 window: &mut Window,
16745 cx: &mut Context<Self>,
16746 ) {
16747 self.transact(window, cx, |this, _, cx| {
16748 this.buffer
16749 .read(cx)
16750 .as_singleton()
16751 .expect("you can only call set_text on editors for singleton buffers")
16752 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16753 });
16754 }
16755
16756 pub fn display_text(&self, cx: &mut App) -> String {
16757 self.display_map
16758 .update(cx, |map, cx| map.snapshot(cx))
16759 .text()
16760 }
16761
16762 fn create_minimap(
16763 &self,
16764 minimap_settings: MinimapSettings,
16765 window: &mut Window,
16766 cx: &mut Context<Self>,
16767 ) -> Option<Entity<Self>> {
16768 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16769 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16770 }
16771
16772 fn initialize_new_minimap(
16773 &self,
16774 minimap_settings: MinimapSettings,
16775 window: &mut Window,
16776 cx: &mut Context<Self>,
16777 ) -> Entity<Self> {
16778 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16779
16780 let mut minimap = Editor::new_internal(
16781 EditorMode::Minimap {
16782 parent: cx.weak_entity(),
16783 },
16784 self.buffer.clone(),
16785 self.project.clone(),
16786 Some(self.display_map.clone()),
16787 window,
16788 cx,
16789 );
16790 minimap.scroll_manager.clone_state(&self.scroll_manager);
16791 minimap.set_text_style_refinement(TextStyleRefinement {
16792 font_size: Some(MINIMAP_FONT_SIZE),
16793 font_weight: Some(MINIMAP_FONT_WEIGHT),
16794 ..Default::default()
16795 });
16796 minimap.update_minimap_configuration(minimap_settings, cx);
16797 cx.new(|_| minimap)
16798 }
16799
16800 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16801 let current_line_highlight = minimap_settings
16802 .current_line_highlight
16803 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16804 self.set_current_line_highlight(Some(current_line_highlight));
16805 }
16806
16807 pub fn minimap(&self) -> Option<&Entity<Self>> {
16808 self.minimap
16809 .as_ref()
16810 .filter(|_| self.minimap_visibility.visible())
16811 }
16812
16813 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16814 let mut wrap_guides = smallvec![];
16815
16816 if self.show_wrap_guides == Some(false) {
16817 return wrap_guides;
16818 }
16819
16820 let settings = self.buffer.read(cx).language_settings(cx);
16821 if settings.show_wrap_guides {
16822 match self.soft_wrap_mode(cx) {
16823 SoftWrap::Column(soft_wrap) => {
16824 wrap_guides.push((soft_wrap as usize, true));
16825 }
16826 SoftWrap::Bounded(soft_wrap) => {
16827 wrap_guides.push((soft_wrap as usize, true));
16828 }
16829 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16830 }
16831 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16832 }
16833
16834 wrap_guides
16835 }
16836
16837 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16838 let settings = self.buffer.read(cx).language_settings(cx);
16839 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16840 match mode {
16841 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16842 SoftWrap::None
16843 }
16844 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16845 language_settings::SoftWrap::PreferredLineLength => {
16846 SoftWrap::Column(settings.preferred_line_length)
16847 }
16848 language_settings::SoftWrap::Bounded => {
16849 SoftWrap::Bounded(settings.preferred_line_length)
16850 }
16851 }
16852 }
16853
16854 pub fn set_soft_wrap_mode(
16855 &mut self,
16856 mode: language_settings::SoftWrap,
16857
16858 cx: &mut Context<Self>,
16859 ) {
16860 self.soft_wrap_mode_override = Some(mode);
16861 cx.notify();
16862 }
16863
16864 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16865 self.hard_wrap = hard_wrap;
16866 cx.notify();
16867 }
16868
16869 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16870 self.text_style_refinement = Some(style);
16871 }
16872
16873 /// called by the Element so we know what style we were most recently rendered with.
16874 pub(crate) fn set_style(
16875 &mut self,
16876 style: EditorStyle,
16877 window: &mut Window,
16878 cx: &mut Context<Self>,
16879 ) {
16880 // We intentionally do not inform the display map about the minimap style
16881 // so that wrapping is not recalculated and stays consistent for the editor
16882 // and its linked minimap.
16883 if !self.mode.is_minimap() {
16884 let rem_size = window.rem_size();
16885 self.display_map.update(cx, |map, cx| {
16886 map.set_font(
16887 style.text.font(),
16888 style.text.font_size.to_pixels(rem_size),
16889 cx,
16890 )
16891 });
16892 }
16893 self.style = Some(style);
16894 }
16895
16896 pub fn style(&self) -> Option<&EditorStyle> {
16897 self.style.as_ref()
16898 }
16899
16900 // Called by the element. This method is not designed to be called outside of the editor
16901 // element's layout code because it does not notify when rewrapping is computed synchronously.
16902 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16903 self.display_map
16904 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16905 }
16906
16907 pub fn set_soft_wrap(&mut self) {
16908 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16909 }
16910
16911 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16912 if self.soft_wrap_mode_override.is_some() {
16913 self.soft_wrap_mode_override.take();
16914 } else {
16915 let soft_wrap = match self.soft_wrap_mode(cx) {
16916 SoftWrap::GitDiff => return,
16917 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16918 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16919 language_settings::SoftWrap::None
16920 }
16921 };
16922 self.soft_wrap_mode_override = Some(soft_wrap);
16923 }
16924 cx.notify();
16925 }
16926
16927 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16928 let Some(workspace) = self.workspace() else {
16929 return;
16930 };
16931 let fs = workspace.read(cx).app_state().fs.clone();
16932 let current_show = TabBarSettings::get_global(cx).show;
16933 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16934 setting.show = Some(!current_show);
16935 });
16936 }
16937
16938 pub fn toggle_indent_guides(
16939 &mut self,
16940 _: &ToggleIndentGuides,
16941 _: &mut Window,
16942 cx: &mut Context<Self>,
16943 ) {
16944 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16945 self.buffer
16946 .read(cx)
16947 .language_settings(cx)
16948 .indent_guides
16949 .enabled
16950 });
16951 self.show_indent_guides = Some(!currently_enabled);
16952 cx.notify();
16953 }
16954
16955 fn should_show_indent_guides(&self) -> Option<bool> {
16956 self.show_indent_guides
16957 }
16958
16959 pub fn toggle_line_numbers(
16960 &mut self,
16961 _: &ToggleLineNumbers,
16962 _: &mut Window,
16963 cx: &mut Context<Self>,
16964 ) {
16965 let mut editor_settings = EditorSettings::get_global(cx).clone();
16966 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16967 EditorSettings::override_global(editor_settings, cx);
16968 }
16969
16970 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16971 if let Some(show_line_numbers) = self.show_line_numbers {
16972 return show_line_numbers;
16973 }
16974 EditorSettings::get_global(cx).gutter.line_numbers
16975 }
16976
16977 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
16978 self.use_relative_line_numbers
16979 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
16980 }
16981
16982 pub fn toggle_relative_line_numbers(
16983 &mut self,
16984 _: &ToggleRelativeLineNumbers,
16985 _: &mut Window,
16986 cx: &mut Context<Self>,
16987 ) {
16988 let is_relative = self.should_use_relative_line_numbers(cx);
16989 self.set_relative_line_number(Some(!is_relative), cx)
16990 }
16991
16992 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
16993 self.use_relative_line_numbers = is_relative;
16994 cx.notify();
16995 }
16996
16997 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
16998 self.show_gutter = show_gutter;
16999 cx.notify();
17000 }
17001
17002 pub fn set_show_scrollbars(&mut self, show_scrollbars: bool, cx: &mut Context<Self>) {
17003 self.show_scrollbars = show_scrollbars;
17004 cx.notify();
17005 }
17006
17007 pub fn set_minimap_visibility(
17008 &mut self,
17009 minimap_visibility: MinimapVisibility,
17010 window: &mut Window,
17011 cx: &mut Context<Self>,
17012 ) {
17013 if self.minimap_visibility != minimap_visibility {
17014 if minimap_visibility.visible() && self.minimap.is_none() {
17015 let minimap_settings = EditorSettings::get_global(cx).minimap;
17016 self.minimap =
17017 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17018 }
17019 self.minimap_visibility = minimap_visibility;
17020 cx.notify();
17021 }
17022 }
17023
17024 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17025 self.set_show_scrollbars(false, cx);
17026 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17027 }
17028
17029 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17030 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17031 }
17032
17033 /// Normally the text in full mode and auto height editors is padded on the
17034 /// left side by roughly half a character width for improved hit testing.
17035 ///
17036 /// Use this method to disable this for cases where this is not wanted (e.g.
17037 /// if you want to align the editor text with some other text above or below)
17038 /// or if you want to add this padding to single-line editors.
17039 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17040 self.offset_content = offset_content;
17041 cx.notify();
17042 }
17043
17044 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17045 self.show_line_numbers = Some(show_line_numbers);
17046 cx.notify();
17047 }
17048
17049 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17050 self.disable_expand_excerpt_buttons = true;
17051 cx.notify();
17052 }
17053
17054 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17055 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17056 cx.notify();
17057 }
17058
17059 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17060 self.show_code_actions = Some(show_code_actions);
17061 cx.notify();
17062 }
17063
17064 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17065 self.show_runnables = Some(show_runnables);
17066 cx.notify();
17067 }
17068
17069 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17070 self.show_breakpoints = Some(show_breakpoints);
17071 cx.notify();
17072 }
17073
17074 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17075 if self.display_map.read(cx).masked != masked {
17076 self.display_map.update(cx, |map, _| map.masked = masked);
17077 }
17078 cx.notify()
17079 }
17080
17081 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17082 self.show_wrap_guides = Some(show_wrap_guides);
17083 cx.notify();
17084 }
17085
17086 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17087 self.show_indent_guides = Some(show_indent_guides);
17088 cx.notify();
17089 }
17090
17091 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17092 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17093 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17094 if let Some(dir) = file.abs_path(cx).parent() {
17095 return Some(dir.to_owned());
17096 }
17097 }
17098
17099 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17100 return Some(project_path.path.to_path_buf());
17101 }
17102 }
17103
17104 None
17105 }
17106
17107 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17108 self.active_excerpt(cx)?
17109 .1
17110 .read(cx)
17111 .file()
17112 .and_then(|f| f.as_local())
17113 }
17114
17115 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17116 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17117 let buffer = buffer.read(cx);
17118 if let Some(project_path) = buffer.project_path(cx) {
17119 let project = self.project.as_ref()?.read(cx);
17120 project.absolute_path(&project_path, cx)
17121 } else {
17122 buffer
17123 .file()
17124 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17125 }
17126 })
17127 }
17128
17129 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17130 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17131 let project_path = buffer.read(cx).project_path(cx)?;
17132 let project = self.project.as_ref()?.read(cx);
17133 let entry = project.entry_for_path(&project_path, cx)?;
17134 let path = entry.path.to_path_buf();
17135 Some(path)
17136 })
17137 }
17138
17139 pub fn reveal_in_finder(
17140 &mut self,
17141 _: &RevealInFileManager,
17142 _window: &mut Window,
17143 cx: &mut Context<Self>,
17144 ) {
17145 if let Some(target) = self.target_file(cx) {
17146 cx.reveal_path(&target.abs_path(cx));
17147 }
17148 }
17149
17150 pub fn copy_path(
17151 &mut self,
17152 _: &zed_actions::workspace::CopyPath,
17153 _window: &mut Window,
17154 cx: &mut Context<Self>,
17155 ) {
17156 if let Some(path) = self.target_file_abs_path(cx) {
17157 if let Some(path) = path.to_str() {
17158 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17159 }
17160 }
17161 }
17162
17163 pub fn copy_relative_path(
17164 &mut self,
17165 _: &zed_actions::workspace::CopyRelativePath,
17166 _window: &mut Window,
17167 cx: &mut Context<Self>,
17168 ) {
17169 if let Some(path) = self.target_file_path(cx) {
17170 if let Some(path) = path.to_str() {
17171 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17172 }
17173 }
17174 }
17175
17176 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17177 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17178 buffer.read(cx).project_path(cx)
17179 } else {
17180 None
17181 }
17182 }
17183
17184 // Returns true if the editor handled a go-to-line request
17185 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17186 maybe!({
17187 let breakpoint_store = self.breakpoint_store.as_ref()?;
17188
17189 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17190 else {
17191 self.clear_row_highlights::<ActiveDebugLine>();
17192 return None;
17193 };
17194
17195 let position = active_stack_frame.position;
17196 let buffer_id = position.buffer_id?;
17197 let snapshot = self
17198 .project
17199 .as_ref()?
17200 .read(cx)
17201 .buffer_for_id(buffer_id, cx)?
17202 .read(cx)
17203 .snapshot();
17204
17205 let mut handled = false;
17206 for (id, ExcerptRange { context, .. }) in
17207 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17208 {
17209 if context.start.cmp(&position, &snapshot).is_ge()
17210 || context.end.cmp(&position, &snapshot).is_lt()
17211 {
17212 continue;
17213 }
17214 let snapshot = self.buffer.read(cx).snapshot(cx);
17215 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17216
17217 handled = true;
17218 self.clear_row_highlights::<ActiveDebugLine>();
17219
17220 self.go_to_line::<ActiveDebugLine>(
17221 multibuffer_anchor,
17222 Some(cx.theme().colors().editor_debugger_active_line_background),
17223 window,
17224 cx,
17225 );
17226
17227 cx.notify();
17228 }
17229
17230 handled.then_some(())
17231 })
17232 .is_some()
17233 }
17234
17235 pub fn copy_file_name_without_extension(
17236 &mut self,
17237 _: &CopyFileNameWithoutExtension,
17238 _: &mut Window,
17239 cx: &mut Context<Self>,
17240 ) {
17241 if let Some(file) = self.target_file(cx) {
17242 if let Some(file_stem) = file.path().file_stem() {
17243 if let Some(name) = file_stem.to_str() {
17244 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17245 }
17246 }
17247 }
17248 }
17249
17250 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17251 if let Some(file) = self.target_file(cx) {
17252 if let Some(file_name) = file.path().file_name() {
17253 if let Some(name) = file_name.to_str() {
17254 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17255 }
17256 }
17257 }
17258 }
17259
17260 pub fn toggle_git_blame(
17261 &mut self,
17262 _: &::git::Blame,
17263 window: &mut Window,
17264 cx: &mut Context<Self>,
17265 ) {
17266 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17267
17268 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17269 self.start_git_blame(true, window, cx);
17270 }
17271
17272 cx.notify();
17273 }
17274
17275 pub fn toggle_git_blame_inline(
17276 &mut self,
17277 _: &ToggleGitBlameInline,
17278 window: &mut Window,
17279 cx: &mut Context<Self>,
17280 ) {
17281 self.toggle_git_blame_inline_internal(true, window, cx);
17282 cx.notify();
17283 }
17284
17285 pub fn open_git_blame_commit(
17286 &mut self,
17287 _: &OpenGitBlameCommit,
17288 window: &mut Window,
17289 cx: &mut Context<Self>,
17290 ) {
17291 self.open_git_blame_commit_internal(window, cx);
17292 }
17293
17294 fn open_git_blame_commit_internal(
17295 &mut self,
17296 window: &mut Window,
17297 cx: &mut Context<Self>,
17298 ) -> Option<()> {
17299 let blame = self.blame.as_ref()?;
17300 let snapshot = self.snapshot(window, cx);
17301 let cursor = self.selections.newest::<Point>(cx).head();
17302 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17303 let blame_entry = blame
17304 .update(cx, |blame, cx| {
17305 blame
17306 .blame_for_rows(
17307 &[RowInfo {
17308 buffer_id: Some(buffer.remote_id()),
17309 buffer_row: Some(point.row),
17310 ..Default::default()
17311 }],
17312 cx,
17313 )
17314 .next()
17315 })
17316 .flatten()?;
17317 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17318 let repo = blame.read(cx).repository(cx)?;
17319 let workspace = self.workspace()?.downgrade();
17320 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17321 None
17322 }
17323
17324 pub fn git_blame_inline_enabled(&self) -> bool {
17325 self.git_blame_inline_enabled
17326 }
17327
17328 pub fn toggle_selection_menu(
17329 &mut self,
17330 _: &ToggleSelectionMenu,
17331 _: &mut Window,
17332 cx: &mut Context<Self>,
17333 ) {
17334 self.show_selection_menu = self
17335 .show_selection_menu
17336 .map(|show_selections_menu| !show_selections_menu)
17337 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17338
17339 cx.notify();
17340 }
17341
17342 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17343 self.show_selection_menu
17344 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17345 }
17346
17347 fn start_git_blame(
17348 &mut self,
17349 user_triggered: bool,
17350 window: &mut Window,
17351 cx: &mut Context<Self>,
17352 ) {
17353 if let Some(project) = self.project.as_ref() {
17354 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17355 return;
17356 };
17357
17358 if buffer.read(cx).file().is_none() {
17359 return;
17360 }
17361
17362 let focused = self.focus_handle(cx).contains_focused(window, cx);
17363
17364 let project = project.clone();
17365 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17366 self.blame_subscription =
17367 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17368 self.blame = Some(blame);
17369 }
17370 }
17371
17372 fn toggle_git_blame_inline_internal(
17373 &mut self,
17374 user_triggered: bool,
17375 window: &mut Window,
17376 cx: &mut Context<Self>,
17377 ) {
17378 if self.git_blame_inline_enabled {
17379 self.git_blame_inline_enabled = false;
17380 self.show_git_blame_inline = false;
17381 self.show_git_blame_inline_delay_task.take();
17382 } else {
17383 self.git_blame_inline_enabled = true;
17384 self.start_git_blame_inline(user_triggered, window, cx);
17385 }
17386
17387 cx.notify();
17388 }
17389
17390 fn start_git_blame_inline(
17391 &mut self,
17392 user_triggered: bool,
17393 window: &mut Window,
17394 cx: &mut Context<Self>,
17395 ) {
17396 self.start_git_blame(user_triggered, window, cx);
17397
17398 if ProjectSettings::get_global(cx)
17399 .git
17400 .inline_blame_delay()
17401 .is_some()
17402 {
17403 self.start_inline_blame_timer(window, cx);
17404 } else {
17405 self.show_git_blame_inline = true
17406 }
17407 }
17408
17409 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17410 self.blame.as_ref()
17411 }
17412
17413 pub fn show_git_blame_gutter(&self) -> bool {
17414 self.show_git_blame_gutter
17415 }
17416
17417 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17418 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17419 }
17420
17421 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17422 self.show_git_blame_inline
17423 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17424 && !self.newest_selection_head_on_empty_line(cx)
17425 && self.has_blame_entries(cx)
17426 }
17427
17428 fn has_blame_entries(&self, cx: &App) -> bool {
17429 self.blame()
17430 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17431 }
17432
17433 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17434 let cursor_anchor = self.selections.newest_anchor().head();
17435
17436 let snapshot = self.buffer.read(cx).snapshot(cx);
17437 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17438
17439 snapshot.line_len(buffer_row) == 0
17440 }
17441
17442 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17443 let buffer_and_selection = maybe!({
17444 let selection = self.selections.newest::<Point>(cx);
17445 let selection_range = selection.range();
17446
17447 let multi_buffer = self.buffer().read(cx);
17448 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17449 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17450
17451 let (buffer, range, _) = if selection.reversed {
17452 buffer_ranges.first()
17453 } else {
17454 buffer_ranges.last()
17455 }?;
17456
17457 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17458 ..text::ToPoint::to_point(&range.end, &buffer).row;
17459 Some((
17460 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17461 selection,
17462 ))
17463 });
17464
17465 let Some((buffer, selection)) = buffer_and_selection else {
17466 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17467 };
17468
17469 let Some(project) = self.project.as_ref() else {
17470 return Task::ready(Err(anyhow!("editor does not have project")));
17471 };
17472
17473 project.update(cx, |project, cx| {
17474 project.get_permalink_to_line(&buffer, selection, cx)
17475 })
17476 }
17477
17478 pub fn copy_permalink_to_line(
17479 &mut self,
17480 _: &CopyPermalinkToLine,
17481 window: &mut Window,
17482 cx: &mut Context<Self>,
17483 ) {
17484 let permalink_task = self.get_permalink_to_line(cx);
17485 let workspace = self.workspace();
17486
17487 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17488 Ok(permalink) => {
17489 cx.update(|_, cx| {
17490 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17491 })
17492 .ok();
17493 }
17494 Err(err) => {
17495 let message = format!("Failed to copy permalink: {err}");
17496
17497 anyhow::Result::<()>::Err(err).log_err();
17498
17499 if let Some(workspace) = workspace {
17500 workspace
17501 .update_in(cx, |workspace, _, cx| {
17502 struct CopyPermalinkToLine;
17503
17504 workspace.show_toast(
17505 Toast::new(
17506 NotificationId::unique::<CopyPermalinkToLine>(),
17507 message,
17508 ),
17509 cx,
17510 )
17511 })
17512 .ok();
17513 }
17514 }
17515 })
17516 .detach();
17517 }
17518
17519 pub fn copy_file_location(
17520 &mut self,
17521 _: &CopyFileLocation,
17522 _: &mut Window,
17523 cx: &mut Context<Self>,
17524 ) {
17525 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17526 if let Some(file) = self.target_file(cx) {
17527 if let Some(path) = file.path().to_str() {
17528 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17529 }
17530 }
17531 }
17532
17533 pub fn open_permalink_to_line(
17534 &mut self,
17535 _: &OpenPermalinkToLine,
17536 window: &mut Window,
17537 cx: &mut Context<Self>,
17538 ) {
17539 let permalink_task = self.get_permalink_to_line(cx);
17540 let workspace = self.workspace();
17541
17542 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17543 Ok(permalink) => {
17544 cx.update(|_, cx| {
17545 cx.open_url(permalink.as_ref());
17546 })
17547 .ok();
17548 }
17549 Err(err) => {
17550 let message = format!("Failed to open permalink: {err}");
17551
17552 anyhow::Result::<()>::Err(err).log_err();
17553
17554 if let Some(workspace) = workspace {
17555 workspace
17556 .update(cx, |workspace, cx| {
17557 struct OpenPermalinkToLine;
17558
17559 workspace.show_toast(
17560 Toast::new(
17561 NotificationId::unique::<OpenPermalinkToLine>(),
17562 message,
17563 ),
17564 cx,
17565 )
17566 })
17567 .ok();
17568 }
17569 }
17570 })
17571 .detach();
17572 }
17573
17574 pub fn insert_uuid_v4(
17575 &mut self,
17576 _: &InsertUuidV4,
17577 window: &mut Window,
17578 cx: &mut Context<Self>,
17579 ) {
17580 self.insert_uuid(UuidVersion::V4, window, cx);
17581 }
17582
17583 pub fn insert_uuid_v7(
17584 &mut self,
17585 _: &InsertUuidV7,
17586 window: &mut Window,
17587 cx: &mut Context<Self>,
17588 ) {
17589 self.insert_uuid(UuidVersion::V7, window, cx);
17590 }
17591
17592 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17593 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17594 self.transact(window, cx, |this, window, cx| {
17595 let edits = this
17596 .selections
17597 .all::<Point>(cx)
17598 .into_iter()
17599 .map(|selection| {
17600 let uuid = match version {
17601 UuidVersion::V4 => uuid::Uuid::new_v4(),
17602 UuidVersion::V7 => uuid::Uuid::now_v7(),
17603 };
17604
17605 (selection.range(), uuid.to_string())
17606 });
17607 this.edit(edits, cx);
17608 this.refresh_inline_completion(true, false, window, cx);
17609 });
17610 }
17611
17612 pub fn open_selections_in_multibuffer(
17613 &mut self,
17614 _: &OpenSelectionsInMultibuffer,
17615 window: &mut Window,
17616 cx: &mut Context<Self>,
17617 ) {
17618 let multibuffer = self.buffer.read(cx);
17619
17620 let Some(buffer) = multibuffer.as_singleton() else {
17621 return;
17622 };
17623
17624 let Some(workspace) = self.workspace() else {
17625 return;
17626 };
17627
17628 let locations = self
17629 .selections
17630 .disjoint_anchors()
17631 .iter()
17632 .map(|selection| {
17633 let range = if selection.reversed {
17634 selection.end.text_anchor..selection.start.text_anchor
17635 } else {
17636 selection.start.text_anchor..selection.end.text_anchor
17637 };
17638 Location {
17639 buffer: buffer.clone(),
17640 range,
17641 }
17642 })
17643 .collect::<Vec<_>>();
17644
17645 let title = multibuffer.title(cx).to_string();
17646
17647 cx.spawn_in(window, async move |_, cx| {
17648 workspace.update_in(cx, |workspace, window, cx| {
17649 Self::open_locations_in_multibuffer(
17650 workspace,
17651 locations,
17652 format!("Selections for '{title}'"),
17653 false,
17654 MultibufferSelectionMode::All,
17655 window,
17656 cx,
17657 );
17658 })
17659 })
17660 .detach();
17661 }
17662
17663 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17664 /// last highlight added will be used.
17665 ///
17666 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17667 pub fn highlight_rows<T: 'static>(
17668 &mut self,
17669 range: Range<Anchor>,
17670 color: Hsla,
17671 options: RowHighlightOptions,
17672 cx: &mut Context<Self>,
17673 ) {
17674 let snapshot = self.buffer().read(cx).snapshot(cx);
17675 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17676 let ix = row_highlights.binary_search_by(|highlight| {
17677 Ordering::Equal
17678 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17679 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17680 });
17681
17682 if let Err(mut ix) = ix {
17683 let index = post_inc(&mut self.highlight_order);
17684
17685 // If this range intersects with the preceding highlight, then merge it with
17686 // the preceding highlight. Otherwise insert a new highlight.
17687 let mut merged = false;
17688 if ix > 0 {
17689 let prev_highlight = &mut row_highlights[ix - 1];
17690 if prev_highlight
17691 .range
17692 .end
17693 .cmp(&range.start, &snapshot)
17694 .is_ge()
17695 {
17696 ix -= 1;
17697 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17698 prev_highlight.range.end = range.end;
17699 }
17700 merged = true;
17701 prev_highlight.index = index;
17702 prev_highlight.color = color;
17703 prev_highlight.options = options;
17704 }
17705 }
17706
17707 if !merged {
17708 row_highlights.insert(
17709 ix,
17710 RowHighlight {
17711 range: range.clone(),
17712 index,
17713 color,
17714 options,
17715 type_id: TypeId::of::<T>(),
17716 },
17717 );
17718 }
17719
17720 // If any of the following highlights intersect with this one, merge them.
17721 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17722 let highlight = &row_highlights[ix];
17723 if next_highlight
17724 .range
17725 .start
17726 .cmp(&highlight.range.end, &snapshot)
17727 .is_le()
17728 {
17729 if next_highlight
17730 .range
17731 .end
17732 .cmp(&highlight.range.end, &snapshot)
17733 .is_gt()
17734 {
17735 row_highlights[ix].range.end = next_highlight.range.end;
17736 }
17737 row_highlights.remove(ix + 1);
17738 } else {
17739 break;
17740 }
17741 }
17742 }
17743 }
17744
17745 /// Remove any highlighted row ranges of the given type that intersect the
17746 /// given ranges.
17747 pub fn remove_highlighted_rows<T: 'static>(
17748 &mut self,
17749 ranges_to_remove: Vec<Range<Anchor>>,
17750 cx: &mut Context<Self>,
17751 ) {
17752 let snapshot = self.buffer().read(cx).snapshot(cx);
17753 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17754 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17755 row_highlights.retain(|highlight| {
17756 while let Some(range_to_remove) = ranges_to_remove.peek() {
17757 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17758 Ordering::Less | Ordering::Equal => {
17759 ranges_to_remove.next();
17760 }
17761 Ordering::Greater => {
17762 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17763 Ordering::Less | Ordering::Equal => {
17764 return false;
17765 }
17766 Ordering::Greater => break,
17767 }
17768 }
17769 }
17770 }
17771
17772 true
17773 })
17774 }
17775
17776 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17777 pub fn clear_row_highlights<T: 'static>(&mut self) {
17778 self.highlighted_rows.remove(&TypeId::of::<T>());
17779 }
17780
17781 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17782 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17783 self.highlighted_rows
17784 .get(&TypeId::of::<T>())
17785 .map_or(&[] as &[_], |vec| vec.as_slice())
17786 .iter()
17787 .map(|highlight| (highlight.range.clone(), highlight.color))
17788 }
17789
17790 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17791 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17792 /// Allows to ignore certain kinds of highlights.
17793 pub fn highlighted_display_rows(
17794 &self,
17795 window: &mut Window,
17796 cx: &mut App,
17797 ) -> BTreeMap<DisplayRow, LineHighlight> {
17798 let snapshot = self.snapshot(window, cx);
17799 let mut used_highlight_orders = HashMap::default();
17800 self.highlighted_rows
17801 .iter()
17802 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17803 .fold(
17804 BTreeMap::<DisplayRow, LineHighlight>::new(),
17805 |mut unique_rows, highlight| {
17806 let start = highlight.range.start.to_display_point(&snapshot);
17807 let end = highlight.range.end.to_display_point(&snapshot);
17808 let start_row = start.row().0;
17809 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17810 && end.column() == 0
17811 {
17812 end.row().0.saturating_sub(1)
17813 } else {
17814 end.row().0
17815 };
17816 for row in start_row..=end_row {
17817 let used_index =
17818 used_highlight_orders.entry(row).or_insert(highlight.index);
17819 if highlight.index >= *used_index {
17820 *used_index = highlight.index;
17821 unique_rows.insert(
17822 DisplayRow(row),
17823 LineHighlight {
17824 include_gutter: highlight.options.include_gutter,
17825 border: None,
17826 background: highlight.color.into(),
17827 type_id: Some(highlight.type_id),
17828 },
17829 );
17830 }
17831 }
17832 unique_rows
17833 },
17834 )
17835 }
17836
17837 pub fn highlighted_display_row_for_autoscroll(
17838 &self,
17839 snapshot: &DisplaySnapshot,
17840 ) -> Option<DisplayRow> {
17841 self.highlighted_rows
17842 .values()
17843 .flat_map(|highlighted_rows| highlighted_rows.iter())
17844 .filter_map(|highlight| {
17845 if highlight.options.autoscroll {
17846 Some(highlight.range.start.to_display_point(snapshot).row())
17847 } else {
17848 None
17849 }
17850 })
17851 .min()
17852 }
17853
17854 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17855 self.highlight_background::<SearchWithinRange>(
17856 ranges,
17857 |colors| colors.editor_document_highlight_read_background,
17858 cx,
17859 )
17860 }
17861
17862 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17863 self.breadcrumb_header = Some(new_header);
17864 }
17865
17866 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17867 self.clear_background_highlights::<SearchWithinRange>(cx);
17868 }
17869
17870 pub fn highlight_background<T: 'static>(
17871 &mut self,
17872 ranges: &[Range<Anchor>],
17873 color_fetcher: fn(&ThemeColors) -> Hsla,
17874 cx: &mut Context<Self>,
17875 ) {
17876 self.background_highlights
17877 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17878 self.scrollbar_marker_state.dirty = true;
17879 cx.notify();
17880 }
17881
17882 pub fn clear_background_highlights<T: 'static>(
17883 &mut self,
17884 cx: &mut Context<Self>,
17885 ) -> Option<BackgroundHighlight> {
17886 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17887 if !text_highlights.1.is_empty() {
17888 self.scrollbar_marker_state.dirty = true;
17889 cx.notify();
17890 }
17891 Some(text_highlights)
17892 }
17893
17894 pub fn highlight_gutter<T: 'static>(
17895 &mut self,
17896 ranges: &[Range<Anchor>],
17897 color_fetcher: fn(&App) -> Hsla,
17898 cx: &mut Context<Self>,
17899 ) {
17900 self.gutter_highlights
17901 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17902 cx.notify();
17903 }
17904
17905 pub fn clear_gutter_highlights<T: 'static>(
17906 &mut self,
17907 cx: &mut Context<Self>,
17908 ) -> Option<GutterHighlight> {
17909 cx.notify();
17910 self.gutter_highlights.remove(&TypeId::of::<T>())
17911 }
17912
17913 #[cfg(feature = "test-support")]
17914 pub fn all_text_background_highlights(
17915 &self,
17916 window: &mut Window,
17917 cx: &mut Context<Self>,
17918 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17919 let snapshot = self.snapshot(window, cx);
17920 let buffer = &snapshot.buffer_snapshot;
17921 let start = buffer.anchor_before(0);
17922 let end = buffer.anchor_after(buffer.len());
17923 let theme = cx.theme().colors();
17924 self.background_highlights_in_range(start..end, &snapshot, theme)
17925 }
17926
17927 #[cfg(feature = "test-support")]
17928 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17929 let snapshot = self.buffer().read(cx).snapshot(cx);
17930
17931 let highlights = self
17932 .background_highlights
17933 .get(&TypeId::of::<items::BufferSearchHighlights>());
17934
17935 if let Some((_color, ranges)) = highlights {
17936 ranges
17937 .iter()
17938 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17939 .collect_vec()
17940 } else {
17941 vec![]
17942 }
17943 }
17944
17945 fn document_highlights_for_position<'a>(
17946 &'a self,
17947 position: Anchor,
17948 buffer: &'a MultiBufferSnapshot,
17949 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17950 let read_highlights = self
17951 .background_highlights
17952 .get(&TypeId::of::<DocumentHighlightRead>())
17953 .map(|h| &h.1);
17954 let write_highlights = self
17955 .background_highlights
17956 .get(&TypeId::of::<DocumentHighlightWrite>())
17957 .map(|h| &h.1);
17958 let left_position = position.bias_left(buffer);
17959 let right_position = position.bias_right(buffer);
17960 read_highlights
17961 .into_iter()
17962 .chain(write_highlights)
17963 .flat_map(move |ranges| {
17964 let start_ix = match ranges.binary_search_by(|probe| {
17965 let cmp = probe.end.cmp(&left_position, buffer);
17966 if cmp.is_ge() {
17967 Ordering::Greater
17968 } else {
17969 Ordering::Less
17970 }
17971 }) {
17972 Ok(i) | Err(i) => i,
17973 };
17974
17975 ranges[start_ix..]
17976 .iter()
17977 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
17978 })
17979 }
17980
17981 pub fn has_background_highlights<T: 'static>(&self) -> bool {
17982 self.background_highlights
17983 .get(&TypeId::of::<T>())
17984 .map_or(false, |(_, highlights)| !highlights.is_empty())
17985 }
17986
17987 pub fn background_highlights_in_range(
17988 &self,
17989 search_range: Range<Anchor>,
17990 display_snapshot: &DisplaySnapshot,
17991 theme: &ThemeColors,
17992 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17993 let mut results = Vec::new();
17994 for (color_fetcher, ranges) in self.background_highlights.values() {
17995 let color = color_fetcher(theme);
17996 let start_ix = match ranges.binary_search_by(|probe| {
17997 let cmp = probe
17998 .end
17999 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18000 if cmp.is_gt() {
18001 Ordering::Greater
18002 } else {
18003 Ordering::Less
18004 }
18005 }) {
18006 Ok(i) | Err(i) => i,
18007 };
18008 for range in &ranges[start_ix..] {
18009 if range
18010 .start
18011 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18012 .is_ge()
18013 {
18014 break;
18015 }
18016
18017 let start = range.start.to_display_point(display_snapshot);
18018 let end = range.end.to_display_point(display_snapshot);
18019 results.push((start..end, color))
18020 }
18021 }
18022 results
18023 }
18024
18025 pub fn background_highlight_row_ranges<T: 'static>(
18026 &self,
18027 search_range: Range<Anchor>,
18028 display_snapshot: &DisplaySnapshot,
18029 count: usize,
18030 ) -> Vec<RangeInclusive<DisplayPoint>> {
18031 let mut results = Vec::new();
18032 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18033 return vec![];
18034 };
18035
18036 let start_ix = match ranges.binary_search_by(|probe| {
18037 let cmp = probe
18038 .end
18039 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18040 if cmp.is_gt() {
18041 Ordering::Greater
18042 } else {
18043 Ordering::Less
18044 }
18045 }) {
18046 Ok(i) | Err(i) => i,
18047 };
18048 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18049 if let (Some(start_display), Some(end_display)) = (start, end) {
18050 results.push(
18051 start_display.to_display_point(display_snapshot)
18052 ..=end_display.to_display_point(display_snapshot),
18053 );
18054 }
18055 };
18056 let mut start_row: Option<Point> = None;
18057 let mut end_row: Option<Point> = None;
18058 if ranges.len() > count {
18059 return Vec::new();
18060 }
18061 for range in &ranges[start_ix..] {
18062 if range
18063 .start
18064 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18065 .is_ge()
18066 {
18067 break;
18068 }
18069 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18070 if let Some(current_row) = &end_row {
18071 if end.row == current_row.row {
18072 continue;
18073 }
18074 }
18075 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18076 if start_row.is_none() {
18077 assert_eq!(end_row, None);
18078 start_row = Some(start);
18079 end_row = Some(end);
18080 continue;
18081 }
18082 if let Some(current_end) = end_row.as_mut() {
18083 if start.row > current_end.row + 1 {
18084 push_region(start_row, end_row);
18085 start_row = Some(start);
18086 end_row = Some(end);
18087 } else {
18088 // Merge two hunks.
18089 *current_end = end;
18090 }
18091 } else {
18092 unreachable!();
18093 }
18094 }
18095 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18096 push_region(start_row, end_row);
18097 results
18098 }
18099
18100 pub fn gutter_highlights_in_range(
18101 &self,
18102 search_range: Range<Anchor>,
18103 display_snapshot: &DisplaySnapshot,
18104 cx: &App,
18105 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18106 let mut results = Vec::new();
18107 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18108 let color = color_fetcher(cx);
18109 let start_ix = match ranges.binary_search_by(|probe| {
18110 let cmp = probe
18111 .end
18112 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18113 if cmp.is_gt() {
18114 Ordering::Greater
18115 } else {
18116 Ordering::Less
18117 }
18118 }) {
18119 Ok(i) | Err(i) => i,
18120 };
18121 for range in &ranges[start_ix..] {
18122 if range
18123 .start
18124 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18125 .is_ge()
18126 {
18127 break;
18128 }
18129
18130 let start = range.start.to_display_point(display_snapshot);
18131 let end = range.end.to_display_point(display_snapshot);
18132 results.push((start..end, color))
18133 }
18134 }
18135 results
18136 }
18137
18138 /// Get the text ranges corresponding to the redaction query
18139 pub fn redacted_ranges(
18140 &self,
18141 search_range: Range<Anchor>,
18142 display_snapshot: &DisplaySnapshot,
18143 cx: &App,
18144 ) -> Vec<Range<DisplayPoint>> {
18145 display_snapshot
18146 .buffer_snapshot
18147 .redacted_ranges(search_range, |file| {
18148 if let Some(file) = file {
18149 file.is_private()
18150 && EditorSettings::get(
18151 Some(SettingsLocation {
18152 worktree_id: file.worktree_id(cx),
18153 path: file.path().as_ref(),
18154 }),
18155 cx,
18156 )
18157 .redact_private_values
18158 } else {
18159 false
18160 }
18161 })
18162 .map(|range| {
18163 range.start.to_display_point(display_snapshot)
18164 ..range.end.to_display_point(display_snapshot)
18165 })
18166 .collect()
18167 }
18168
18169 pub fn highlight_text<T: 'static>(
18170 &mut self,
18171 ranges: Vec<Range<Anchor>>,
18172 style: HighlightStyle,
18173 cx: &mut Context<Self>,
18174 ) {
18175 self.display_map.update(cx, |map, _| {
18176 map.highlight_text(TypeId::of::<T>(), ranges, style)
18177 });
18178 cx.notify();
18179 }
18180
18181 pub(crate) fn highlight_inlays<T: 'static>(
18182 &mut self,
18183 highlights: Vec<InlayHighlight>,
18184 style: HighlightStyle,
18185 cx: &mut Context<Self>,
18186 ) {
18187 self.display_map.update(cx, |map, _| {
18188 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18189 });
18190 cx.notify();
18191 }
18192
18193 pub fn text_highlights<'a, T: 'static>(
18194 &'a self,
18195 cx: &'a App,
18196 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18197 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18198 }
18199
18200 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18201 let cleared = self
18202 .display_map
18203 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18204 if cleared {
18205 cx.notify();
18206 }
18207 }
18208
18209 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18210 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18211 && self.focus_handle.is_focused(window)
18212 }
18213
18214 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18215 self.show_cursor_when_unfocused = is_enabled;
18216 cx.notify();
18217 }
18218
18219 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18220 cx.notify();
18221 }
18222
18223 fn on_debug_session_event(
18224 &mut self,
18225 _session: Entity<Session>,
18226 event: &SessionEvent,
18227 cx: &mut Context<Self>,
18228 ) {
18229 match event {
18230 SessionEvent::InvalidateInlineValue => {
18231 self.refresh_inline_values(cx);
18232 }
18233 _ => {}
18234 }
18235 }
18236
18237 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18238 let Some(project) = self.project.clone() else {
18239 return;
18240 };
18241
18242 if !self.inline_value_cache.enabled {
18243 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18244 self.splice_inlays(&inlays, Vec::new(), cx);
18245 return;
18246 }
18247
18248 let current_execution_position = self
18249 .highlighted_rows
18250 .get(&TypeId::of::<ActiveDebugLine>())
18251 .and_then(|lines| lines.last().map(|line| line.range.start));
18252
18253 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18254 let inline_values = editor
18255 .update(cx, |editor, cx| {
18256 let Some(current_execution_position) = current_execution_position else {
18257 return Some(Task::ready(Ok(Vec::new())));
18258 };
18259
18260 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18261 let snapshot = buffer.snapshot(cx);
18262
18263 let excerpt = snapshot.excerpt_containing(
18264 current_execution_position..current_execution_position,
18265 )?;
18266
18267 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18268 })?;
18269
18270 let range =
18271 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18272
18273 project.inline_values(buffer, range, cx)
18274 })
18275 .ok()
18276 .flatten()?
18277 .await
18278 .context("refreshing debugger inlays")
18279 .log_err()?;
18280
18281 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18282
18283 for (buffer_id, inline_value) in inline_values
18284 .into_iter()
18285 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18286 {
18287 buffer_inline_values
18288 .entry(buffer_id)
18289 .or_default()
18290 .push(inline_value);
18291 }
18292
18293 editor
18294 .update(cx, |editor, cx| {
18295 let snapshot = editor.buffer.read(cx).snapshot(cx);
18296 let mut new_inlays = Vec::default();
18297
18298 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18299 let buffer_id = buffer_snapshot.remote_id();
18300 buffer_inline_values
18301 .get(&buffer_id)
18302 .into_iter()
18303 .flatten()
18304 .for_each(|hint| {
18305 let inlay = Inlay::debugger_hint(
18306 post_inc(&mut editor.next_inlay_id),
18307 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18308 hint.text(),
18309 );
18310
18311 new_inlays.push(inlay);
18312 });
18313 }
18314
18315 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18316 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18317
18318 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18319 })
18320 .ok()?;
18321 Some(())
18322 });
18323 }
18324
18325 fn on_buffer_event(
18326 &mut self,
18327 multibuffer: &Entity<MultiBuffer>,
18328 event: &multi_buffer::Event,
18329 window: &mut Window,
18330 cx: &mut Context<Self>,
18331 ) {
18332 match event {
18333 multi_buffer::Event::Edited {
18334 singleton_buffer_edited,
18335 edited_buffer: buffer_edited,
18336 } => {
18337 self.scrollbar_marker_state.dirty = true;
18338 self.active_indent_guides_state.dirty = true;
18339 self.refresh_active_diagnostics(cx);
18340 self.refresh_code_actions(window, cx);
18341 self.refresh_selected_text_highlights(true, window, cx);
18342 refresh_matching_bracket_highlights(self, window, cx);
18343 if self.has_active_inline_completion() {
18344 self.update_visible_inline_completion(window, cx);
18345 }
18346 if let Some(buffer) = buffer_edited {
18347 let buffer_id = buffer.read(cx).remote_id();
18348 if !self.registered_buffers.contains_key(&buffer_id) {
18349 if let Some(project) = self.project.as_ref() {
18350 project.update(cx, |project, cx| {
18351 self.registered_buffers.insert(
18352 buffer_id,
18353 project.register_buffer_with_language_servers(&buffer, cx),
18354 );
18355 })
18356 }
18357 }
18358 }
18359 cx.emit(EditorEvent::BufferEdited);
18360 cx.emit(SearchEvent::MatchesInvalidated);
18361 if *singleton_buffer_edited {
18362 if let Some(project) = &self.project {
18363 #[allow(clippy::mutable_key_type)]
18364 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18365 multibuffer
18366 .all_buffers()
18367 .into_iter()
18368 .filter_map(|buffer| {
18369 buffer.update(cx, |buffer, cx| {
18370 let language = buffer.language()?;
18371 let should_discard = project.update(cx, |project, cx| {
18372 project.is_local()
18373 && !project.has_language_servers_for(buffer, cx)
18374 });
18375 should_discard.not().then_some(language.clone())
18376 })
18377 })
18378 .collect::<HashSet<_>>()
18379 });
18380 if !languages_affected.is_empty() {
18381 self.refresh_inlay_hints(
18382 InlayHintRefreshReason::BufferEdited(languages_affected),
18383 cx,
18384 );
18385 }
18386 }
18387 }
18388
18389 let Some(project) = &self.project else { return };
18390 let (telemetry, is_via_ssh) = {
18391 let project = project.read(cx);
18392 let telemetry = project.client().telemetry().clone();
18393 let is_via_ssh = project.is_via_ssh();
18394 (telemetry, is_via_ssh)
18395 };
18396 refresh_linked_ranges(self, window, cx);
18397 telemetry.log_edit_event("editor", is_via_ssh);
18398 }
18399 multi_buffer::Event::ExcerptsAdded {
18400 buffer,
18401 predecessor,
18402 excerpts,
18403 } => {
18404 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18405 let buffer_id = buffer.read(cx).remote_id();
18406 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18407 if let Some(project) = &self.project {
18408 update_uncommitted_diff_for_buffer(
18409 cx.entity(),
18410 project,
18411 [buffer.clone()],
18412 self.buffer.clone(),
18413 cx,
18414 )
18415 .detach();
18416 }
18417 }
18418 cx.emit(EditorEvent::ExcerptsAdded {
18419 buffer: buffer.clone(),
18420 predecessor: *predecessor,
18421 excerpts: excerpts.clone(),
18422 });
18423 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18424 }
18425 multi_buffer::Event::ExcerptsRemoved {
18426 ids,
18427 removed_buffer_ids,
18428 } => {
18429 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18430 let buffer = self.buffer.read(cx);
18431 self.registered_buffers
18432 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18433 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18434 cx.emit(EditorEvent::ExcerptsRemoved {
18435 ids: ids.clone(),
18436 removed_buffer_ids: removed_buffer_ids.clone(),
18437 })
18438 }
18439 multi_buffer::Event::ExcerptsEdited {
18440 excerpt_ids,
18441 buffer_ids,
18442 } => {
18443 self.display_map.update(cx, |map, cx| {
18444 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18445 });
18446 cx.emit(EditorEvent::ExcerptsEdited {
18447 ids: excerpt_ids.clone(),
18448 })
18449 }
18450 multi_buffer::Event::ExcerptsExpanded { ids } => {
18451 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18452 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18453 }
18454 multi_buffer::Event::Reparsed(buffer_id) => {
18455 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18456 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18457
18458 cx.emit(EditorEvent::Reparsed(*buffer_id));
18459 }
18460 multi_buffer::Event::DiffHunksToggled => {
18461 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18462 }
18463 multi_buffer::Event::LanguageChanged(buffer_id) => {
18464 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18465 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18466 cx.emit(EditorEvent::Reparsed(*buffer_id));
18467 cx.notify();
18468 }
18469 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18470 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18471 multi_buffer::Event::FileHandleChanged
18472 | multi_buffer::Event::Reloaded
18473 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18474 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18475 multi_buffer::Event::DiagnosticsUpdated => {
18476 self.refresh_active_diagnostics(cx);
18477 self.refresh_inline_diagnostics(true, window, cx);
18478 self.scrollbar_marker_state.dirty = true;
18479 cx.notify();
18480 }
18481 _ => {}
18482 };
18483 }
18484
18485 pub fn start_temporary_diff_override(&mut self) {
18486 self.load_diff_task.take();
18487 self.temporary_diff_override = true;
18488 }
18489
18490 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18491 self.temporary_diff_override = false;
18492 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18493 self.buffer.update(cx, |buffer, cx| {
18494 buffer.set_all_diff_hunks_collapsed(cx);
18495 });
18496
18497 if let Some(project) = self.project.clone() {
18498 self.load_diff_task = Some(
18499 update_uncommitted_diff_for_buffer(
18500 cx.entity(),
18501 &project,
18502 self.buffer.read(cx).all_buffers(),
18503 self.buffer.clone(),
18504 cx,
18505 )
18506 .shared(),
18507 );
18508 }
18509 }
18510
18511 fn on_display_map_changed(
18512 &mut self,
18513 _: Entity<DisplayMap>,
18514 _: &mut Window,
18515 cx: &mut Context<Self>,
18516 ) {
18517 cx.notify();
18518 }
18519
18520 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18521 let new_severity = if self.diagnostics_enabled() {
18522 EditorSettings::get_global(cx)
18523 .diagnostics_max_severity
18524 .unwrap_or(DiagnosticSeverity::Hint)
18525 } else {
18526 DiagnosticSeverity::Off
18527 };
18528 self.set_max_diagnostics_severity(new_severity, cx);
18529 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18530 self.update_edit_prediction_settings(cx);
18531 self.refresh_inline_completion(true, false, window, cx);
18532 self.refresh_inlay_hints(
18533 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18534 self.selections.newest_anchor().head(),
18535 &self.buffer.read(cx).snapshot(cx),
18536 cx,
18537 )),
18538 cx,
18539 );
18540
18541 let old_cursor_shape = self.cursor_shape;
18542
18543 {
18544 let editor_settings = EditorSettings::get_global(cx);
18545 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18546 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18547 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18548 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18549 }
18550
18551 if old_cursor_shape != self.cursor_shape {
18552 cx.emit(EditorEvent::CursorShapeChanged);
18553 }
18554
18555 let project_settings = ProjectSettings::get_global(cx);
18556 self.serialize_dirty_buffers =
18557 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18558
18559 if self.mode.is_full() {
18560 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18561 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18562 if self.show_inline_diagnostics != show_inline_diagnostics {
18563 self.show_inline_diagnostics = show_inline_diagnostics;
18564 self.refresh_inline_diagnostics(false, window, cx);
18565 }
18566
18567 if self.git_blame_inline_enabled != inline_blame_enabled {
18568 self.toggle_git_blame_inline_internal(false, window, cx);
18569 }
18570
18571 let minimap_settings = EditorSettings::get_global(cx).minimap;
18572 if self.minimap_visibility.settings_visibility() != minimap_settings.minimap_enabled() {
18573 self.set_minimap_visibility(
18574 MinimapVisibility::for_mode(self.mode(), cx),
18575 window,
18576 cx,
18577 );
18578 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18579 minimap_entity.update(cx, |minimap_editor, cx| {
18580 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18581 })
18582 }
18583 }
18584
18585 cx.notify();
18586 }
18587
18588 pub fn set_searchable(&mut self, searchable: bool) {
18589 self.searchable = searchable;
18590 }
18591
18592 pub fn searchable(&self) -> bool {
18593 self.searchable
18594 }
18595
18596 fn open_proposed_changes_editor(
18597 &mut self,
18598 _: &OpenProposedChangesEditor,
18599 window: &mut Window,
18600 cx: &mut Context<Self>,
18601 ) {
18602 let Some(workspace) = self.workspace() else {
18603 cx.propagate();
18604 return;
18605 };
18606
18607 let selections = self.selections.all::<usize>(cx);
18608 let multi_buffer = self.buffer.read(cx);
18609 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18610 let mut new_selections_by_buffer = HashMap::default();
18611 for selection in selections {
18612 for (buffer, range, _) in
18613 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18614 {
18615 let mut range = range.to_point(buffer);
18616 range.start.column = 0;
18617 range.end.column = buffer.line_len(range.end.row);
18618 new_selections_by_buffer
18619 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18620 .or_insert(Vec::new())
18621 .push(range)
18622 }
18623 }
18624
18625 let proposed_changes_buffers = new_selections_by_buffer
18626 .into_iter()
18627 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18628 .collect::<Vec<_>>();
18629 let proposed_changes_editor = cx.new(|cx| {
18630 ProposedChangesEditor::new(
18631 "Proposed changes",
18632 proposed_changes_buffers,
18633 self.project.clone(),
18634 window,
18635 cx,
18636 )
18637 });
18638
18639 window.defer(cx, move |window, cx| {
18640 workspace.update(cx, |workspace, cx| {
18641 workspace.active_pane().update(cx, |pane, cx| {
18642 pane.add_item(
18643 Box::new(proposed_changes_editor),
18644 true,
18645 true,
18646 None,
18647 window,
18648 cx,
18649 );
18650 });
18651 });
18652 });
18653 }
18654
18655 pub fn open_excerpts_in_split(
18656 &mut self,
18657 _: &OpenExcerptsSplit,
18658 window: &mut Window,
18659 cx: &mut Context<Self>,
18660 ) {
18661 self.open_excerpts_common(None, true, window, cx)
18662 }
18663
18664 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18665 self.open_excerpts_common(None, false, window, cx)
18666 }
18667
18668 fn open_excerpts_common(
18669 &mut self,
18670 jump_data: Option<JumpData>,
18671 split: bool,
18672 window: &mut Window,
18673 cx: &mut Context<Self>,
18674 ) {
18675 let Some(workspace) = self.workspace() else {
18676 cx.propagate();
18677 return;
18678 };
18679
18680 if self.buffer.read(cx).is_singleton() {
18681 cx.propagate();
18682 return;
18683 }
18684
18685 let mut new_selections_by_buffer = HashMap::default();
18686 match &jump_data {
18687 Some(JumpData::MultiBufferPoint {
18688 excerpt_id,
18689 position,
18690 anchor,
18691 line_offset_from_top,
18692 }) => {
18693 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18694 if let Some(buffer) = multi_buffer_snapshot
18695 .buffer_id_for_excerpt(*excerpt_id)
18696 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18697 {
18698 let buffer_snapshot = buffer.read(cx).snapshot();
18699 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18700 language::ToPoint::to_point(anchor, &buffer_snapshot)
18701 } else {
18702 buffer_snapshot.clip_point(*position, Bias::Left)
18703 };
18704 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18705 new_selections_by_buffer.insert(
18706 buffer,
18707 (
18708 vec![jump_to_offset..jump_to_offset],
18709 Some(*line_offset_from_top),
18710 ),
18711 );
18712 }
18713 }
18714 Some(JumpData::MultiBufferRow {
18715 row,
18716 line_offset_from_top,
18717 }) => {
18718 let point = MultiBufferPoint::new(row.0, 0);
18719 if let Some((buffer, buffer_point, _)) =
18720 self.buffer.read(cx).point_to_buffer_point(point, cx)
18721 {
18722 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18723 new_selections_by_buffer
18724 .entry(buffer)
18725 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18726 .0
18727 .push(buffer_offset..buffer_offset)
18728 }
18729 }
18730 None => {
18731 let selections = self.selections.all::<usize>(cx);
18732 let multi_buffer = self.buffer.read(cx);
18733 for selection in selections {
18734 for (snapshot, range, _, anchor) in multi_buffer
18735 .snapshot(cx)
18736 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18737 {
18738 if let Some(anchor) = anchor {
18739 // selection is in a deleted hunk
18740 let Some(buffer_id) = anchor.buffer_id else {
18741 continue;
18742 };
18743 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18744 continue;
18745 };
18746 let offset = text::ToOffset::to_offset(
18747 &anchor.text_anchor,
18748 &buffer_handle.read(cx).snapshot(),
18749 );
18750 let range = offset..offset;
18751 new_selections_by_buffer
18752 .entry(buffer_handle)
18753 .or_insert((Vec::new(), None))
18754 .0
18755 .push(range)
18756 } else {
18757 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18758 else {
18759 continue;
18760 };
18761 new_selections_by_buffer
18762 .entry(buffer_handle)
18763 .or_insert((Vec::new(), None))
18764 .0
18765 .push(range)
18766 }
18767 }
18768 }
18769 }
18770 }
18771
18772 new_selections_by_buffer
18773 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18774
18775 if new_selections_by_buffer.is_empty() {
18776 return;
18777 }
18778
18779 // We defer the pane interaction because we ourselves are a workspace item
18780 // and activating a new item causes the pane to call a method on us reentrantly,
18781 // which panics if we're on the stack.
18782 window.defer(cx, move |window, cx| {
18783 workspace.update(cx, |workspace, cx| {
18784 let pane = if split {
18785 workspace.adjacent_pane(window, cx)
18786 } else {
18787 workspace.active_pane().clone()
18788 };
18789
18790 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18791 let editor = buffer
18792 .read(cx)
18793 .file()
18794 .is_none()
18795 .then(|| {
18796 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18797 // so `workspace.open_project_item` will never find them, always opening a new editor.
18798 // Instead, we try to activate the existing editor in the pane first.
18799 let (editor, pane_item_index) =
18800 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18801 let editor = item.downcast::<Editor>()?;
18802 let singleton_buffer =
18803 editor.read(cx).buffer().read(cx).as_singleton()?;
18804 if singleton_buffer == buffer {
18805 Some((editor, i))
18806 } else {
18807 None
18808 }
18809 })?;
18810 pane.update(cx, |pane, cx| {
18811 pane.activate_item(pane_item_index, true, true, window, cx)
18812 });
18813 Some(editor)
18814 })
18815 .flatten()
18816 .unwrap_or_else(|| {
18817 workspace.open_project_item::<Self>(
18818 pane.clone(),
18819 buffer,
18820 true,
18821 true,
18822 window,
18823 cx,
18824 )
18825 });
18826
18827 editor.update(cx, |editor, cx| {
18828 let autoscroll = match scroll_offset {
18829 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18830 None => Autoscroll::newest(),
18831 };
18832 let nav_history = editor.nav_history.take();
18833 editor.change_selections(Some(autoscroll), window, cx, |s| {
18834 s.select_ranges(ranges);
18835 });
18836 editor.nav_history = nav_history;
18837 });
18838 }
18839 })
18840 });
18841 }
18842
18843 // For now, don't allow opening excerpts in buffers that aren't backed by
18844 // regular project files.
18845 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18846 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18847 }
18848
18849 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18850 let snapshot = self.buffer.read(cx).read(cx);
18851 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18852 Some(
18853 ranges
18854 .iter()
18855 .map(move |range| {
18856 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18857 })
18858 .collect(),
18859 )
18860 }
18861
18862 fn selection_replacement_ranges(
18863 &self,
18864 range: Range<OffsetUtf16>,
18865 cx: &mut App,
18866 ) -> Vec<Range<OffsetUtf16>> {
18867 let selections = self.selections.all::<OffsetUtf16>(cx);
18868 let newest_selection = selections
18869 .iter()
18870 .max_by_key(|selection| selection.id)
18871 .unwrap();
18872 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18873 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18874 let snapshot = self.buffer.read(cx).read(cx);
18875 selections
18876 .into_iter()
18877 .map(|mut selection| {
18878 selection.start.0 =
18879 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18880 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18881 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18882 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18883 })
18884 .collect()
18885 }
18886
18887 fn report_editor_event(
18888 &self,
18889 event_type: &'static str,
18890 file_extension: Option<String>,
18891 cx: &App,
18892 ) {
18893 if cfg!(any(test, feature = "test-support")) {
18894 return;
18895 }
18896
18897 let Some(project) = &self.project else { return };
18898
18899 // If None, we are in a file without an extension
18900 let file = self
18901 .buffer
18902 .read(cx)
18903 .as_singleton()
18904 .and_then(|b| b.read(cx).file());
18905 let file_extension = file_extension.or(file
18906 .as_ref()
18907 .and_then(|file| Path::new(file.file_name(cx)).extension())
18908 .and_then(|e| e.to_str())
18909 .map(|a| a.to_string()));
18910
18911 let vim_mode = vim_enabled(cx);
18912
18913 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18914 let copilot_enabled = edit_predictions_provider
18915 == language::language_settings::EditPredictionProvider::Copilot;
18916 let copilot_enabled_for_language = self
18917 .buffer
18918 .read(cx)
18919 .language_settings(cx)
18920 .show_edit_predictions;
18921
18922 let project = project.read(cx);
18923 telemetry::event!(
18924 event_type,
18925 file_extension,
18926 vim_mode,
18927 copilot_enabled,
18928 copilot_enabled_for_language,
18929 edit_predictions_provider,
18930 is_via_ssh = project.is_via_ssh(),
18931 );
18932 }
18933
18934 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18935 /// with each line being an array of {text, highlight} objects.
18936 fn copy_highlight_json(
18937 &mut self,
18938 _: &CopyHighlightJson,
18939 window: &mut Window,
18940 cx: &mut Context<Self>,
18941 ) {
18942 #[derive(Serialize)]
18943 struct Chunk<'a> {
18944 text: String,
18945 highlight: Option<&'a str>,
18946 }
18947
18948 let snapshot = self.buffer.read(cx).snapshot(cx);
18949 let range = self
18950 .selected_text_range(false, window, cx)
18951 .and_then(|selection| {
18952 if selection.range.is_empty() {
18953 None
18954 } else {
18955 Some(selection.range)
18956 }
18957 })
18958 .unwrap_or_else(|| 0..snapshot.len());
18959
18960 let chunks = snapshot.chunks(range, true);
18961 let mut lines = Vec::new();
18962 let mut line: VecDeque<Chunk> = VecDeque::new();
18963
18964 let Some(style) = self.style.as_ref() else {
18965 return;
18966 };
18967
18968 for chunk in chunks {
18969 let highlight = chunk
18970 .syntax_highlight_id
18971 .and_then(|id| id.name(&style.syntax));
18972 let mut chunk_lines = chunk.text.split('\n').peekable();
18973 while let Some(text) = chunk_lines.next() {
18974 let mut merged_with_last_token = false;
18975 if let Some(last_token) = line.back_mut() {
18976 if last_token.highlight == highlight {
18977 last_token.text.push_str(text);
18978 merged_with_last_token = true;
18979 }
18980 }
18981
18982 if !merged_with_last_token {
18983 line.push_back(Chunk {
18984 text: text.into(),
18985 highlight,
18986 });
18987 }
18988
18989 if chunk_lines.peek().is_some() {
18990 if line.len() > 1 && line.front().unwrap().text.is_empty() {
18991 line.pop_front();
18992 }
18993 if line.len() > 1 && line.back().unwrap().text.is_empty() {
18994 line.pop_back();
18995 }
18996
18997 lines.push(mem::take(&mut line));
18998 }
18999 }
19000 }
19001
19002 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19003 return;
19004 };
19005 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19006 }
19007
19008 pub fn open_context_menu(
19009 &mut self,
19010 _: &OpenContextMenu,
19011 window: &mut Window,
19012 cx: &mut Context<Self>,
19013 ) {
19014 self.request_autoscroll(Autoscroll::newest(), cx);
19015 let position = self.selections.newest_display(cx).start;
19016 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19017 }
19018
19019 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19020 &self.inlay_hint_cache
19021 }
19022
19023 pub fn replay_insert_event(
19024 &mut self,
19025 text: &str,
19026 relative_utf16_range: Option<Range<isize>>,
19027 window: &mut Window,
19028 cx: &mut Context<Self>,
19029 ) {
19030 if !self.input_enabled {
19031 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19032 return;
19033 }
19034 if let Some(relative_utf16_range) = relative_utf16_range {
19035 let selections = self.selections.all::<OffsetUtf16>(cx);
19036 self.change_selections(None, window, cx, |s| {
19037 let new_ranges = selections.into_iter().map(|range| {
19038 let start = OffsetUtf16(
19039 range
19040 .head()
19041 .0
19042 .saturating_add_signed(relative_utf16_range.start),
19043 );
19044 let end = OffsetUtf16(
19045 range
19046 .head()
19047 .0
19048 .saturating_add_signed(relative_utf16_range.end),
19049 );
19050 start..end
19051 });
19052 s.select_ranges(new_ranges);
19053 });
19054 }
19055
19056 self.handle_input(text, window, cx);
19057 }
19058
19059 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19060 let Some(provider) = self.semantics_provider.as_ref() else {
19061 return false;
19062 };
19063
19064 let mut supports = false;
19065 self.buffer().update(cx, |this, cx| {
19066 this.for_each_buffer(|buffer| {
19067 supports |= provider.supports_inlay_hints(buffer, cx);
19068 });
19069 });
19070
19071 supports
19072 }
19073
19074 pub fn is_focused(&self, window: &Window) -> bool {
19075 self.focus_handle.is_focused(window)
19076 }
19077
19078 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19079 cx.emit(EditorEvent::Focused);
19080
19081 if let Some(descendant) = self
19082 .last_focused_descendant
19083 .take()
19084 .and_then(|descendant| descendant.upgrade())
19085 {
19086 window.focus(&descendant);
19087 } else {
19088 if let Some(blame) = self.blame.as_ref() {
19089 blame.update(cx, GitBlame::focus)
19090 }
19091
19092 self.blink_manager.update(cx, BlinkManager::enable);
19093 self.show_cursor_names(window, cx);
19094 self.buffer.update(cx, |buffer, cx| {
19095 buffer.finalize_last_transaction(cx);
19096 if self.leader_id.is_none() {
19097 buffer.set_active_selections(
19098 &self.selections.disjoint_anchors(),
19099 self.selections.line_mode,
19100 self.cursor_shape,
19101 cx,
19102 );
19103 }
19104 });
19105 }
19106 }
19107
19108 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19109 cx.emit(EditorEvent::FocusedIn)
19110 }
19111
19112 fn handle_focus_out(
19113 &mut self,
19114 event: FocusOutEvent,
19115 _window: &mut Window,
19116 cx: &mut Context<Self>,
19117 ) {
19118 if event.blurred != self.focus_handle {
19119 self.last_focused_descendant = Some(event.blurred);
19120 }
19121 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19122 }
19123
19124 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19125 self.blink_manager.update(cx, BlinkManager::disable);
19126 self.buffer
19127 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19128
19129 if let Some(blame) = self.blame.as_ref() {
19130 blame.update(cx, GitBlame::blur)
19131 }
19132 if !self.hover_state.focused(window, cx) {
19133 hide_hover(self, cx);
19134 }
19135 if !self
19136 .context_menu
19137 .borrow()
19138 .as_ref()
19139 .is_some_and(|context_menu| context_menu.focused(window, cx))
19140 {
19141 self.hide_context_menu(window, cx);
19142 }
19143 self.discard_inline_completion(false, cx);
19144 cx.emit(EditorEvent::Blurred);
19145 cx.notify();
19146 }
19147
19148 pub fn register_action<A: Action>(
19149 &mut self,
19150 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19151 ) -> Subscription {
19152 let id = self.next_editor_action_id.post_inc();
19153 let listener = Arc::new(listener);
19154 self.editor_actions.borrow_mut().insert(
19155 id,
19156 Box::new(move |window, _| {
19157 let listener = listener.clone();
19158 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19159 let action = action.downcast_ref().unwrap();
19160 if phase == DispatchPhase::Bubble {
19161 listener(action, window, cx)
19162 }
19163 })
19164 }),
19165 );
19166
19167 let editor_actions = self.editor_actions.clone();
19168 Subscription::new(move || {
19169 editor_actions.borrow_mut().remove(&id);
19170 })
19171 }
19172
19173 pub fn file_header_size(&self) -> u32 {
19174 FILE_HEADER_HEIGHT
19175 }
19176
19177 pub fn restore(
19178 &mut self,
19179 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19180 window: &mut Window,
19181 cx: &mut Context<Self>,
19182 ) {
19183 let workspace = self.workspace();
19184 let project = self.project.as_ref();
19185 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19186 let mut tasks = Vec::new();
19187 for (buffer_id, changes) in revert_changes {
19188 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19189 buffer.update(cx, |buffer, cx| {
19190 buffer.edit(
19191 changes
19192 .into_iter()
19193 .map(|(range, text)| (range, text.to_string())),
19194 None,
19195 cx,
19196 );
19197 });
19198
19199 if let Some(project) =
19200 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19201 {
19202 project.update(cx, |project, cx| {
19203 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19204 })
19205 }
19206 }
19207 }
19208 tasks
19209 });
19210 cx.spawn_in(window, async move |_, cx| {
19211 for (buffer, task) in save_tasks {
19212 let result = task.await;
19213 if result.is_err() {
19214 let Some(path) = buffer
19215 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19216 .ok()
19217 else {
19218 continue;
19219 };
19220 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19221 let Some(task) = cx
19222 .update_window_entity(&workspace, |workspace, window, cx| {
19223 workspace
19224 .open_path_preview(path, None, false, false, false, window, cx)
19225 })
19226 .ok()
19227 else {
19228 continue;
19229 };
19230 task.await.log_err();
19231 }
19232 }
19233 }
19234 })
19235 .detach();
19236 self.change_selections(None, window, cx, |selections| selections.refresh());
19237 }
19238
19239 pub fn to_pixel_point(
19240 &self,
19241 source: multi_buffer::Anchor,
19242 editor_snapshot: &EditorSnapshot,
19243 window: &mut Window,
19244 ) -> Option<gpui::Point<Pixels>> {
19245 let source_point = source.to_display_point(editor_snapshot);
19246 self.display_to_pixel_point(source_point, editor_snapshot, window)
19247 }
19248
19249 pub fn display_to_pixel_point(
19250 &self,
19251 source: DisplayPoint,
19252 editor_snapshot: &EditorSnapshot,
19253 window: &mut Window,
19254 ) -> Option<gpui::Point<Pixels>> {
19255 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19256 let text_layout_details = self.text_layout_details(window);
19257 let scroll_top = text_layout_details
19258 .scroll_anchor
19259 .scroll_position(editor_snapshot)
19260 .y;
19261
19262 if source.row().as_f32() < scroll_top.floor() {
19263 return None;
19264 }
19265 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19266 let source_y = line_height * (source.row().as_f32() - scroll_top);
19267 Some(gpui::Point::new(source_x, source_y))
19268 }
19269
19270 pub fn has_visible_completions_menu(&self) -> bool {
19271 !self.edit_prediction_preview_is_active()
19272 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19273 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19274 })
19275 }
19276
19277 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19278 if self.mode.is_minimap() {
19279 return;
19280 }
19281 self.addons
19282 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19283 }
19284
19285 pub fn unregister_addon<T: Addon>(&mut self) {
19286 self.addons.remove(&std::any::TypeId::of::<T>());
19287 }
19288
19289 pub fn addon<T: Addon>(&self) -> Option<&T> {
19290 let type_id = std::any::TypeId::of::<T>();
19291 self.addons
19292 .get(&type_id)
19293 .and_then(|item| item.to_any().downcast_ref::<T>())
19294 }
19295
19296 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19297 let type_id = std::any::TypeId::of::<T>();
19298 self.addons
19299 .get_mut(&type_id)
19300 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19301 }
19302
19303 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19304 let text_layout_details = self.text_layout_details(window);
19305 let style = &text_layout_details.editor_style;
19306 let font_id = window.text_system().resolve_font(&style.text.font());
19307 let font_size = style.text.font_size.to_pixels(window.rem_size());
19308 let line_height = style.text.line_height_in_pixels(window.rem_size());
19309 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19310
19311 gpui::Size::new(em_width, line_height)
19312 }
19313
19314 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19315 self.load_diff_task.clone()
19316 }
19317
19318 fn read_metadata_from_db(
19319 &mut self,
19320 item_id: u64,
19321 workspace_id: WorkspaceId,
19322 window: &mut Window,
19323 cx: &mut Context<Editor>,
19324 ) {
19325 if self.is_singleton(cx)
19326 && !self.mode.is_minimap()
19327 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19328 {
19329 let buffer_snapshot = OnceCell::new();
19330
19331 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19332 if !folds.is_empty() {
19333 let snapshot =
19334 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19335 self.fold_ranges(
19336 folds
19337 .into_iter()
19338 .map(|(start, end)| {
19339 snapshot.clip_offset(start, Bias::Left)
19340 ..snapshot.clip_offset(end, Bias::Right)
19341 })
19342 .collect(),
19343 false,
19344 window,
19345 cx,
19346 );
19347 }
19348 }
19349
19350 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19351 if !selections.is_empty() {
19352 let snapshot =
19353 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19354 self.change_selections(None, window, cx, |s| {
19355 s.select_ranges(selections.into_iter().map(|(start, end)| {
19356 snapshot.clip_offset(start, Bias::Left)
19357 ..snapshot.clip_offset(end, Bias::Right)
19358 }));
19359 });
19360 }
19361 };
19362 }
19363
19364 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19365 }
19366}
19367
19368fn vim_enabled(cx: &App) -> bool {
19369 cx.global::<SettingsStore>()
19370 .raw_user_settings()
19371 .get("vim_mode")
19372 == Some(&serde_json::Value::Bool(true))
19373}
19374
19375// Consider user intent and default settings
19376fn choose_completion_range(
19377 completion: &Completion,
19378 intent: CompletionIntent,
19379 buffer: &Entity<Buffer>,
19380 cx: &mut Context<Editor>,
19381) -> Range<usize> {
19382 fn should_replace(
19383 completion: &Completion,
19384 insert_range: &Range<text::Anchor>,
19385 intent: CompletionIntent,
19386 completion_mode_setting: LspInsertMode,
19387 buffer: &Buffer,
19388 ) -> bool {
19389 // specific actions take precedence over settings
19390 match intent {
19391 CompletionIntent::CompleteWithInsert => return false,
19392 CompletionIntent::CompleteWithReplace => return true,
19393 CompletionIntent::Complete | CompletionIntent::Compose => {}
19394 }
19395
19396 match completion_mode_setting {
19397 LspInsertMode::Insert => false,
19398 LspInsertMode::Replace => true,
19399 LspInsertMode::ReplaceSubsequence => {
19400 let mut text_to_replace = buffer.chars_for_range(
19401 buffer.anchor_before(completion.replace_range.start)
19402 ..buffer.anchor_after(completion.replace_range.end),
19403 );
19404 let mut completion_text = completion.new_text.chars();
19405
19406 // is `text_to_replace` a subsequence of `completion_text`
19407 text_to_replace
19408 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19409 }
19410 LspInsertMode::ReplaceSuffix => {
19411 let range_after_cursor = insert_range.end..completion.replace_range.end;
19412
19413 let text_after_cursor = buffer
19414 .text_for_range(
19415 buffer.anchor_before(range_after_cursor.start)
19416 ..buffer.anchor_after(range_after_cursor.end),
19417 )
19418 .collect::<String>();
19419 completion.new_text.ends_with(&text_after_cursor)
19420 }
19421 }
19422 }
19423
19424 let buffer = buffer.read(cx);
19425
19426 if let CompletionSource::Lsp {
19427 insert_range: Some(insert_range),
19428 ..
19429 } = &completion.source
19430 {
19431 let completion_mode_setting =
19432 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19433 .completions
19434 .lsp_insert_mode;
19435
19436 if !should_replace(
19437 completion,
19438 &insert_range,
19439 intent,
19440 completion_mode_setting,
19441 buffer,
19442 ) {
19443 return insert_range.to_offset(buffer);
19444 }
19445 }
19446
19447 completion.replace_range.to_offset(buffer)
19448}
19449
19450fn insert_extra_newline_brackets(
19451 buffer: &MultiBufferSnapshot,
19452 range: Range<usize>,
19453 language: &language::LanguageScope,
19454) -> bool {
19455 let leading_whitespace_len = buffer
19456 .reversed_chars_at(range.start)
19457 .take_while(|c| c.is_whitespace() && *c != '\n')
19458 .map(|c| c.len_utf8())
19459 .sum::<usize>();
19460 let trailing_whitespace_len = buffer
19461 .chars_at(range.end)
19462 .take_while(|c| c.is_whitespace() && *c != '\n')
19463 .map(|c| c.len_utf8())
19464 .sum::<usize>();
19465 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19466
19467 language.brackets().any(|(pair, enabled)| {
19468 let pair_start = pair.start.trim_end();
19469 let pair_end = pair.end.trim_start();
19470
19471 enabled
19472 && pair.newline
19473 && buffer.contains_str_at(range.end, pair_end)
19474 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19475 })
19476}
19477
19478fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19479 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19480 [(buffer, range, _)] => (*buffer, range.clone()),
19481 _ => return false,
19482 };
19483 let pair = {
19484 let mut result: Option<BracketMatch> = None;
19485
19486 for pair in buffer
19487 .all_bracket_ranges(range.clone())
19488 .filter(move |pair| {
19489 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19490 })
19491 {
19492 let len = pair.close_range.end - pair.open_range.start;
19493
19494 if let Some(existing) = &result {
19495 let existing_len = existing.close_range.end - existing.open_range.start;
19496 if len > existing_len {
19497 continue;
19498 }
19499 }
19500
19501 result = Some(pair);
19502 }
19503
19504 result
19505 };
19506 let Some(pair) = pair else {
19507 return false;
19508 };
19509 pair.newline_only
19510 && buffer
19511 .chars_for_range(pair.open_range.end..range.start)
19512 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19513 .all(|c| c.is_whitespace() && c != '\n')
19514}
19515
19516fn update_uncommitted_diff_for_buffer(
19517 editor: Entity<Editor>,
19518 project: &Entity<Project>,
19519 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19520 buffer: Entity<MultiBuffer>,
19521 cx: &mut App,
19522) -> Task<()> {
19523 let mut tasks = Vec::new();
19524 project.update(cx, |project, cx| {
19525 for buffer in buffers {
19526 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19527 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19528 }
19529 }
19530 });
19531 cx.spawn(async move |cx| {
19532 let diffs = future::join_all(tasks).await;
19533 if editor
19534 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19535 .unwrap_or(false)
19536 {
19537 return;
19538 }
19539
19540 buffer
19541 .update(cx, |buffer, cx| {
19542 for diff in diffs.into_iter().flatten() {
19543 buffer.add_diff(diff, cx);
19544 }
19545 })
19546 .ok();
19547 })
19548}
19549
19550pub trait CollaborationHub {
19551 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19552 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19553 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19554}
19555
19556impl CollaborationHub for Entity<Project> {
19557 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19558 self.read(cx).collaborators()
19559 }
19560
19561 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19562 self.read(cx).user_store().read(cx).participant_indices()
19563 }
19564
19565 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19566 let this = self.read(cx);
19567 let user_ids = this.collaborators().values().map(|c| c.user_id);
19568 this.user_store().read(cx).participant_names(user_ids, cx)
19569 }
19570}
19571
19572pub trait SemanticsProvider {
19573 fn hover(
19574 &self,
19575 buffer: &Entity<Buffer>,
19576 position: text::Anchor,
19577 cx: &mut App,
19578 ) -> Option<Task<Vec<project::Hover>>>;
19579
19580 fn inline_values(
19581 &self,
19582 buffer_handle: Entity<Buffer>,
19583 range: Range<text::Anchor>,
19584 cx: &mut App,
19585 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19586
19587 fn inlay_hints(
19588 &self,
19589 buffer_handle: Entity<Buffer>,
19590 range: Range<text::Anchor>,
19591 cx: &mut App,
19592 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19593
19594 fn resolve_inlay_hint(
19595 &self,
19596 hint: InlayHint,
19597 buffer_handle: Entity<Buffer>,
19598 server_id: LanguageServerId,
19599 cx: &mut App,
19600 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19601
19602 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19603
19604 fn document_highlights(
19605 &self,
19606 buffer: &Entity<Buffer>,
19607 position: text::Anchor,
19608 cx: &mut App,
19609 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19610
19611 fn definitions(
19612 &self,
19613 buffer: &Entity<Buffer>,
19614 position: text::Anchor,
19615 kind: GotoDefinitionKind,
19616 cx: &mut App,
19617 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19618
19619 fn range_for_rename(
19620 &self,
19621 buffer: &Entity<Buffer>,
19622 position: text::Anchor,
19623 cx: &mut App,
19624 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19625
19626 fn perform_rename(
19627 &self,
19628 buffer: &Entity<Buffer>,
19629 position: text::Anchor,
19630 new_name: String,
19631 cx: &mut App,
19632 ) -> Option<Task<Result<ProjectTransaction>>>;
19633}
19634
19635pub trait CompletionProvider {
19636 fn completions(
19637 &self,
19638 excerpt_id: ExcerptId,
19639 buffer: &Entity<Buffer>,
19640 buffer_position: text::Anchor,
19641 trigger: CompletionContext,
19642 window: &mut Window,
19643 cx: &mut Context<Editor>,
19644 ) -> Task<Result<Option<Vec<Completion>>>>;
19645
19646 fn resolve_completions(
19647 &self,
19648 buffer: Entity<Buffer>,
19649 completion_indices: Vec<usize>,
19650 completions: Rc<RefCell<Box<[Completion]>>>,
19651 cx: &mut Context<Editor>,
19652 ) -> Task<Result<bool>>;
19653
19654 fn apply_additional_edits_for_completion(
19655 &self,
19656 _buffer: Entity<Buffer>,
19657 _completions: Rc<RefCell<Box<[Completion]>>>,
19658 _completion_index: usize,
19659 _push_to_history: bool,
19660 _cx: &mut Context<Editor>,
19661 ) -> Task<Result<Option<language::Transaction>>> {
19662 Task::ready(Ok(None))
19663 }
19664
19665 fn is_completion_trigger(
19666 &self,
19667 buffer: &Entity<Buffer>,
19668 position: language::Anchor,
19669 text: &str,
19670 trigger_in_words: bool,
19671 cx: &mut Context<Editor>,
19672 ) -> bool;
19673
19674 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
19675
19676 fn sort_completions(&self) -> bool {
19677 true
19678 }
19679
19680 fn filter_completions(&self) -> bool {
19681 true
19682 }
19683}
19684
19685pub trait CodeActionProvider {
19686 fn id(&self) -> Arc<str>;
19687
19688 fn code_actions(
19689 &self,
19690 buffer: &Entity<Buffer>,
19691 range: Range<text::Anchor>,
19692 window: &mut Window,
19693 cx: &mut App,
19694 ) -> Task<Result<Vec<CodeAction>>>;
19695
19696 fn apply_code_action(
19697 &self,
19698 buffer_handle: Entity<Buffer>,
19699 action: CodeAction,
19700 excerpt_id: ExcerptId,
19701 push_to_history: bool,
19702 window: &mut Window,
19703 cx: &mut App,
19704 ) -> Task<Result<ProjectTransaction>>;
19705}
19706
19707impl CodeActionProvider for Entity<Project> {
19708 fn id(&self) -> Arc<str> {
19709 "project".into()
19710 }
19711
19712 fn code_actions(
19713 &self,
19714 buffer: &Entity<Buffer>,
19715 range: Range<text::Anchor>,
19716 _window: &mut Window,
19717 cx: &mut App,
19718 ) -> Task<Result<Vec<CodeAction>>> {
19719 self.update(cx, |project, cx| {
19720 let code_lens = project.code_lens(buffer, range.clone(), cx);
19721 let code_actions = project.code_actions(buffer, range, None, cx);
19722 cx.background_spawn(async move {
19723 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19724 Ok(code_lens
19725 .context("code lens fetch")?
19726 .into_iter()
19727 .chain(code_actions.context("code action fetch")?)
19728 .collect())
19729 })
19730 })
19731 }
19732
19733 fn apply_code_action(
19734 &self,
19735 buffer_handle: Entity<Buffer>,
19736 action: CodeAction,
19737 _excerpt_id: ExcerptId,
19738 push_to_history: bool,
19739 _window: &mut Window,
19740 cx: &mut App,
19741 ) -> Task<Result<ProjectTransaction>> {
19742 self.update(cx, |project, cx| {
19743 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19744 })
19745 }
19746}
19747
19748fn snippet_completions(
19749 project: &Project,
19750 buffer: &Entity<Buffer>,
19751 buffer_position: text::Anchor,
19752 cx: &mut App,
19753) -> Task<Result<Vec<Completion>>> {
19754 let languages = buffer.read(cx).languages_at(buffer_position);
19755 let snippet_store = project.snippets().read(cx);
19756
19757 let scopes: Vec<_> = languages
19758 .iter()
19759 .filter_map(|language| {
19760 let language_name = language.lsp_id();
19761 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19762
19763 if snippets.is_empty() {
19764 None
19765 } else {
19766 Some((language.default_scope(), snippets))
19767 }
19768 })
19769 .collect();
19770
19771 if scopes.is_empty() {
19772 return Task::ready(Ok(vec![]));
19773 }
19774
19775 let snapshot = buffer.read(cx).text_snapshot();
19776 let chars: String = snapshot
19777 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19778 .collect();
19779 let executor = cx.background_executor().clone();
19780
19781 cx.background_spawn(async move {
19782 let mut all_results: Vec<Completion> = Vec::new();
19783 for (scope, snippets) in scopes.into_iter() {
19784 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19785 let mut last_word = chars
19786 .chars()
19787 .take_while(|c| classifier.is_word(*c))
19788 .collect::<String>();
19789 last_word = last_word.chars().rev().collect();
19790
19791 if last_word.is_empty() {
19792 return Ok(vec![]);
19793 }
19794
19795 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19796 let to_lsp = |point: &text::Anchor| {
19797 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19798 point_to_lsp(end)
19799 };
19800 let lsp_end = to_lsp(&buffer_position);
19801
19802 let candidates = snippets
19803 .iter()
19804 .enumerate()
19805 .flat_map(|(ix, snippet)| {
19806 snippet
19807 .prefix
19808 .iter()
19809 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19810 })
19811 .collect::<Vec<StringMatchCandidate>>();
19812
19813 let mut matches = fuzzy::match_strings(
19814 &candidates,
19815 &last_word,
19816 last_word.chars().any(|c| c.is_uppercase()),
19817 100,
19818 &Default::default(),
19819 executor.clone(),
19820 )
19821 .await;
19822
19823 // Remove all candidates where the query's start does not match the start of any word in the candidate
19824 if let Some(query_start) = last_word.chars().next() {
19825 matches.retain(|string_match| {
19826 split_words(&string_match.string).any(|word| {
19827 // Check that the first codepoint of the word as lowercase matches the first
19828 // codepoint of the query as lowercase
19829 word.chars()
19830 .flat_map(|codepoint| codepoint.to_lowercase())
19831 .zip(query_start.to_lowercase())
19832 .all(|(word_cp, query_cp)| word_cp == query_cp)
19833 })
19834 });
19835 }
19836
19837 let matched_strings = matches
19838 .into_iter()
19839 .map(|m| m.string)
19840 .collect::<HashSet<_>>();
19841
19842 let mut result: Vec<Completion> = snippets
19843 .iter()
19844 .filter_map(|snippet| {
19845 let matching_prefix = snippet
19846 .prefix
19847 .iter()
19848 .find(|prefix| matched_strings.contains(*prefix))?;
19849 let start = as_offset - last_word.len();
19850 let start = snapshot.anchor_before(start);
19851 let range = start..buffer_position;
19852 let lsp_start = to_lsp(&start);
19853 let lsp_range = lsp::Range {
19854 start: lsp_start,
19855 end: lsp_end,
19856 };
19857 Some(Completion {
19858 replace_range: range,
19859 new_text: snippet.body.clone(),
19860 source: CompletionSource::Lsp {
19861 insert_range: None,
19862 server_id: LanguageServerId(usize::MAX),
19863 resolved: true,
19864 lsp_completion: Box::new(lsp::CompletionItem {
19865 label: snippet.prefix.first().unwrap().clone(),
19866 kind: Some(CompletionItemKind::SNIPPET),
19867 label_details: snippet.description.as_ref().map(|description| {
19868 lsp::CompletionItemLabelDetails {
19869 detail: Some(description.clone()),
19870 description: None,
19871 }
19872 }),
19873 insert_text_format: Some(InsertTextFormat::SNIPPET),
19874 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19875 lsp::InsertReplaceEdit {
19876 new_text: snippet.body.clone(),
19877 insert: lsp_range,
19878 replace: lsp_range,
19879 },
19880 )),
19881 filter_text: Some(snippet.body.clone()),
19882 sort_text: Some(char::MAX.to_string()),
19883 ..lsp::CompletionItem::default()
19884 }),
19885 lsp_defaults: None,
19886 },
19887 label: CodeLabel {
19888 text: matching_prefix.clone(),
19889 runs: Vec::new(),
19890 filter_range: 0..matching_prefix.len(),
19891 },
19892 icon_path: None,
19893 documentation: Some(
19894 CompletionDocumentation::SingleLineAndMultiLinePlainText {
19895 single_line: snippet.name.clone().into(),
19896 plain_text: snippet
19897 .description
19898 .clone()
19899 .map(|description| description.into()),
19900 },
19901 ),
19902 insert_text_mode: None,
19903 confirm: None,
19904 })
19905 })
19906 .collect();
19907
19908 all_results.append(&mut result);
19909 }
19910
19911 Ok(all_results)
19912 })
19913}
19914
19915impl CompletionProvider for Entity<Project> {
19916 fn completions(
19917 &self,
19918 _excerpt_id: ExcerptId,
19919 buffer: &Entity<Buffer>,
19920 buffer_position: text::Anchor,
19921 options: CompletionContext,
19922 _window: &mut Window,
19923 cx: &mut Context<Editor>,
19924 ) -> Task<Result<Option<Vec<Completion>>>> {
19925 self.update(cx, |project, cx| {
19926 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19927 let project_completions = project.completions(buffer, buffer_position, options, cx);
19928 cx.background_spawn(async move {
19929 let snippets_completions = snippets.await?;
19930 match project_completions.await? {
19931 Some(mut completions) => {
19932 completions.extend(snippets_completions);
19933 Ok(Some(completions))
19934 }
19935 None => {
19936 if snippets_completions.is_empty() {
19937 Ok(None)
19938 } else {
19939 Ok(Some(snippets_completions))
19940 }
19941 }
19942 }
19943 })
19944 })
19945 }
19946
19947 fn resolve_completions(
19948 &self,
19949 buffer: Entity<Buffer>,
19950 completion_indices: Vec<usize>,
19951 completions: Rc<RefCell<Box<[Completion]>>>,
19952 cx: &mut Context<Editor>,
19953 ) -> Task<Result<bool>> {
19954 self.update(cx, |project, cx| {
19955 project.lsp_store().update(cx, |lsp_store, cx| {
19956 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19957 })
19958 })
19959 }
19960
19961 fn apply_additional_edits_for_completion(
19962 &self,
19963 buffer: Entity<Buffer>,
19964 completions: Rc<RefCell<Box<[Completion]>>>,
19965 completion_index: usize,
19966 push_to_history: bool,
19967 cx: &mut Context<Editor>,
19968 ) -> Task<Result<Option<language::Transaction>>> {
19969 self.update(cx, |project, cx| {
19970 project.lsp_store().update(cx, |lsp_store, cx| {
19971 lsp_store.apply_additional_edits_for_completion(
19972 buffer,
19973 completions,
19974 completion_index,
19975 push_to_history,
19976 cx,
19977 )
19978 })
19979 })
19980 }
19981
19982 fn is_completion_trigger(
19983 &self,
19984 buffer: &Entity<Buffer>,
19985 position: language::Anchor,
19986 text: &str,
19987 trigger_in_words: bool,
19988 cx: &mut Context<Editor>,
19989 ) -> bool {
19990 let mut chars = text.chars();
19991 let char = if let Some(char) = chars.next() {
19992 char
19993 } else {
19994 return false;
19995 };
19996 if chars.next().is_some() {
19997 return false;
19998 }
19999
20000 let buffer = buffer.read(cx);
20001 let snapshot = buffer.snapshot();
20002 if !snapshot.settings_at(position, cx).show_completions_on_input {
20003 return false;
20004 }
20005 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20006 if trigger_in_words && classifier.is_word(char) {
20007 return true;
20008 }
20009
20010 buffer.completion_triggers().contains(text)
20011 }
20012}
20013
20014impl SemanticsProvider for Entity<Project> {
20015 fn hover(
20016 &self,
20017 buffer: &Entity<Buffer>,
20018 position: text::Anchor,
20019 cx: &mut App,
20020 ) -> Option<Task<Vec<project::Hover>>> {
20021 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20022 }
20023
20024 fn document_highlights(
20025 &self,
20026 buffer: &Entity<Buffer>,
20027 position: text::Anchor,
20028 cx: &mut App,
20029 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20030 Some(self.update(cx, |project, cx| {
20031 project.document_highlights(buffer, position, cx)
20032 }))
20033 }
20034
20035 fn definitions(
20036 &self,
20037 buffer: &Entity<Buffer>,
20038 position: text::Anchor,
20039 kind: GotoDefinitionKind,
20040 cx: &mut App,
20041 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20042 Some(self.update(cx, |project, cx| match kind {
20043 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20044 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20045 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20046 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20047 }))
20048 }
20049
20050 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20051 // TODO: make this work for remote projects
20052 self.update(cx, |project, cx| {
20053 if project
20054 .active_debug_session(cx)
20055 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20056 {
20057 return true;
20058 }
20059
20060 buffer.update(cx, |buffer, cx| {
20061 project.any_language_server_supports_inlay_hints(buffer, cx)
20062 })
20063 })
20064 }
20065
20066 fn inline_values(
20067 &self,
20068 buffer_handle: Entity<Buffer>,
20069
20070 range: Range<text::Anchor>,
20071 cx: &mut App,
20072 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20073 self.update(cx, |project, cx| {
20074 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20075
20076 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20077 })
20078 }
20079
20080 fn inlay_hints(
20081 &self,
20082 buffer_handle: Entity<Buffer>,
20083 range: Range<text::Anchor>,
20084 cx: &mut App,
20085 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20086 Some(self.update(cx, |project, cx| {
20087 project.inlay_hints(buffer_handle, range, cx)
20088 }))
20089 }
20090
20091 fn resolve_inlay_hint(
20092 &self,
20093 hint: InlayHint,
20094 buffer_handle: Entity<Buffer>,
20095 server_id: LanguageServerId,
20096 cx: &mut App,
20097 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20098 Some(self.update(cx, |project, cx| {
20099 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20100 }))
20101 }
20102
20103 fn range_for_rename(
20104 &self,
20105 buffer: &Entity<Buffer>,
20106 position: text::Anchor,
20107 cx: &mut App,
20108 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20109 Some(self.update(cx, |project, cx| {
20110 let buffer = buffer.clone();
20111 let task = project.prepare_rename(buffer.clone(), position, cx);
20112 cx.spawn(async move |_, cx| {
20113 Ok(match task.await? {
20114 PrepareRenameResponse::Success(range) => Some(range),
20115 PrepareRenameResponse::InvalidPosition => None,
20116 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20117 // Fallback on using TreeSitter info to determine identifier range
20118 buffer.update(cx, |buffer, _| {
20119 let snapshot = buffer.snapshot();
20120 let (range, kind) = snapshot.surrounding_word(position);
20121 if kind != Some(CharKind::Word) {
20122 return None;
20123 }
20124 Some(
20125 snapshot.anchor_before(range.start)
20126 ..snapshot.anchor_after(range.end),
20127 )
20128 })?
20129 }
20130 })
20131 })
20132 }))
20133 }
20134
20135 fn perform_rename(
20136 &self,
20137 buffer: &Entity<Buffer>,
20138 position: text::Anchor,
20139 new_name: String,
20140 cx: &mut App,
20141 ) -> Option<Task<Result<ProjectTransaction>>> {
20142 Some(self.update(cx, |project, cx| {
20143 project.perform_rename(buffer.clone(), position, new_name, cx)
20144 }))
20145 }
20146}
20147
20148fn inlay_hint_settings(
20149 location: Anchor,
20150 snapshot: &MultiBufferSnapshot,
20151 cx: &mut Context<Editor>,
20152) -> InlayHintSettings {
20153 let file = snapshot.file_at(location);
20154 let language = snapshot.language_at(location).map(|l| l.name());
20155 language_settings(language, file, cx).inlay_hints
20156}
20157
20158fn consume_contiguous_rows(
20159 contiguous_row_selections: &mut Vec<Selection<Point>>,
20160 selection: &Selection<Point>,
20161 display_map: &DisplaySnapshot,
20162 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20163) -> (MultiBufferRow, MultiBufferRow) {
20164 contiguous_row_selections.push(selection.clone());
20165 let start_row = MultiBufferRow(selection.start.row);
20166 let mut end_row = ending_row(selection, display_map);
20167
20168 while let Some(next_selection) = selections.peek() {
20169 if next_selection.start.row <= end_row.0 {
20170 end_row = ending_row(next_selection, display_map);
20171 contiguous_row_selections.push(selections.next().unwrap().clone());
20172 } else {
20173 break;
20174 }
20175 }
20176 (start_row, end_row)
20177}
20178
20179fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20180 if next_selection.end.column > 0 || next_selection.is_empty() {
20181 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20182 } else {
20183 MultiBufferRow(next_selection.end.row)
20184 }
20185}
20186
20187impl EditorSnapshot {
20188 pub fn remote_selections_in_range<'a>(
20189 &'a self,
20190 range: &'a Range<Anchor>,
20191 collaboration_hub: &dyn CollaborationHub,
20192 cx: &'a App,
20193 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20194 let participant_names = collaboration_hub.user_names(cx);
20195 let participant_indices = collaboration_hub.user_participant_indices(cx);
20196 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20197 let collaborators_by_replica_id = collaborators_by_peer_id
20198 .values()
20199 .map(|collaborator| (collaborator.replica_id, collaborator))
20200 .collect::<HashMap<_, _>>();
20201 self.buffer_snapshot
20202 .selections_in_range(range, false)
20203 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20204 if replica_id == AGENT_REPLICA_ID {
20205 Some(RemoteSelection {
20206 replica_id,
20207 selection,
20208 cursor_shape,
20209 line_mode,
20210 collaborator_id: CollaboratorId::Agent,
20211 user_name: Some("Agent".into()),
20212 color: cx.theme().players().agent(),
20213 })
20214 } else {
20215 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20216 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20217 let user_name = participant_names.get(&collaborator.user_id).cloned();
20218 Some(RemoteSelection {
20219 replica_id,
20220 selection,
20221 cursor_shape,
20222 line_mode,
20223 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20224 user_name,
20225 color: if let Some(index) = participant_index {
20226 cx.theme().players().color_for_participant(index.0)
20227 } else {
20228 cx.theme().players().absent()
20229 },
20230 })
20231 }
20232 })
20233 }
20234
20235 pub fn hunks_for_ranges(
20236 &self,
20237 ranges: impl IntoIterator<Item = Range<Point>>,
20238 ) -> Vec<MultiBufferDiffHunk> {
20239 let mut hunks = Vec::new();
20240 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20241 HashMap::default();
20242 for query_range in ranges {
20243 let query_rows =
20244 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20245 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20246 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20247 ) {
20248 // Include deleted hunks that are adjacent to the query range, because
20249 // otherwise they would be missed.
20250 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20251 if hunk.status().is_deleted() {
20252 intersects_range |= hunk.row_range.start == query_rows.end;
20253 intersects_range |= hunk.row_range.end == query_rows.start;
20254 }
20255 if intersects_range {
20256 if !processed_buffer_rows
20257 .entry(hunk.buffer_id)
20258 .or_default()
20259 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20260 {
20261 continue;
20262 }
20263 hunks.push(hunk);
20264 }
20265 }
20266 }
20267
20268 hunks
20269 }
20270
20271 fn display_diff_hunks_for_rows<'a>(
20272 &'a self,
20273 display_rows: Range<DisplayRow>,
20274 folded_buffers: &'a HashSet<BufferId>,
20275 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20276 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20277 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20278
20279 self.buffer_snapshot
20280 .diff_hunks_in_range(buffer_start..buffer_end)
20281 .filter_map(|hunk| {
20282 if folded_buffers.contains(&hunk.buffer_id) {
20283 return None;
20284 }
20285
20286 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20287 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20288
20289 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20290 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20291
20292 let display_hunk = if hunk_display_start.column() != 0 {
20293 DisplayDiffHunk::Folded {
20294 display_row: hunk_display_start.row(),
20295 }
20296 } else {
20297 let mut end_row = hunk_display_end.row();
20298 if hunk_display_end.column() > 0 {
20299 end_row.0 += 1;
20300 }
20301 let is_created_file = hunk.is_created_file();
20302 DisplayDiffHunk::Unfolded {
20303 status: hunk.status(),
20304 diff_base_byte_range: hunk.diff_base_byte_range,
20305 display_row_range: hunk_display_start.row()..end_row,
20306 multi_buffer_range: Anchor::range_in_buffer(
20307 hunk.excerpt_id,
20308 hunk.buffer_id,
20309 hunk.buffer_range,
20310 ),
20311 is_created_file,
20312 }
20313 };
20314
20315 Some(display_hunk)
20316 })
20317 }
20318
20319 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20320 self.display_snapshot.buffer_snapshot.language_at(position)
20321 }
20322
20323 pub fn is_focused(&self) -> bool {
20324 self.is_focused
20325 }
20326
20327 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20328 self.placeholder_text.as_ref()
20329 }
20330
20331 pub fn scroll_position(&self) -> gpui::Point<f32> {
20332 self.scroll_anchor.scroll_position(&self.display_snapshot)
20333 }
20334
20335 fn gutter_dimensions(
20336 &self,
20337 font_id: FontId,
20338 font_size: Pixels,
20339 max_line_number_width: Pixels,
20340 cx: &App,
20341 ) -> Option<GutterDimensions> {
20342 if !self.show_gutter {
20343 return None;
20344 }
20345
20346 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20347 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20348
20349 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20350 matches!(
20351 ProjectSettings::get_global(cx).git.git_gutter,
20352 Some(GitGutterSetting::TrackedFiles)
20353 )
20354 });
20355 let gutter_settings = EditorSettings::get_global(cx).gutter;
20356 let show_line_numbers = self
20357 .show_line_numbers
20358 .unwrap_or(gutter_settings.line_numbers);
20359 let line_gutter_width = if show_line_numbers {
20360 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20361 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20362 max_line_number_width.max(min_width_for_number_on_gutter)
20363 } else {
20364 0.0.into()
20365 };
20366
20367 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20368 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20369
20370 let git_blame_entries_width =
20371 self.git_blame_gutter_max_author_length
20372 .map(|max_author_length| {
20373 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20374 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20375
20376 /// The number of characters to dedicate to gaps and margins.
20377 const SPACING_WIDTH: usize = 4;
20378
20379 let max_char_count = max_author_length.min(renderer.max_author_length())
20380 + ::git::SHORT_SHA_LENGTH
20381 + MAX_RELATIVE_TIMESTAMP.len()
20382 + SPACING_WIDTH;
20383
20384 em_advance * max_char_count
20385 });
20386
20387 let is_singleton = self.buffer_snapshot.is_singleton();
20388
20389 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20390 left_padding += if !is_singleton {
20391 em_width * 4.0
20392 } else if show_runnables || show_breakpoints {
20393 em_width * 3.0
20394 } else if show_git_gutter && show_line_numbers {
20395 em_width * 2.0
20396 } else if show_git_gutter || show_line_numbers {
20397 em_width
20398 } else {
20399 px(0.)
20400 };
20401
20402 let shows_folds = is_singleton && gutter_settings.folds;
20403
20404 let right_padding = if shows_folds && show_line_numbers {
20405 em_width * 4.0
20406 } else if shows_folds || (!is_singleton && show_line_numbers) {
20407 em_width * 3.0
20408 } else if show_line_numbers {
20409 em_width
20410 } else {
20411 px(0.)
20412 };
20413
20414 Some(GutterDimensions {
20415 left_padding,
20416 right_padding,
20417 width: line_gutter_width + left_padding + right_padding,
20418 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20419 git_blame_entries_width,
20420 })
20421 }
20422
20423 pub fn render_crease_toggle(
20424 &self,
20425 buffer_row: MultiBufferRow,
20426 row_contains_cursor: bool,
20427 editor: Entity<Editor>,
20428 window: &mut Window,
20429 cx: &mut App,
20430 ) -> Option<AnyElement> {
20431 let folded = self.is_line_folded(buffer_row);
20432 let mut is_foldable = false;
20433
20434 if let Some(crease) = self
20435 .crease_snapshot
20436 .query_row(buffer_row, &self.buffer_snapshot)
20437 {
20438 is_foldable = true;
20439 match crease {
20440 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20441 if let Some(render_toggle) = render_toggle {
20442 let toggle_callback =
20443 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20444 if folded {
20445 editor.update(cx, |editor, cx| {
20446 editor.fold_at(buffer_row, window, cx)
20447 });
20448 } else {
20449 editor.update(cx, |editor, cx| {
20450 editor.unfold_at(buffer_row, window, cx)
20451 });
20452 }
20453 });
20454 return Some((render_toggle)(
20455 buffer_row,
20456 folded,
20457 toggle_callback,
20458 window,
20459 cx,
20460 ));
20461 }
20462 }
20463 }
20464 }
20465
20466 is_foldable |= self.starts_indent(buffer_row);
20467
20468 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20469 Some(
20470 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20471 .toggle_state(folded)
20472 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20473 if folded {
20474 this.unfold_at(buffer_row, window, cx);
20475 } else {
20476 this.fold_at(buffer_row, window, cx);
20477 }
20478 }))
20479 .into_any_element(),
20480 )
20481 } else {
20482 None
20483 }
20484 }
20485
20486 pub fn render_crease_trailer(
20487 &self,
20488 buffer_row: MultiBufferRow,
20489 window: &mut Window,
20490 cx: &mut App,
20491 ) -> Option<AnyElement> {
20492 let folded = self.is_line_folded(buffer_row);
20493 if let Crease::Inline { render_trailer, .. } = self
20494 .crease_snapshot
20495 .query_row(buffer_row, &self.buffer_snapshot)?
20496 {
20497 let render_trailer = render_trailer.as_ref()?;
20498 Some(render_trailer(buffer_row, folded, window, cx))
20499 } else {
20500 None
20501 }
20502 }
20503}
20504
20505impl Deref for EditorSnapshot {
20506 type Target = DisplaySnapshot;
20507
20508 fn deref(&self) -> &Self::Target {
20509 &self.display_snapshot
20510 }
20511}
20512
20513#[derive(Clone, Debug, PartialEq, Eq)]
20514pub enum EditorEvent {
20515 InputIgnored {
20516 text: Arc<str>,
20517 },
20518 InputHandled {
20519 utf16_range_to_replace: Option<Range<isize>>,
20520 text: Arc<str>,
20521 },
20522 ExcerptsAdded {
20523 buffer: Entity<Buffer>,
20524 predecessor: ExcerptId,
20525 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20526 },
20527 ExcerptsRemoved {
20528 ids: Vec<ExcerptId>,
20529 removed_buffer_ids: Vec<BufferId>,
20530 },
20531 BufferFoldToggled {
20532 ids: Vec<ExcerptId>,
20533 folded: bool,
20534 },
20535 ExcerptsEdited {
20536 ids: Vec<ExcerptId>,
20537 },
20538 ExcerptsExpanded {
20539 ids: Vec<ExcerptId>,
20540 },
20541 BufferEdited,
20542 Edited {
20543 transaction_id: clock::Lamport,
20544 },
20545 Reparsed(BufferId),
20546 Focused,
20547 FocusedIn,
20548 Blurred,
20549 DirtyChanged,
20550 Saved,
20551 TitleChanged,
20552 DiffBaseChanged,
20553 SelectionsChanged {
20554 local: bool,
20555 },
20556 ScrollPositionChanged {
20557 local: bool,
20558 autoscroll: bool,
20559 },
20560 Closed,
20561 TransactionUndone {
20562 transaction_id: clock::Lamport,
20563 },
20564 TransactionBegun {
20565 transaction_id: clock::Lamport,
20566 },
20567 Reloaded,
20568 CursorShapeChanged,
20569 PushedToNavHistory {
20570 anchor: Anchor,
20571 is_deactivate: bool,
20572 },
20573}
20574
20575impl EventEmitter<EditorEvent> for Editor {}
20576
20577impl Focusable for Editor {
20578 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20579 self.focus_handle.clone()
20580 }
20581}
20582
20583impl Render for Editor {
20584 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20585 let settings = ThemeSettings::get_global(cx);
20586
20587 let mut text_style = match self.mode {
20588 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20589 color: cx.theme().colors().editor_foreground,
20590 font_family: settings.ui_font.family.clone(),
20591 font_features: settings.ui_font.features.clone(),
20592 font_fallbacks: settings.ui_font.fallbacks.clone(),
20593 font_size: rems(0.875).into(),
20594 font_weight: settings.ui_font.weight,
20595 line_height: relative(settings.buffer_line_height.value()),
20596 ..Default::default()
20597 },
20598 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20599 color: cx.theme().colors().editor_foreground,
20600 font_family: settings.buffer_font.family.clone(),
20601 font_features: settings.buffer_font.features.clone(),
20602 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20603 font_size: settings.buffer_font_size(cx).into(),
20604 font_weight: settings.buffer_font.weight,
20605 line_height: relative(settings.buffer_line_height.value()),
20606 ..Default::default()
20607 },
20608 };
20609 if let Some(text_style_refinement) = &self.text_style_refinement {
20610 text_style.refine(text_style_refinement)
20611 }
20612
20613 let background = match self.mode {
20614 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20615 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20616 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20617 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20618 };
20619
20620 EditorElement::new(
20621 &cx.entity(),
20622 EditorStyle {
20623 background,
20624 local_player: cx.theme().players().local(),
20625 text: text_style,
20626 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20627 syntax: cx.theme().syntax().clone(),
20628 status: cx.theme().status().clone(),
20629 inlay_hints_style: make_inlay_hints_style(cx),
20630 inline_completion_styles: make_suggestion_styles(cx),
20631 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20632 show_underlines: !self.mode.is_minimap(),
20633 },
20634 )
20635 }
20636}
20637
20638impl EntityInputHandler for Editor {
20639 fn text_for_range(
20640 &mut self,
20641 range_utf16: Range<usize>,
20642 adjusted_range: &mut Option<Range<usize>>,
20643 _: &mut Window,
20644 cx: &mut Context<Self>,
20645 ) -> Option<String> {
20646 let snapshot = self.buffer.read(cx).read(cx);
20647 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20648 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20649 if (start.0..end.0) != range_utf16 {
20650 adjusted_range.replace(start.0..end.0);
20651 }
20652 Some(snapshot.text_for_range(start..end).collect())
20653 }
20654
20655 fn selected_text_range(
20656 &mut self,
20657 ignore_disabled_input: bool,
20658 _: &mut Window,
20659 cx: &mut Context<Self>,
20660 ) -> Option<UTF16Selection> {
20661 // Prevent the IME menu from appearing when holding down an alphabetic key
20662 // while input is disabled.
20663 if !ignore_disabled_input && !self.input_enabled {
20664 return None;
20665 }
20666
20667 let selection = self.selections.newest::<OffsetUtf16>(cx);
20668 let range = selection.range();
20669
20670 Some(UTF16Selection {
20671 range: range.start.0..range.end.0,
20672 reversed: selection.reversed,
20673 })
20674 }
20675
20676 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20677 let snapshot = self.buffer.read(cx).read(cx);
20678 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20679 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20680 }
20681
20682 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20683 self.clear_highlights::<InputComposition>(cx);
20684 self.ime_transaction.take();
20685 }
20686
20687 fn replace_text_in_range(
20688 &mut self,
20689 range_utf16: Option<Range<usize>>,
20690 text: &str,
20691 window: &mut Window,
20692 cx: &mut Context<Self>,
20693 ) {
20694 if !self.input_enabled {
20695 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20696 return;
20697 }
20698
20699 self.transact(window, cx, |this, window, cx| {
20700 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20701 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20702 Some(this.selection_replacement_ranges(range_utf16, cx))
20703 } else {
20704 this.marked_text_ranges(cx)
20705 };
20706
20707 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20708 let newest_selection_id = this.selections.newest_anchor().id;
20709 this.selections
20710 .all::<OffsetUtf16>(cx)
20711 .iter()
20712 .zip(ranges_to_replace.iter())
20713 .find_map(|(selection, range)| {
20714 if selection.id == newest_selection_id {
20715 Some(
20716 (range.start.0 as isize - selection.head().0 as isize)
20717 ..(range.end.0 as isize - selection.head().0 as isize),
20718 )
20719 } else {
20720 None
20721 }
20722 })
20723 });
20724
20725 cx.emit(EditorEvent::InputHandled {
20726 utf16_range_to_replace: range_to_replace,
20727 text: text.into(),
20728 });
20729
20730 if let Some(new_selected_ranges) = new_selected_ranges {
20731 this.change_selections(None, window, cx, |selections| {
20732 selections.select_ranges(new_selected_ranges)
20733 });
20734 this.backspace(&Default::default(), window, cx);
20735 }
20736
20737 this.handle_input(text, window, cx);
20738 });
20739
20740 if let Some(transaction) = self.ime_transaction {
20741 self.buffer.update(cx, |buffer, cx| {
20742 buffer.group_until_transaction(transaction, cx);
20743 });
20744 }
20745
20746 self.unmark_text(window, cx);
20747 }
20748
20749 fn replace_and_mark_text_in_range(
20750 &mut self,
20751 range_utf16: Option<Range<usize>>,
20752 text: &str,
20753 new_selected_range_utf16: Option<Range<usize>>,
20754 window: &mut Window,
20755 cx: &mut Context<Self>,
20756 ) {
20757 if !self.input_enabled {
20758 return;
20759 }
20760
20761 let transaction = self.transact(window, cx, |this, window, cx| {
20762 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20763 let snapshot = this.buffer.read(cx).read(cx);
20764 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20765 for marked_range in &mut marked_ranges {
20766 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20767 marked_range.start.0 += relative_range_utf16.start;
20768 marked_range.start =
20769 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20770 marked_range.end =
20771 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20772 }
20773 }
20774 Some(marked_ranges)
20775 } else if let Some(range_utf16) = range_utf16 {
20776 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20777 Some(this.selection_replacement_ranges(range_utf16, cx))
20778 } else {
20779 None
20780 };
20781
20782 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20783 let newest_selection_id = this.selections.newest_anchor().id;
20784 this.selections
20785 .all::<OffsetUtf16>(cx)
20786 .iter()
20787 .zip(ranges_to_replace.iter())
20788 .find_map(|(selection, range)| {
20789 if selection.id == newest_selection_id {
20790 Some(
20791 (range.start.0 as isize - selection.head().0 as isize)
20792 ..(range.end.0 as isize - selection.head().0 as isize),
20793 )
20794 } else {
20795 None
20796 }
20797 })
20798 });
20799
20800 cx.emit(EditorEvent::InputHandled {
20801 utf16_range_to_replace: range_to_replace,
20802 text: text.into(),
20803 });
20804
20805 if let Some(ranges) = ranges_to_replace {
20806 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20807 }
20808
20809 let marked_ranges = {
20810 let snapshot = this.buffer.read(cx).read(cx);
20811 this.selections
20812 .disjoint_anchors()
20813 .iter()
20814 .map(|selection| {
20815 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20816 })
20817 .collect::<Vec<_>>()
20818 };
20819
20820 if text.is_empty() {
20821 this.unmark_text(window, cx);
20822 } else {
20823 this.highlight_text::<InputComposition>(
20824 marked_ranges.clone(),
20825 HighlightStyle {
20826 underline: Some(UnderlineStyle {
20827 thickness: px(1.),
20828 color: None,
20829 wavy: false,
20830 }),
20831 ..Default::default()
20832 },
20833 cx,
20834 );
20835 }
20836
20837 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20838 let use_autoclose = this.use_autoclose;
20839 let use_auto_surround = this.use_auto_surround;
20840 this.set_use_autoclose(false);
20841 this.set_use_auto_surround(false);
20842 this.handle_input(text, window, cx);
20843 this.set_use_autoclose(use_autoclose);
20844 this.set_use_auto_surround(use_auto_surround);
20845
20846 if let Some(new_selected_range) = new_selected_range_utf16 {
20847 let snapshot = this.buffer.read(cx).read(cx);
20848 let new_selected_ranges = marked_ranges
20849 .into_iter()
20850 .map(|marked_range| {
20851 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20852 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20853 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20854 snapshot.clip_offset_utf16(new_start, Bias::Left)
20855 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20856 })
20857 .collect::<Vec<_>>();
20858
20859 drop(snapshot);
20860 this.change_selections(None, window, cx, |selections| {
20861 selections.select_ranges(new_selected_ranges)
20862 });
20863 }
20864 });
20865
20866 self.ime_transaction = self.ime_transaction.or(transaction);
20867 if let Some(transaction) = self.ime_transaction {
20868 self.buffer.update(cx, |buffer, cx| {
20869 buffer.group_until_transaction(transaction, cx);
20870 });
20871 }
20872
20873 if self.text_highlights::<InputComposition>(cx).is_none() {
20874 self.ime_transaction.take();
20875 }
20876 }
20877
20878 fn bounds_for_range(
20879 &mut self,
20880 range_utf16: Range<usize>,
20881 element_bounds: gpui::Bounds<Pixels>,
20882 window: &mut Window,
20883 cx: &mut Context<Self>,
20884 ) -> Option<gpui::Bounds<Pixels>> {
20885 let text_layout_details = self.text_layout_details(window);
20886 let gpui::Size {
20887 width: em_width,
20888 height: line_height,
20889 } = self.character_size(window);
20890
20891 let snapshot = self.snapshot(window, cx);
20892 let scroll_position = snapshot.scroll_position();
20893 let scroll_left = scroll_position.x * em_width;
20894
20895 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20896 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20897 + self.gutter_dimensions.width
20898 + self.gutter_dimensions.margin;
20899 let y = line_height * (start.row().as_f32() - scroll_position.y);
20900
20901 Some(Bounds {
20902 origin: element_bounds.origin + point(x, y),
20903 size: size(em_width, line_height),
20904 })
20905 }
20906
20907 fn character_index_for_point(
20908 &mut self,
20909 point: gpui::Point<Pixels>,
20910 _window: &mut Window,
20911 _cx: &mut Context<Self>,
20912 ) -> Option<usize> {
20913 let position_map = self.last_position_map.as_ref()?;
20914 if !position_map.text_hitbox.contains(&point) {
20915 return None;
20916 }
20917 let display_point = position_map.point_for_position(point).previous_valid;
20918 let anchor = position_map
20919 .snapshot
20920 .display_point_to_anchor(display_point, Bias::Left);
20921 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20922 Some(utf16_offset.0)
20923 }
20924}
20925
20926trait SelectionExt {
20927 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20928 fn spanned_rows(
20929 &self,
20930 include_end_if_at_line_start: bool,
20931 map: &DisplaySnapshot,
20932 ) -> Range<MultiBufferRow>;
20933}
20934
20935impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20936 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20937 let start = self
20938 .start
20939 .to_point(&map.buffer_snapshot)
20940 .to_display_point(map);
20941 let end = self
20942 .end
20943 .to_point(&map.buffer_snapshot)
20944 .to_display_point(map);
20945 if self.reversed {
20946 end..start
20947 } else {
20948 start..end
20949 }
20950 }
20951
20952 fn spanned_rows(
20953 &self,
20954 include_end_if_at_line_start: bool,
20955 map: &DisplaySnapshot,
20956 ) -> Range<MultiBufferRow> {
20957 let start = self.start.to_point(&map.buffer_snapshot);
20958 let mut end = self.end.to_point(&map.buffer_snapshot);
20959 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
20960 end.row -= 1;
20961 }
20962
20963 let buffer_start = map.prev_line_boundary(start).0;
20964 let buffer_end = map.next_line_boundary(end).0;
20965 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
20966 }
20967}
20968
20969impl<T: InvalidationRegion> InvalidationStack<T> {
20970 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
20971 where
20972 S: Clone + ToOffset,
20973 {
20974 while let Some(region) = self.last() {
20975 let all_selections_inside_invalidation_ranges =
20976 if selections.len() == region.ranges().len() {
20977 selections
20978 .iter()
20979 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
20980 .all(|(selection, invalidation_range)| {
20981 let head = selection.head().to_offset(buffer);
20982 invalidation_range.start <= head && invalidation_range.end >= head
20983 })
20984 } else {
20985 false
20986 };
20987
20988 if all_selections_inside_invalidation_ranges {
20989 break;
20990 } else {
20991 self.pop();
20992 }
20993 }
20994 }
20995}
20996
20997impl<T> Default for InvalidationStack<T> {
20998 fn default() -> Self {
20999 Self(Default::default())
21000 }
21001}
21002
21003impl<T> Deref for InvalidationStack<T> {
21004 type Target = Vec<T>;
21005
21006 fn deref(&self) -> &Self::Target {
21007 &self.0
21008 }
21009}
21010
21011impl<T> DerefMut for InvalidationStack<T> {
21012 fn deref_mut(&mut self) -> &mut Self::Target {
21013 &mut self.0
21014 }
21015}
21016
21017impl InvalidationRegion for SnippetState {
21018 fn ranges(&self) -> &[Range<Anchor>] {
21019 &self.ranges[self.active_index]
21020 }
21021}
21022
21023fn inline_completion_edit_text(
21024 current_snapshot: &BufferSnapshot,
21025 edits: &[(Range<Anchor>, String)],
21026 edit_preview: &EditPreview,
21027 include_deletions: bool,
21028 cx: &App,
21029) -> HighlightedText {
21030 let edits = edits
21031 .iter()
21032 .map(|(anchor, text)| {
21033 (
21034 anchor.start.text_anchor..anchor.end.text_anchor,
21035 text.clone(),
21036 )
21037 })
21038 .collect::<Vec<_>>();
21039
21040 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21041}
21042
21043pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21044 match severity {
21045 lsp::DiagnosticSeverity::ERROR => colors.error,
21046 lsp::DiagnosticSeverity::WARNING => colors.warning,
21047 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21048 lsp::DiagnosticSeverity::HINT => colors.info,
21049 _ => colors.ignored,
21050 }
21051}
21052
21053pub fn styled_runs_for_code_label<'a>(
21054 label: &'a CodeLabel,
21055 syntax_theme: &'a theme::SyntaxTheme,
21056) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21057 let fade_out = HighlightStyle {
21058 fade_out: Some(0.35),
21059 ..Default::default()
21060 };
21061
21062 let mut prev_end = label.filter_range.end;
21063 label
21064 .runs
21065 .iter()
21066 .enumerate()
21067 .flat_map(move |(ix, (range, highlight_id))| {
21068 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21069 style
21070 } else {
21071 return Default::default();
21072 };
21073 let mut muted_style = style;
21074 muted_style.highlight(fade_out);
21075
21076 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21077 if range.start >= label.filter_range.end {
21078 if range.start > prev_end {
21079 runs.push((prev_end..range.start, fade_out));
21080 }
21081 runs.push((range.clone(), muted_style));
21082 } else if range.end <= label.filter_range.end {
21083 runs.push((range.clone(), style));
21084 } else {
21085 runs.push((range.start..label.filter_range.end, style));
21086 runs.push((label.filter_range.end..range.end, muted_style));
21087 }
21088 prev_end = cmp::max(prev_end, range.end);
21089
21090 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21091 runs.push((prev_end..label.text.len(), fade_out));
21092 }
21093
21094 runs
21095 })
21096}
21097
21098pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21099 let mut prev_index = 0;
21100 let mut prev_codepoint: Option<char> = None;
21101 text.char_indices()
21102 .chain([(text.len(), '\0')])
21103 .filter_map(move |(index, codepoint)| {
21104 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21105 let is_boundary = index == text.len()
21106 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21107 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21108 if is_boundary {
21109 let chunk = &text[prev_index..index];
21110 prev_index = index;
21111 Some(chunk)
21112 } else {
21113 None
21114 }
21115 })
21116}
21117
21118pub trait RangeToAnchorExt: Sized {
21119 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21120
21121 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21122 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21123 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21124 }
21125}
21126
21127impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21128 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21129 let start_offset = self.start.to_offset(snapshot);
21130 let end_offset = self.end.to_offset(snapshot);
21131 if start_offset == end_offset {
21132 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21133 } else {
21134 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21135 }
21136 }
21137}
21138
21139pub trait RowExt {
21140 fn as_f32(&self) -> f32;
21141
21142 fn next_row(&self) -> Self;
21143
21144 fn previous_row(&self) -> Self;
21145
21146 fn minus(&self, other: Self) -> u32;
21147}
21148
21149impl RowExt for DisplayRow {
21150 fn as_f32(&self) -> f32 {
21151 self.0 as f32
21152 }
21153
21154 fn next_row(&self) -> Self {
21155 Self(self.0 + 1)
21156 }
21157
21158 fn previous_row(&self) -> Self {
21159 Self(self.0.saturating_sub(1))
21160 }
21161
21162 fn minus(&self, other: Self) -> u32 {
21163 self.0 - other.0
21164 }
21165}
21166
21167impl RowExt for MultiBufferRow {
21168 fn as_f32(&self) -> f32 {
21169 self.0 as f32
21170 }
21171
21172 fn next_row(&self) -> Self {
21173 Self(self.0 + 1)
21174 }
21175
21176 fn previous_row(&self) -> Self {
21177 Self(self.0.saturating_sub(1))
21178 }
21179
21180 fn minus(&self, other: Self) -> u32 {
21181 self.0 - other.0
21182 }
21183}
21184
21185trait RowRangeExt {
21186 type Row;
21187
21188 fn len(&self) -> usize;
21189
21190 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21191}
21192
21193impl RowRangeExt for Range<MultiBufferRow> {
21194 type Row = MultiBufferRow;
21195
21196 fn len(&self) -> usize {
21197 (self.end.0 - self.start.0) as usize
21198 }
21199
21200 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21201 (self.start.0..self.end.0).map(MultiBufferRow)
21202 }
21203}
21204
21205impl RowRangeExt for Range<DisplayRow> {
21206 type Row = DisplayRow;
21207
21208 fn len(&self) -> usize {
21209 (self.end.0 - self.start.0) as usize
21210 }
21211
21212 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21213 (self.start.0..self.end.0).map(DisplayRow)
21214 }
21215}
21216
21217/// If select range has more than one line, we
21218/// just point the cursor to range.start.
21219fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21220 if range.start.row == range.end.row {
21221 range
21222 } else {
21223 range.start..range.start
21224 }
21225}
21226pub struct KillRing(ClipboardItem);
21227impl Global for KillRing {}
21228
21229const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21230
21231enum BreakpointPromptEditAction {
21232 Log,
21233 Condition,
21234 HitCondition,
21235}
21236
21237struct BreakpointPromptEditor {
21238 pub(crate) prompt: Entity<Editor>,
21239 editor: WeakEntity<Editor>,
21240 breakpoint_anchor: Anchor,
21241 breakpoint: Breakpoint,
21242 edit_action: BreakpointPromptEditAction,
21243 block_ids: HashSet<CustomBlockId>,
21244 editor_margins: Arc<Mutex<EditorMargins>>,
21245 _subscriptions: Vec<Subscription>,
21246}
21247
21248impl BreakpointPromptEditor {
21249 const MAX_LINES: u8 = 4;
21250
21251 fn new(
21252 editor: WeakEntity<Editor>,
21253 breakpoint_anchor: Anchor,
21254 breakpoint: Breakpoint,
21255 edit_action: BreakpointPromptEditAction,
21256 window: &mut Window,
21257 cx: &mut Context<Self>,
21258 ) -> Self {
21259 let base_text = match edit_action {
21260 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21261 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21262 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21263 }
21264 .map(|msg| msg.to_string())
21265 .unwrap_or_default();
21266
21267 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21268 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21269
21270 let prompt = cx.new(|cx| {
21271 let mut prompt = Editor::new(
21272 EditorMode::AutoHeight {
21273 max_lines: Self::MAX_LINES as usize,
21274 },
21275 buffer,
21276 None,
21277 window,
21278 cx,
21279 );
21280 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21281 prompt.set_show_cursor_when_unfocused(false, cx);
21282 prompt.set_placeholder_text(
21283 match edit_action {
21284 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21285 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21286 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21287 },
21288 cx,
21289 );
21290
21291 prompt
21292 });
21293
21294 Self {
21295 prompt,
21296 editor,
21297 breakpoint_anchor,
21298 breakpoint,
21299 edit_action,
21300 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21301 block_ids: Default::default(),
21302 _subscriptions: vec![],
21303 }
21304 }
21305
21306 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21307 self.block_ids.extend(block_ids)
21308 }
21309
21310 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21311 if let Some(editor) = self.editor.upgrade() {
21312 let message = self
21313 .prompt
21314 .read(cx)
21315 .buffer
21316 .read(cx)
21317 .as_singleton()
21318 .expect("A multi buffer in breakpoint prompt isn't possible")
21319 .read(cx)
21320 .as_rope()
21321 .to_string();
21322
21323 editor.update(cx, |editor, cx| {
21324 editor.edit_breakpoint_at_anchor(
21325 self.breakpoint_anchor,
21326 self.breakpoint.clone(),
21327 match self.edit_action {
21328 BreakpointPromptEditAction::Log => {
21329 BreakpointEditAction::EditLogMessage(message.into())
21330 }
21331 BreakpointPromptEditAction::Condition => {
21332 BreakpointEditAction::EditCondition(message.into())
21333 }
21334 BreakpointPromptEditAction::HitCondition => {
21335 BreakpointEditAction::EditHitCondition(message.into())
21336 }
21337 },
21338 cx,
21339 );
21340
21341 editor.remove_blocks(self.block_ids.clone(), None, cx);
21342 cx.focus_self(window);
21343 });
21344 }
21345 }
21346
21347 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21348 self.editor
21349 .update(cx, |editor, cx| {
21350 editor.remove_blocks(self.block_ids.clone(), None, cx);
21351 window.focus(&editor.focus_handle);
21352 })
21353 .log_err();
21354 }
21355
21356 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21357 let settings = ThemeSettings::get_global(cx);
21358 let text_style = TextStyle {
21359 color: if self.prompt.read(cx).read_only(cx) {
21360 cx.theme().colors().text_disabled
21361 } else {
21362 cx.theme().colors().text
21363 },
21364 font_family: settings.buffer_font.family.clone(),
21365 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21366 font_size: settings.buffer_font_size(cx).into(),
21367 font_weight: settings.buffer_font.weight,
21368 line_height: relative(settings.buffer_line_height.value()),
21369 ..Default::default()
21370 };
21371 EditorElement::new(
21372 &self.prompt,
21373 EditorStyle {
21374 background: cx.theme().colors().editor_background,
21375 local_player: cx.theme().players().local(),
21376 text: text_style,
21377 ..Default::default()
21378 },
21379 )
21380 }
21381}
21382
21383impl Render for BreakpointPromptEditor {
21384 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21385 let editor_margins = *self.editor_margins.lock();
21386 let gutter_dimensions = editor_margins.gutter;
21387 h_flex()
21388 .key_context("Editor")
21389 .bg(cx.theme().colors().editor_background)
21390 .border_y_1()
21391 .border_color(cx.theme().status().info_border)
21392 .size_full()
21393 .py(window.line_height() / 2.5)
21394 .on_action(cx.listener(Self::confirm))
21395 .on_action(cx.listener(Self::cancel))
21396 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21397 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21398 }
21399}
21400
21401impl Focusable for BreakpointPromptEditor {
21402 fn focus_handle(&self, cx: &App) -> FocusHandle {
21403 self.prompt.focus_handle(cx)
21404 }
21405}
21406
21407fn all_edits_insertions_or_deletions(
21408 edits: &Vec<(Range<Anchor>, String)>,
21409 snapshot: &MultiBufferSnapshot,
21410) -> bool {
21411 let mut all_insertions = true;
21412 let mut all_deletions = true;
21413
21414 for (range, new_text) in edits.iter() {
21415 let range_is_empty = range.to_offset(&snapshot).is_empty();
21416 let text_is_empty = new_text.is_empty();
21417
21418 if range_is_empty != text_is_empty {
21419 if range_is_empty {
21420 all_deletions = false;
21421 } else {
21422 all_insertions = false;
21423 }
21424 } else {
21425 return false;
21426 }
21427
21428 if !all_insertions && !all_deletions {
21429 return false;
21430 }
21431 }
21432 all_insertions || all_deletions
21433}
21434
21435struct MissingEditPredictionKeybindingTooltip;
21436
21437impl Render for MissingEditPredictionKeybindingTooltip {
21438 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21439 ui::tooltip_container(window, cx, |container, _, cx| {
21440 container
21441 .flex_shrink_0()
21442 .max_w_80()
21443 .min_h(rems_from_px(124.))
21444 .justify_between()
21445 .child(
21446 v_flex()
21447 .flex_1()
21448 .text_ui_sm(cx)
21449 .child(Label::new("Conflict with Accept Keybinding"))
21450 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21451 )
21452 .child(
21453 h_flex()
21454 .pb_1()
21455 .gap_1()
21456 .items_end()
21457 .w_full()
21458 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21459 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21460 }))
21461 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21462 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21463 })),
21464 )
21465 })
21466 }
21467}
21468
21469#[derive(Debug, Clone, Copy, PartialEq)]
21470pub struct LineHighlight {
21471 pub background: Background,
21472 pub border: Option<gpui::Hsla>,
21473 pub include_gutter: bool,
21474 pub type_id: Option<TypeId>,
21475}
21476
21477fn render_diff_hunk_controls(
21478 row: u32,
21479 status: &DiffHunkStatus,
21480 hunk_range: Range<Anchor>,
21481 is_created_file: bool,
21482 line_height: Pixels,
21483 editor: &Entity<Editor>,
21484 _window: &mut Window,
21485 cx: &mut App,
21486) -> AnyElement {
21487 h_flex()
21488 .h(line_height)
21489 .mr_1()
21490 .gap_1()
21491 .px_0p5()
21492 .pb_1()
21493 .border_x_1()
21494 .border_b_1()
21495 .border_color(cx.theme().colors().border_variant)
21496 .rounded_b_lg()
21497 .bg(cx.theme().colors().editor_background)
21498 .gap_1()
21499 .occlude()
21500 .shadow_md()
21501 .child(if status.has_secondary_hunk() {
21502 Button::new(("stage", row as u64), "Stage")
21503 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21504 .tooltip({
21505 let focus_handle = editor.focus_handle(cx);
21506 move |window, cx| {
21507 Tooltip::for_action_in(
21508 "Stage Hunk",
21509 &::git::ToggleStaged,
21510 &focus_handle,
21511 window,
21512 cx,
21513 )
21514 }
21515 })
21516 .on_click({
21517 let editor = editor.clone();
21518 move |_event, _window, cx| {
21519 editor.update(cx, |editor, cx| {
21520 editor.stage_or_unstage_diff_hunks(
21521 true,
21522 vec![hunk_range.start..hunk_range.start],
21523 cx,
21524 );
21525 });
21526 }
21527 })
21528 } else {
21529 Button::new(("unstage", row as u64), "Unstage")
21530 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21531 .tooltip({
21532 let focus_handle = editor.focus_handle(cx);
21533 move |window, cx| {
21534 Tooltip::for_action_in(
21535 "Unstage Hunk",
21536 &::git::ToggleStaged,
21537 &focus_handle,
21538 window,
21539 cx,
21540 )
21541 }
21542 })
21543 .on_click({
21544 let editor = editor.clone();
21545 move |_event, _window, cx| {
21546 editor.update(cx, |editor, cx| {
21547 editor.stage_or_unstage_diff_hunks(
21548 false,
21549 vec![hunk_range.start..hunk_range.start],
21550 cx,
21551 );
21552 });
21553 }
21554 })
21555 })
21556 .child(
21557 Button::new(("restore", row as u64), "Restore")
21558 .tooltip({
21559 let focus_handle = editor.focus_handle(cx);
21560 move |window, cx| {
21561 Tooltip::for_action_in(
21562 "Restore Hunk",
21563 &::git::Restore,
21564 &focus_handle,
21565 window,
21566 cx,
21567 )
21568 }
21569 })
21570 .on_click({
21571 let editor = editor.clone();
21572 move |_event, window, cx| {
21573 editor.update(cx, |editor, cx| {
21574 let snapshot = editor.snapshot(window, cx);
21575 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21576 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21577 });
21578 }
21579 })
21580 .disabled(is_created_file),
21581 )
21582 .when(
21583 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21584 |el| {
21585 el.child(
21586 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21587 .shape(IconButtonShape::Square)
21588 .icon_size(IconSize::Small)
21589 // .disabled(!has_multiple_hunks)
21590 .tooltip({
21591 let focus_handle = editor.focus_handle(cx);
21592 move |window, cx| {
21593 Tooltip::for_action_in(
21594 "Next Hunk",
21595 &GoToHunk,
21596 &focus_handle,
21597 window,
21598 cx,
21599 )
21600 }
21601 })
21602 .on_click({
21603 let editor = editor.clone();
21604 move |_event, window, cx| {
21605 editor.update(cx, |editor, cx| {
21606 let snapshot = editor.snapshot(window, cx);
21607 let position =
21608 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21609 editor.go_to_hunk_before_or_after_position(
21610 &snapshot,
21611 position,
21612 Direction::Next,
21613 window,
21614 cx,
21615 );
21616 editor.expand_selected_diff_hunks(cx);
21617 });
21618 }
21619 }),
21620 )
21621 .child(
21622 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21623 .shape(IconButtonShape::Square)
21624 .icon_size(IconSize::Small)
21625 // .disabled(!has_multiple_hunks)
21626 .tooltip({
21627 let focus_handle = editor.focus_handle(cx);
21628 move |window, cx| {
21629 Tooltip::for_action_in(
21630 "Previous Hunk",
21631 &GoToPreviousHunk,
21632 &focus_handle,
21633 window,
21634 cx,
21635 )
21636 }
21637 })
21638 .on_click({
21639 let editor = editor.clone();
21640 move |_event, window, cx| {
21641 editor.update(cx, |editor, cx| {
21642 let snapshot = editor.snapshot(window, cx);
21643 let point =
21644 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21645 editor.go_to_hunk_before_or_after_position(
21646 &snapshot,
21647 point,
21648 Direction::Prev,
21649 window,
21650 cx,
21651 );
21652 editor.expand_selected_diff_hunks(cx);
21653 });
21654 }
21655 }),
21656 )
21657 },
21658 )
21659 .into_any_element()
21660}