1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt,
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: ScrollbarAxes,
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: ScrollbarAxes {
1805 horizontal: full_mode,
1806 vertical: full_mode,
1807 },
1808 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1809 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1810 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1811 show_gutter: mode.is_full(),
1812 show_line_numbers: None,
1813 use_relative_line_numbers: None,
1814 disable_expand_excerpt_buttons: false,
1815 show_git_diff_gutter: None,
1816 show_code_actions: None,
1817 show_runnables: None,
1818 show_breakpoints: None,
1819 show_wrap_guides: None,
1820 show_indent_guides,
1821 placeholder_text: None,
1822 highlight_order: 0,
1823 highlighted_rows: HashMap::default(),
1824 background_highlights: TreeMap::default(),
1825 gutter_highlights: TreeMap::default(),
1826 scrollbar_marker_state: ScrollbarMarkerState::default(),
1827 active_indent_guides_state: ActiveIndentGuidesState::default(),
1828 nav_history: None,
1829 context_menu: RefCell::new(None),
1830 context_menu_options: None,
1831 mouse_context_menu: None,
1832 completion_tasks: Vec::new(),
1833 inline_blame_popover: None,
1834 signature_help_state: SignatureHelpState::default(),
1835 auto_signature_help: None,
1836 find_all_references_task_sources: Vec::new(),
1837 next_completion_id: 0,
1838 next_inlay_id: 0,
1839 code_action_providers,
1840 available_code_actions: None,
1841 code_actions_task: None,
1842 quick_selection_highlight_task: None,
1843 debounced_selection_highlight_task: None,
1844 document_highlights_task: None,
1845 linked_editing_range_task: None,
1846 pending_rename: None,
1847 searchable: true,
1848 cursor_shape: EditorSettings::get_global(cx)
1849 .cursor_shape
1850 .unwrap_or_default(),
1851 current_line_highlight: None,
1852 autoindent_mode: Some(AutoindentMode::EachLine),
1853 collapse_matches: false,
1854 workspace: None,
1855 input_enabled: true,
1856 use_modal_editing: mode.is_full(),
1857 read_only: mode.is_minimap(),
1858 use_autoclose: true,
1859 use_auto_surround: true,
1860 auto_replace_emoji_shortcode: false,
1861 jsx_tag_auto_close_enabled_in_any_buffer: false,
1862 leader_id: None,
1863 remote_id: None,
1864 hover_state: HoverState::default(),
1865 pending_mouse_down: None,
1866 hovered_link_state: None,
1867 edit_prediction_provider: None,
1868 active_inline_completion: None,
1869 stale_inline_completion_in_menu: None,
1870 edit_prediction_preview: EditPredictionPreview::Inactive {
1871 released_too_fast: false,
1872 },
1873 inline_diagnostics_enabled: mode.is_full(),
1874 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1875 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1876
1877 gutter_hovered: false,
1878 pixel_position_of_newest_cursor: None,
1879 last_bounds: None,
1880 last_position_map: None,
1881 expect_bounds_change: None,
1882 gutter_dimensions: GutterDimensions::default(),
1883 style: None,
1884 show_cursor_names: false,
1885 hovered_cursors: HashMap::default(),
1886 next_editor_action_id: EditorActionId::default(),
1887 editor_actions: Rc::default(),
1888 inline_completions_hidden_for_vim_mode: false,
1889 show_inline_completions_override: None,
1890 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1891 edit_prediction_settings: EditPredictionSettings::Disabled,
1892 edit_prediction_indent_conflict: false,
1893 edit_prediction_requires_modifier_in_indent_conflict: true,
1894 custom_context_menu: None,
1895 show_git_blame_gutter: false,
1896 show_git_blame_inline: false,
1897 show_selection_menu: None,
1898 show_git_blame_inline_delay_task: None,
1899 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1900 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1901 serialize_dirty_buffers: !mode.is_minimap()
1902 && ProjectSettings::get_global(cx)
1903 .session
1904 .restore_unsaved_buffers,
1905 blame: None,
1906 blame_subscription: None,
1907 tasks: BTreeMap::default(),
1908
1909 breakpoint_store,
1910 gutter_breakpoint_indicator: (None, None),
1911 _subscriptions: vec![
1912 cx.observe(&buffer, Self::on_buffer_changed),
1913 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1914 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1915 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1916 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1917 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1918 cx.observe_window_activation(window, |editor, window, cx| {
1919 let active = window.is_window_active();
1920 editor.blink_manager.update(cx, |blink_manager, cx| {
1921 if active {
1922 blink_manager.enable(cx);
1923 } else {
1924 blink_manager.disable(cx);
1925 }
1926 });
1927 if active {
1928 editor.show_mouse_cursor();
1929 }
1930 }),
1931 ],
1932 tasks_update_task: None,
1933 linked_edit_ranges: Default::default(),
1934 in_project_search: false,
1935 previous_search_ranges: None,
1936 breadcrumb_header: None,
1937 focused_block: None,
1938 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
1939 addons: HashMap::default(),
1940 registered_buffers: HashMap::default(),
1941 _scroll_cursor_center_top_bottom_task: Task::ready(()),
1942 selection_mark_mode: false,
1943 toggle_fold_multiple_buffers: Task::ready(()),
1944 serialize_selections: Task::ready(()),
1945 serialize_folds: Task::ready(()),
1946 text_style_refinement: None,
1947 load_diff_task: load_uncommitted_diff,
1948 temporary_diff_override: false,
1949 mouse_cursor_hidden: false,
1950 minimap: None,
1951 hide_mouse_mode: EditorSettings::get_global(cx)
1952 .hide_mouse
1953 .unwrap_or_default(),
1954 change_list: ChangeList::new(),
1955 mode,
1956 };
1957 if let Some(breakpoints) = this.breakpoint_store.as_ref() {
1958 this._subscriptions
1959 .push(cx.observe(breakpoints, |_, _, cx| {
1960 cx.notify();
1961 }));
1962 }
1963 this.tasks_update_task = Some(this.refresh_runnables(window, cx));
1964 this._subscriptions.extend(project_subscriptions);
1965
1966 this._subscriptions.push(cx.subscribe_in(
1967 &cx.entity(),
1968 window,
1969 |editor, _, e: &EditorEvent, window, cx| match e {
1970 EditorEvent::ScrollPositionChanged { local, .. } => {
1971 if *local {
1972 let new_anchor = editor.scroll_manager.anchor();
1973 let snapshot = editor.snapshot(window, cx);
1974 editor.update_restoration_data(cx, move |data| {
1975 data.scroll_position = (
1976 new_anchor.top_row(&snapshot.buffer_snapshot),
1977 new_anchor.offset,
1978 );
1979 });
1980 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
1981 editor.inline_blame_popover.take();
1982 }
1983 }
1984 EditorEvent::Edited { .. } => {
1985 if !vim_enabled(cx) {
1986 let (map, selections) = editor.selections.all_adjusted_display(cx);
1987 let pop_state = editor
1988 .change_list
1989 .last()
1990 .map(|previous| {
1991 previous.len() == selections.len()
1992 && previous.iter().enumerate().all(|(ix, p)| {
1993 p.to_display_point(&map).row()
1994 == selections[ix].head().row()
1995 })
1996 })
1997 .unwrap_or(false);
1998 let new_positions = selections
1999 .into_iter()
2000 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2001 .collect();
2002 editor
2003 .change_list
2004 .push_to_change_list(pop_state, new_positions);
2005 }
2006 }
2007 _ => (),
2008 },
2009 ));
2010
2011 if let Some(dap_store) = this
2012 .project
2013 .as_ref()
2014 .map(|project| project.read(cx).dap_store())
2015 {
2016 let weak_editor = cx.weak_entity();
2017
2018 this._subscriptions
2019 .push(
2020 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2021 let session_entity = cx.entity();
2022 weak_editor
2023 .update(cx, |editor, cx| {
2024 editor._subscriptions.push(
2025 cx.subscribe(&session_entity, Self::on_debug_session_event),
2026 );
2027 })
2028 .ok();
2029 }),
2030 );
2031
2032 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2033 this._subscriptions
2034 .push(cx.subscribe(&session, Self::on_debug_session_event));
2035 }
2036 }
2037
2038 this.end_selection(window, cx);
2039 this.scroll_manager.show_scrollbars(window, cx);
2040 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut this, &buffer, cx);
2041
2042 if full_mode {
2043 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2044 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2045
2046 if this.git_blame_inline_enabled {
2047 this.start_git_blame_inline(false, window, cx);
2048 }
2049
2050 this.go_to_active_debug_line(window, cx);
2051
2052 if let Some(buffer) = buffer.read(cx).as_singleton() {
2053 if let Some(project) = this.project.as_ref() {
2054 let handle = project.update(cx, |project, cx| {
2055 project.register_buffer_with_language_servers(&buffer, cx)
2056 });
2057 this.registered_buffers
2058 .insert(buffer.read(cx).remote_id(), handle);
2059 }
2060 }
2061
2062 this.minimap = this.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2063 }
2064
2065 this.report_editor_event("Editor Opened", None, cx);
2066 this
2067 }
2068
2069 pub fn deploy_mouse_context_menu(
2070 &mut self,
2071 position: gpui::Point<Pixels>,
2072 context_menu: Entity<ContextMenu>,
2073 window: &mut Window,
2074 cx: &mut Context<Self>,
2075 ) {
2076 self.mouse_context_menu = Some(MouseContextMenu::new(
2077 self,
2078 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2079 context_menu,
2080 window,
2081 cx,
2082 ));
2083 }
2084
2085 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2086 self.mouse_context_menu
2087 .as_ref()
2088 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2089 }
2090
2091 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2092 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2093 }
2094
2095 fn key_context_internal(
2096 &self,
2097 has_active_edit_prediction: bool,
2098 window: &Window,
2099 cx: &App,
2100 ) -> KeyContext {
2101 let mut key_context = KeyContext::new_with_defaults();
2102 key_context.add("Editor");
2103 let mode = match self.mode {
2104 EditorMode::SingleLine { .. } => "single_line",
2105 EditorMode::AutoHeight { .. } => "auto_height",
2106 EditorMode::Minimap { .. } => "minimap",
2107 EditorMode::Full { .. } => "full",
2108 };
2109
2110 if EditorSettings::jupyter_enabled(cx) {
2111 key_context.add("jupyter");
2112 }
2113
2114 key_context.set("mode", mode);
2115 if self.pending_rename.is_some() {
2116 key_context.add("renaming");
2117 }
2118
2119 match self.context_menu.borrow().as_ref() {
2120 Some(CodeContextMenu::Completions(_)) => {
2121 key_context.add("menu");
2122 key_context.add("showing_completions");
2123 }
2124 Some(CodeContextMenu::CodeActions(_)) => {
2125 key_context.add("menu");
2126 key_context.add("showing_code_actions")
2127 }
2128 None => {}
2129 }
2130
2131 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2132 if !self.focus_handle(cx).contains_focused(window, cx)
2133 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2134 {
2135 for addon in self.addons.values() {
2136 addon.extend_key_context(&mut key_context, cx)
2137 }
2138 }
2139
2140 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2141 if let Some(extension) = singleton_buffer
2142 .read(cx)
2143 .file()
2144 .and_then(|file| file.path().extension()?.to_str())
2145 {
2146 key_context.set("extension", extension.to_string());
2147 }
2148 } else {
2149 key_context.add("multibuffer");
2150 }
2151
2152 if has_active_edit_prediction {
2153 if self.edit_prediction_in_conflict() {
2154 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2155 } else {
2156 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2157 key_context.add("copilot_suggestion");
2158 }
2159 }
2160
2161 if self.selection_mark_mode {
2162 key_context.add("selection_mode");
2163 }
2164
2165 key_context
2166 }
2167
2168 fn show_mouse_cursor(&mut self) {
2169 self.mouse_cursor_hidden = false;
2170 }
2171
2172 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2173 self.mouse_cursor_hidden = match origin {
2174 HideMouseCursorOrigin::TypingAction => {
2175 matches!(
2176 self.hide_mouse_mode,
2177 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2178 )
2179 }
2180 HideMouseCursorOrigin::MovementAction => {
2181 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2182 }
2183 };
2184 }
2185
2186 pub fn edit_prediction_in_conflict(&self) -> bool {
2187 if !self.show_edit_predictions_in_menu() {
2188 return false;
2189 }
2190
2191 let showing_completions = self
2192 .context_menu
2193 .borrow()
2194 .as_ref()
2195 .map_or(false, |context| {
2196 matches!(context, CodeContextMenu::Completions(_))
2197 });
2198
2199 showing_completions
2200 || self.edit_prediction_requires_modifier()
2201 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2202 // bindings to insert tab characters.
2203 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2204 }
2205
2206 pub fn accept_edit_prediction_keybind(
2207 &self,
2208 window: &Window,
2209 cx: &App,
2210 ) -> AcceptEditPredictionBinding {
2211 let key_context = self.key_context_internal(true, window, cx);
2212 let in_conflict = self.edit_prediction_in_conflict();
2213
2214 AcceptEditPredictionBinding(
2215 window
2216 .bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2217 .into_iter()
2218 .filter(|binding| {
2219 !in_conflict
2220 || binding
2221 .keystrokes()
2222 .first()
2223 .map_or(false, |keystroke| keystroke.modifiers.modified())
2224 })
2225 .rev()
2226 .min_by_key(|binding| {
2227 binding
2228 .keystrokes()
2229 .first()
2230 .map_or(u8::MAX, |k| k.modifiers.number_of_modifiers())
2231 }),
2232 )
2233 }
2234
2235 pub fn new_file(
2236 workspace: &mut Workspace,
2237 _: &workspace::NewFile,
2238 window: &mut Window,
2239 cx: &mut Context<Workspace>,
2240 ) {
2241 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2242 "Failed to create buffer",
2243 window,
2244 cx,
2245 |e, _, _| match e.error_code() {
2246 ErrorCode::RemoteUpgradeRequired => Some(format!(
2247 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2248 e.error_tag("required").unwrap_or("the latest version")
2249 )),
2250 _ => None,
2251 },
2252 );
2253 }
2254
2255 pub fn new_in_workspace(
2256 workspace: &mut Workspace,
2257 window: &mut Window,
2258 cx: &mut Context<Workspace>,
2259 ) -> Task<Result<Entity<Editor>>> {
2260 let project = workspace.project().clone();
2261 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2262
2263 cx.spawn_in(window, async move |workspace, cx| {
2264 let buffer = create.await?;
2265 workspace.update_in(cx, |workspace, window, cx| {
2266 let editor =
2267 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2268 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2269 editor
2270 })
2271 })
2272 }
2273
2274 fn new_file_vertical(
2275 workspace: &mut Workspace,
2276 _: &workspace::NewFileSplitVertical,
2277 window: &mut Window,
2278 cx: &mut Context<Workspace>,
2279 ) {
2280 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2281 }
2282
2283 fn new_file_horizontal(
2284 workspace: &mut Workspace,
2285 _: &workspace::NewFileSplitHorizontal,
2286 window: &mut Window,
2287 cx: &mut Context<Workspace>,
2288 ) {
2289 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2290 }
2291
2292 fn new_file_in_direction(
2293 workspace: &mut Workspace,
2294 direction: SplitDirection,
2295 window: &mut Window,
2296 cx: &mut Context<Workspace>,
2297 ) {
2298 let project = workspace.project().clone();
2299 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2300
2301 cx.spawn_in(window, async move |workspace, cx| {
2302 let buffer = create.await?;
2303 workspace.update_in(cx, move |workspace, window, cx| {
2304 workspace.split_item(
2305 direction,
2306 Box::new(
2307 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2308 ),
2309 window,
2310 cx,
2311 )
2312 })?;
2313 anyhow::Ok(())
2314 })
2315 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2316 match e.error_code() {
2317 ErrorCode::RemoteUpgradeRequired => Some(format!(
2318 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2319 e.error_tag("required").unwrap_or("the latest version")
2320 )),
2321 _ => None,
2322 }
2323 });
2324 }
2325
2326 pub fn leader_id(&self) -> Option<CollaboratorId> {
2327 self.leader_id
2328 }
2329
2330 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2331 &self.buffer
2332 }
2333
2334 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2335 self.workspace.as_ref()?.0.upgrade()
2336 }
2337
2338 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2339 self.buffer().read(cx).title(cx)
2340 }
2341
2342 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2343 let git_blame_gutter_max_author_length = self
2344 .render_git_blame_gutter(cx)
2345 .then(|| {
2346 if let Some(blame) = self.blame.as_ref() {
2347 let max_author_length =
2348 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2349 Some(max_author_length)
2350 } else {
2351 None
2352 }
2353 })
2354 .flatten();
2355
2356 EditorSnapshot {
2357 mode: self.mode.clone(),
2358 show_gutter: self.show_gutter,
2359 show_line_numbers: self.show_line_numbers,
2360 show_git_diff_gutter: self.show_git_diff_gutter,
2361 show_code_actions: self.show_code_actions,
2362 show_runnables: self.show_runnables,
2363 show_breakpoints: self.show_breakpoints,
2364 git_blame_gutter_max_author_length,
2365 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2366 scroll_anchor: self.scroll_manager.anchor(),
2367 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2368 placeholder_text: self.placeholder_text.clone(),
2369 is_focused: self.focus_handle.is_focused(window),
2370 current_line_highlight: self
2371 .current_line_highlight
2372 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2373 gutter_hovered: self.gutter_hovered,
2374 }
2375 }
2376
2377 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2378 self.buffer.read(cx).language_at(point, cx)
2379 }
2380
2381 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2382 self.buffer.read(cx).read(cx).file_at(point).cloned()
2383 }
2384
2385 pub fn active_excerpt(
2386 &self,
2387 cx: &App,
2388 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2389 self.buffer
2390 .read(cx)
2391 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2392 }
2393
2394 pub fn mode(&self) -> &EditorMode {
2395 &self.mode
2396 }
2397
2398 pub fn set_mode(&mut self, mode: EditorMode) {
2399 self.mode = mode;
2400 }
2401
2402 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2403 self.collaboration_hub.as_deref()
2404 }
2405
2406 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2407 self.collaboration_hub = Some(hub);
2408 }
2409
2410 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2411 self.in_project_search = in_project_search;
2412 }
2413
2414 pub fn set_custom_context_menu(
2415 &mut self,
2416 f: impl 'static
2417 + Fn(
2418 &mut Self,
2419 DisplayPoint,
2420 &mut Window,
2421 &mut Context<Self>,
2422 ) -> Option<Entity<ui::ContextMenu>>,
2423 ) {
2424 self.custom_context_menu = Some(Box::new(f))
2425 }
2426
2427 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2428 self.completion_provider = provider;
2429 }
2430
2431 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2432 self.semantics_provider.clone()
2433 }
2434
2435 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2436 self.semantics_provider = provider;
2437 }
2438
2439 pub fn set_edit_prediction_provider<T>(
2440 &mut self,
2441 provider: Option<Entity<T>>,
2442 window: &mut Window,
2443 cx: &mut Context<Self>,
2444 ) where
2445 T: EditPredictionProvider,
2446 {
2447 self.edit_prediction_provider =
2448 provider.map(|provider| RegisteredInlineCompletionProvider {
2449 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2450 if this.focus_handle.is_focused(window) {
2451 this.update_visible_inline_completion(window, cx);
2452 }
2453 }),
2454 provider: Arc::new(provider),
2455 });
2456 self.update_edit_prediction_settings(cx);
2457 self.refresh_inline_completion(false, false, window, cx);
2458 }
2459
2460 pub fn placeholder_text(&self) -> Option<&str> {
2461 self.placeholder_text.as_deref()
2462 }
2463
2464 pub fn set_placeholder_text(
2465 &mut self,
2466 placeholder_text: impl Into<Arc<str>>,
2467 cx: &mut Context<Self>,
2468 ) {
2469 let placeholder_text = Some(placeholder_text.into());
2470 if self.placeholder_text != placeholder_text {
2471 self.placeholder_text = placeholder_text;
2472 cx.notify();
2473 }
2474 }
2475
2476 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2477 self.cursor_shape = cursor_shape;
2478
2479 // Disrupt blink for immediate user feedback that the cursor shape has changed
2480 self.blink_manager.update(cx, BlinkManager::show_cursor);
2481
2482 cx.notify();
2483 }
2484
2485 pub fn set_current_line_highlight(
2486 &mut self,
2487 current_line_highlight: Option<CurrentLineHighlight>,
2488 ) {
2489 self.current_line_highlight = current_line_highlight;
2490 }
2491
2492 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2493 self.collapse_matches = collapse_matches;
2494 }
2495
2496 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2497 let buffers = self.buffer.read(cx).all_buffers();
2498 let Some(project) = self.project.as_ref() else {
2499 return;
2500 };
2501 project.update(cx, |project, cx| {
2502 for buffer in buffers {
2503 self.registered_buffers
2504 .entry(buffer.read(cx).remote_id())
2505 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2506 }
2507 })
2508 }
2509
2510 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2511 if self.collapse_matches {
2512 return range.start..range.start;
2513 }
2514 range.clone()
2515 }
2516
2517 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2518 if self.display_map.read(cx).clip_at_line_ends != clip {
2519 self.display_map
2520 .update(cx, |map, _| map.clip_at_line_ends = clip);
2521 }
2522 }
2523
2524 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2525 self.input_enabled = input_enabled;
2526 }
2527
2528 pub fn set_inline_completions_hidden_for_vim_mode(
2529 &mut self,
2530 hidden: bool,
2531 window: &mut Window,
2532 cx: &mut Context<Self>,
2533 ) {
2534 if hidden != self.inline_completions_hidden_for_vim_mode {
2535 self.inline_completions_hidden_for_vim_mode = hidden;
2536 if hidden {
2537 self.update_visible_inline_completion(window, cx);
2538 } else {
2539 self.refresh_inline_completion(true, false, window, cx);
2540 }
2541 }
2542 }
2543
2544 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2545 self.menu_inline_completions_policy = value;
2546 }
2547
2548 pub fn set_autoindent(&mut self, autoindent: bool) {
2549 if autoindent {
2550 self.autoindent_mode = Some(AutoindentMode::EachLine);
2551 } else {
2552 self.autoindent_mode = None;
2553 }
2554 }
2555
2556 pub fn read_only(&self, cx: &App) -> bool {
2557 self.read_only || self.buffer.read(cx).read_only()
2558 }
2559
2560 pub fn set_read_only(&mut self, read_only: bool) {
2561 self.read_only = read_only;
2562 }
2563
2564 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2565 self.use_autoclose = autoclose;
2566 }
2567
2568 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2569 self.use_auto_surround = auto_surround;
2570 }
2571
2572 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2573 self.auto_replace_emoji_shortcode = auto_replace;
2574 }
2575
2576 pub fn toggle_edit_predictions(
2577 &mut self,
2578 _: &ToggleEditPrediction,
2579 window: &mut Window,
2580 cx: &mut Context<Self>,
2581 ) {
2582 if self.show_inline_completions_override.is_some() {
2583 self.set_show_edit_predictions(None, window, cx);
2584 } else {
2585 let show_edit_predictions = !self.edit_predictions_enabled();
2586 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2587 }
2588 }
2589
2590 pub fn set_show_edit_predictions(
2591 &mut self,
2592 show_edit_predictions: Option<bool>,
2593 window: &mut Window,
2594 cx: &mut Context<Self>,
2595 ) {
2596 self.show_inline_completions_override = show_edit_predictions;
2597 self.update_edit_prediction_settings(cx);
2598
2599 if let Some(false) = show_edit_predictions {
2600 self.discard_inline_completion(false, cx);
2601 } else {
2602 self.refresh_inline_completion(false, true, window, cx);
2603 }
2604 }
2605
2606 fn inline_completions_disabled_in_scope(
2607 &self,
2608 buffer: &Entity<Buffer>,
2609 buffer_position: language::Anchor,
2610 cx: &App,
2611 ) -> bool {
2612 let snapshot = buffer.read(cx).snapshot();
2613 let settings = snapshot.settings_at(buffer_position, cx);
2614
2615 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2616 return false;
2617 };
2618
2619 scope.override_name().map_or(false, |scope_name| {
2620 settings
2621 .edit_predictions_disabled_in
2622 .iter()
2623 .any(|s| s == scope_name)
2624 })
2625 }
2626
2627 pub fn set_use_modal_editing(&mut self, to: bool) {
2628 self.use_modal_editing = to;
2629 }
2630
2631 pub fn use_modal_editing(&self) -> bool {
2632 self.use_modal_editing
2633 }
2634
2635 fn selections_did_change(
2636 &mut self,
2637 local: bool,
2638 old_cursor_position: &Anchor,
2639 show_completions: bool,
2640 window: &mut Window,
2641 cx: &mut Context<Self>,
2642 ) {
2643 window.invalidate_character_coordinates();
2644
2645 // Copy selections to primary selection buffer
2646 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2647 if local {
2648 let selections = self.selections.all::<usize>(cx);
2649 let buffer_handle = self.buffer.read(cx).read(cx);
2650
2651 let mut text = String::new();
2652 for (index, selection) in selections.iter().enumerate() {
2653 let text_for_selection = buffer_handle
2654 .text_for_range(selection.start..selection.end)
2655 .collect::<String>();
2656
2657 text.push_str(&text_for_selection);
2658 if index != selections.len() - 1 {
2659 text.push('\n');
2660 }
2661 }
2662
2663 if !text.is_empty() {
2664 cx.write_to_primary(ClipboardItem::new_string(text));
2665 }
2666 }
2667
2668 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2669 self.buffer.update(cx, |buffer, cx| {
2670 buffer.set_active_selections(
2671 &self.selections.disjoint_anchors(),
2672 self.selections.line_mode,
2673 self.cursor_shape,
2674 cx,
2675 )
2676 });
2677 }
2678 let display_map = self
2679 .display_map
2680 .update(cx, |display_map, cx| display_map.snapshot(cx));
2681 let buffer = &display_map.buffer_snapshot;
2682 self.add_selections_state = None;
2683 self.select_next_state = None;
2684 self.select_prev_state = None;
2685 self.select_syntax_node_history.try_clear();
2686 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2687 self.snippet_stack
2688 .invalidate(&self.selections.disjoint_anchors(), buffer);
2689 self.take_rename(false, window, cx);
2690
2691 let new_cursor_position = self.selections.newest_anchor().head();
2692
2693 self.push_to_nav_history(
2694 *old_cursor_position,
2695 Some(new_cursor_position.to_point(buffer)),
2696 false,
2697 cx,
2698 );
2699
2700 if local {
2701 let new_cursor_position = self.selections.newest_anchor().head();
2702 let mut context_menu = self.context_menu.borrow_mut();
2703 let completion_menu = match context_menu.as_ref() {
2704 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2705 _ => {
2706 *context_menu = None;
2707 None
2708 }
2709 };
2710 if let Some(buffer_id) = new_cursor_position.buffer_id {
2711 if !self.registered_buffers.contains_key(&buffer_id) {
2712 if let Some(project) = self.project.as_ref() {
2713 project.update(cx, |project, cx| {
2714 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2715 return;
2716 };
2717 self.registered_buffers.insert(
2718 buffer_id,
2719 project.register_buffer_with_language_servers(&buffer, cx),
2720 );
2721 })
2722 }
2723 }
2724 }
2725
2726 if let Some(completion_menu) = completion_menu {
2727 let cursor_position = new_cursor_position.to_offset(buffer);
2728 let (word_range, kind) =
2729 buffer.surrounding_word(completion_menu.initial_position, true);
2730 if kind == Some(CharKind::Word)
2731 && word_range.to_inclusive().contains(&cursor_position)
2732 {
2733 let mut completion_menu = completion_menu.clone();
2734 drop(context_menu);
2735
2736 let query = Self::completion_query(buffer, cursor_position);
2737 let completion_provider = self.completion_provider.clone();
2738 cx.spawn_in(window, async move |this, cx| {
2739 completion_menu
2740 .filter(query.as_deref(), completion_provider, this.clone(), cx)
2741 .await;
2742
2743 this.update(cx, |this, cx| {
2744 let mut context_menu = this.context_menu.borrow_mut();
2745 let Some(CodeContextMenu::Completions(menu)) = context_menu.as_ref()
2746 else {
2747 return;
2748 };
2749
2750 if menu.id > completion_menu.id {
2751 return;
2752 }
2753
2754 *context_menu = Some(CodeContextMenu::Completions(completion_menu));
2755 drop(context_menu);
2756 cx.notify();
2757 })
2758 })
2759 .detach();
2760
2761 if show_completions {
2762 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2763 }
2764 } else {
2765 drop(context_menu);
2766 self.hide_context_menu(window, cx);
2767 }
2768 } else {
2769 drop(context_menu);
2770 }
2771
2772 hide_hover(self, cx);
2773
2774 if old_cursor_position.to_display_point(&display_map).row()
2775 != new_cursor_position.to_display_point(&display_map).row()
2776 {
2777 self.available_code_actions.take();
2778 }
2779 self.refresh_code_actions(window, cx);
2780 self.refresh_document_highlights(cx);
2781 self.refresh_selected_text_highlights(false, window, cx);
2782 refresh_matching_bracket_highlights(self, window, cx);
2783 self.update_visible_inline_completion(window, cx);
2784 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2785 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2786 self.inline_blame_popover.take();
2787 if self.git_blame_inline_enabled {
2788 self.start_inline_blame_timer(window, cx);
2789 }
2790 }
2791
2792 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2793 cx.emit(EditorEvent::SelectionsChanged { local });
2794
2795 let selections = &self.selections.disjoint;
2796 if selections.len() == 1 {
2797 cx.emit(SearchEvent::ActiveMatchChanged)
2798 }
2799 if local {
2800 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2801 let inmemory_selections = selections
2802 .iter()
2803 .map(|s| {
2804 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2805 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2806 })
2807 .collect();
2808 self.update_restoration_data(cx, |data| {
2809 data.selections = inmemory_selections;
2810 });
2811
2812 if WorkspaceSettings::get(None, cx).restore_on_startup
2813 != RestoreOnStartupBehavior::None
2814 {
2815 if let Some(workspace_id) =
2816 self.workspace.as_ref().and_then(|workspace| workspace.1)
2817 {
2818 let snapshot = self.buffer().read(cx).snapshot(cx);
2819 let selections = selections.clone();
2820 let background_executor = cx.background_executor().clone();
2821 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2822 self.serialize_selections = cx.background_spawn(async move {
2823 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2824 let db_selections = selections
2825 .iter()
2826 .map(|selection| {
2827 (
2828 selection.start.to_offset(&snapshot),
2829 selection.end.to_offset(&snapshot),
2830 )
2831 })
2832 .collect();
2833
2834 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2835 .await
2836 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2837 .log_err();
2838 });
2839 }
2840 }
2841 }
2842 }
2843
2844 cx.notify();
2845 }
2846
2847 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2848 use text::ToOffset as _;
2849 use text::ToPoint as _;
2850
2851 if self.mode.is_minimap()
2852 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2853 {
2854 return;
2855 }
2856
2857 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2858 return;
2859 };
2860
2861 let snapshot = singleton.read(cx).snapshot();
2862 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2863 let display_snapshot = display_map.snapshot(cx);
2864
2865 display_snapshot
2866 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2867 .map(|fold| {
2868 fold.range.start.text_anchor.to_point(&snapshot)
2869 ..fold.range.end.text_anchor.to_point(&snapshot)
2870 })
2871 .collect()
2872 });
2873 self.update_restoration_data(cx, |data| {
2874 data.folds = inmemory_folds;
2875 });
2876
2877 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2878 return;
2879 };
2880 let background_executor = cx.background_executor().clone();
2881 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2882 let db_folds = self.display_map.update(cx, |display_map, cx| {
2883 display_map
2884 .snapshot(cx)
2885 .folds_in_range(0..snapshot.len())
2886 .map(|fold| {
2887 (
2888 fold.range.start.text_anchor.to_offset(&snapshot),
2889 fold.range.end.text_anchor.to_offset(&snapshot),
2890 )
2891 })
2892 .collect()
2893 });
2894 self.serialize_folds = cx.background_spawn(async move {
2895 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2896 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2897 .await
2898 .with_context(|| {
2899 format!(
2900 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2901 )
2902 })
2903 .log_err();
2904 });
2905 }
2906
2907 pub fn sync_selections(
2908 &mut self,
2909 other: Entity<Editor>,
2910 cx: &mut Context<Self>,
2911 ) -> gpui::Subscription {
2912 let other_selections = other.read(cx).selections.disjoint.to_vec();
2913 self.selections.change_with(cx, |selections| {
2914 selections.select_anchors(other_selections);
2915 });
2916
2917 let other_subscription =
2918 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2919 EditorEvent::SelectionsChanged { local: true } => {
2920 let other_selections = other.read(cx).selections.disjoint.to_vec();
2921 if other_selections.is_empty() {
2922 return;
2923 }
2924 this.selections.change_with(cx, |selections| {
2925 selections.select_anchors(other_selections);
2926 });
2927 }
2928 _ => {}
2929 });
2930
2931 let this_subscription =
2932 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2933 EditorEvent::SelectionsChanged { local: true } => {
2934 let these_selections = this.selections.disjoint.to_vec();
2935 if these_selections.is_empty() {
2936 return;
2937 }
2938 other.update(cx, |other_editor, cx| {
2939 other_editor.selections.change_with(cx, |selections| {
2940 selections.select_anchors(these_selections);
2941 })
2942 });
2943 }
2944 _ => {}
2945 });
2946
2947 Subscription::join(other_subscription, this_subscription)
2948 }
2949
2950 pub fn change_selections<R>(
2951 &mut self,
2952 autoscroll: Option<Autoscroll>,
2953 window: &mut Window,
2954 cx: &mut Context<Self>,
2955 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2956 ) -> R {
2957 self.change_selections_inner(autoscroll, true, window, cx, change)
2958 }
2959
2960 fn change_selections_inner<R>(
2961 &mut self,
2962 autoscroll: Option<Autoscroll>,
2963 request_completions: bool,
2964 window: &mut Window,
2965 cx: &mut Context<Self>,
2966 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
2967 ) -> R {
2968 let old_cursor_position = self.selections.newest_anchor().head();
2969 self.push_to_selection_history();
2970
2971 let (changed, result) = self.selections.change_with(cx, change);
2972
2973 if changed {
2974 if let Some(autoscroll) = autoscroll {
2975 self.request_autoscroll(autoscroll, cx);
2976 }
2977 self.selections_did_change(true, &old_cursor_position, request_completions, window, cx);
2978
2979 if self.should_open_signature_help_automatically(
2980 &old_cursor_position,
2981 self.signature_help_state.backspace_pressed(),
2982 cx,
2983 ) {
2984 self.show_signature_help(&ShowSignatureHelp, window, cx);
2985 }
2986 self.signature_help_state.set_backspace_pressed(false);
2987 }
2988
2989 result
2990 }
2991
2992 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
2993 where
2994 I: IntoIterator<Item = (Range<S>, T)>,
2995 S: ToOffset,
2996 T: Into<Arc<str>>,
2997 {
2998 if self.read_only(cx) {
2999 return;
3000 }
3001
3002 self.buffer
3003 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3004 }
3005
3006 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3007 where
3008 I: IntoIterator<Item = (Range<S>, T)>,
3009 S: ToOffset,
3010 T: Into<Arc<str>>,
3011 {
3012 if self.read_only(cx) {
3013 return;
3014 }
3015
3016 self.buffer.update(cx, |buffer, cx| {
3017 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3018 });
3019 }
3020
3021 pub fn edit_with_block_indent<I, S, T>(
3022 &mut self,
3023 edits: I,
3024 original_indent_columns: Vec<Option<u32>>,
3025 cx: &mut Context<Self>,
3026 ) where
3027 I: IntoIterator<Item = (Range<S>, T)>,
3028 S: ToOffset,
3029 T: Into<Arc<str>>,
3030 {
3031 if self.read_only(cx) {
3032 return;
3033 }
3034
3035 self.buffer.update(cx, |buffer, cx| {
3036 buffer.edit(
3037 edits,
3038 Some(AutoindentMode::Block {
3039 original_indent_columns,
3040 }),
3041 cx,
3042 )
3043 });
3044 }
3045
3046 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3047 self.hide_context_menu(window, cx);
3048
3049 match phase {
3050 SelectPhase::Begin {
3051 position,
3052 add,
3053 click_count,
3054 } => self.begin_selection(position, add, click_count, window, cx),
3055 SelectPhase::BeginColumnar {
3056 position,
3057 goal_column,
3058 reset,
3059 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3060 SelectPhase::Extend {
3061 position,
3062 click_count,
3063 } => self.extend_selection(position, click_count, window, cx),
3064 SelectPhase::Update {
3065 position,
3066 goal_column,
3067 scroll_delta,
3068 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3069 SelectPhase::End => self.end_selection(window, cx),
3070 }
3071 }
3072
3073 fn extend_selection(
3074 &mut self,
3075 position: DisplayPoint,
3076 click_count: usize,
3077 window: &mut Window,
3078 cx: &mut Context<Self>,
3079 ) {
3080 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3081 let tail = self.selections.newest::<usize>(cx).tail();
3082 self.begin_selection(position, false, click_count, window, cx);
3083
3084 let position = position.to_offset(&display_map, Bias::Left);
3085 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3086
3087 let mut pending_selection = self
3088 .selections
3089 .pending_anchor()
3090 .expect("extend_selection not called with pending selection");
3091 if position >= tail {
3092 pending_selection.start = tail_anchor;
3093 } else {
3094 pending_selection.end = tail_anchor;
3095 pending_selection.reversed = true;
3096 }
3097
3098 let mut pending_mode = self.selections.pending_mode().unwrap();
3099 match &mut pending_mode {
3100 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3101 _ => {}
3102 }
3103
3104 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3105
3106 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3107 s.set_pending(pending_selection, pending_mode)
3108 });
3109 }
3110
3111 fn begin_selection(
3112 &mut self,
3113 position: DisplayPoint,
3114 add: bool,
3115 click_count: usize,
3116 window: &mut Window,
3117 cx: &mut Context<Self>,
3118 ) {
3119 if !self.focus_handle.is_focused(window) {
3120 self.last_focused_descendant = None;
3121 window.focus(&self.focus_handle);
3122 }
3123
3124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3125 let buffer = &display_map.buffer_snapshot;
3126 let position = display_map.clip_point(position, Bias::Left);
3127
3128 let start;
3129 let end;
3130 let mode;
3131 let mut auto_scroll;
3132 match click_count {
3133 1 => {
3134 start = buffer.anchor_before(position.to_point(&display_map));
3135 end = start;
3136 mode = SelectMode::Character;
3137 auto_scroll = true;
3138 }
3139 2 => {
3140 let range = movement::surrounding_word(&display_map, position);
3141 start = buffer.anchor_before(range.start.to_point(&display_map));
3142 end = buffer.anchor_before(range.end.to_point(&display_map));
3143 mode = SelectMode::Word(start..end);
3144 auto_scroll = true;
3145 }
3146 3 => {
3147 let position = display_map
3148 .clip_point(position, Bias::Left)
3149 .to_point(&display_map);
3150 let line_start = display_map.prev_line_boundary(position).0;
3151 let next_line_start = buffer.clip_point(
3152 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3153 Bias::Left,
3154 );
3155 start = buffer.anchor_before(line_start);
3156 end = buffer.anchor_before(next_line_start);
3157 mode = SelectMode::Line(start..end);
3158 auto_scroll = true;
3159 }
3160 _ => {
3161 start = buffer.anchor_before(0);
3162 end = buffer.anchor_before(buffer.len());
3163 mode = SelectMode::All;
3164 auto_scroll = false;
3165 }
3166 }
3167 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3168
3169 let point_to_delete: Option<usize> = {
3170 let selected_points: Vec<Selection<Point>> =
3171 self.selections.disjoint_in_range(start..end, cx);
3172
3173 if !add || click_count > 1 {
3174 None
3175 } else if !selected_points.is_empty() {
3176 Some(selected_points[0].id)
3177 } else {
3178 let clicked_point_already_selected =
3179 self.selections.disjoint.iter().find(|selection| {
3180 selection.start.to_point(buffer) == start.to_point(buffer)
3181 || selection.end.to_point(buffer) == end.to_point(buffer)
3182 });
3183
3184 clicked_point_already_selected.map(|selection| selection.id)
3185 }
3186 };
3187
3188 let selections_count = self.selections.count();
3189
3190 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3191 if let Some(point_to_delete) = point_to_delete {
3192 s.delete(point_to_delete);
3193
3194 if selections_count == 1 {
3195 s.set_pending_anchor_range(start..end, mode);
3196 }
3197 } else {
3198 if !add {
3199 s.clear_disjoint();
3200 }
3201
3202 s.set_pending_anchor_range(start..end, mode);
3203 }
3204 });
3205 }
3206
3207 fn begin_columnar_selection(
3208 &mut self,
3209 position: DisplayPoint,
3210 goal_column: u32,
3211 reset: bool,
3212 window: &mut Window,
3213 cx: &mut Context<Self>,
3214 ) {
3215 if !self.focus_handle.is_focused(window) {
3216 self.last_focused_descendant = None;
3217 window.focus(&self.focus_handle);
3218 }
3219
3220 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3221
3222 if reset {
3223 let pointer_position = display_map
3224 .buffer_snapshot
3225 .anchor_before(position.to_point(&display_map));
3226
3227 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3228 s.clear_disjoint();
3229 s.set_pending_anchor_range(
3230 pointer_position..pointer_position,
3231 SelectMode::Character,
3232 );
3233 });
3234 }
3235
3236 let tail = self.selections.newest::<Point>(cx).tail();
3237 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3238
3239 if !reset {
3240 self.select_columns(
3241 tail.to_display_point(&display_map),
3242 position,
3243 goal_column,
3244 &display_map,
3245 window,
3246 cx,
3247 );
3248 }
3249 }
3250
3251 fn update_selection(
3252 &mut self,
3253 position: DisplayPoint,
3254 goal_column: u32,
3255 scroll_delta: gpui::Point<f32>,
3256 window: &mut Window,
3257 cx: &mut Context<Self>,
3258 ) {
3259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3260
3261 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3262 let tail = tail.to_display_point(&display_map);
3263 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3264 } else if let Some(mut pending) = self.selections.pending_anchor() {
3265 let buffer = self.buffer.read(cx).snapshot(cx);
3266 let head;
3267 let tail;
3268 let mode = self.selections.pending_mode().unwrap();
3269 match &mode {
3270 SelectMode::Character => {
3271 head = position.to_point(&display_map);
3272 tail = pending.tail().to_point(&buffer);
3273 }
3274 SelectMode::Word(original_range) => {
3275 let original_display_range = original_range.start.to_display_point(&display_map)
3276 ..original_range.end.to_display_point(&display_map);
3277 let original_buffer_range = original_display_range.start.to_point(&display_map)
3278 ..original_display_range.end.to_point(&display_map);
3279 if movement::is_inside_word(&display_map, position)
3280 || original_display_range.contains(&position)
3281 {
3282 let word_range = movement::surrounding_word(&display_map, position);
3283 if word_range.start < original_display_range.start {
3284 head = word_range.start.to_point(&display_map);
3285 } else {
3286 head = word_range.end.to_point(&display_map);
3287 }
3288 } else {
3289 head = position.to_point(&display_map);
3290 }
3291
3292 if head <= original_buffer_range.start {
3293 tail = original_buffer_range.end;
3294 } else {
3295 tail = original_buffer_range.start;
3296 }
3297 }
3298 SelectMode::Line(original_range) => {
3299 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3300
3301 let position = display_map
3302 .clip_point(position, Bias::Left)
3303 .to_point(&display_map);
3304 let line_start = display_map.prev_line_boundary(position).0;
3305 let next_line_start = buffer.clip_point(
3306 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3307 Bias::Left,
3308 );
3309
3310 if line_start < original_range.start {
3311 head = line_start
3312 } else {
3313 head = next_line_start
3314 }
3315
3316 if head <= original_range.start {
3317 tail = original_range.end;
3318 } else {
3319 tail = original_range.start;
3320 }
3321 }
3322 SelectMode::All => {
3323 return;
3324 }
3325 };
3326
3327 if head < tail {
3328 pending.start = buffer.anchor_before(head);
3329 pending.end = buffer.anchor_before(tail);
3330 pending.reversed = true;
3331 } else {
3332 pending.start = buffer.anchor_before(tail);
3333 pending.end = buffer.anchor_before(head);
3334 pending.reversed = false;
3335 }
3336
3337 self.change_selections(None, window, cx, |s| {
3338 s.set_pending(pending, mode);
3339 });
3340 } else {
3341 log::error!("update_selection dispatched with no pending selection");
3342 return;
3343 }
3344
3345 self.apply_scroll_delta(scroll_delta, window, cx);
3346 cx.notify();
3347 }
3348
3349 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3350 self.columnar_selection_tail.take();
3351 if self.selections.pending_anchor().is_some() {
3352 let selections = self.selections.all::<usize>(cx);
3353 self.change_selections(None, window, cx, |s| {
3354 s.select(selections);
3355 s.clear_pending();
3356 });
3357 }
3358 }
3359
3360 fn select_columns(
3361 &mut self,
3362 tail: DisplayPoint,
3363 head: DisplayPoint,
3364 goal_column: u32,
3365 display_map: &DisplaySnapshot,
3366 window: &mut Window,
3367 cx: &mut Context<Self>,
3368 ) {
3369 let start_row = cmp::min(tail.row(), head.row());
3370 let end_row = cmp::max(tail.row(), head.row());
3371 let start_column = cmp::min(tail.column(), goal_column);
3372 let end_column = cmp::max(tail.column(), goal_column);
3373 let reversed = start_column < tail.column();
3374
3375 let selection_ranges = (start_row.0..=end_row.0)
3376 .map(DisplayRow)
3377 .filter_map(|row| {
3378 if start_column <= display_map.line_len(row) && !display_map.is_block_line(row) {
3379 let start = display_map
3380 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3381 .to_point(display_map);
3382 let end = display_map
3383 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3384 .to_point(display_map);
3385 if reversed {
3386 Some(end..start)
3387 } else {
3388 Some(start..end)
3389 }
3390 } else {
3391 None
3392 }
3393 })
3394 .collect::<Vec<_>>();
3395
3396 self.change_selections(None, window, cx, |s| {
3397 s.select_ranges(selection_ranges);
3398 });
3399 cx.notify();
3400 }
3401
3402 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3403 self.selections
3404 .all_adjusted(cx)
3405 .iter()
3406 .any(|selection| !selection.is_empty())
3407 }
3408
3409 pub fn has_pending_nonempty_selection(&self) -> bool {
3410 let pending_nonempty_selection = match self.selections.pending_anchor() {
3411 Some(Selection { start, end, .. }) => start != end,
3412 None => false,
3413 };
3414
3415 pending_nonempty_selection
3416 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3417 }
3418
3419 pub fn has_pending_selection(&self) -> bool {
3420 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3421 }
3422
3423 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3424 self.selection_mark_mode = false;
3425
3426 if self.clear_expanded_diff_hunks(cx) {
3427 cx.notify();
3428 return;
3429 }
3430 if self.dismiss_menus_and_popups(true, window, cx) {
3431 return;
3432 }
3433
3434 if self.mode.is_full()
3435 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3436 {
3437 return;
3438 }
3439
3440 cx.propagate();
3441 }
3442
3443 pub fn dismiss_menus_and_popups(
3444 &mut self,
3445 is_user_requested: bool,
3446 window: &mut Window,
3447 cx: &mut Context<Self>,
3448 ) -> bool {
3449 if self.take_rename(false, window, cx).is_some() {
3450 return true;
3451 }
3452
3453 if hide_hover(self, cx) {
3454 return true;
3455 }
3456
3457 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3458 return true;
3459 }
3460
3461 if self.hide_context_menu(window, cx).is_some() {
3462 return true;
3463 }
3464
3465 if self.mouse_context_menu.take().is_some() {
3466 return true;
3467 }
3468
3469 if is_user_requested && self.discard_inline_completion(true, cx) {
3470 return true;
3471 }
3472
3473 if self.snippet_stack.pop().is_some() {
3474 return true;
3475 }
3476
3477 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3478 self.dismiss_diagnostics(cx);
3479 return true;
3480 }
3481
3482 false
3483 }
3484
3485 fn linked_editing_ranges_for(
3486 &self,
3487 selection: Range<text::Anchor>,
3488 cx: &App,
3489 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3490 if self.linked_edit_ranges.is_empty() {
3491 return None;
3492 }
3493 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3494 selection.end.buffer_id.and_then(|end_buffer_id| {
3495 if selection.start.buffer_id != Some(end_buffer_id) {
3496 return None;
3497 }
3498 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3499 let snapshot = buffer.read(cx).snapshot();
3500 self.linked_edit_ranges
3501 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3502 .map(|ranges| (ranges, snapshot, buffer))
3503 })?;
3504 use text::ToOffset as TO;
3505 // find offset from the start of current range to current cursor position
3506 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3507
3508 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3509 let start_difference = start_offset - start_byte_offset;
3510 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3511 let end_difference = end_offset - start_byte_offset;
3512 // Current range has associated linked ranges.
3513 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3514 for range in linked_ranges.iter() {
3515 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3516 let end_offset = start_offset + end_difference;
3517 let start_offset = start_offset + start_difference;
3518 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3519 continue;
3520 }
3521 if self.selections.disjoint_anchor_ranges().any(|s| {
3522 if s.start.buffer_id != selection.start.buffer_id
3523 || s.end.buffer_id != selection.end.buffer_id
3524 {
3525 return false;
3526 }
3527 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3528 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3529 }) {
3530 continue;
3531 }
3532 let start = buffer_snapshot.anchor_after(start_offset);
3533 let end = buffer_snapshot.anchor_after(end_offset);
3534 linked_edits
3535 .entry(buffer.clone())
3536 .or_default()
3537 .push(start..end);
3538 }
3539 Some(linked_edits)
3540 }
3541
3542 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3543 let text: Arc<str> = text.into();
3544
3545 if self.read_only(cx) {
3546 return;
3547 }
3548
3549 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3550
3551 let selections = self.selections.all_adjusted(cx);
3552 let mut bracket_inserted = false;
3553 let mut edits = Vec::new();
3554 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3555 let mut new_selections = Vec::with_capacity(selections.len());
3556 let mut new_autoclose_regions = Vec::new();
3557 let snapshot = self.buffer.read(cx).read(cx);
3558 let mut clear_linked_edit_ranges = false;
3559
3560 for (selection, autoclose_region) in
3561 self.selections_with_autoclose_regions(selections, &snapshot)
3562 {
3563 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3564 // Determine if the inserted text matches the opening or closing
3565 // bracket of any of this language's bracket pairs.
3566 let mut bracket_pair = None;
3567 let mut is_bracket_pair_start = false;
3568 let mut is_bracket_pair_end = false;
3569 if !text.is_empty() {
3570 let mut bracket_pair_matching_end = None;
3571 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3572 // and they are removing the character that triggered IME popup.
3573 for (pair, enabled) in scope.brackets() {
3574 if !pair.close && !pair.surround {
3575 continue;
3576 }
3577
3578 if enabled && pair.start.ends_with(text.as_ref()) {
3579 let prefix_len = pair.start.len() - text.len();
3580 let preceding_text_matches_prefix = prefix_len == 0
3581 || (selection.start.column >= (prefix_len as u32)
3582 && snapshot.contains_str_at(
3583 Point::new(
3584 selection.start.row,
3585 selection.start.column - (prefix_len as u32),
3586 ),
3587 &pair.start[..prefix_len],
3588 ));
3589 if preceding_text_matches_prefix {
3590 bracket_pair = Some(pair.clone());
3591 is_bracket_pair_start = true;
3592 break;
3593 }
3594 }
3595 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3596 {
3597 // take first bracket pair matching end, but don't break in case a later bracket
3598 // pair matches start
3599 bracket_pair_matching_end = Some(pair.clone());
3600 }
3601 }
3602 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3603 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3604 is_bracket_pair_end = true;
3605 }
3606 }
3607
3608 if let Some(bracket_pair) = bracket_pair {
3609 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3610 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3611 let auto_surround =
3612 self.use_auto_surround && snapshot_settings.use_auto_surround;
3613 if selection.is_empty() {
3614 if is_bracket_pair_start {
3615 // If the inserted text is a suffix of an opening bracket and the
3616 // selection is preceded by the rest of the opening bracket, then
3617 // insert the closing bracket.
3618 let following_text_allows_autoclose = snapshot
3619 .chars_at(selection.start)
3620 .next()
3621 .map_or(true, |c| scope.should_autoclose_before(c));
3622
3623 let preceding_text_allows_autoclose = selection.start.column == 0
3624 || snapshot.reversed_chars_at(selection.start).next().map_or(
3625 true,
3626 |c| {
3627 bracket_pair.start != bracket_pair.end
3628 || !snapshot
3629 .char_classifier_at(selection.start)
3630 .is_word(c)
3631 },
3632 );
3633
3634 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3635 && bracket_pair.start.len() == 1
3636 {
3637 let target = bracket_pair.start.chars().next().unwrap();
3638 let current_line_count = snapshot
3639 .reversed_chars_at(selection.start)
3640 .take_while(|&c| c != '\n')
3641 .filter(|&c| c == target)
3642 .count();
3643 current_line_count % 2 == 1
3644 } else {
3645 false
3646 };
3647
3648 if autoclose
3649 && bracket_pair.close
3650 && following_text_allows_autoclose
3651 && preceding_text_allows_autoclose
3652 && !is_closing_quote
3653 {
3654 let anchor = snapshot.anchor_before(selection.end);
3655 new_selections.push((selection.map(|_| anchor), text.len()));
3656 new_autoclose_regions.push((
3657 anchor,
3658 text.len(),
3659 selection.id,
3660 bracket_pair.clone(),
3661 ));
3662 edits.push((
3663 selection.range(),
3664 format!("{}{}", text, bracket_pair.end).into(),
3665 ));
3666 bracket_inserted = true;
3667 continue;
3668 }
3669 }
3670
3671 if let Some(region) = autoclose_region {
3672 // If the selection is followed by an auto-inserted closing bracket,
3673 // then don't insert that closing bracket again; just move the selection
3674 // past the closing bracket.
3675 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3676 && text.as_ref() == region.pair.end.as_str();
3677 if should_skip {
3678 let anchor = snapshot.anchor_after(selection.end);
3679 new_selections
3680 .push((selection.map(|_| anchor), region.pair.end.len()));
3681 continue;
3682 }
3683 }
3684
3685 let always_treat_brackets_as_autoclosed = snapshot
3686 .language_settings_at(selection.start, cx)
3687 .always_treat_brackets_as_autoclosed;
3688 if always_treat_brackets_as_autoclosed
3689 && is_bracket_pair_end
3690 && snapshot.contains_str_at(selection.end, text.as_ref())
3691 {
3692 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3693 // and the inserted text is a closing bracket and the selection is followed
3694 // by the closing bracket then move the selection past the closing bracket.
3695 let anchor = snapshot.anchor_after(selection.end);
3696 new_selections.push((selection.map(|_| anchor), text.len()));
3697 continue;
3698 }
3699 }
3700 // If an opening bracket is 1 character long and is typed while
3701 // text is selected, then surround that text with the bracket pair.
3702 else if auto_surround
3703 && bracket_pair.surround
3704 && is_bracket_pair_start
3705 && bracket_pair.start.chars().count() == 1
3706 {
3707 edits.push((selection.start..selection.start, text.clone()));
3708 edits.push((
3709 selection.end..selection.end,
3710 bracket_pair.end.as_str().into(),
3711 ));
3712 bracket_inserted = true;
3713 new_selections.push((
3714 Selection {
3715 id: selection.id,
3716 start: snapshot.anchor_after(selection.start),
3717 end: snapshot.anchor_before(selection.end),
3718 reversed: selection.reversed,
3719 goal: selection.goal,
3720 },
3721 0,
3722 ));
3723 continue;
3724 }
3725 }
3726 }
3727
3728 if self.auto_replace_emoji_shortcode
3729 && selection.is_empty()
3730 && text.as_ref().ends_with(':')
3731 {
3732 if let Some(possible_emoji_short_code) =
3733 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3734 {
3735 if !possible_emoji_short_code.is_empty() {
3736 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3737 let emoji_shortcode_start = Point::new(
3738 selection.start.row,
3739 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3740 );
3741
3742 // Remove shortcode from buffer
3743 edits.push((
3744 emoji_shortcode_start..selection.start,
3745 "".to_string().into(),
3746 ));
3747 new_selections.push((
3748 Selection {
3749 id: selection.id,
3750 start: snapshot.anchor_after(emoji_shortcode_start),
3751 end: snapshot.anchor_before(selection.start),
3752 reversed: selection.reversed,
3753 goal: selection.goal,
3754 },
3755 0,
3756 ));
3757
3758 // Insert emoji
3759 let selection_start_anchor = snapshot.anchor_after(selection.start);
3760 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3761 edits.push((selection.start..selection.end, emoji.to_string().into()));
3762
3763 continue;
3764 }
3765 }
3766 }
3767 }
3768
3769 // If not handling any auto-close operation, then just replace the selected
3770 // text with the given input and move the selection to the end of the
3771 // newly inserted text.
3772 let anchor = snapshot.anchor_after(selection.end);
3773 if !self.linked_edit_ranges.is_empty() {
3774 let start_anchor = snapshot.anchor_before(selection.start);
3775
3776 let is_word_char = text.chars().next().map_or(true, |char| {
3777 let classifier = snapshot
3778 .char_classifier_at(start_anchor.to_offset(&snapshot))
3779 .ignore_punctuation(true);
3780 classifier.is_word(char)
3781 });
3782
3783 if is_word_char {
3784 if let Some(ranges) = self
3785 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3786 {
3787 for (buffer, edits) in ranges {
3788 linked_edits
3789 .entry(buffer.clone())
3790 .or_default()
3791 .extend(edits.into_iter().map(|range| (range, text.clone())));
3792 }
3793 }
3794 } else {
3795 clear_linked_edit_ranges = true;
3796 }
3797 }
3798
3799 new_selections.push((selection.map(|_| anchor), 0));
3800 edits.push((selection.start..selection.end, text.clone()));
3801 }
3802
3803 drop(snapshot);
3804
3805 self.transact(window, cx, |this, window, cx| {
3806 if clear_linked_edit_ranges {
3807 this.linked_edit_ranges.clear();
3808 }
3809 let initial_buffer_versions =
3810 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3811
3812 this.buffer.update(cx, |buffer, cx| {
3813 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3814 });
3815 for (buffer, edits) in linked_edits {
3816 buffer.update(cx, |buffer, cx| {
3817 let snapshot = buffer.snapshot();
3818 let edits = edits
3819 .into_iter()
3820 .map(|(range, text)| {
3821 use text::ToPoint as TP;
3822 let end_point = TP::to_point(&range.end, &snapshot);
3823 let start_point = TP::to_point(&range.start, &snapshot);
3824 (start_point..end_point, text)
3825 })
3826 .sorted_by_key(|(range, _)| range.start);
3827 buffer.edit(edits, None, cx);
3828 })
3829 }
3830 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3831 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3832 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3833 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3834 .zip(new_selection_deltas)
3835 .map(|(selection, delta)| Selection {
3836 id: selection.id,
3837 start: selection.start + delta,
3838 end: selection.end + delta,
3839 reversed: selection.reversed,
3840 goal: SelectionGoal::None,
3841 })
3842 .collect::<Vec<_>>();
3843
3844 let mut i = 0;
3845 for (position, delta, selection_id, pair) in new_autoclose_regions {
3846 let position = position.to_offset(&map.buffer_snapshot) + delta;
3847 let start = map.buffer_snapshot.anchor_before(position);
3848 let end = map.buffer_snapshot.anchor_after(position);
3849 while let Some(existing_state) = this.autoclose_regions.get(i) {
3850 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3851 Ordering::Less => i += 1,
3852 Ordering::Greater => break,
3853 Ordering::Equal => {
3854 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
3855 Ordering::Less => i += 1,
3856 Ordering::Equal => break,
3857 Ordering::Greater => break,
3858 }
3859 }
3860 }
3861 }
3862 this.autoclose_regions.insert(
3863 i,
3864 AutocloseRegion {
3865 selection_id,
3866 range: start..end,
3867 pair,
3868 },
3869 );
3870 }
3871
3872 let had_active_inline_completion = this.has_active_inline_completion();
3873 this.change_selections_inner(Some(Autoscroll::fit()), false, window, cx, |s| {
3874 s.select(new_selections)
3875 });
3876
3877 if !bracket_inserted {
3878 if let Some(on_type_format_task) =
3879 this.trigger_on_type_formatting(text.to_string(), window, cx)
3880 {
3881 on_type_format_task.detach_and_log_err(cx);
3882 }
3883 }
3884
3885 let editor_settings = EditorSettings::get_global(cx);
3886 if bracket_inserted
3887 && (editor_settings.auto_signature_help
3888 || editor_settings.show_signature_help_after_edits)
3889 {
3890 this.show_signature_help(&ShowSignatureHelp, window, cx);
3891 }
3892
3893 let trigger_in_words =
3894 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
3895 if this.hard_wrap.is_some() {
3896 let latest: Range<Point> = this.selections.newest(cx).range();
3897 if latest.is_empty()
3898 && this
3899 .buffer()
3900 .read(cx)
3901 .snapshot(cx)
3902 .line_len(MultiBufferRow(latest.start.row))
3903 == latest.start.column
3904 {
3905 this.rewrap_impl(
3906 RewrapOptions {
3907 override_language_settings: true,
3908 preserve_existing_whitespace: true,
3909 },
3910 cx,
3911 )
3912 }
3913 }
3914 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
3915 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
3916 this.refresh_inline_completion(true, false, window, cx);
3917 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
3918 });
3919 }
3920
3921 fn find_possible_emoji_shortcode_at_position(
3922 snapshot: &MultiBufferSnapshot,
3923 position: Point,
3924 ) -> Option<String> {
3925 let mut chars = Vec::new();
3926 let mut found_colon = false;
3927 for char in snapshot.reversed_chars_at(position).take(100) {
3928 // Found a possible emoji shortcode in the middle of the buffer
3929 if found_colon {
3930 if char.is_whitespace() {
3931 chars.reverse();
3932 return Some(chars.iter().collect());
3933 }
3934 // If the previous character is not a whitespace, we are in the middle of a word
3935 // and we only want to complete the shortcode if the word is made up of other emojis
3936 let mut containing_word = String::new();
3937 for ch in snapshot
3938 .reversed_chars_at(position)
3939 .skip(chars.len() + 1)
3940 .take(100)
3941 {
3942 if ch.is_whitespace() {
3943 break;
3944 }
3945 containing_word.push(ch);
3946 }
3947 let containing_word = containing_word.chars().rev().collect::<String>();
3948 if util::word_consists_of_emojis(containing_word.as_str()) {
3949 chars.reverse();
3950 return Some(chars.iter().collect());
3951 }
3952 }
3953
3954 if char.is_whitespace() || !char.is_ascii() {
3955 return None;
3956 }
3957 if char == ':' {
3958 found_colon = true;
3959 } else {
3960 chars.push(char);
3961 }
3962 }
3963 // Found a possible emoji shortcode at the beginning of the buffer
3964 chars.reverse();
3965 Some(chars.iter().collect())
3966 }
3967
3968 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
3969 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3970 self.transact(window, cx, |this, window, cx| {
3971 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
3972 let selections = this.selections.all::<usize>(cx);
3973 let multi_buffer = this.buffer.read(cx);
3974 let buffer = multi_buffer.snapshot(cx);
3975 selections
3976 .iter()
3977 .map(|selection| {
3978 let start_point = selection.start.to_point(&buffer);
3979 let mut existing_indent =
3980 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
3981 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
3982 let start = selection.start;
3983 let end = selection.end;
3984 let selection_is_empty = start == end;
3985 let language_scope = buffer.language_scope_at(start);
3986 let (
3987 comment_delimiter,
3988 doc_delimiter,
3989 insert_extra_newline,
3990 indent_on_newline,
3991 indent_on_extra_newline,
3992 ) = if let Some(language) = &language_scope {
3993 let mut insert_extra_newline =
3994 insert_extra_newline_brackets(&buffer, start..end, language)
3995 || insert_extra_newline_tree_sitter(&buffer, start..end);
3996
3997 // Comment extension on newline is allowed only for cursor selections
3998 let comment_delimiter = maybe!({
3999 if !selection_is_empty {
4000 return None;
4001 }
4002
4003 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4004 return None;
4005 }
4006
4007 let delimiters = language.line_comment_prefixes();
4008 let max_len_of_delimiter =
4009 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4010 let (snapshot, range) =
4011 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4012
4013 let num_of_whitespaces = snapshot
4014 .chars_for_range(range.clone())
4015 .take_while(|c| c.is_whitespace())
4016 .count();
4017 let comment_candidate = snapshot
4018 .chars_for_range(range)
4019 .skip(num_of_whitespaces)
4020 .take(max_len_of_delimiter)
4021 .collect::<String>();
4022 let (delimiter, trimmed_len) = delimiters
4023 .iter()
4024 .filter_map(|delimiter| {
4025 let prefix = delimiter.trim_end();
4026 if comment_candidate.starts_with(prefix) {
4027 Some((delimiter, prefix.len()))
4028 } else {
4029 None
4030 }
4031 })
4032 .max_by_key(|(_, len)| *len)?;
4033
4034 let cursor_is_placed_after_comment_marker =
4035 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4036 if cursor_is_placed_after_comment_marker {
4037 Some(delimiter.clone())
4038 } else {
4039 None
4040 }
4041 });
4042
4043 let mut indent_on_newline = IndentSize::spaces(0);
4044 let mut indent_on_extra_newline = IndentSize::spaces(0);
4045
4046 let doc_delimiter = maybe!({
4047 if !selection_is_empty {
4048 return None;
4049 }
4050
4051 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4052 return None;
4053 }
4054
4055 let DocumentationConfig {
4056 start: start_tag,
4057 end: end_tag,
4058 prefix: delimiter,
4059 tab_size: len,
4060 } = language.documentation()?;
4061
4062 let is_within_block_comment = buffer
4063 .language_scope_at(start_point)
4064 .is_some_and(|scope| scope.override_name() == Some("comment"));
4065 if !is_within_block_comment {
4066 return None;
4067 }
4068
4069 let (snapshot, range) =
4070 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4071
4072 let num_of_whitespaces = snapshot
4073 .chars_for_range(range.clone())
4074 .take_while(|c| c.is_whitespace())
4075 .count();
4076
4077 // 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.
4078 let column = start_point.column;
4079 let cursor_is_after_start_tag = {
4080 let start_tag_len = start_tag.len();
4081 let start_tag_line = snapshot
4082 .chars_for_range(range.clone())
4083 .skip(num_of_whitespaces)
4084 .take(start_tag_len)
4085 .collect::<String>();
4086 if start_tag_line.starts_with(start_tag.as_ref()) {
4087 num_of_whitespaces + start_tag_len <= column as usize
4088 } else {
4089 false
4090 }
4091 };
4092
4093 let cursor_is_after_delimiter = {
4094 let delimiter_trim = delimiter.trim_end();
4095 let delimiter_line = snapshot
4096 .chars_for_range(range.clone())
4097 .skip(num_of_whitespaces)
4098 .take(delimiter_trim.len())
4099 .collect::<String>();
4100 if delimiter_line.starts_with(delimiter_trim) {
4101 num_of_whitespaces + delimiter_trim.len() <= column as usize
4102 } else {
4103 false
4104 }
4105 };
4106
4107 let cursor_is_before_end_tag_if_exists = {
4108 let mut char_position = 0u32;
4109 let mut end_tag_offset = None;
4110
4111 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4112 if let Some(byte_pos) = chunk.find(&**end_tag) {
4113 let chars_before_match =
4114 chunk[..byte_pos].chars().count() as u32;
4115 end_tag_offset =
4116 Some(char_position + chars_before_match);
4117 break 'outer;
4118 }
4119 char_position += chunk.chars().count() as u32;
4120 }
4121
4122 if let Some(end_tag_offset) = end_tag_offset {
4123 let cursor_is_before_end_tag = column <= end_tag_offset;
4124 if cursor_is_after_start_tag {
4125 if cursor_is_before_end_tag {
4126 insert_extra_newline = true;
4127 }
4128 let cursor_is_at_start_of_end_tag =
4129 column == end_tag_offset;
4130 if cursor_is_at_start_of_end_tag {
4131 indent_on_extra_newline.len = (*len).into();
4132 }
4133 }
4134 cursor_is_before_end_tag
4135 } else {
4136 true
4137 }
4138 };
4139
4140 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4141 && cursor_is_before_end_tag_if_exists
4142 {
4143 if cursor_is_after_start_tag {
4144 indent_on_newline.len = (*len).into();
4145 }
4146 Some(delimiter.clone())
4147 } else {
4148 None
4149 }
4150 });
4151
4152 (
4153 comment_delimiter,
4154 doc_delimiter,
4155 insert_extra_newline,
4156 indent_on_newline,
4157 indent_on_extra_newline,
4158 )
4159 } else {
4160 (
4161 None,
4162 None,
4163 false,
4164 IndentSize::default(),
4165 IndentSize::default(),
4166 )
4167 };
4168
4169 let prevent_auto_indent = doc_delimiter.is_some();
4170 let delimiter = comment_delimiter.or(doc_delimiter);
4171
4172 let capacity_for_delimiter =
4173 delimiter.as_deref().map(str::len).unwrap_or_default();
4174 let mut new_text = String::with_capacity(
4175 1 + capacity_for_delimiter
4176 + existing_indent.len as usize
4177 + indent_on_newline.len as usize
4178 + indent_on_extra_newline.len as usize,
4179 );
4180 new_text.push('\n');
4181 new_text.extend(existing_indent.chars());
4182 new_text.extend(indent_on_newline.chars());
4183
4184 if let Some(delimiter) = &delimiter {
4185 new_text.push_str(delimiter);
4186 }
4187
4188 if insert_extra_newline {
4189 new_text.push('\n');
4190 new_text.extend(existing_indent.chars());
4191 new_text.extend(indent_on_extra_newline.chars());
4192 }
4193
4194 let anchor = buffer.anchor_after(end);
4195 let new_selection = selection.map(|_| anchor);
4196 (
4197 ((start..end, new_text), prevent_auto_indent),
4198 (insert_extra_newline, new_selection),
4199 )
4200 })
4201 .unzip()
4202 };
4203
4204 let mut auto_indent_edits = Vec::new();
4205 let mut edits = Vec::new();
4206 for (edit, prevent_auto_indent) in edits_with_flags {
4207 if prevent_auto_indent {
4208 edits.push(edit);
4209 } else {
4210 auto_indent_edits.push(edit);
4211 }
4212 }
4213 if !edits.is_empty() {
4214 this.edit(edits, cx);
4215 }
4216 if !auto_indent_edits.is_empty() {
4217 this.edit_with_autoindent(auto_indent_edits, cx);
4218 }
4219
4220 let buffer = this.buffer.read(cx).snapshot(cx);
4221 let new_selections = selection_info
4222 .into_iter()
4223 .map(|(extra_newline_inserted, new_selection)| {
4224 let mut cursor = new_selection.end.to_point(&buffer);
4225 if extra_newline_inserted {
4226 cursor.row -= 1;
4227 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4228 }
4229 new_selection.map(|_| cursor)
4230 })
4231 .collect();
4232
4233 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4234 s.select(new_selections)
4235 });
4236 this.refresh_inline_completion(true, false, window, cx);
4237 });
4238 }
4239
4240 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4241 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4242
4243 let buffer = self.buffer.read(cx);
4244 let snapshot = buffer.snapshot(cx);
4245
4246 let mut edits = Vec::new();
4247 let mut rows = Vec::new();
4248
4249 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4250 let cursor = selection.head();
4251 let row = cursor.row;
4252
4253 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4254
4255 let newline = "\n".to_string();
4256 edits.push((start_of_line..start_of_line, newline));
4257
4258 rows.push(row + rows_inserted as u32);
4259 }
4260
4261 self.transact(window, cx, |editor, window, cx| {
4262 editor.edit(edits, cx);
4263
4264 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4265 let mut index = 0;
4266 s.move_cursors_with(|map, _, _| {
4267 let row = rows[index];
4268 index += 1;
4269
4270 let point = Point::new(row, 0);
4271 let boundary = map.next_line_boundary(point).1;
4272 let clipped = map.clip_point(boundary, Bias::Left);
4273
4274 (clipped, SelectionGoal::None)
4275 });
4276 });
4277
4278 let mut indent_edits = Vec::new();
4279 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4280 for row in rows {
4281 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4282 for (row, indent) in indents {
4283 if indent.len == 0 {
4284 continue;
4285 }
4286
4287 let text = match indent.kind {
4288 IndentKind::Space => " ".repeat(indent.len as usize),
4289 IndentKind::Tab => "\t".repeat(indent.len as usize),
4290 };
4291 let point = Point::new(row.0, 0);
4292 indent_edits.push((point..point, text));
4293 }
4294 }
4295 editor.edit(indent_edits, cx);
4296 });
4297 }
4298
4299 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4300 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4301
4302 let buffer = self.buffer.read(cx);
4303 let snapshot = buffer.snapshot(cx);
4304
4305 let mut edits = Vec::new();
4306 let mut rows = Vec::new();
4307 let mut rows_inserted = 0;
4308
4309 for selection in self.selections.all_adjusted(cx) {
4310 let cursor = selection.head();
4311 let row = cursor.row;
4312
4313 let point = Point::new(row + 1, 0);
4314 let start_of_line = snapshot.clip_point(point, Bias::Left);
4315
4316 let newline = "\n".to_string();
4317 edits.push((start_of_line..start_of_line, newline));
4318
4319 rows_inserted += 1;
4320 rows.push(row + rows_inserted);
4321 }
4322
4323 self.transact(window, cx, |editor, window, cx| {
4324 editor.edit(edits, cx);
4325
4326 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4327 let mut index = 0;
4328 s.move_cursors_with(|map, _, _| {
4329 let row = rows[index];
4330 index += 1;
4331
4332 let point = Point::new(row, 0);
4333 let boundary = map.next_line_boundary(point).1;
4334 let clipped = map.clip_point(boundary, Bias::Left);
4335
4336 (clipped, SelectionGoal::None)
4337 });
4338 });
4339
4340 let mut indent_edits = Vec::new();
4341 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4342 for row in rows {
4343 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4344 for (row, indent) in indents {
4345 if indent.len == 0 {
4346 continue;
4347 }
4348
4349 let text = match indent.kind {
4350 IndentKind::Space => " ".repeat(indent.len as usize),
4351 IndentKind::Tab => "\t".repeat(indent.len as usize),
4352 };
4353 let point = Point::new(row.0, 0);
4354 indent_edits.push((point..point, text));
4355 }
4356 }
4357 editor.edit(indent_edits, cx);
4358 });
4359 }
4360
4361 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4362 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4363 original_indent_columns: Vec::new(),
4364 });
4365 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4366 }
4367
4368 fn insert_with_autoindent_mode(
4369 &mut self,
4370 text: &str,
4371 autoindent_mode: Option<AutoindentMode>,
4372 window: &mut Window,
4373 cx: &mut Context<Self>,
4374 ) {
4375 if self.read_only(cx) {
4376 return;
4377 }
4378
4379 let text: Arc<str> = text.into();
4380 self.transact(window, cx, |this, window, cx| {
4381 let old_selections = this.selections.all_adjusted(cx);
4382 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4383 let anchors = {
4384 let snapshot = buffer.read(cx);
4385 old_selections
4386 .iter()
4387 .map(|s| {
4388 let anchor = snapshot.anchor_after(s.head());
4389 s.map(|_| anchor)
4390 })
4391 .collect::<Vec<_>>()
4392 };
4393 buffer.edit(
4394 old_selections
4395 .iter()
4396 .map(|s| (s.start..s.end, text.clone())),
4397 autoindent_mode,
4398 cx,
4399 );
4400 anchors
4401 });
4402
4403 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4404 s.select_anchors(selection_anchors);
4405 });
4406
4407 cx.notify();
4408 });
4409 }
4410
4411 fn trigger_completion_on_input(
4412 &mut self,
4413 text: &str,
4414 trigger_in_words: bool,
4415 window: &mut Window,
4416 cx: &mut Context<Self>,
4417 ) {
4418 let ignore_completion_provider = self
4419 .context_menu
4420 .borrow()
4421 .as_ref()
4422 .map(|menu| match menu {
4423 CodeContextMenu::Completions(completions_menu) => {
4424 completions_menu.ignore_completion_provider
4425 }
4426 CodeContextMenu::CodeActions(_) => false,
4427 })
4428 .unwrap_or(false);
4429
4430 if ignore_completion_provider {
4431 self.show_word_completions(&ShowWordCompletions, window, cx);
4432 } else if self.is_completion_trigger(text, trigger_in_words, cx) {
4433 self.show_completions(
4434 &ShowCompletions {
4435 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4436 },
4437 window,
4438 cx,
4439 );
4440 } else {
4441 self.hide_context_menu(window, cx);
4442 }
4443 }
4444
4445 fn is_completion_trigger(
4446 &self,
4447 text: &str,
4448 trigger_in_words: bool,
4449 cx: &mut Context<Self>,
4450 ) -> bool {
4451 let position = self.selections.newest_anchor().head();
4452 let multibuffer = self.buffer.read(cx);
4453 let Some(buffer) = position
4454 .buffer_id
4455 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4456 else {
4457 return false;
4458 };
4459
4460 if let Some(completion_provider) = &self.completion_provider {
4461 completion_provider.is_completion_trigger(
4462 &buffer,
4463 position.text_anchor,
4464 text,
4465 trigger_in_words,
4466 cx,
4467 )
4468 } else {
4469 false
4470 }
4471 }
4472
4473 /// If any empty selections is touching the start of its innermost containing autoclose
4474 /// region, expand it to select the brackets.
4475 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4476 let selections = self.selections.all::<usize>(cx);
4477 let buffer = self.buffer.read(cx).read(cx);
4478 let new_selections = self
4479 .selections_with_autoclose_regions(selections, &buffer)
4480 .map(|(mut selection, region)| {
4481 if !selection.is_empty() {
4482 return selection;
4483 }
4484
4485 if let Some(region) = region {
4486 let mut range = region.range.to_offset(&buffer);
4487 if selection.start == range.start && range.start >= region.pair.start.len() {
4488 range.start -= region.pair.start.len();
4489 if buffer.contains_str_at(range.start, ®ion.pair.start)
4490 && buffer.contains_str_at(range.end, ®ion.pair.end)
4491 {
4492 range.end += region.pair.end.len();
4493 selection.start = range.start;
4494 selection.end = range.end;
4495
4496 return selection;
4497 }
4498 }
4499 }
4500
4501 let always_treat_brackets_as_autoclosed = buffer
4502 .language_settings_at(selection.start, cx)
4503 .always_treat_brackets_as_autoclosed;
4504
4505 if !always_treat_brackets_as_autoclosed {
4506 return selection;
4507 }
4508
4509 if let Some(scope) = buffer.language_scope_at(selection.start) {
4510 for (pair, enabled) in scope.brackets() {
4511 if !enabled || !pair.close {
4512 continue;
4513 }
4514
4515 if buffer.contains_str_at(selection.start, &pair.end) {
4516 let pair_start_len = pair.start.len();
4517 if buffer.contains_str_at(
4518 selection.start.saturating_sub(pair_start_len),
4519 &pair.start,
4520 ) {
4521 selection.start -= pair_start_len;
4522 selection.end += pair.end.len();
4523
4524 return selection;
4525 }
4526 }
4527 }
4528 }
4529
4530 selection
4531 })
4532 .collect();
4533
4534 drop(buffer);
4535 self.change_selections(None, window, cx, |selections| {
4536 selections.select(new_selections)
4537 });
4538 }
4539
4540 /// Iterate the given selections, and for each one, find the smallest surrounding
4541 /// autoclose region. This uses the ordering of the selections and the autoclose
4542 /// regions to avoid repeated comparisons.
4543 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4544 &'a self,
4545 selections: impl IntoIterator<Item = Selection<D>>,
4546 buffer: &'a MultiBufferSnapshot,
4547 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4548 let mut i = 0;
4549 let mut regions = self.autoclose_regions.as_slice();
4550 selections.into_iter().map(move |selection| {
4551 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4552
4553 let mut enclosing = None;
4554 while let Some(pair_state) = regions.get(i) {
4555 if pair_state.range.end.to_offset(buffer) < range.start {
4556 regions = ®ions[i + 1..];
4557 i = 0;
4558 } else if pair_state.range.start.to_offset(buffer) > range.end {
4559 break;
4560 } else {
4561 if pair_state.selection_id == selection.id {
4562 enclosing = Some(pair_state);
4563 }
4564 i += 1;
4565 }
4566 }
4567
4568 (selection, enclosing)
4569 })
4570 }
4571
4572 /// Remove any autoclose regions that no longer contain their selection.
4573 fn invalidate_autoclose_regions(
4574 &mut self,
4575 mut selections: &[Selection<Anchor>],
4576 buffer: &MultiBufferSnapshot,
4577 ) {
4578 self.autoclose_regions.retain(|state| {
4579 let mut i = 0;
4580 while let Some(selection) = selections.get(i) {
4581 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4582 selections = &selections[1..];
4583 continue;
4584 }
4585 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4586 break;
4587 }
4588 if selection.id == state.selection_id {
4589 return true;
4590 } else {
4591 i += 1;
4592 }
4593 }
4594 false
4595 });
4596 }
4597
4598 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4599 let offset = position.to_offset(buffer);
4600 let (word_range, kind) = buffer.surrounding_word(offset, true);
4601 if offset > word_range.start && kind == Some(CharKind::Word) {
4602 Some(
4603 buffer
4604 .text_for_range(word_range.start..offset)
4605 .collect::<String>(),
4606 )
4607 } else {
4608 None
4609 }
4610 }
4611
4612 pub fn toggle_inline_values(
4613 &mut self,
4614 _: &ToggleInlineValues,
4615 _: &mut Window,
4616 cx: &mut Context<Self>,
4617 ) {
4618 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4619
4620 self.refresh_inline_values(cx);
4621 }
4622
4623 pub fn toggle_inlay_hints(
4624 &mut self,
4625 _: &ToggleInlayHints,
4626 _: &mut Window,
4627 cx: &mut Context<Self>,
4628 ) {
4629 self.refresh_inlay_hints(
4630 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4631 cx,
4632 );
4633 }
4634
4635 pub fn inlay_hints_enabled(&self) -> bool {
4636 self.inlay_hint_cache.enabled
4637 }
4638
4639 pub fn inline_values_enabled(&self) -> bool {
4640 self.inline_value_cache.enabled
4641 }
4642
4643 #[cfg(any(test, feature = "test-support"))]
4644 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4645 self.display_map
4646 .read(cx)
4647 .current_inlays()
4648 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4649 .cloned()
4650 .collect()
4651 }
4652
4653 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4654 if self.semantics_provider.is_none() || !self.mode.is_full() {
4655 return;
4656 }
4657
4658 let reason_description = reason.description();
4659 let ignore_debounce = matches!(
4660 reason,
4661 InlayHintRefreshReason::SettingsChange(_)
4662 | InlayHintRefreshReason::Toggle(_)
4663 | InlayHintRefreshReason::ExcerptsRemoved(_)
4664 | InlayHintRefreshReason::ModifiersChanged(_)
4665 );
4666 let (invalidate_cache, required_languages) = match reason {
4667 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4668 match self.inlay_hint_cache.modifiers_override(enabled) {
4669 Some(enabled) => {
4670 if enabled {
4671 (InvalidationStrategy::RefreshRequested, None)
4672 } else {
4673 self.splice_inlays(
4674 &self
4675 .visible_inlay_hints(cx)
4676 .iter()
4677 .map(|inlay| inlay.id)
4678 .collect::<Vec<InlayId>>(),
4679 Vec::new(),
4680 cx,
4681 );
4682 return;
4683 }
4684 }
4685 None => return,
4686 }
4687 }
4688 InlayHintRefreshReason::Toggle(enabled) => {
4689 if self.inlay_hint_cache.toggle(enabled) {
4690 if enabled {
4691 (InvalidationStrategy::RefreshRequested, None)
4692 } else {
4693 self.splice_inlays(
4694 &self
4695 .visible_inlay_hints(cx)
4696 .iter()
4697 .map(|inlay| inlay.id)
4698 .collect::<Vec<InlayId>>(),
4699 Vec::new(),
4700 cx,
4701 );
4702 return;
4703 }
4704 } else {
4705 return;
4706 }
4707 }
4708 InlayHintRefreshReason::SettingsChange(new_settings) => {
4709 match self.inlay_hint_cache.update_settings(
4710 &self.buffer,
4711 new_settings,
4712 self.visible_inlay_hints(cx),
4713 cx,
4714 ) {
4715 ControlFlow::Break(Some(InlaySplice {
4716 to_remove,
4717 to_insert,
4718 })) => {
4719 self.splice_inlays(&to_remove, to_insert, cx);
4720 return;
4721 }
4722 ControlFlow::Break(None) => return,
4723 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4724 }
4725 }
4726 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4727 if let Some(InlaySplice {
4728 to_remove,
4729 to_insert,
4730 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4731 {
4732 self.splice_inlays(&to_remove, to_insert, cx);
4733 }
4734 self.display_map.update(cx, |display_map, _| {
4735 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4736 });
4737 return;
4738 }
4739 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4740 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4741 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4742 }
4743 InlayHintRefreshReason::RefreshRequested => {
4744 (InvalidationStrategy::RefreshRequested, None)
4745 }
4746 };
4747
4748 if let Some(InlaySplice {
4749 to_remove,
4750 to_insert,
4751 }) = self.inlay_hint_cache.spawn_hint_refresh(
4752 reason_description,
4753 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4754 invalidate_cache,
4755 ignore_debounce,
4756 cx,
4757 ) {
4758 self.splice_inlays(&to_remove, to_insert, cx);
4759 }
4760 }
4761
4762 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4763 self.display_map
4764 .read(cx)
4765 .current_inlays()
4766 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4767 .cloned()
4768 .collect()
4769 }
4770
4771 pub fn excerpts_for_inlay_hints_query(
4772 &self,
4773 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4774 cx: &mut Context<Editor>,
4775 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4776 let Some(project) = self.project.as_ref() else {
4777 return HashMap::default();
4778 };
4779 let project = project.read(cx);
4780 let multi_buffer = self.buffer().read(cx);
4781 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4782 let multi_buffer_visible_start = self
4783 .scroll_manager
4784 .anchor()
4785 .anchor
4786 .to_point(&multi_buffer_snapshot);
4787 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4788 multi_buffer_visible_start
4789 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4790 Bias::Left,
4791 );
4792 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4793 multi_buffer_snapshot
4794 .range_to_buffer_ranges(multi_buffer_visible_range)
4795 .into_iter()
4796 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4797 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4798 let buffer_file = project::File::from_dyn(buffer.file())?;
4799 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4800 let worktree_entry = buffer_worktree
4801 .read(cx)
4802 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4803 if worktree_entry.is_ignored {
4804 return None;
4805 }
4806
4807 let language = buffer.language()?;
4808 if let Some(restrict_to_languages) = restrict_to_languages {
4809 if !restrict_to_languages.contains(language) {
4810 return None;
4811 }
4812 }
4813 Some((
4814 excerpt_id,
4815 (
4816 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4817 buffer.version().clone(),
4818 excerpt_visible_range,
4819 ),
4820 ))
4821 })
4822 .collect()
4823 }
4824
4825 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4826 TextLayoutDetails {
4827 text_system: window.text_system().clone(),
4828 editor_style: self.style.clone().unwrap(),
4829 rem_size: window.rem_size(),
4830 scroll_anchor: self.scroll_manager.anchor(),
4831 visible_rows: self.visible_line_count(),
4832 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4833 }
4834 }
4835
4836 pub fn splice_inlays(
4837 &self,
4838 to_remove: &[InlayId],
4839 to_insert: Vec<Inlay>,
4840 cx: &mut Context<Self>,
4841 ) {
4842 self.display_map.update(cx, |display_map, cx| {
4843 display_map.splice_inlays(to_remove, to_insert, cx)
4844 });
4845 cx.notify();
4846 }
4847
4848 fn trigger_on_type_formatting(
4849 &self,
4850 input: String,
4851 window: &mut Window,
4852 cx: &mut Context<Self>,
4853 ) -> Option<Task<Result<()>>> {
4854 if input.len() != 1 {
4855 return None;
4856 }
4857
4858 let project = self.project.as_ref()?;
4859 let position = self.selections.newest_anchor().head();
4860 let (buffer, buffer_position) = self
4861 .buffer
4862 .read(cx)
4863 .text_anchor_for_position(position, cx)?;
4864
4865 let settings = language_settings::language_settings(
4866 buffer
4867 .read(cx)
4868 .language_at(buffer_position)
4869 .map(|l| l.name()),
4870 buffer.read(cx).file(),
4871 cx,
4872 );
4873 if !settings.use_on_type_format {
4874 return None;
4875 }
4876
4877 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
4878 // hence we do LSP request & edit on host side only — add formats to host's history.
4879 let push_to_lsp_host_history = true;
4880 // If this is not the host, append its history with new edits.
4881 let push_to_client_history = project.read(cx).is_via_collab();
4882
4883 let on_type_formatting = project.update(cx, |project, cx| {
4884 project.on_type_format(
4885 buffer.clone(),
4886 buffer_position,
4887 input,
4888 push_to_lsp_host_history,
4889 cx,
4890 )
4891 });
4892 Some(cx.spawn_in(window, async move |editor, cx| {
4893 if let Some(transaction) = on_type_formatting.await? {
4894 if push_to_client_history {
4895 buffer
4896 .update(cx, |buffer, _| {
4897 buffer.push_transaction(transaction, Instant::now());
4898 buffer.finalize_last_transaction();
4899 })
4900 .ok();
4901 }
4902 editor.update(cx, |editor, cx| {
4903 editor.refresh_document_highlights(cx);
4904 })?;
4905 }
4906 Ok(())
4907 }))
4908 }
4909
4910 pub fn show_word_completions(
4911 &mut self,
4912 _: &ShowWordCompletions,
4913 window: &mut Window,
4914 cx: &mut Context<Self>,
4915 ) {
4916 self.open_completions_menu(true, None, window, cx);
4917 }
4918
4919 pub fn show_completions(
4920 &mut self,
4921 options: &ShowCompletions,
4922 window: &mut Window,
4923 cx: &mut Context<Self>,
4924 ) {
4925 self.open_completions_menu(false, options.trigger.as_deref(), window, cx);
4926 }
4927
4928 fn open_completions_menu(
4929 &mut self,
4930 ignore_completion_provider: bool,
4931 trigger: Option<&str>,
4932 window: &mut Window,
4933 cx: &mut Context<Self>,
4934 ) {
4935 if self.pending_rename.is_some() {
4936 return;
4937 }
4938 if !self.snippet_stack.is_empty() && self.context_menu.borrow().as_ref().is_some() {
4939 return;
4940 }
4941
4942 let position = self.selections.newest_anchor().head();
4943 if position.diff_base_anchor.is_some() {
4944 return;
4945 }
4946 let (buffer, buffer_position) =
4947 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
4948 output
4949 } else {
4950 return;
4951 };
4952 let buffer_snapshot = buffer.read(cx).snapshot();
4953 let show_completion_documentation = buffer_snapshot
4954 .settings_at(buffer_position, cx)
4955 .show_completion_documentation;
4956
4957 let query = Self::completion_query(&self.buffer.read(cx).read(cx), position);
4958
4959 let trigger_kind = match trigger {
4960 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
4961 CompletionTriggerKind::TRIGGER_CHARACTER
4962 }
4963 _ => CompletionTriggerKind::INVOKED,
4964 };
4965 let completion_context = CompletionContext {
4966 trigger_character: trigger.and_then(|trigger| {
4967 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
4968 Some(String::from(trigger))
4969 } else {
4970 None
4971 }
4972 }),
4973 trigger_kind,
4974 };
4975
4976 let (old_range, word_kind) = buffer_snapshot.surrounding_word(buffer_position);
4977 let (old_range, word_to_exclude) = if word_kind == Some(CharKind::Word) {
4978 let word_to_exclude = buffer_snapshot
4979 .text_for_range(old_range.clone())
4980 .collect::<String>();
4981 (
4982 buffer_snapshot.anchor_before(old_range.start)
4983 ..buffer_snapshot.anchor_after(old_range.end),
4984 Some(word_to_exclude),
4985 )
4986 } else {
4987 (buffer_position..buffer_position, None)
4988 };
4989
4990 let language = buffer_snapshot
4991 .language_at(buffer_position)
4992 .map(|language| language.name());
4993
4994 let completion_settings =
4995 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
4996
4997 // The document can be large, so stay in reasonable bounds when searching for words,
4998 // otherwise completion pop-up might be slow to appear.
4999 const WORD_LOOKUP_ROWS: u32 = 5_000;
5000 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5001 let min_word_search = buffer_snapshot.clip_point(
5002 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5003 Bias::Left,
5004 );
5005 let max_word_search = buffer_snapshot.clip_point(
5006 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5007 Bias::Right,
5008 );
5009 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5010 ..buffer_snapshot.point_to_offset(max_word_search);
5011
5012 let provider = if ignore_completion_provider {
5013 None
5014 } else {
5015 self.completion_provider.clone()
5016 };
5017 let skip_digits = query
5018 .as_ref()
5019 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5020
5021 let (mut words, provided_completions) = match &provider {
5022 Some(provider) => {
5023 let completions = provider.completions(
5024 position.excerpt_id,
5025 &buffer,
5026 buffer_position,
5027 completion_context,
5028 window,
5029 cx,
5030 );
5031
5032 let words = match completion_settings.words {
5033 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5034 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5035 .background_spawn(async move {
5036 buffer_snapshot.words_in_range(WordsQuery {
5037 fuzzy_contents: None,
5038 range: word_search_range,
5039 skip_digits,
5040 })
5041 }),
5042 };
5043
5044 (words, completions)
5045 }
5046 None => (
5047 cx.background_spawn(async move {
5048 buffer_snapshot.words_in_range(WordsQuery {
5049 fuzzy_contents: None,
5050 range: word_search_range,
5051 skip_digits,
5052 })
5053 }),
5054 Task::ready(Ok(None)),
5055 ),
5056 };
5057
5058 let sort_completions = provider
5059 .as_ref()
5060 .map_or(false, |provider| provider.sort_completions());
5061
5062 let filter_completions = provider
5063 .as_ref()
5064 .map_or(true, |provider| provider.filter_completions());
5065
5066 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5067
5068 let id = post_inc(&mut self.next_completion_id);
5069 let task = cx.spawn_in(window, async move |editor, cx| {
5070 async move {
5071 editor.update(cx, |this, _| {
5072 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5073 })?;
5074
5075 let mut completions = Vec::new();
5076 if let Some(provided_completions) = provided_completions.await.log_err().flatten() {
5077 completions.extend(provided_completions);
5078 if completion_settings.words == WordsCompletionMode::Fallback {
5079 words = Task::ready(BTreeMap::default());
5080 }
5081 }
5082
5083 let mut words = words.await;
5084 if let Some(word_to_exclude) = &word_to_exclude {
5085 words.remove(word_to_exclude);
5086 }
5087 for lsp_completion in &completions {
5088 words.remove(&lsp_completion.new_text);
5089 }
5090 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5091 replace_range: old_range.clone(),
5092 new_text: word.clone(),
5093 label: CodeLabel::plain(word, None),
5094 icon_path: None,
5095 documentation: None,
5096 source: CompletionSource::BufferWord {
5097 word_range,
5098 resolved: false,
5099 },
5100 insert_text_mode: Some(InsertTextMode::AS_IS),
5101 confirm: None,
5102 }));
5103
5104 let menu = if completions.is_empty() {
5105 None
5106 } else {
5107 let mut menu = editor.update(cx, |editor, cx| {
5108 let languages = editor
5109 .workspace
5110 .as_ref()
5111 .and_then(|(workspace, _)| workspace.upgrade())
5112 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5113 CompletionsMenu::new(
5114 id,
5115 sort_completions,
5116 show_completion_documentation,
5117 ignore_completion_provider,
5118 position,
5119 buffer.clone(),
5120 completions.into(),
5121 snippet_sort_order,
5122 languages,
5123 language,
5124 cx,
5125 )
5126 })?;
5127
5128 menu.filter(
5129 if filter_completions {
5130 query.as_deref()
5131 } else {
5132 None
5133 },
5134 provider,
5135 editor.clone(),
5136 cx,
5137 )
5138 .await;
5139
5140 menu.visible().then_some(menu)
5141 };
5142
5143 editor.update_in(cx, |editor, window, cx| {
5144 match editor.context_menu.borrow().as_ref() {
5145 None => {}
5146 Some(CodeContextMenu::Completions(prev_menu)) => {
5147 if prev_menu.id > id {
5148 return;
5149 }
5150 }
5151 _ => return,
5152 }
5153
5154 if editor.focus_handle.is_focused(window) && menu.is_some() {
5155 let mut menu = menu.unwrap();
5156 menu.resolve_visible_completions(editor.completion_provider.as_deref(), cx);
5157 crate::hover_popover::hide_hover(editor, cx);
5158 *editor.context_menu.borrow_mut() =
5159 Some(CodeContextMenu::Completions(menu));
5160
5161 if editor.show_edit_predictions_in_menu() {
5162 editor.update_visible_inline_completion(window, cx);
5163 } else {
5164 editor.discard_inline_completion(false, cx);
5165 }
5166
5167 cx.notify();
5168 } else if editor.completion_tasks.len() <= 1 {
5169 // If there are no more completion tasks and the last menu was
5170 // empty, we should hide it.
5171 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5172 // If it was already hidden and we don't show inline
5173 // completions in the menu, we should also show the
5174 // inline-completion when available.
5175 if was_hidden && editor.show_edit_predictions_in_menu() {
5176 editor.update_visible_inline_completion(window, cx);
5177 }
5178 }
5179 })?;
5180
5181 anyhow::Ok(())
5182 }
5183 .log_err()
5184 .await
5185 });
5186
5187 self.completion_tasks.push((id, task));
5188 }
5189
5190 #[cfg(feature = "test-support")]
5191 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5192 let menu = self.context_menu.borrow();
5193 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5194 let completions = menu.completions.borrow();
5195 Some(completions.to_vec())
5196 } else {
5197 None
5198 }
5199 }
5200
5201 pub fn with_completions_menu_matching_id<R>(
5202 &self,
5203 id: CompletionId,
5204 on_absent: impl FnOnce() -> R,
5205 on_match: impl FnOnce(&mut CompletionsMenu) -> R,
5206 ) -> R {
5207 let mut context_menu = self.context_menu.borrow_mut();
5208 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5209 return on_absent();
5210 };
5211 if completions_menu.id != id {
5212 return on_absent();
5213 }
5214 on_match(completions_menu)
5215 }
5216
5217 pub fn confirm_completion(
5218 &mut self,
5219 action: &ConfirmCompletion,
5220 window: &mut Window,
5221 cx: &mut Context<Self>,
5222 ) -> Option<Task<Result<()>>> {
5223 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5224 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5225 }
5226
5227 pub fn confirm_completion_insert(
5228 &mut self,
5229 _: &ConfirmCompletionInsert,
5230 window: &mut Window,
5231 cx: &mut Context<Self>,
5232 ) -> Option<Task<Result<()>>> {
5233 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5234 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5235 }
5236
5237 pub fn confirm_completion_replace(
5238 &mut self,
5239 _: &ConfirmCompletionReplace,
5240 window: &mut Window,
5241 cx: &mut Context<Self>,
5242 ) -> Option<Task<Result<()>>> {
5243 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5244 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5245 }
5246
5247 pub fn compose_completion(
5248 &mut self,
5249 action: &ComposeCompletion,
5250 window: &mut Window,
5251 cx: &mut Context<Self>,
5252 ) -> Option<Task<Result<()>>> {
5253 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5254 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5255 }
5256
5257 fn do_completion(
5258 &mut self,
5259 item_ix: Option<usize>,
5260 intent: CompletionIntent,
5261 window: &mut Window,
5262 cx: &mut Context<Editor>,
5263 ) -> Option<Task<Result<()>>> {
5264 use language::ToOffset as _;
5265
5266 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5267 else {
5268 return None;
5269 };
5270
5271 let candidate_id = {
5272 let entries = completions_menu.entries.borrow();
5273 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5274 if self.show_edit_predictions_in_menu() {
5275 self.discard_inline_completion(true, cx);
5276 }
5277 mat.candidate_id
5278 };
5279
5280 let buffer_handle = completions_menu.buffer;
5281 let completion = completions_menu
5282 .completions
5283 .borrow()
5284 .get(candidate_id)?
5285 .clone();
5286 cx.stop_propagation();
5287
5288 let snapshot = self.buffer.read(cx).snapshot(cx);
5289 let newest_anchor = self.selections.newest_anchor();
5290
5291 let snippet;
5292 let new_text;
5293 if completion.is_snippet() {
5294 let mut snippet_source = completion.new_text.clone();
5295 if let Some(scope) = snapshot.language_scope_at(newest_anchor.head()) {
5296 if scope.prefers_label_for_snippet_in_completion() {
5297 if let Some(label) = completion.label() {
5298 if matches!(
5299 completion.kind(),
5300 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
5301 ) {
5302 snippet_source = label;
5303 }
5304 }
5305 }
5306 }
5307 snippet = Some(Snippet::parse(&snippet_source).log_err()?);
5308 new_text = snippet.as_ref().unwrap().text.clone();
5309 } else {
5310 snippet = None;
5311 new_text = completion.new_text.clone();
5312 };
5313
5314 let replace_range = choose_completion_range(&completion, intent, &buffer_handle, cx);
5315 let buffer = buffer_handle.read(cx);
5316 let replace_range_multibuffer = {
5317 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5318 let multibuffer_anchor = snapshot
5319 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5320 .unwrap()
5321 ..snapshot
5322 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5323 .unwrap();
5324 multibuffer_anchor.start.to_offset(&snapshot)
5325 ..multibuffer_anchor.end.to_offset(&snapshot)
5326 };
5327 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5328 return None;
5329 }
5330
5331 let old_text = buffer
5332 .text_for_range(replace_range.clone())
5333 .collect::<String>();
5334 let lookbehind = newest_anchor
5335 .start
5336 .text_anchor
5337 .to_offset(buffer)
5338 .saturating_sub(replace_range.start);
5339 let lookahead = replace_range
5340 .end
5341 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5342 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5343 let suffix = &old_text[lookbehind.min(old_text.len())..];
5344
5345 let selections = self.selections.all::<usize>(cx);
5346 let mut ranges = Vec::new();
5347 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5348
5349 for selection in &selections {
5350 let range = if selection.id == newest_anchor.id {
5351 replace_range_multibuffer.clone()
5352 } else {
5353 let mut range = selection.range();
5354
5355 // if prefix is present, don't duplicate it
5356 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5357 range.start = range.start.saturating_sub(lookbehind);
5358
5359 // if suffix is also present, mimic the newest cursor and replace it
5360 if selection.id != newest_anchor.id
5361 && snapshot.contains_str_at(range.end, suffix)
5362 {
5363 range.end += lookahead;
5364 }
5365 }
5366 range
5367 };
5368
5369 ranges.push(range.clone());
5370
5371 if !self.linked_edit_ranges.is_empty() {
5372 let start_anchor = snapshot.anchor_before(range.start);
5373 let end_anchor = snapshot.anchor_after(range.end);
5374 if let Some(ranges) = self
5375 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5376 {
5377 for (buffer, edits) in ranges {
5378 linked_edits
5379 .entry(buffer.clone())
5380 .or_default()
5381 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5382 }
5383 }
5384 }
5385 }
5386
5387 cx.emit(EditorEvent::InputHandled {
5388 utf16_range_to_replace: None,
5389 text: new_text.clone().into(),
5390 });
5391
5392 self.transact(window, cx, |this, window, cx| {
5393 if let Some(mut snippet) = snippet {
5394 snippet.text = new_text.to_string();
5395 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5396 } else {
5397 this.buffer.update(cx, |buffer, cx| {
5398 let auto_indent = match completion.insert_text_mode {
5399 Some(InsertTextMode::AS_IS) => None,
5400 _ => this.autoindent_mode.clone(),
5401 };
5402 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5403 buffer.edit(edits, auto_indent, cx);
5404 });
5405 }
5406 for (buffer, edits) in linked_edits {
5407 buffer.update(cx, |buffer, cx| {
5408 let snapshot = buffer.snapshot();
5409 let edits = edits
5410 .into_iter()
5411 .map(|(range, text)| {
5412 use text::ToPoint as TP;
5413 let end_point = TP::to_point(&range.end, &snapshot);
5414 let start_point = TP::to_point(&range.start, &snapshot);
5415 (start_point..end_point, text)
5416 })
5417 .sorted_by_key(|(range, _)| range.start);
5418 buffer.edit(edits, None, cx);
5419 })
5420 }
5421
5422 this.refresh_inline_completion(true, false, window, cx);
5423 });
5424
5425 let show_new_completions_on_confirm = completion
5426 .confirm
5427 .as_ref()
5428 .map_or(false, |confirm| confirm(intent, window, cx));
5429 if show_new_completions_on_confirm {
5430 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5431 }
5432
5433 let provider = self.completion_provider.as_ref()?;
5434 drop(completion);
5435 let apply_edits = provider.apply_additional_edits_for_completion(
5436 buffer_handle,
5437 completions_menu.completions.clone(),
5438 candidate_id,
5439 true,
5440 cx,
5441 );
5442
5443 let editor_settings = EditorSettings::get_global(cx);
5444 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5445 // After the code completion is finished, users often want to know what signatures are needed.
5446 // so we should automatically call signature_help
5447 self.show_signature_help(&ShowSignatureHelp, window, cx);
5448 }
5449
5450 Some(cx.foreground_executor().spawn(async move {
5451 apply_edits.await?;
5452 Ok(())
5453 }))
5454 }
5455
5456 pub fn toggle_code_actions(
5457 &mut self,
5458 action: &ToggleCodeActions,
5459 window: &mut Window,
5460 cx: &mut Context<Self>,
5461 ) {
5462 let quick_launch = action.quick_launch;
5463 let mut context_menu = self.context_menu.borrow_mut();
5464 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5465 if code_actions.deployed_from == action.deployed_from {
5466 // Toggle if we're selecting the same one
5467 *context_menu = None;
5468 cx.notify();
5469 return;
5470 } else {
5471 // Otherwise, clear it and start a new one
5472 *context_menu = None;
5473 cx.notify();
5474 }
5475 }
5476 drop(context_menu);
5477 let snapshot = self.snapshot(window, cx);
5478 let deployed_from = action.deployed_from.clone();
5479 let mut task = self.code_actions_task.take();
5480 let action = action.clone();
5481 cx.spawn_in(window, async move |editor, cx| {
5482 while let Some(prev_task) = task {
5483 prev_task.await.log_err();
5484 task = editor.update(cx, |this, _| this.code_actions_task.take())?;
5485 }
5486
5487 let spawned_test_task = editor.update_in(cx, |editor, window, cx| {
5488 if editor.focus_handle.is_focused(window) {
5489 let multibuffer_point = match &action.deployed_from {
5490 Some(CodeActionSource::Indicator(row)) => {
5491 DisplayPoint::new(*row, 0).to_point(&snapshot)
5492 }
5493 _ => editor.selections.newest::<Point>(cx).head(),
5494 };
5495 let (buffer, buffer_row) = snapshot
5496 .buffer_snapshot
5497 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5498 .and_then(|(buffer_snapshot, range)| {
5499 editor
5500 .buffer
5501 .read(cx)
5502 .buffer(buffer_snapshot.remote_id())
5503 .map(|buffer| (buffer, range.start.row))
5504 })?;
5505 let (_, code_actions) = editor
5506 .available_code_actions
5507 .clone()
5508 .and_then(|(location, code_actions)| {
5509 let snapshot = location.buffer.read(cx).snapshot();
5510 let point_range = location.range.to_point(&snapshot);
5511 let point_range = point_range.start.row..=point_range.end.row;
5512 if point_range.contains(&buffer_row) {
5513 Some((location, code_actions))
5514 } else {
5515 None
5516 }
5517 })
5518 .unzip();
5519 let buffer_id = buffer.read(cx).remote_id();
5520 let tasks = editor
5521 .tasks
5522 .get(&(buffer_id, buffer_row))
5523 .map(|t| Arc::new(t.to_owned()));
5524 if tasks.is_none() && code_actions.is_none() {
5525 return None;
5526 }
5527
5528 editor.completion_tasks.clear();
5529 editor.discard_inline_completion(false, cx);
5530 let task_context =
5531 tasks
5532 .as_ref()
5533 .zip(editor.project.clone())
5534 .map(|(tasks, project)| {
5535 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx)
5536 });
5537
5538 Some(cx.spawn_in(window, async move |editor, cx| {
5539 let task_context = match task_context {
5540 Some(task_context) => task_context.await,
5541 None => None,
5542 };
5543 let resolved_tasks =
5544 tasks
5545 .zip(task_context.clone())
5546 .map(|(tasks, task_context)| ResolvedTasks {
5547 templates: tasks.resolve(&task_context).collect(),
5548 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5549 multibuffer_point.row,
5550 tasks.column,
5551 )),
5552 });
5553 let debug_scenarios = editor.update(cx, |editor, cx| {
5554 if cx.has_flag::<DebuggerFeatureFlag>() {
5555 maybe!({
5556 let project = editor.project.as_ref()?;
5557 let dap_store = project.read(cx).dap_store();
5558 let mut scenarios = vec![];
5559 let resolved_tasks = resolved_tasks.as_ref()?;
5560 let buffer = buffer.read(cx);
5561 let language = buffer.language()?;
5562 let file = buffer.file();
5563 let debug_adapter =
5564 language_settings(language.name().into(), file, cx)
5565 .debuggers
5566 .first()
5567 .map(SharedString::from)
5568 .or_else(|| {
5569 language
5570 .config()
5571 .debuggers
5572 .first()
5573 .map(SharedString::from)
5574 })?;
5575
5576 dap_store.update(cx, |dap_store, cx| {
5577 for (_, task) in &resolved_tasks.templates {
5578 if let Some(scenario) = dap_store
5579 .debug_scenario_for_build_task(
5580 task.original_task().clone(),
5581 debug_adapter.clone().into(),
5582 task.display_label().to_owned().into(),
5583 cx,
5584 )
5585 {
5586 scenarios.push(scenario);
5587 }
5588 }
5589 });
5590 Some(scenarios)
5591 })
5592 .unwrap_or_default()
5593 } else {
5594 vec![]
5595 }
5596 })?;
5597 let spawn_straight_away = quick_launch
5598 && resolved_tasks
5599 .as_ref()
5600 .map_or(false, |tasks| tasks.templates.len() == 1)
5601 && code_actions
5602 .as_ref()
5603 .map_or(true, |actions| actions.is_empty())
5604 && debug_scenarios.is_empty();
5605 if let Ok(task) = editor.update_in(cx, |editor, window, cx| {
5606 crate::hover_popover::hide_hover(editor, cx);
5607 *editor.context_menu.borrow_mut() =
5608 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5609 buffer,
5610 actions: CodeActionContents::new(
5611 resolved_tasks,
5612 code_actions,
5613 debug_scenarios,
5614 task_context.unwrap_or_default(),
5615 ),
5616 selected_item: Default::default(),
5617 scroll_handle: UniformListScrollHandle::default(),
5618 deployed_from,
5619 }));
5620 if spawn_straight_away {
5621 if let Some(task) = editor.confirm_code_action(
5622 &ConfirmCodeAction { item_ix: Some(0) },
5623 window,
5624 cx,
5625 ) {
5626 cx.notify();
5627 return task;
5628 }
5629 }
5630 cx.notify();
5631 Task::ready(Ok(()))
5632 }) {
5633 task.await
5634 } else {
5635 Ok(())
5636 }
5637 }))
5638 } else {
5639 Some(Task::ready(Ok(())))
5640 }
5641 })?;
5642 if let Some(task) = spawned_test_task {
5643 task.await?;
5644 }
5645
5646 anyhow::Ok(())
5647 })
5648 .detach_and_log_err(cx);
5649 }
5650
5651 pub fn confirm_code_action(
5652 &mut self,
5653 action: &ConfirmCodeAction,
5654 window: &mut Window,
5655 cx: &mut Context<Self>,
5656 ) -> Option<Task<Result<()>>> {
5657 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5658
5659 let actions_menu =
5660 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5661 menu
5662 } else {
5663 return None;
5664 };
5665
5666 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5667 let action = actions_menu.actions.get(action_ix)?;
5668 let title = action.label();
5669 let buffer = actions_menu.buffer;
5670 let workspace = self.workspace()?;
5671
5672 match action {
5673 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5674 workspace.update(cx, |workspace, cx| {
5675 workspace.schedule_resolved_task(
5676 task_source_kind,
5677 resolved_task,
5678 false,
5679 window,
5680 cx,
5681 );
5682
5683 Some(Task::ready(Ok(())))
5684 })
5685 }
5686 CodeActionsItem::CodeAction {
5687 excerpt_id,
5688 action,
5689 provider,
5690 } => {
5691 let apply_code_action =
5692 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5693 let workspace = workspace.downgrade();
5694 Some(cx.spawn_in(window, async move |editor, cx| {
5695 let project_transaction = apply_code_action.await?;
5696 Self::open_project_transaction(
5697 &editor,
5698 workspace,
5699 project_transaction,
5700 title,
5701 cx,
5702 )
5703 .await
5704 }))
5705 }
5706 CodeActionsItem::DebugScenario(scenario) => {
5707 let context = actions_menu.actions.context.clone();
5708
5709 workspace.update(cx, |workspace, cx| {
5710 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5711 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5712 });
5713 Some(Task::ready(Ok(())))
5714 }
5715 }
5716 }
5717
5718 pub async fn open_project_transaction(
5719 this: &WeakEntity<Editor>,
5720 workspace: WeakEntity<Workspace>,
5721 transaction: ProjectTransaction,
5722 title: String,
5723 cx: &mut AsyncWindowContext,
5724 ) -> Result<()> {
5725 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5726 cx.update(|_, cx| {
5727 entries.sort_unstable_by_key(|(buffer, _)| {
5728 buffer.read(cx).file().map(|f| f.path().clone())
5729 });
5730 })?;
5731
5732 // If the project transaction's edits are all contained within this editor, then
5733 // avoid opening a new editor to display them.
5734
5735 if let Some((buffer, transaction)) = entries.first() {
5736 if entries.len() == 1 {
5737 let excerpt = this.update(cx, |editor, cx| {
5738 editor
5739 .buffer()
5740 .read(cx)
5741 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
5742 })?;
5743 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
5744 if excerpted_buffer == *buffer {
5745 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
5746 let excerpt_range = excerpt_range.to_offset(buffer);
5747 buffer
5748 .edited_ranges_for_transaction::<usize>(transaction)
5749 .all(|range| {
5750 excerpt_range.start <= range.start
5751 && excerpt_range.end >= range.end
5752 })
5753 })?;
5754
5755 if all_edits_within_excerpt {
5756 return Ok(());
5757 }
5758 }
5759 }
5760 }
5761 } else {
5762 return Ok(());
5763 }
5764
5765 let mut ranges_to_highlight = Vec::new();
5766 let excerpt_buffer = cx.new(|cx| {
5767 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
5768 for (buffer_handle, transaction) in &entries {
5769 let edited_ranges = buffer_handle
5770 .read(cx)
5771 .edited_ranges_for_transaction::<Point>(transaction)
5772 .collect::<Vec<_>>();
5773 let (ranges, _) = multibuffer.set_excerpts_for_path(
5774 PathKey::for_buffer(buffer_handle, cx),
5775 buffer_handle.clone(),
5776 edited_ranges,
5777 DEFAULT_MULTIBUFFER_CONTEXT,
5778 cx,
5779 );
5780
5781 ranges_to_highlight.extend(ranges);
5782 }
5783 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
5784 multibuffer
5785 })?;
5786
5787 workspace.update_in(cx, |workspace, window, cx| {
5788 let project = workspace.project().clone();
5789 let editor =
5790 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
5791 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
5792 editor.update(cx, |editor, cx| {
5793 editor.highlight_background::<Self>(
5794 &ranges_to_highlight,
5795 |theme| theme.editor_highlighted_line_background,
5796 cx,
5797 );
5798 });
5799 })?;
5800
5801 Ok(())
5802 }
5803
5804 pub fn clear_code_action_providers(&mut self) {
5805 self.code_action_providers.clear();
5806 self.available_code_actions.take();
5807 }
5808
5809 pub fn add_code_action_provider(
5810 &mut self,
5811 provider: Rc<dyn CodeActionProvider>,
5812 window: &mut Window,
5813 cx: &mut Context<Self>,
5814 ) {
5815 if self
5816 .code_action_providers
5817 .iter()
5818 .any(|existing_provider| existing_provider.id() == provider.id())
5819 {
5820 return;
5821 }
5822
5823 self.code_action_providers.push(provider);
5824 self.refresh_code_actions(window, cx);
5825 }
5826
5827 pub fn remove_code_action_provider(
5828 &mut self,
5829 id: Arc<str>,
5830 window: &mut Window,
5831 cx: &mut Context<Self>,
5832 ) {
5833 self.code_action_providers
5834 .retain(|provider| provider.id() != id);
5835 self.refresh_code_actions(window, cx);
5836 }
5837
5838 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
5839 !self.code_action_providers.is_empty()
5840 && EditorSettings::get_global(cx).toolbar.code_actions
5841 }
5842
5843 pub fn has_available_code_actions(&self) -> bool {
5844 self.available_code_actions
5845 .as_ref()
5846 .is_some_and(|(_, actions)| !actions.is_empty())
5847 }
5848
5849 fn render_inline_code_actions(
5850 &self,
5851 icon_size: ui::IconSize,
5852 display_row: DisplayRow,
5853 is_active: bool,
5854 cx: &mut Context<Self>,
5855 ) -> AnyElement {
5856 let show_tooltip = !self.context_menu_visible();
5857 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
5858 .icon_size(icon_size)
5859 .shape(ui::IconButtonShape::Square)
5860 .style(ButtonStyle::Transparent)
5861 .icon_color(ui::Color::Hidden)
5862 .toggle_state(is_active)
5863 .when(show_tooltip, |this| {
5864 this.tooltip({
5865 let focus_handle = self.focus_handle.clone();
5866 move |window, cx| {
5867 Tooltip::for_action_in(
5868 "Toggle Code Actions",
5869 &ToggleCodeActions {
5870 deployed_from: None,
5871 quick_launch: false,
5872 },
5873 &focus_handle,
5874 window,
5875 cx,
5876 )
5877 }
5878 })
5879 })
5880 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
5881 window.focus(&editor.focus_handle(cx));
5882 editor.toggle_code_actions(
5883 &crate::actions::ToggleCodeActions {
5884 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
5885 display_row,
5886 )),
5887 quick_launch: false,
5888 },
5889 window,
5890 cx,
5891 );
5892 }))
5893 .into_any_element()
5894 }
5895
5896 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
5897 &self.context_menu
5898 }
5899
5900 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
5901 let newest_selection = self.selections.newest_anchor().clone();
5902 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
5903 let buffer = self.buffer.read(cx);
5904 if newest_selection.head().diff_base_anchor.is_some() {
5905 return None;
5906 }
5907 let (start_buffer, start) =
5908 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
5909 let (end_buffer, end) =
5910 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
5911 if start_buffer != end_buffer {
5912 return None;
5913 }
5914
5915 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
5916 cx.background_executor()
5917 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
5918 .await;
5919
5920 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
5921 let providers = this.code_action_providers.clone();
5922 let tasks = this
5923 .code_action_providers
5924 .iter()
5925 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
5926 .collect::<Vec<_>>();
5927 (providers, tasks)
5928 })?;
5929
5930 let mut actions = Vec::new();
5931 for (provider, provider_actions) in
5932 providers.into_iter().zip(future::join_all(tasks).await)
5933 {
5934 if let Some(provider_actions) = provider_actions.log_err() {
5935 actions.extend(provider_actions.into_iter().map(|action| {
5936 AvailableCodeAction {
5937 excerpt_id: newest_selection.start.excerpt_id,
5938 action,
5939 provider: provider.clone(),
5940 }
5941 }));
5942 }
5943 }
5944
5945 this.update(cx, |this, cx| {
5946 this.available_code_actions = if actions.is_empty() {
5947 None
5948 } else {
5949 Some((
5950 Location {
5951 buffer: start_buffer,
5952 range: start..end,
5953 },
5954 actions.into(),
5955 ))
5956 };
5957 cx.notify();
5958 })
5959 }));
5960 None
5961 }
5962
5963 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5964 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
5965 self.show_git_blame_inline = false;
5966
5967 self.show_git_blame_inline_delay_task =
5968 Some(cx.spawn_in(window, async move |this, cx| {
5969 cx.background_executor().timer(delay).await;
5970
5971 this.update(cx, |this, cx| {
5972 this.show_git_blame_inline = true;
5973 cx.notify();
5974 })
5975 .log_err();
5976 }));
5977 }
5978 }
5979
5980 fn show_blame_popover(
5981 &mut self,
5982 blame_entry: &BlameEntry,
5983 position: gpui::Point<Pixels>,
5984 cx: &mut Context<Self>,
5985 ) {
5986 if let Some(state) = &mut self.inline_blame_popover {
5987 state.hide_task.take();
5988 cx.notify();
5989 } else {
5990 let delay = EditorSettings::get_global(cx).hover_popover_delay;
5991 let show_task = cx.spawn(async move |editor, cx| {
5992 cx.background_executor()
5993 .timer(std::time::Duration::from_millis(delay))
5994 .await;
5995 editor
5996 .update(cx, |editor, cx| {
5997 if let Some(state) = &mut editor.inline_blame_popover {
5998 state.show_task = None;
5999 cx.notify();
6000 }
6001 })
6002 .ok();
6003 });
6004 let Some(blame) = self.blame.as_ref() else {
6005 return;
6006 };
6007 let blame = blame.read(cx);
6008 let details = blame.details_for_entry(&blame_entry);
6009 let markdown = cx.new(|cx| {
6010 Markdown::new(
6011 details
6012 .as_ref()
6013 .map(|message| message.message.clone())
6014 .unwrap_or_default(),
6015 None,
6016 None,
6017 cx,
6018 )
6019 });
6020 self.inline_blame_popover = Some(InlineBlamePopover {
6021 position,
6022 show_task: Some(show_task),
6023 hide_task: None,
6024 popover_bounds: None,
6025 popover_state: InlineBlamePopoverState {
6026 scroll_handle: ScrollHandle::new(),
6027 commit_message: details,
6028 markdown,
6029 },
6030 });
6031 }
6032 }
6033
6034 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6035 if let Some(state) = &mut self.inline_blame_popover {
6036 if state.show_task.is_some() {
6037 self.inline_blame_popover.take();
6038 cx.notify();
6039 } else {
6040 let hide_task = cx.spawn(async move |editor, cx| {
6041 cx.background_executor()
6042 .timer(std::time::Duration::from_millis(100))
6043 .await;
6044 editor
6045 .update(cx, |editor, cx| {
6046 editor.inline_blame_popover.take();
6047 cx.notify();
6048 })
6049 .ok();
6050 });
6051 state.hide_task = Some(hide_task);
6052 }
6053 }
6054 }
6055
6056 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6057 if self.pending_rename.is_some() {
6058 return None;
6059 }
6060
6061 let provider = self.semantics_provider.clone()?;
6062 let buffer = self.buffer.read(cx);
6063 let newest_selection = self.selections.newest_anchor().clone();
6064 let cursor_position = newest_selection.head();
6065 let (cursor_buffer, cursor_buffer_position) =
6066 buffer.text_anchor_for_position(cursor_position, cx)?;
6067 let (tail_buffer, tail_buffer_position) =
6068 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6069 if cursor_buffer != tail_buffer {
6070 return None;
6071 }
6072
6073 let snapshot = cursor_buffer.read(cx).snapshot();
6074 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6075 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6076 if start_word_range != end_word_range {
6077 self.document_highlights_task.take();
6078 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6079 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6080 return None;
6081 }
6082
6083 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6084 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6085 cx.background_executor()
6086 .timer(Duration::from_millis(debounce))
6087 .await;
6088
6089 let highlights = if let Some(highlights) = cx
6090 .update(|cx| {
6091 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6092 })
6093 .ok()
6094 .flatten()
6095 {
6096 highlights.await.log_err()
6097 } else {
6098 None
6099 };
6100
6101 if let Some(highlights) = highlights {
6102 this.update(cx, |this, cx| {
6103 if this.pending_rename.is_some() {
6104 return;
6105 }
6106
6107 let buffer_id = cursor_position.buffer_id;
6108 let buffer = this.buffer.read(cx);
6109 if !buffer
6110 .text_anchor_for_position(cursor_position, cx)
6111 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6112 {
6113 return;
6114 }
6115
6116 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6117 let mut write_ranges = Vec::new();
6118 let mut read_ranges = Vec::new();
6119 for highlight in highlights {
6120 for (excerpt_id, excerpt_range) in
6121 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6122 {
6123 let start = highlight
6124 .range
6125 .start
6126 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6127 let end = highlight
6128 .range
6129 .end
6130 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6131 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6132 continue;
6133 }
6134
6135 let range = Anchor {
6136 buffer_id,
6137 excerpt_id,
6138 text_anchor: start,
6139 diff_base_anchor: None,
6140 }..Anchor {
6141 buffer_id,
6142 excerpt_id,
6143 text_anchor: end,
6144 diff_base_anchor: None,
6145 };
6146 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6147 write_ranges.push(range);
6148 } else {
6149 read_ranges.push(range);
6150 }
6151 }
6152 }
6153
6154 this.highlight_background::<DocumentHighlightRead>(
6155 &read_ranges,
6156 |theme| theme.editor_document_highlight_read_background,
6157 cx,
6158 );
6159 this.highlight_background::<DocumentHighlightWrite>(
6160 &write_ranges,
6161 |theme| theme.editor_document_highlight_write_background,
6162 cx,
6163 );
6164 cx.notify();
6165 })
6166 .log_err();
6167 }
6168 }));
6169 None
6170 }
6171
6172 fn prepare_highlight_query_from_selection(
6173 &mut self,
6174 cx: &mut Context<Editor>,
6175 ) -> Option<(String, Range<Anchor>)> {
6176 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6177 return None;
6178 }
6179 if !EditorSettings::get_global(cx).selection_highlight {
6180 return None;
6181 }
6182 if self.selections.count() != 1 || self.selections.line_mode {
6183 return None;
6184 }
6185 let selection = self.selections.newest::<Point>(cx);
6186 if selection.is_empty() || selection.start.row != selection.end.row {
6187 return None;
6188 }
6189 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6190 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6191 let query = multi_buffer_snapshot
6192 .text_for_range(selection_anchor_range.clone())
6193 .collect::<String>();
6194 if query.trim().is_empty() {
6195 return None;
6196 }
6197 Some((query, selection_anchor_range))
6198 }
6199
6200 fn update_selection_occurrence_highlights(
6201 &mut self,
6202 query_text: String,
6203 query_range: Range<Anchor>,
6204 multi_buffer_range_to_query: Range<Point>,
6205 use_debounce: bool,
6206 window: &mut Window,
6207 cx: &mut Context<Editor>,
6208 ) -> Task<()> {
6209 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6210 cx.spawn_in(window, async move |editor, cx| {
6211 if use_debounce {
6212 cx.background_executor()
6213 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6214 .await;
6215 }
6216 let match_task = cx.background_spawn(async move {
6217 let buffer_ranges = multi_buffer_snapshot
6218 .range_to_buffer_ranges(multi_buffer_range_to_query)
6219 .into_iter()
6220 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6221 let mut match_ranges = Vec::new();
6222 let Ok(regex) = project::search::SearchQuery::text(
6223 query_text.clone(),
6224 false,
6225 false,
6226 false,
6227 Default::default(),
6228 Default::default(),
6229 false,
6230 None,
6231 ) else {
6232 return Vec::default();
6233 };
6234 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6235 match_ranges.extend(
6236 regex
6237 .search(&buffer_snapshot, Some(search_range.clone()))
6238 .await
6239 .into_iter()
6240 .filter_map(|match_range| {
6241 let match_start = buffer_snapshot
6242 .anchor_after(search_range.start + match_range.start);
6243 let match_end = buffer_snapshot
6244 .anchor_before(search_range.start + match_range.end);
6245 let match_anchor_range = Anchor::range_in_buffer(
6246 excerpt_id,
6247 buffer_snapshot.remote_id(),
6248 match_start..match_end,
6249 );
6250 (match_anchor_range != query_range).then_some(match_anchor_range)
6251 }),
6252 );
6253 }
6254 match_ranges
6255 });
6256 let match_ranges = match_task.await;
6257 editor
6258 .update_in(cx, |editor, _, cx| {
6259 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6260 if !match_ranges.is_empty() {
6261 editor.highlight_background::<SelectedTextHighlight>(
6262 &match_ranges,
6263 |theme| theme.editor_document_highlight_bracket_background,
6264 cx,
6265 )
6266 }
6267 })
6268 .log_err();
6269 })
6270 }
6271
6272 fn refresh_selected_text_highlights(
6273 &mut self,
6274 on_buffer_edit: bool,
6275 window: &mut Window,
6276 cx: &mut Context<Editor>,
6277 ) {
6278 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6279 else {
6280 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6281 self.quick_selection_highlight_task.take();
6282 self.debounced_selection_highlight_task.take();
6283 return;
6284 };
6285 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6286 if on_buffer_edit
6287 || self
6288 .quick_selection_highlight_task
6289 .as_ref()
6290 .map_or(true, |(prev_anchor_range, _)| {
6291 prev_anchor_range != &query_range
6292 })
6293 {
6294 let multi_buffer_visible_start = self
6295 .scroll_manager
6296 .anchor()
6297 .anchor
6298 .to_point(&multi_buffer_snapshot);
6299 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6300 multi_buffer_visible_start
6301 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6302 Bias::Left,
6303 );
6304 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6305 self.quick_selection_highlight_task = Some((
6306 query_range.clone(),
6307 self.update_selection_occurrence_highlights(
6308 query_text.clone(),
6309 query_range.clone(),
6310 multi_buffer_visible_range,
6311 false,
6312 window,
6313 cx,
6314 ),
6315 ));
6316 }
6317 if on_buffer_edit
6318 || self
6319 .debounced_selection_highlight_task
6320 .as_ref()
6321 .map_or(true, |(prev_anchor_range, _)| {
6322 prev_anchor_range != &query_range
6323 })
6324 {
6325 let multi_buffer_start = multi_buffer_snapshot
6326 .anchor_before(0)
6327 .to_point(&multi_buffer_snapshot);
6328 let multi_buffer_end = multi_buffer_snapshot
6329 .anchor_after(multi_buffer_snapshot.len())
6330 .to_point(&multi_buffer_snapshot);
6331 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6332 self.debounced_selection_highlight_task = Some((
6333 query_range.clone(),
6334 self.update_selection_occurrence_highlights(
6335 query_text,
6336 query_range,
6337 multi_buffer_full_range,
6338 true,
6339 window,
6340 cx,
6341 ),
6342 ));
6343 }
6344 }
6345
6346 pub fn refresh_inline_completion(
6347 &mut self,
6348 debounce: bool,
6349 user_requested: bool,
6350 window: &mut Window,
6351 cx: &mut Context<Self>,
6352 ) -> Option<()> {
6353 let provider = self.edit_prediction_provider()?;
6354 let cursor = self.selections.newest_anchor().head();
6355 let (buffer, cursor_buffer_position) =
6356 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6357
6358 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6359 self.discard_inline_completion(false, cx);
6360 return None;
6361 }
6362
6363 if !user_requested
6364 && (!self.should_show_edit_predictions()
6365 || !self.is_focused(window)
6366 || buffer.read(cx).is_empty())
6367 {
6368 self.discard_inline_completion(false, cx);
6369 return None;
6370 }
6371
6372 self.update_visible_inline_completion(window, cx);
6373 provider.refresh(
6374 self.project.clone(),
6375 buffer,
6376 cursor_buffer_position,
6377 debounce,
6378 cx,
6379 );
6380 Some(())
6381 }
6382
6383 fn show_edit_predictions_in_menu(&self) -> bool {
6384 match self.edit_prediction_settings {
6385 EditPredictionSettings::Disabled => false,
6386 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6387 }
6388 }
6389
6390 pub fn edit_predictions_enabled(&self) -> bool {
6391 match self.edit_prediction_settings {
6392 EditPredictionSettings::Disabled => false,
6393 EditPredictionSettings::Enabled { .. } => true,
6394 }
6395 }
6396
6397 fn edit_prediction_requires_modifier(&self) -> bool {
6398 match self.edit_prediction_settings {
6399 EditPredictionSettings::Disabled => false,
6400 EditPredictionSettings::Enabled {
6401 preview_requires_modifier,
6402 ..
6403 } => preview_requires_modifier,
6404 }
6405 }
6406
6407 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6408 if self.edit_prediction_provider.is_none() {
6409 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6410 } else {
6411 let selection = self.selections.newest_anchor();
6412 let cursor = selection.head();
6413
6414 if let Some((buffer, cursor_buffer_position)) =
6415 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6416 {
6417 self.edit_prediction_settings =
6418 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6419 }
6420 }
6421 }
6422
6423 fn edit_prediction_settings_at_position(
6424 &self,
6425 buffer: &Entity<Buffer>,
6426 buffer_position: language::Anchor,
6427 cx: &App,
6428 ) -> EditPredictionSettings {
6429 if !self.mode.is_full()
6430 || !self.show_inline_completions_override.unwrap_or(true)
6431 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6432 {
6433 return EditPredictionSettings::Disabled;
6434 }
6435
6436 let buffer = buffer.read(cx);
6437
6438 let file = buffer.file();
6439
6440 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6441 return EditPredictionSettings::Disabled;
6442 };
6443
6444 let by_provider = matches!(
6445 self.menu_inline_completions_policy,
6446 MenuInlineCompletionsPolicy::ByProvider
6447 );
6448
6449 let show_in_menu = by_provider
6450 && self
6451 .edit_prediction_provider
6452 .as_ref()
6453 .map_or(false, |provider| {
6454 provider.provider.show_completions_in_menu()
6455 });
6456
6457 let preview_requires_modifier =
6458 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6459
6460 EditPredictionSettings::Enabled {
6461 show_in_menu,
6462 preview_requires_modifier,
6463 }
6464 }
6465
6466 fn should_show_edit_predictions(&self) -> bool {
6467 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6468 }
6469
6470 pub fn edit_prediction_preview_is_active(&self) -> bool {
6471 matches!(
6472 self.edit_prediction_preview,
6473 EditPredictionPreview::Active { .. }
6474 )
6475 }
6476
6477 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6478 let cursor = self.selections.newest_anchor().head();
6479 if let Some((buffer, cursor_position)) =
6480 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6481 {
6482 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6483 } else {
6484 false
6485 }
6486 }
6487
6488 pub fn supports_minimap(&self, cx: &App) -> bool {
6489 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6490 }
6491
6492 fn edit_predictions_enabled_in_buffer(
6493 &self,
6494 buffer: &Entity<Buffer>,
6495 buffer_position: language::Anchor,
6496 cx: &App,
6497 ) -> bool {
6498 maybe!({
6499 if self.read_only(cx) {
6500 return Some(false);
6501 }
6502 let provider = self.edit_prediction_provider()?;
6503 if !provider.is_enabled(&buffer, buffer_position, cx) {
6504 return Some(false);
6505 }
6506 let buffer = buffer.read(cx);
6507 let Some(file) = buffer.file() else {
6508 return Some(true);
6509 };
6510 let settings = all_language_settings(Some(file), cx);
6511 Some(settings.edit_predictions_enabled_for_file(file, cx))
6512 })
6513 .unwrap_or(false)
6514 }
6515
6516 fn cycle_inline_completion(
6517 &mut self,
6518 direction: Direction,
6519 window: &mut Window,
6520 cx: &mut Context<Self>,
6521 ) -> Option<()> {
6522 let provider = self.edit_prediction_provider()?;
6523 let cursor = self.selections.newest_anchor().head();
6524 let (buffer, cursor_buffer_position) =
6525 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6526 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6527 return None;
6528 }
6529
6530 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6531 self.update_visible_inline_completion(window, cx);
6532
6533 Some(())
6534 }
6535
6536 pub fn show_inline_completion(
6537 &mut self,
6538 _: &ShowEditPrediction,
6539 window: &mut Window,
6540 cx: &mut Context<Self>,
6541 ) {
6542 if !self.has_active_inline_completion() {
6543 self.refresh_inline_completion(false, true, window, cx);
6544 return;
6545 }
6546
6547 self.update_visible_inline_completion(window, cx);
6548 }
6549
6550 pub fn display_cursor_names(
6551 &mut self,
6552 _: &DisplayCursorNames,
6553 window: &mut Window,
6554 cx: &mut Context<Self>,
6555 ) {
6556 self.show_cursor_names(window, cx);
6557 }
6558
6559 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6560 self.show_cursor_names = true;
6561 cx.notify();
6562 cx.spawn_in(window, async move |this, cx| {
6563 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6564 this.update(cx, |this, cx| {
6565 this.show_cursor_names = false;
6566 cx.notify()
6567 })
6568 .ok()
6569 })
6570 .detach();
6571 }
6572
6573 pub fn next_edit_prediction(
6574 &mut self,
6575 _: &NextEditPrediction,
6576 window: &mut Window,
6577 cx: &mut Context<Self>,
6578 ) {
6579 if self.has_active_inline_completion() {
6580 self.cycle_inline_completion(Direction::Next, window, cx);
6581 } else {
6582 let is_copilot_disabled = self
6583 .refresh_inline_completion(false, true, window, cx)
6584 .is_none();
6585 if is_copilot_disabled {
6586 cx.propagate();
6587 }
6588 }
6589 }
6590
6591 pub fn previous_edit_prediction(
6592 &mut self,
6593 _: &PreviousEditPrediction,
6594 window: &mut Window,
6595 cx: &mut Context<Self>,
6596 ) {
6597 if self.has_active_inline_completion() {
6598 self.cycle_inline_completion(Direction::Prev, window, cx);
6599 } else {
6600 let is_copilot_disabled = self
6601 .refresh_inline_completion(false, true, window, cx)
6602 .is_none();
6603 if is_copilot_disabled {
6604 cx.propagate();
6605 }
6606 }
6607 }
6608
6609 pub fn accept_edit_prediction(
6610 &mut self,
6611 _: &AcceptEditPrediction,
6612 window: &mut Window,
6613 cx: &mut Context<Self>,
6614 ) {
6615 if self.show_edit_predictions_in_menu() {
6616 self.hide_context_menu(window, cx);
6617 }
6618
6619 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6620 return;
6621 };
6622
6623 self.report_inline_completion_event(
6624 active_inline_completion.completion_id.clone(),
6625 true,
6626 cx,
6627 );
6628
6629 match &active_inline_completion.completion {
6630 InlineCompletion::Move { target, .. } => {
6631 let target = *target;
6632
6633 if let Some(position_map) = &self.last_position_map {
6634 if position_map
6635 .visible_row_range
6636 .contains(&target.to_display_point(&position_map.snapshot).row())
6637 || !self.edit_prediction_requires_modifier()
6638 {
6639 self.unfold_ranges(&[target..target], true, false, cx);
6640 // Note that this is also done in vim's handler of the Tab action.
6641 self.change_selections(
6642 Some(Autoscroll::newest()),
6643 window,
6644 cx,
6645 |selections| {
6646 selections.select_anchor_ranges([target..target]);
6647 },
6648 );
6649 self.clear_row_highlights::<EditPredictionPreview>();
6650
6651 self.edit_prediction_preview
6652 .set_previous_scroll_position(None);
6653 } else {
6654 self.edit_prediction_preview
6655 .set_previous_scroll_position(Some(
6656 position_map.snapshot.scroll_anchor,
6657 ));
6658
6659 self.highlight_rows::<EditPredictionPreview>(
6660 target..target,
6661 cx.theme().colors().editor_highlighted_line_background,
6662 RowHighlightOptions {
6663 autoscroll: true,
6664 ..Default::default()
6665 },
6666 cx,
6667 );
6668 self.request_autoscroll(Autoscroll::fit(), cx);
6669 }
6670 }
6671 }
6672 InlineCompletion::Edit { edits, .. } => {
6673 if let Some(provider) = self.edit_prediction_provider() {
6674 provider.accept(cx);
6675 }
6676
6677 // Store the transaction ID and selections before applying the edit
6678 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6679
6680 let snapshot = self.buffer.read(cx).snapshot(cx);
6681 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6682
6683 self.buffer.update(cx, |buffer, cx| {
6684 buffer.edit(edits.iter().cloned(), None, cx)
6685 });
6686
6687 self.change_selections(None, window, cx, |s| {
6688 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6689 });
6690
6691 let selections = self.selections.disjoint_anchors();
6692 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6693 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6694 if has_new_transaction {
6695 self.selection_history
6696 .insert_transaction(transaction_id_now, selections);
6697 }
6698 }
6699
6700 self.update_visible_inline_completion(window, cx);
6701 if self.active_inline_completion.is_none() {
6702 self.refresh_inline_completion(true, true, window, cx);
6703 }
6704
6705 cx.notify();
6706 }
6707 }
6708
6709 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6710 }
6711
6712 pub fn accept_partial_inline_completion(
6713 &mut self,
6714 _: &AcceptPartialEditPrediction,
6715 window: &mut Window,
6716 cx: &mut Context<Self>,
6717 ) {
6718 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6719 return;
6720 };
6721 if self.selections.count() != 1 {
6722 return;
6723 }
6724
6725 self.report_inline_completion_event(
6726 active_inline_completion.completion_id.clone(),
6727 true,
6728 cx,
6729 );
6730
6731 match &active_inline_completion.completion {
6732 InlineCompletion::Move { target, .. } => {
6733 let target = *target;
6734 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6735 selections.select_anchor_ranges([target..target]);
6736 });
6737 }
6738 InlineCompletion::Edit { edits, .. } => {
6739 // Find an insertion that starts at the cursor position.
6740 let snapshot = self.buffer.read(cx).snapshot(cx);
6741 let cursor_offset = self.selections.newest::<usize>(cx).head();
6742 let insertion = edits.iter().find_map(|(range, text)| {
6743 let range = range.to_offset(&snapshot);
6744 if range.is_empty() && range.start == cursor_offset {
6745 Some(text)
6746 } else {
6747 None
6748 }
6749 });
6750
6751 if let Some(text) = insertion {
6752 let mut partial_completion = text
6753 .chars()
6754 .by_ref()
6755 .take_while(|c| c.is_alphabetic())
6756 .collect::<String>();
6757 if partial_completion.is_empty() {
6758 partial_completion = text
6759 .chars()
6760 .by_ref()
6761 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
6762 .collect::<String>();
6763 }
6764
6765 cx.emit(EditorEvent::InputHandled {
6766 utf16_range_to_replace: None,
6767 text: partial_completion.clone().into(),
6768 });
6769
6770 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
6771
6772 self.refresh_inline_completion(true, true, window, cx);
6773 cx.notify();
6774 } else {
6775 self.accept_edit_prediction(&Default::default(), window, cx);
6776 }
6777 }
6778 }
6779 }
6780
6781 fn discard_inline_completion(
6782 &mut self,
6783 should_report_inline_completion_event: bool,
6784 cx: &mut Context<Self>,
6785 ) -> bool {
6786 if should_report_inline_completion_event {
6787 let completion_id = self
6788 .active_inline_completion
6789 .as_ref()
6790 .and_then(|active_completion| active_completion.completion_id.clone());
6791
6792 self.report_inline_completion_event(completion_id, false, cx);
6793 }
6794
6795 if let Some(provider) = self.edit_prediction_provider() {
6796 provider.discard(cx);
6797 }
6798
6799 self.take_active_inline_completion(cx)
6800 }
6801
6802 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
6803 let Some(provider) = self.edit_prediction_provider() else {
6804 return;
6805 };
6806
6807 let Some((_, buffer, _)) = self
6808 .buffer
6809 .read(cx)
6810 .excerpt_containing(self.selections.newest_anchor().head(), cx)
6811 else {
6812 return;
6813 };
6814
6815 let extension = buffer
6816 .read(cx)
6817 .file()
6818 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
6819
6820 let event_type = match accepted {
6821 true => "Edit Prediction Accepted",
6822 false => "Edit Prediction Discarded",
6823 };
6824 telemetry::event!(
6825 event_type,
6826 provider = provider.name(),
6827 prediction_id = id,
6828 suggestion_accepted = accepted,
6829 file_extension = extension,
6830 );
6831 }
6832
6833 pub fn has_active_inline_completion(&self) -> bool {
6834 self.active_inline_completion.is_some()
6835 }
6836
6837 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
6838 let Some(active_inline_completion) = self.active_inline_completion.take() else {
6839 return false;
6840 };
6841
6842 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
6843 self.clear_highlights::<InlineCompletionHighlight>(cx);
6844 self.stale_inline_completion_in_menu = Some(active_inline_completion);
6845 true
6846 }
6847
6848 /// Returns true when we're displaying the edit prediction popover below the cursor
6849 /// like we are not previewing and the LSP autocomplete menu is visible
6850 /// or we are in `when_holding_modifier` mode.
6851 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
6852 if self.edit_prediction_preview_is_active()
6853 || !self.show_edit_predictions_in_menu()
6854 || !self.edit_predictions_enabled()
6855 {
6856 return false;
6857 }
6858
6859 if self.has_visible_completions_menu() {
6860 return true;
6861 }
6862
6863 has_completion && self.edit_prediction_requires_modifier()
6864 }
6865
6866 fn handle_modifiers_changed(
6867 &mut self,
6868 modifiers: Modifiers,
6869 position_map: &PositionMap,
6870 window: &mut Window,
6871 cx: &mut Context<Self>,
6872 ) {
6873 if self.show_edit_predictions_in_menu() {
6874 self.update_edit_prediction_preview(&modifiers, window, cx);
6875 }
6876
6877 self.update_selection_mode(&modifiers, position_map, window, cx);
6878
6879 let mouse_position = window.mouse_position();
6880 if !position_map.text_hitbox.is_hovered(window) {
6881 return;
6882 }
6883
6884 self.update_hovered_link(
6885 position_map.point_for_position(mouse_position),
6886 &position_map.snapshot,
6887 modifiers,
6888 window,
6889 cx,
6890 )
6891 }
6892
6893 fn update_selection_mode(
6894 &mut self,
6895 modifiers: &Modifiers,
6896 position_map: &PositionMap,
6897 window: &mut Window,
6898 cx: &mut Context<Self>,
6899 ) {
6900 if modifiers != &COLUMNAR_SELECTION_MODIFIERS || self.selections.pending.is_none() {
6901 return;
6902 }
6903
6904 let mouse_position = window.mouse_position();
6905 let point_for_position = position_map.point_for_position(mouse_position);
6906 let position = point_for_position.previous_valid;
6907
6908 self.select(
6909 SelectPhase::BeginColumnar {
6910 position,
6911 reset: false,
6912 goal_column: point_for_position.exact_unclipped.column(),
6913 },
6914 window,
6915 cx,
6916 );
6917 }
6918
6919 fn update_edit_prediction_preview(
6920 &mut self,
6921 modifiers: &Modifiers,
6922 window: &mut Window,
6923 cx: &mut Context<Self>,
6924 ) {
6925 let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
6926 let Some(accept_keystroke) = accept_keybind.keystroke() else {
6927 return;
6928 };
6929
6930 if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
6931 if matches!(
6932 self.edit_prediction_preview,
6933 EditPredictionPreview::Inactive { .. }
6934 ) {
6935 self.edit_prediction_preview = EditPredictionPreview::Active {
6936 previous_scroll_position: None,
6937 since: Instant::now(),
6938 };
6939
6940 self.update_visible_inline_completion(window, cx);
6941 cx.notify();
6942 }
6943 } else if let EditPredictionPreview::Active {
6944 previous_scroll_position,
6945 since,
6946 } = self.edit_prediction_preview
6947 {
6948 if let (Some(previous_scroll_position), Some(position_map)) =
6949 (previous_scroll_position, self.last_position_map.as_ref())
6950 {
6951 self.set_scroll_position(
6952 previous_scroll_position
6953 .scroll_position(&position_map.snapshot.display_snapshot),
6954 window,
6955 cx,
6956 );
6957 }
6958
6959 self.edit_prediction_preview = EditPredictionPreview::Inactive {
6960 released_too_fast: since.elapsed() < Duration::from_millis(200),
6961 };
6962 self.clear_row_highlights::<EditPredictionPreview>();
6963 self.update_visible_inline_completion(window, cx);
6964 cx.notify();
6965 }
6966 }
6967
6968 fn update_visible_inline_completion(
6969 &mut self,
6970 _window: &mut Window,
6971 cx: &mut Context<Self>,
6972 ) -> Option<()> {
6973 let selection = self.selections.newest_anchor();
6974 let cursor = selection.head();
6975 let multibuffer = self.buffer.read(cx).snapshot(cx);
6976 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
6977 let excerpt_id = cursor.excerpt_id;
6978
6979 let show_in_menu = self.show_edit_predictions_in_menu();
6980 let completions_menu_has_precedence = !show_in_menu
6981 && (self.context_menu.borrow().is_some()
6982 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
6983
6984 if completions_menu_has_precedence
6985 || !offset_selection.is_empty()
6986 || self
6987 .active_inline_completion
6988 .as_ref()
6989 .map_or(false, |completion| {
6990 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
6991 let invalidation_range = invalidation_range.start..=invalidation_range.end;
6992 !invalidation_range.contains(&offset_selection.head())
6993 })
6994 {
6995 self.discard_inline_completion(false, cx);
6996 return None;
6997 }
6998
6999 self.take_active_inline_completion(cx);
7000 let Some(provider) = self.edit_prediction_provider() else {
7001 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7002 return None;
7003 };
7004
7005 let (buffer, cursor_buffer_position) =
7006 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7007
7008 self.edit_prediction_settings =
7009 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7010
7011 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7012
7013 if self.edit_prediction_indent_conflict {
7014 let cursor_point = cursor.to_point(&multibuffer);
7015
7016 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7017
7018 if let Some((_, indent)) = indents.iter().next() {
7019 if indent.len == cursor_point.column {
7020 self.edit_prediction_indent_conflict = false;
7021 }
7022 }
7023 }
7024
7025 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7026 let edits = inline_completion
7027 .edits
7028 .into_iter()
7029 .flat_map(|(range, new_text)| {
7030 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7031 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7032 Some((start..end, new_text))
7033 })
7034 .collect::<Vec<_>>();
7035 if edits.is_empty() {
7036 return None;
7037 }
7038
7039 let first_edit_start = edits.first().unwrap().0.start;
7040 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7041 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7042
7043 let last_edit_end = edits.last().unwrap().0.end;
7044 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7045 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7046
7047 let cursor_row = cursor.to_point(&multibuffer).row;
7048
7049 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7050
7051 let mut inlay_ids = Vec::new();
7052 let invalidation_row_range;
7053 let move_invalidation_row_range = if cursor_row < edit_start_row {
7054 Some(cursor_row..edit_end_row)
7055 } else if cursor_row > edit_end_row {
7056 Some(edit_start_row..cursor_row)
7057 } else {
7058 None
7059 };
7060 let is_move =
7061 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7062 let completion = if is_move {
7063 invalidation_row_range =
7064 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7065 let target = first_edit_start;
7066 InlineCompletion::Move { target, snapshot }
7067 } else {
7068 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7069 && !self.inline_completions_hidden_for_vim_mode;
7070
7071 if show_completions_in_buffer {
7072 if edits
7073 .iter()
7074 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7075 {
7076 let mut inlays = Vec::new();
7077 for (range, new_text) in &edits {
7078 let inlay = Inlay::inline_completion(
7079 post_inc(&mut self.next_inlay_id),
7080 range.start,
7081 new_text.as_str(),
7082 );
7083 inlay_ids.push(inlay.id);
7084 inlays.push(inlay);
7085 }
7086
7087 self.splice_inlays(&[], inlays, cx);
7088 } else {
7089 let background_color = cx.theme().status().deleted_background;
7090 self.highlight_text::<InlineCompletionHighlight>(
7091 edits.iter().map(|(range, _)| range.clone()).collect(),
7092 HighlightStyle {
7093 background_color: Some(background_color),
7094 ..Default::default()
7095 },
7096 cx,
7097 );
7098 }
7099 }
7100
7101 invalidation_row_range = edit_start_row..edit_end_row;
7102
7103 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7104 if provider.show_tab_accept_marker() {
7105 EditDisplayMode::TabAccept
7106 } else {
7107 EditDisplayMode::Inline
7108 }
7109 } else {
7110 EditDisplayMode::DiffPopover
7111 };
7112
7113 InlineCompletion::Edit {
7114 edits,
7115 edit_preview: inline_completion.edit_preview,
7116 display_mode,
7117 snapshot,
7118 }
7119 };
7120
7121 let invalidation_range = multibuffer
7122 .anchor_before(Point::new(invalidation_row_range.start, 0))
7123 ..multibuffer.anchor_after(Point::new(
7124 invalidation_row_range.end,
7125 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7126 ));
7127
7128 self.stale_inline_completion_in_menu = None;
7129 self.active_inline_completion = Some(InlineCompletionState {
7130 inlay_ids,
7131 completion,
7132 completion_id: inline_completion.id,
7133 invalidation_range,
7134 });
7135
7136 cx.notify();
7137
7138 Some(())
7139 }
7140
7141 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7142 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7143 }
7144
7145 fn clear_tasks(&mut self) {
7146 self.tasks.clear()
7147 }
7148
7149 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7150 if self.tasks.insert(key, value).is_some() {
7151 // This case should hopefully be rare, but just in case...
7152 log::error!(
7153 "multiple different run targets found on a single line, only the last target will be rendered"
7154 )
7155 }
7156 }
7157
7158 /// Get all display points of breakpoints that will be rendered within editor
7159 ///
7160 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7161 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7162 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7163 fn active_breakpoints(
7164 &self,
7165 range: Range<DisplayRow>,
7166 window: &mut Window,
7167 cx: &mut Context<Self>,
7168 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7169 let mut breakpoint_display_points = HashMap::default();
7170
7171 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7172 return breakpoint_display_points;
7173 };
7174
7175 let snapshot = self.snapshot(window, cx);
7176
7177 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7178 let Some(project) = self.project.as_ref() else {
7179 return breakpoint_display_points;
7180 };
7181
7182 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7183 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7184
7185 for (buffer_snapshot, range, excerpt_id) in
7186 multi_buffer_snapshot.range_to_buffer_ranges(range)
7187 {
7188 let Some(buffer) = project
7189 .read(cx)
7190 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7191 else {
7192 continue;
7193 };
7194 let breakpoints = breakpoint_store.read(cx).breakpoints(
7195 &buffer,
7196 Some(
7197 buffer_snapshot.anchor_before(range.start)
7198 ..buffer_snapshot.anchor_after(range.end),
7199 ),
7200 buffer_snapshot,
7201 cx,
7202 );
7203 for (breakpoint, state) in breakpoints {
7204 let multi_buffer_anchor =
7205 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7206 let position = multi_buffer_anchor
7207 .to_point(&multi_buffer_snapshot)
7208 .to_display_point(&snapshot);
7209
7210 breakpoint_display_points.insert(
7211 position.row(),
7212 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7213 );
7214 }
7215 }
7216
7217 breakpoint_display_points
7218 }
7219
7220 fn breakpoint_context_menu(
7221 &self,
7222 anchor: Anchor,
7223 window: &mut Window,
7224 cx: &mut Context<Self>,
7225 ) -> Entity<ui::ContextMenu> {
7226 let weak_editor = cx.weak_entity();
7227 let focus_handle = self.focus_handle(cx);
7228
7229 let row = self
7230 .buffer
7231 .read(cx)
7232 .snapshot(cx)
7233 .summary_for_anchor::<Point>(&anchor)
7234 .row;
7235
7236 let breakpoint = self
7237 .breakpoint_at_row(row, window, cx)
7238 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7239
7240 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7241 "Edit Log Breakpoint"
7242 } else {
7243 "Set Log Breakpoint"
7244 };
7245
7246 let condition_breakpoint_msg = if breakpoint
7247 .as_ref()
7248 .is_some_and(|bp| bp.1.condition.is_some())
7249 {
7250 "Edit Condition Breakpoint"
7251 } else {
7252 "Set Condition Breakpoint"
7253 };
7254
7255 let hit_condition_breakpoint_msg = if breakpoint
7256 .as_ref()
7257 .is_some_and(|bp| bp.1.hit_condition.is_some())
7258 {
7259 "Edit Hit Condition Breakpoint"
7260 } else {
7261 "Set Hit Condition Breakpoint"
7262 };
7263
7264 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7265 "Unset Breakpoint"
7266 } else {
7267 "Set Breakpoint"
7268 };
7269
7270 let run_to_cursor = command_palette_hooks::CommandPaletteFilter::try_global(cx)
7271 .map_or(false, |filter| !filter.is_hidden(&DebuggerRunToCursor));
7272
7273 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7274 BreakpointState::Enabled => Some("Disable"),
7275 BreakpointState::Disabled => Some("Enable"),
7276 });
7277
7278 let (anchor, breakpoint) =
7279 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7280
7281 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7282 menu.on_blur_subscription(Subscription::new(|| {}))
7283 .context(focus_handle)
7284 .when(run_to_cursor, |this| {
7285 let weak_editor = weak_editor.clone();
7286 this.entry("Run to cursor", None, move |window, cx| {
7287 weak_editor
7288 .update(cx, |editor, cx| {
7289 editor.change_selections(None, window, cx, |s| {
7290 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7291 });
7292 })
7293 .ok();
7294
7295 window.dispatch_action(Box::new(DebuggerRunToCursor), cx);
7296 })
7297 .separator()
7298 })
7299 .when_some(toggle_state_msg, |this, msg| {
7300 this.entry(msg, None, {
7301 let weak_editor = weak_editor.clone();
7302 let breakpoint = breakpoint.clone();
7303 move |_window, cx| {
7304 weak_editor
7305 .update(cx, |this, cx| {
7306 this.edit_breakpoint_at_anchor(
7307 anchor,
7308 breakpoint.as_ref().clone(),
7309 BreakpointEditAction::InvertState,
7310 cx,
7311 );
7312 })
7313 .log_err();
7314 }
7315 })
7316 })
7317 .entry(set_breakpoint_msg, None, {
7318 let weak_editor = weak_editor.clone();
7319 let breakpoint = breakpoint.clone();
7320 move |_window, cx| {
7321 weak_editor
7322 .update(cx, |this, cx| {
7323 this.edit_breakpoint_at_anchor(
7324 anchor,
7325 breakpoint.as_ref().clone(),
7326 BreakpointEditAction::Toggle,
7327 cx,
7328 );
7329 })
7330 .log_err();
7331 }
7332 })
7333 .entry(log_breakpoint_msg, None, {
7334 let breakpoint = breakpoint.clone();
7335 let weak_editor = weak_editor.clone();
7336 move |window, cx| {
7337 weak_editor
7338 .update(cx, |this, cx| {
7339 this.add_edit_breakpoint_block(
7340 anchor,
7341 breakpoint.as_ref(),
7342 BreakpointPromptEditAction::Log,
7343 window,
7344 cx,
7345 );
7346 })
7347 .log_err();
7348 }
7349 })
7350 .entry(condition_breakpoint_msg, None, {
7351 let breakpoint = breakpoint.clone();
7352 let weak_editor = weak_editor.clone();
7353 move |window, cx| {
7354 weak_editor
7355 .update(cx, |this, cx| {
7356 this.add_edit_breakpoint_block(
7357 anchor,
7358 breakpoint.as_ref(),
7359 BreakpointPromptEditAction::Condition,
7360 window,
7361 cx,
7362 );
7363 })
7364 .log_err();
7365 }
7366 })
7367 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7368 weak_editor
7369 .update(cx, |this, cx| {
7370 this.add_edit_breakpoint_block(
7371 anchor,
7372 breakpoint.as_ref(),
7373 BreakpointPromptEditAction::HitCondition,
7374 window,
7375 cx,
7376 );
7377 })
7378 .log_err();
7379 })
7380 })
7381 }
7382
7383 fn render_breakpoint(
7384 &self,
7385 position: Anchor,
7386 row: DisplayRow,
7387 breakpoint: &Breakpoint,
7388 state: Option<BreakpointSessionState>,
7389 cx: &mut Context<Self>,
7390 ) -> IconButton {
7391 let is_rejected = state.is_some_and(|s| !s.verified);
7392 // Is it a breakpoint that shows up when hovering over gutter?
7393 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7394 (false, false),
7395 |PhantomBreakpointIndicator {
7396 is_active,
7397 display_row,
7398 collides_with_existing_breakpoint,
7399 }| {
7400 (
7401 is_active && display_row == row,
7402 collides_with_existing_breakpoint,
7403 )
7404 },
7405 );
7406
7407 let (color, icon) = {
7408 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7409 (false, false) => ui::IconName::DebugBreakpoint,
7410 (true, false) => ui::IconName::DebugLogBreakpoint,
7411 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7412 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7413 };
7414
7415 let color = if is_phantom {
7416 Color::Hint
7417 } else if is_rejected {
7418 Color::Disabled
7419 } else {
7420 Color::Debugger
7421 };
7422
7423 (color, icon)
7424 };
7425
7426 let breakpoint = Arc::from(breakpoint.clone());
7427
7428 let alt_as_text = gpui::Keystroke {
7429 modifiers: Modifiers::secondary_key(),
7430 ..Default::default()
7431 };
7432 let primary_action_text = if breakpoint.is_disabled() {
7433 "Enable breakpoint"
7434 } else if is_phantom && !collides_with_existing {
7435 "Set breakpoint"
7436 } else {
7437 "Unset breakpoint"
7438 };
7439 let focus_handle = self.focus_handle.clone();
7440
7441 let meta = if is_rejected {
7442 SharedString::from("No executable code is associated with this line.")
7443 } else if collides_with_existing && !breakpoint.is_disabled() {
7444 SharedString::from(format!(
7445 "{alt_as_text}-click to disable,\nright-click for more options."
7446 ))
7447 } else {
7448 SharedString::from("Right-click for more options.")
7449 };
7450 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7451 .icon_size(IconSize::XSmall)
7452 .size(ui::ButtonSize::None)
7453 .when(is_rejected, |this| {
7454 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7455 })
7456 .icon_color(color)
7457 .style(ButtonStyle::Transparent)
7458 .on_click(cx.listener({
7459 let breakpoint = breakpoint.clone();
7460
7461 move |editor, event: &ClickEvent, window, cx| {
7462 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7463 BreakpointEditAction::InvertState
7464 } else {
7465 BreakpointEditAction::Toggle
7466 };
7467
7468 window.focus(&editor.focus_handle(cx));
7469 editor.edit_breakpoint_at_anchor(
7470 position,
7471 breakpoint.as_ref().clone(),
7472 edit_action,
7473 cx,
7474 );
7475 }
7476 }))
7477 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7478 editor.set_breakpoint_context_menu(
7479 row,
7480 Some(position),
7481 event.down.position,
7482 window,
7483 cx,
7484 );
7485 }))
7486 .tooltip(move |window, cx| {
7487 Tooltip::with_meta_in(
7488 primary_action_text,
7489 Some(&ToggleBreakpoint),
7490 meta.clone(),
7491 &focus_handle,
7492 window,
7493 cx,
7494 )
7495 })
7496 }
7497
7498 fn build_tasks_context(
7499 project: &Entity<Project>,
7500 buffer: &Entity<Buffer>,
7501 buffer_row: u32,
7502 tasks: &Arc<RunnableTasks>,
7503 cx: &mut Context<Self>,
7504 ) -> Task<Option<task::TaskContext>> {
7505 let position = Point::new(buffer_row, tasks.column);
7506 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7507 let location = Location {
7508 buffer: buffer.clone(),
7509 range: range_start..range_start,
7510 };
7511 // Fill in the environmental variables from the tree-sitter captures
7512 let mut captured_task_variables = TaskVariables::default();
7513 for (capture_name, value) in tasks.extra_variables.clone() {
7514 captured_task_variables.insert(
7515 task::VariableName::Custom(capture_name.into()),
7516 value.clone(),
7517 );
7518 }
7519 project.update(cx, |project, cx| {
7520 project.task_store().update(cx, |task_store, cx| {
7521 task_store.task_context_for_location(captured_task_variables, location, cx)
7522 })
7523 })
7524 }
7525
7526 pub fn spawn_nearest_task(
7527 &mut self,
7528 action: &SpawnNearestTask,
7529 window: &mut Window,
7530 cx: &mut Context<Self>,
7531 ) {
7532 let Some((workspace, _)) = self.workspace.clone() else {
7533 return;
7534 };
7535 let Some(project) = self.project.clone() else {
7536 return;
7537 };
7538
7539 // Try to find a closest, enclosing node using tree-sitter that has a
7540 // task
7541 let Some((buffer, buffer_row, tasks)) = self
7542 .find_enclosing_node_task(cx)
7543 // Or find the task that's closest in row-distance.
7544 .or_else(|| self.find_closest_task(cx))
7545 else {
7546 return;
7547 };
7548
7549 let reveal_strategy = action.reveal;
7550 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7551 cx.spawn_in(window, async move |_, cx| {
7552 let context = task_context.await?;
7553 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7554
7555 let resolved = &mut resolved_task.resolved;
7556 resolved.reveal = reveal_strategy;
7557
7558 workspace
7559 .update_in(cx, |workspace, window, cx| {
7560 workspace.schedule_resolved_task(
7561 task_source_kind,
7562 resolved_task,
7563 false,
7564 window,
7565 cx,
7566 );
7567 })
7568 .ok()
7569 })
7570 .detach();
7571 }
7572
7573 fn find_closest_task(
7574 &mut self,
7575 cx: &mut Context<Self>,
7576 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7577 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7578
7579 let ((buffer_id, row), tasks) = self
7580 .tasks
7581 .iter()
7582 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7583
7584 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7585 let tasks = Arc::new(tasks.to_owned());
7586 Some((buffer, *row, tasks))
7587 }
7588
7589 fn find_enclosing_node_task(
7590 &mut self,
7591 cx: &mut Context<Self>,
7592 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7593 let snapshot = self.buffer.read(cx).snapshot(cx);
7594 let offset = self.selections.newest::<usize>(cx).head();
7595 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7596 let buffer_id = excerpt.buffer().remote_id();
7597
7598 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7599 let mut cursor = layer.node().walk();
7600
7601 while cursor.goto_first_child_for_byte(offset).is_some() {
7602 if cursor.node().end_byte() == offset {
7603 cursor.goto_next_sibling();
7604 }
7605 }
7606
7607 // Ascend to the smallest ancestor that contains the range and has a task.
7608 loop {
7609 let node = cursor.node();
7610 let node_range = node.byte_range();
7611 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7612
7613 // Check if this node contains our offset
7614 if node_range.start <= offset && node_range.end >= offset {
7615 // If it contains offset, check for task
7616 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7617 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7618 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7619 }
7620 }
7621
7622 if !cursor.goto_parent() {
7623 break;
7624 }
7625 }
7626 None
7627 }
7628
7629 fn render_run_indicator(
7630 &self,
7631 _style: &EditorStyle,
7632 is_active: bool,
7633 row: DisplayRow,
7634 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7635 cx: &mut Context<Self>,
7636 ) -> IconButton {
7637 let color = Color::Muted;
7638 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7639
7640 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7641 .shape(ui::IconButtonShape::Square)
7642 .icon_size(IconSize::XSmall)
7643 .icon_color(color)
7644 .toggle_state(is_active)
7645 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7646 let quick_launch = e.down.button == MouseButton::Left;
7647 window.focus(&editor.focus_handle(cx));
7648 editor.toggle_code_actions(
7649 &ToggleCodeActions {
7650 deployed_from: Some(CodeActionSource::Indicator(row)),
7651 quick_launch,
7652 },
7653 window,
7654 cx,
7655 );
7656 }))
7657 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7658 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7659 }))
7660 }
7661
7662 pub fn context_menu_visible(&self) -> bool {
7663 !self.edit_prediction_preview_is_active()
7664 && self
7665 .context_menu
7666 .borrow()
7667 .as_ref()
7668 .map_or(false, |menu| menu.visible())
7669 }
7670
7671 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7672 self.context_menu
7673 .borrow()
7674 .as_ref()
7675 .map(|menu| menu.origin())
7676 }
7677
7678 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7679 self.context_menu_options = Some(options);
7680 }
7681
7682 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7683 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7684
7685 fn render_edit_prediction_popover(
7686 &mut self,
7687 text_bounds: &Bounds<Pixels>,
7688 content_origin: gpui::Point<Pixels>,
7689 right_margin: Pixels,
7690 editor_snapshot: &EditorSnapshot,
7691 visible_row_range: Range<DisplayRow>,
7692 scroll_top: f32,
7693 scroll_bottom: f32,
7694 line_layouts: &[LineWithInvisibles],
7695 line_height: Pixels,
7696 scroll_pixel_position: gpui::Point<Pixels>,
7697 newest_selection_head: Option<DisplayPoint>,
7698 editor_width: Pixels,
7699 style: &EditorStyle,
7700 window: &mut Window,
7701 cx: &mut App,
7702 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7703 if self.mode().is_minimap() {
7704 return None;
7705 }
7706 let active_inline_completion = self.active_inline_completion.as_ref()?;
7707
7708 if self.edit_prediction_visible_in_cursor_popover(true) {
7709 return None;
7710 }
7711
7712 match &active_inline_completion.completion {
7713 InlineCompletion::Move { target, .. } => {
7714 let target_display_point = target.to_display_point(editor_snapshot);
7715
7716 if self.edit_prediction_requires_modifier() {
7717 if !self.edit_prediction_preview_is_active() {
7718 return None;
7719 }
7720
7721 self.render_edit_prediction_modifier_jump_popover(
7722 text_bounds,
7723 content_origin,
7724 visible_row_range,
7725 line_layouts,
7726 line_height,
7727 scroll_pixel_position,
7728 newest_selection_head,
7729 target_display_point,
7730 window,
7731 cx,
7732 )
7733 } else {
7734 self.render_edit_prediction_eager_jump_popover(
7735 text_bounds,
7736 content_origin,
7737 editor_snapshot,
7738 visible_row_range,
7739 scroll_top,
7740 scroll_bottom,
7741 line_height,
7742 scroll_pixel_position,
7743 target_display_point,
7744 editor_width,
7745 window,
7746 cx,
7747 )
7748 }
7749 }
7750 InlineCompletion::Edit {
7751 display_mode: EditDisplayMode::Inline,
7752 ..
7753 } => None,
7754 InlineCompletion::Edit {
7755 display_mode: EditDisplayMode::TabAccept,
7756 edits,
7757 ..
7758 } => {
7759 let range = &edits.first()?.0;
7760 let target_display_point = range.end.to_display_point(editor_snapshot);
7761
7762 self.render_edit_prediction_end_of_line_popover(
7763 "Accept",
7764 editor_snapshot,
7765 visible_row_range,
7766 target_display_point,
7767 line_height,
7768 scroll_pixel_position,
7769 content_origin,
7770 editor_width,
7771 window,
7772 cx,
7773 )
7774 }
7775 InlineCompletion::Edit {
7776 edits,
7777 edit_preview,
7778 display_mode: EditDisplayMode::DiffPopover,
7779 snapshot,
7780 } => self.render_edit_prediction_diff_popover(
7781 text_bounds,
7782 content_origin,
7783 right_margin,
7784 editor_snapshot,
7785 visible_row_range,
7786 line_layouts,
7787 line_height,
7788 scroll_pixel_position,
7789 newest_selection_head,
7790 editor_width,
7791 style,
7792 edits,
7793 edit_preview,
7794 snapshot,
7795 window,
7796 cx,
7797 ),
7798 }
7799 }
7800
7801 fn render_edit_prediction_modifier_jump_popover(
7802 &mut self,
7803 text_bounds: &Bounds<Pixels>,
7804 content_origin: gpui::Point<Pixels>,
7805 visible_row_range: Range<DisplayRow>,
7806 line_layouts: &[LineWithInvisibles],
7807 line_height: Pixels,
7808 scroll_pixel_position: gpui::Point<Pixels>,
7809 newest_selection_head: Option<DisplayPoint>,
7810 target_display_point: DisplayPoint,
7811 window: &mut Window,
7812 cx: &mut App,
7813 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7814 let scrolled_content_origin =
7815 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
7816
7817 const SCROLL_PADDING_Y: Pixels = px(12.);
7818
7819 if target_display_point.row() < visible_row_range.start {
7820 return self.render_edit_prediction_scroll_popover(
7821 |_| SCROLL_PADDING_Y,
7822 IconName::ArrowUp,
7823 visible_row_range,
7824 line_layouts,
7825 newest_selection_head,
7826 scrolled_content_origin,
7827 window,
7828 cx,
7829 );
7830 } else if target_display_point.row() >= visible_row_range.end {
7831 return self.render_edit_prediction_scroll_popover(
7832 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
7833 IconName::ArrowDown,
7834 visible_row_range,
7835 line_layouts,
7836 newest_selection_head,
7837 scrolled_content_origin,
7838 window,
7839 cx,
7840 );
7841 }
7842
7843 const POLE_WIDTH: Pixels = px(2.);
7844
7845 let line_layout =
7846 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
7847 let target_column = target_display_point.column() as usize;
7848
7849 let target_x = line_layout.x_for_index(target_column);
7850 let target_y =
7851 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
7852
7853 let flag_on_right = target_x < text_bounds.size.width / 2.;
7854
7855 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
7856 border_color.l += 0.001;
7857
7858 let mut element = v_flex()
7859 .items_end()
7860 .when(flag_on_right, |el| el.items_start())
7861 .child(if flag_on_right {
7862 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7863 .rounded_bl(px(0.))
7864 .rounded_tl(px(0.))
7865 .border_l_2()
7866 .border_color(border_color)
7867 } else {
7868 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
7869 .rounded_br(px(0.))
7870 .rounded_tr(px(0.))
7871 .border_r_2()
7872 .border_color(border_color)
7873 })
7874 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
7875 .into_any();
7876
7877 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7878
7879 let mut origin = scrolled_content_origin + point(target_x, target_y)
7880 - point(
7881 if flag_on_right {
7882 POLE_WIDTH
7883 } else {
7884 size.width - POLE_WIDTH
7885 },
7886 size.height - line_height,
7887 );
7888
7889 origin.x = origin.x.max(content_origin.x);
7890
7891 element.prepaint_at(origin, window, cx);
7892
7893 Some((element, origin))
7894 }
7895
7896 fn render_edit_prediction_scroll_popover(
7897 &mut self,
7898 to_y: impl Fn(Size<Pixels>) -> Pixels,
7899 scroll_icon: IconName,
7900 visible_row_range: Range<DisplayRow>,
7901 line_layouts: &[LineWithInvisibles],
7902 newest_selection_head: Option<DisplayPoint>,
7903 scrolled_content_origin: gpui::Point<Pixels>,
7904 window: &mut Window,
7905 cx: &mut App,
7906 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7907 let mut element = self
7908 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
7909 .into_any();
7910
7911 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7912
7913 let cursor = newest_selection_head?;
7914 let cursor_row_layout =
7915 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
7916 let cursor_column = cursor.column() as usize;
7917
7918 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
7919
7920 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
7921
7922 element.prepaint_at(origin, window, cx);
7923 Some((element, origin))
7924 }
7925
7926 fn render_edit_prediction_eager_jump_popover(
7927 &mut self,
7928 text_bounds: &Bounds<Pixels>,
7929 content_origin: gpui::Point<Pixels>,
7930 editor_snapshot: &EditorSnapshot,
7931 visible_row_range: Range<DisplayRow>,
7932 scroll_top: f32,
7933 scroll_bottom: f32,
7934 line_height: Pixels,
7935 scroll_pixel_position: gpui::Point<Pixels>,
7936 target_display_point: DisplayPoint,
7937 editor_width: Pixels,
7938 window: &mut Window,
7939 cx: &mut App,
7940 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7941 if target_display_point.row().as_f32() < scroll_top {
7942 let mut element = self
7943 .render_edit_prediction_line_popover(
7944 "Jump to Edit",
7945 Some(IconName::ArrowUp),
7946 window,
7947 cx,
7948 )?
7949 .into_any();
7950
7951 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7952 let offset = point(
7953 (text_bounds.size.width - size.width) / 2.,
7954 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7955 );
7956
7957 let origin = text_bounds.origin + offset;
7958 element.prepaint_at(origin, window, cx);
7959 Some((element, origin))
7960 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
7961 let mut element = self
7962 .render_edit_prediction_line_popover(
7963 "Jump to Edit",
7964 Some(IconName::ArrowDown),
7965 window,
7966 cx,
7967 )?
7968 .into_any();
7969
7970 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
7971 let offset = point(
7972 (text_bounds.size.width - size.width) / 2.,
7973 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
7974 );
7975
7976 let origin = text_bounds.origin + offset;
7977 element.prepaint_at(origin, window, cx);
7978 Some((element, origin))
7979 } else {
7980 self.render_edit_prediction_end_of_line_popover(
7981 "Jump to Edit",
7982 editor_snapshot,
7983 visible_row_range,
7984 target_display_point,
7985 line_height,
7986 scroll_pixel_position,
7987 content_origin,
7988 editor_width,
7989 window,
7990 cx,
7991 )
7992 }
7993 }
7994
7995 fn render_edit_prediction_end_of_line_popover(
7996 self: &mut Editor,
7997 label: &'static str,
7998 editor_snapshot: &EditorSnapshot,
7999 visible_row_range: Range<DisplayRow>,
8000 target_display_point: DisplayPoint,
8001 line_height: Pixels,
8002 scroll_pixel_position: gpui::Point<Pixels>,
8003 content_origin: gpui::Point<Pixels>,
8004 editor_width: Pixels,
8005 window: &mut Window,
8006 cx: &mut App,
8007 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8008 let target_line_end = DisplayPoint::new(
8009 target_display_point.row(),
8010 editor_snapshot.line_len(target_display_point.row()),
8011 );
8012
8013 let mut element = self
8014 .render_edit_prediction_line_popover(label, None, window, cx)?
8015 .into_any();
8016
8017 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8018
8019 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8020
8021 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8022 let mut origin = start_point
8023 + line_origin
8024 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8025 origin.x = origin.x.max(content_origin.x);
8026
8027 let max_x = content_origin.x + editor_width - size.width;
8028
8029 if origin.x > max_x {
8030 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8031
8032 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8033 origin.y += offset;
8034 IconName::ArrowUp
8035 } else {
8036 origin.y -= offset;
8037 IconName::ArrowDown
8038 };
8039
8040 element = self
8041 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8042 .into_any();
8043
8044 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8045
8046 origin.x = content_origin.x + editor_width - size.width - px(2.);
8047 }
8048
8049 element.prepaint_at(origin, window, cx);
8050 Some((element, origin))
8051 }
8052
8053 fn render_edit_prediction_diff_popover(
8054 self: &Editor,
8055 text_bounds: &Bounds<Pixels>,
8056 content_origin: gpui::Point<Pixels>,
8057 right_margin: Pixels,
8058 editor_snapshot: &EditorSnapshot,
8059 visible_row_range: Range<DisplayRow>,
8060 line_layouts: &[LineWithInvisibles],
8061 line_height: Pixels,
8062 scroll_pixel_position: gpui::Point<Pixels>,
8063 newest_selection_head: Option<DisplayPoint>,
8064 editor_width: Pixels,
8065 style: &EditorStyle,
8066 edits: &Vec<(Range<Anchor>, String)>,
8067 edit_preview: &Option<language::EditPreview>,
8068 snapshot: &language::BufferSnapshot,
8069 window: &mut Window,
8070 cx: &mut App,
8071 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8072 let edit_start = edits
8073 .first()
8074 .unwrap()
8075 .0
8076 .start
8077 .to_display_point(editor_snapshot);
8078 let edit_end = edits
8079 .last()
8080 .unwrap()
8081 .0
8082 .end
8083 .to_display_point(editor_snapshot);
8084
8085 let is_visible = visible_row_range.contains(&edit_start.row())
8086 || visible_row_range.contains(&edit_end.row());
8087 if !is_visible {
8088 return None;
8089 }
8090
8091 let highlighted_edits =
8092 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8093
8094 let styled_text = highlighted_edits.to_styled_text(&style.text);
8095 let line_count = highlighted_edits.text.lines().count();
8096
8097 const BORDER_WIDTH: Pixels = px(1.);
8098
8099 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8100 let has_keybind = keybind.is_some();
8101
8102 let mut element = h_flex()
8103 .items_start()
8104 .child(
8105 h_flex()
8106 .bg(cx.theme().colors().editor_background)
8107 .border(BORDER_WIDTH)
8108 .shadow_sm()
8109 .border_color(cx.theme().colors().border)
8110 .rounded_l_lg()
8111 .when(line_count > 1, |el| el.rounded_br_lg())
8112 .pr_1()
8113 .child(styled_text),
8114 )
8115 .child(
8116 h_flex()
8117 .h(line_height + BORDER_WIDTH * 2.)
8118 .px_1p5()
8119 .gap_1()
8120 // Workaround: For some reason, there's a gap if we don't do this
8121 .ml(-BORDER_WIDTH)
8122 .shadow(vec![gpui::BoxShadow {
8123 color: gpui::black().opacity(0.05),
8124 offset: point(px(1.), px(1.)),
8125 blur_radius: px(2.),
8126 spread_radius: px(0.),
8127 }])
8128 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8129 .border(BORDER_WIDTH)
8130 .border_color(cx.theme().colors().border)
8131 .rounded_r_lg()
8132 .id("edit_prediction_diff_popover_keybind")
8133 .when(!has_keybind, |el| {
8134 let status_colors = cx.theme().status();
8135
8136 el.bg(status_colors.error_background)
8137 .border_color(status_colors.error.opacity(0.6))
8138 .child(Icon::new(IconName::Info).color(Color::Error))
8139 .cursor_default()
8140 .hoverable_tooltip(move |_window, cx| {
8141 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8142 })
8143 })
8144 .children(keybind),
8145 )
8146 .into_any();
8147
8148 let longest_row =
8149 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8150 let longest_line_width = if visible_row_range.contains(&longest_row) {
8151 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8152 } else {
8153 layout_line(
8154 longest_row,
8155 editor_snapshot,
8156 style,
8157 editor_width,
8158 |_| false,
8159 window,
8160 cx,
8161 )
8162 .width
8163 };
8164
8165 let viewport_bounds =
8166 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8167 right: -right_margin,
8168 ..Default::default()
8169 });
8170
8171 let x_after_longest =
8172 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8173 - scroll_pixel_position.x;
8174
8175 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8176
8177 // Fully visible if it can be displayed within the window (allow overlapping other
8178 // panes). However, this is only allowed if the popover starts within text_bounds.
8179 let can_position_to_the_right = x_after_longest < text_bounds.right()
8180 && x_after_longest + element_bounds.width < viewport_bounds.right();
8181
8182 let mut origin = if can_position_to_the_right {
8183 point(
8184 x_after_longest,
8185 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8186 - scroll_pixel_position.y,
8187 )
8188 } else {
8189 let cursor_row = newest_selection_head.map(|head| head.row());
8190 let above_edit = edit_start
8191 .row()
8192 .0
8193 .checked_sub(line_count as u32)
8194 .map(DisplayRow);
8195 let below_edit = Some(edit_end.row() + 1);
8196 let above_cursor =
8197 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8198 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8199
8200 // Place the edit popover adjacent to the edit if there is a location
8201 // available that is onscreen and does not obscure the cursor. Otherwise,
8202 // place it adjacent to the cursor.
8203 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8204 .into_iter()
8205 .flatten()
8206 .find(|&start_row| {
8207 let end_row = start_row + line_count as u32;
8208 visible_row_range.contains(&start_row)
8209 && visible_row_range.contains(&end_row)
8210 && cursor_row.map_or(true, |cursor_row| {
8211 !((start_row..end_row).contains(&cursor_row))
8212 })
8213 })?;
8214
8215 content_origin
8216 + point(
8217 -scroll_pixel_position.x,
8218 row_target.as_f32() * line_height - scroll_pixel_position.y,
8219 )
8220 };
8221
8222 origin.x -= BORDER_WIDTH;
8223
8224 window.defer_draw(element, origin, 1);
8225
8226 // Do not return an element, since it will already be drawn due to defer_draw.
8227 None
8228 }
8229
8230 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8231 px(30.)
8232 }
8233
8234 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8235 if self.read_only(cx) {
8236 cx.theme().players().read_only()
8237 } else {
8238 self.style.as_ref().unwrap().local_player
8239 }
8240 }
8241
8242 fn render_edit_prediction_accept_keybind(
8243 &self,
8244 window: &mut Window,
8245 cx: &App,
8246 ) -> Option<AnyElement> {
8247 let accept_binding = self.accept_edit_prediction_keybind(window, cx);
8248 let accept_keystroke = accept_binding.keystroke()?;
8249
8250 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8251
8252 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8253 Color::Accent
8254 } else {
8255 Color::Muted
8256 };
8257
8258 h_flex()
8259 .px_0p5()
8260 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8261 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8262 .text_size(TextSize::XSmall.rems(cx))
8263 .child(h_flex().children(ui::render_modifiers(
8264 &accept_keystroke.modifiers,
8265 PlatformStyle::platform(),
8266 Some(modifiers_color),
8267 Some(IconSize::XSmall.rems().into()),
8268 true,
8269 )))
8270 .when(is_platform_style_mac, |parent| {
8271 parent.child(accept_keystroke.key.clone())
8272 })
8273 .when(!is_platform_style_mac, |parent| {
8274 parent.child(
8275 Key::new(
8276 util::capitalize(&accept_keystroke.key),
8277 Some(Color::Default),
8278 )
8279 .size(Some(IconSize::XSmall.rems().into())),
8280 )
8281 })
8282 .into_any()
8283 .into()
8284 }
8285
8286 fn render_edit_prediction_line_popover(
8287 &self,
8288 label: impl Into<SharedString>,
8289 icon: Option<IconName>,
8290 window: &mut Window,
8291 cx: &App,
8292 ) -> Option<Stateful<Div>> {
8293 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8294
8295 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8296 let has_keybind = keybind.is_some();
8297
8298 let result = h_flex()
8299 .id("ep-line-popover")
8300 .py_0p5()
8301 .pl_1()
8302 .pr(padding_right)
8303 .gap_1()
8304 .rounded_md()
8305 .border_1()
8306 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8307 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8308 .shadow_sm()
8309 .when(!has_keybind, |el| {
8310 let status_colors = cx.theme().status();
8311
8312 el.bg(status_colors.error_background)
8313 .border_color(status_colors.error.opacity(0.6))
8314 .pl_2()
8315 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8316 .cursor_default()
8317 .hoverable_tooltip(move |_window, cx| {
8318 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8319 })
8320 })
8321 .children(keybind)
8322 .child(
8323 Label::new(label)
8324 .size(LabelSize::Small)
8325 .when(!has_keybind, |el| {
8326 el.color(cx.theme().status().error.into()).strikethrough()
8327 }),
8328 )
8329 .when(!has_keybind, |el| {
8330 el.child(
8331 h_flex().ml_1().child(
8332 Icon::new(IconName::Info)
8333 .size(IconSize::Small)
8334 .color(cx.theme().status().error.into()),
8335 ),
8336 )
8337 })
8338 .when_some(icon, |element, icon| {
8339 element.child(
8340 div()
8341 .mt(px(1.5))
8342 .child(Icon::new(icon).size(IconSize::Small)),
8343 )
8344 });
8345
8346 Some(result)
8347 }
8348
8349 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8350 let accent_color = cx.theme().colors().text_accent;
8351 let editor_bg_color = cx.theme().colors().editor_background;
8352 editor_bg_color.blend(accent_color.opacity(0.1))
8353 }
8354
8355 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8356 let accent_color = cx.theme().colors().text_accent;
8357 let editor_bg_color = cx.theme().colors().editor_background;
8358 editor_bg_color.blend(accent_color.opacity(0.6))
8359 }
8360
8361 fn render_edit_prediction_cursor_popover(
8362 &self,
8363 min_width: Pixels,
8364 max_width: Pixels,
8365 cursor_point: Point,
8366 style: &EditorStyle,
8367 accept_keystroke: Option<&gpui::Keystroke>,
8368 _window: &Window,
8369 cx: &mut Context<Editor>,
8370 ) -> Option<AnyElement> {
8371 let provider = self.edit_prediction_provider.as_ref()?;
8372
8373 if provider.provider.needs_terms_acceptance(cx) {
8374 return Some(
8375 h_flex()
8376 .min_w(min_width)
8377 .flex_1()
8378 .px_2()
8379 .py_1()
8380 .gap_3()
8381 .elevation_2(cx)
8382 .hover(|style| style.bg(cx.theme().colors().element_hover))
8383 .id("accept-terms")
8384 .cursor_pointer()
8385 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8386 .on_click(cx.listener(|this, _event, window, cx| {
8387 cx.stop_propagation();
8388 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8389 window.dispatch_action(
8390 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8391 cx,
8392 );
8393 }))
8394 .child(
8395 h_flex()
8396 .flex_1()
8397 .gap_2()
8398 .child(Icon::new(IconName::ZedPredict))
8399 .child(Label::new("Accept Terms of Service"))
8400 .child(div().w_full())
8401 .child(
8402 Icon::new(IconName::ArrowUpRight)
8403 .color(Color::Muted)
8404 .size(IconSize::Small),
8405 )
8406 .into_any_element(),
8407 )
8408 .into_any(),
8409 );
8410 }
8411
8412 let is_refreshing = provider.provider.is_refreshing(cx);
8413
8414 fn pending_completion_container() -> Div {
8415 h_flex()
8416 .h_full()
8417 .flex_1()
8418 .gap_2()
8419 .child(Icon::new(IconName::ZedPredict))
8420 }
8421
8422 let completion = match &self.active_inline_completion {
8423 Some(prediction) => {
8424 if !self.has_visible_completions_menu() {
8425 const RADIUS: Pixels = px(6.);
8426 const BORDER_WIDTH: Pixels = px(1.);
8427
8428 return Some(
8429 h_flex()
8430 .elevation_2(cx)
8431 .border(BORDER_WIDTH)
8432 .border_color(cx.theme().colors().border)
8433 .when(accept_keystroke.is_none(), |el| {
8434 el.border_color(cx.theme().status().error)
8435 })
8436 .rounded(RADIUS)
8437 .rounded_tl(px(0.))
8438 .overflow_hidden()
8439 .child(div().px_1p5().child(match &prediction.completion {
8440 InlineCompletion::Move { target, snapshot } => {
8441 use text::ToPoint as _;
8442 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8443 {
8444 Icon::new(IconName::ZedPredictDown)
8445 } else {
8446 Icon::new(IconName::ZedPredictUp)
8447 }
8448 }
8449 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8450 }))
8451 .child(
8452 h_flex()
8453 .gap_1()
8454 .py_1()
8455 .px_2()
8456 .rounded_r(RADIUS - BORDER_WIDTH)
8457 .border_l_1()
8458 .border_color(cx.theme().colors().border)
8459 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8460 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8461 el.child(
8462 Label::new("Hold")
8463 .size(LabelSize::Small)
8464 .when(accept_keystroke.is_none(), |el| {
8465 el.strikethrough()
8466 })
8467 .line_height_style(LineHeightStyle::UiLabel),
8468 )
8469 })
8470 .id("edit_prediction_cursor_popover_keybind")
8471 .when(accept_keystroke.is_none(), |el| {
8472 let status_colors = cx.theme().status();
8473
8474 el.bg(status_colors.error_background)
8475 .border_color(status_colors.error.opacity(0.6))
8476 .child(Icon::new(IconName::Info).color(Color::Error))
8477 .cursor_default()
8478 .hoverable_tooltip(move |_window, cx| {
8479 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8480 .into()
8481 })
8482 })
8483 .when_some(
8484 accept_keystroke.as_ref(),
8485 |el, accept_keystroke| {
8486 el.child(h_flex().children(ui::render_modifiers(
8487 &accept_keystroke.modifiers,
8488 PlatformStyle::platform(),
8489 Some(Color::Default),
8490 Some(IconSize::XSmall.rems().into()),
8491 false,
8492 )))
8493 },
8494 ),
8495 )
8496 .into_any(),
8497 );
8498 }
8499
8500 self.render_edit_prediction_cursor_popover_preview(
8501 prediction,
8502 cursor_point,
8503 style,
8504 cx,
8505 )?
8506 }
8507
8508 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8509 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8510 stale_completion,
8511 cursor_point,
8512 style,
8513 cx,
8514 )?,
8515
8516 None => {
8517 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8518 }
8519 },
8520
8521 None => pending_completion_container().child(Label::new("No Prediction")),
8522 };
8523
8524 let completion = if is_refreshing {
8525 completion
8526 .with_animation(
8527 "loading-completion",
8528 Animation::new(Duration::from_secs(2))
8529 .repeat()
8530 .with_easing(pulsating_between(0.4, 0.8)),
8531 |label, delta| label.opacity(delta),
8532 )
8533 .into_any_element()
8534 } else {
8535 completion.into_any_element()
8536 };
8537
8538 let has_completion = self.active_inline_completion.is_some();
8539
8540 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8541 Some(
8542 h_flex()
8543 .min_w(min_width)
8544 .max_w(max_width)
8545 .flex_1()
8546 .elevation_2(cx)
8547 .border_color(cx.theme().colors().border)
8548 .child(
8549 div()
8550 .flex_1()
8551 .py_1()
8552 .px_2()
8553 .overflow_hidden()
8554 .child(completion),
8555 )
8556 .when_some(accept_keystroke, |el, accept_keystroke| {
8557 if !accept_keystroke.modifiers.modified() {
8558 return el;
8559 }
8560
8561 el.child(
8562 h_flex()
8563 .h_full()
8564 .border_l_1()
8565 .rounded_r_lg()
8566 .border_color(cx.theme().colors().border)
8567 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8568 .gap_1()
8569 .py_1()
8570 .px_2()
8571 .child(
8572 h_flex()
8573 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8574 .when(is_platform_style_mac, |parent| parent.gap_1())
8575 .child(h_flex().children(ui::render_modifiers(
8576 &accept_keystroke.modifiers,
8577 PlatformStyle::platform(),
8578 Some(if !has_completion {
8579 Color::Muted
8580 } else {
8581 Color::Default
8582 }),
8583 None,
8584 false,
8585 ))),
8586 )
8587 .child(Label::new("Preview").into_any_element())
8588 .opacity(if has_completion { 1.0 } else { 0.4 }),
8589 )
8590 })
8591 .into_any(),
8592 )
8593 }
8594
8595 fn render_edit_prediction_cursor_popover_preview(
8596 &self,
8597 completion: &InlineCompletionState,
8598 cursor_point: Point,
8599 style: &EditorStyle,
8600 cx: &mut Context<Editor>,
8601 ) -> Option<Div> {
8602 use text::ToPoint as _;
8603
8604 fn render_relative_row_jump(
8605 prefix: impl Into<String>,
8606 current_row: u32,
8607 target_row: u32,
8608 ) -> Div {
8609 let (row_diff, arrow) = if target_row < current_row {
8610 (current_row - target_row, IconName::ArrowUp)
8611 } else {
8612 (target_row - current_row, IconName::ArrowDown)
8613 };
8614
8615 h_flex()
8616 .child(
8617 Label::new(format!("{}{}", prefix.into(), row_diff))
8618 .color(Color::Muted)
8619 .size(LabelSize::Small),
8620 )
8621 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8622 }
8623
8624 match &completion.completion {
8625 InlineCompletion::Move {
8626 target, snapshot, ..
8627 } => Some(
8628 h_flex()
8629 .px_2()
8630 .gap_2()
8631 .flex_1()
8632 .child(
8633 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8634 Icon::new(IconName::ZedPredictDown)
8635 } else {
8636 Icon::new(IconName::ZedPredictUp)
8637 },
8638 )
8639 .child(Label::new("Jump to Edit")),
8640 ),
8641
8642 InlineCompletion::Edit {
8643 edits,
8644 edit_preview,
8645 snapshot,
8646 display_mode: _,
8647 } => {
8648 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8649
8650 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8651 &snapshot,
8652 &edits,
8653 edit_preview.as_ref()?,
8654 true,
8655 cx,
8656 )
8657 .first_line_preview();
8658
8659 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8660 .with_default_highlights(&style.text, highlighted_edits.highlights);
8661
8662 let preview = h_flex()
8663 .gap_1()
8664 .min_w_16()
8665 .child(styled_text)
8666 .when(has_more_lines, |parent| parent.child("…"));
8667
8668 let left = if first_edit_row != cursor_point.row {
8669 render_relative_row_jump("", cursor_point.row, first_edit_row)
8670 .into_any_element()
8671 } else {
8672 Icon::new(IconName::ZedPredict).into_any_element()
8673 };
8674
8675 Some(
8676 h_flex()
8677 .h_full()
8678 .flex_1()
8679 .gap_2()
8680 .pr_1()
8681 .overflow_x_hidden()
8682 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8683 .child(left)
8684 .child(preview),
8685 )
8686 }
8687 }
8688 }
8689
8690 pub fn render_context_menu(
8691 &self,
8692 style: &EditorStyle,
8693 max_height_in_lines: u32,
8694 window: &mut Window,
8695 cx: &mut Context<Editor>,
8696 ) -> Option<AnyElement> {
8697 let menu = self.context_menu.borrow();
8698 let menu = menu.as_ref()?;
8699 if !menu.visible() {
8700 return None;
8701 };
8702 Some(menu.render(style, max_height_in_lines, window, cx))
8703 }
8704
8705 fn render_context_menu_aside(
8706 &mut self,
8707 max_size: Size<Pixels>,
8708 window: &mut Window,
8709 cx: &mut Context<Editor>,
8710 ) -> Option<AnyElement> {
8711 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
8712 if menu.visible() {
8713 menu.render_aside(max_size, window, cx)
8714 } else {
8715 None
8716 }
8717 })
8718 }
8719
8720 fn hide_context_menu(
8721 &mut self,
8722 window: &mut Window,
8723 cx: &mut Context<Self>,
8724 ) -> Option<CodeContextMenu> {
8725 cx.notify();
8726 self.completion_tasks.clear();
8727 let context_menu = self.context_menu.borrow_mut().take();
8728 self.stale_inline_completion_in_menu.take();
8729 self.update_visible_inline_completion(window, cx);
8730 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
8731 if let Some(completion_provider) = &self.completion_provider {
8732 completion_provider.selection_changed(None, window, cx);
8733 }
8734 }
8735 context_menu
8736 }
8737
8738 fn show_snippet_choices(
8739 &mut self,
8740 choices: &Vec<String>,
8741 selection: Range<Anchor>,
8742 cx: &mut Context<Self>,
8743 ) {
8744 if selection.start.buffer_id.is_none() {
8745 return;
8746 }
8747 let buffer_id = selection.start.buffer_id.unwrap();
8748 let buffer = self.buffer().read(cx).buffer(buffer_id);
8749 let id = post_inc(&mut self.next_completion_id);
8750 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
8751
8752 if let Some(buffer) = buffer {
8753 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
8754 CompletionsMenu::new_snippet_choices(
8755 id,
8756 true,
8757 choices,
8758 selection,
8759 buffer,
8760 snippet_sort_order,
8761 ),
8762 ));
8763 }
8764 }
8765
8766 pub fn insert_snippet(
8767 &mut self,
8768 insertion_ranges: &[Range<usize>],
8769 snippet: Snippet,
8770 window: &mut Window,
8771 cx: &mut Context<Self>,
8772 ) -> Result<()> {
8773 struct Tabstop<T> {
8774 is_end_tabstop: bool,
8775 ranges: Vec<Range<T>>,
8776 choices: Option<Vec<String>>,
8777 }
8778
8779 let tabstops = self.buffer.update(cx, |buffer, cx| {
8780 let snippet_text: Arc<str> = snippet.text.clone().into();
8781 let edits = insertion_ranges
8782 .iter()
8783 .cloned()
8784 .map(|range| (range, snippet_text.clone()));
8785 buffer.edit(edits, Some(AutoindentMode::EachLine), cx);
8786
8787 let snapshot = &*buffer.read(cx);
8788 let snippet = &snippet;
8789 snippet
8790 .tabstops
8791 .iter()
8792 .map(|tabstop| {
8793 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
8794 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
8795 });
8796 let mut tabstop_ranges = tabstop
8797 .ranges
8798 .iter()
8799 .flat_map(|tabstop_range| {
8800 let mut delta = 0_isize;
8801 insertion_ranges.iter().map(move |insertion_range| {
8802 let insertion_start = insertion_range.start as isize + delta;
8803 delta +=
8804 snippet.text.len() as isize - insertion_range.len() as isize;
8805
8806 let start = ((insertion_start + tabstop_range.start) as usize)
8807 .min(snapshot.len());
8808 let end = ((insertion_start + tabstop_range.end) as usize)
8809 .min(snapshot.len());
8810 snapshot.anchor_before(start)..snapshot.anchor_after(end)
8811 })
8812 })
8813 .collect::<Vec<_>>();
8814 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
8815
8816 Tabstop {
8817 is_end_tabstop,
8818 ranges: tabstop_ranges,
8819 choices: tabstop.choices.clone(),
8820 }
8821 })
8822 .collect::<Vec<_>>()
8823 });
8824 if let Some(tabstop) = tabstops.first() {
8825 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8826 s.select_ranges(tabstop.ranges.iter().cloned());
8827 });
8828
8829 if let Some(choices) = &tabstop.choices {
8830 if let Some(selection) = tabstop.ranges.first() {
8831 self.show_snippet_choices(choices, selection.clone(), cx)
8832 }
8833 }
8834
8835 // If we're already at the last tabstop and it's at the end of the snippet,
8836 // we're done, we don't need to keep the state around.
8837 if !tabstop.is_end_tabstop {
8838 let choices = tabstops
8839 .iter()
8840 .map(|tabstop| tabstop.choices.clone())
8841 .collect();
8842
8843 let ranges = tabstops
8844 .into_iter()
8845 .map(|tabstop| tabstop.ranges)
8846 .collect::<Vec<_>>();
8847
8848 self.snippet_stack.push(SnippetState {
8849 active_index: 0,
8850 ranges,
8851 choices,
8852 });
8853 }
8854
8855 // Check whether the just-entered snippet ends with an auto-closable bracket.
8856 if self.autoclose_regions.is_empty() {
8857 let snapshot = self.buffer.read(cx).snapshot(cx);
8858 for selection in &mut self.selections.all::<Point>(cx) {
8859 let selection_head = selection.head();
8860 let Some(scope) = snapshot.language_scope_at(selection_head) else {
8861 continue;
8862 };
8863
8864 let mut bracket_pair = None;
8865 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
8866 let prev_chars = snapshot
8867 .reversed_chars_at(selection_head)
8868 .collect::<String>();
8869 for (pair, enabled) in scope.brackets() {
8870 if enabled
8871 && pair.close
8872 && prev_chars.starts_with(pair.start.as_str())
8873 && next_chars.starts_with(pair.end.as_str())
8874 {
8875 bracket_pair = Some(pair.clone());
8876 break;
8877 }
8878 }
8879 if let Some(pair) = bracket_pair {
8880 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
8881 let autoclose_enabled =
8882 self.use_autoclose && snapshot_settings.use_autoclose;
8883 if autoclose_enabled {
8884 let start = snapshot.anchor_after(selection_head);
8885 let end = snapshot.anchor_after(selection_head);
8886 self.autoclose_regions.push(AutocloseRegion {
8887 selection_id: selection.id,
8888 range: start..end,
8889 pair,
8890 });
8891 }
8892 }
8893 }
8894 }
8895 }
8896 Ok(())
8897 }
8898
8899 pub fn move_to_next_snippet_tabstop(
8900 &mut self,
8901 window: &mut Window,
8902 cx: &mut Context<Self>,
8903 ) -> bool {
8904 self.move_to_snippet_tabstop(Bias::Right, window, cx)
8905 }
8906
8907 pub fn move_to_prev_snippet_tabstop(
8908 &mut self,
8909 window: &mut Window,
8910 cx: &mut Context<Self>,
8911 ) -> bool {
8912 self.move_to_snippet_tabstop(Bias::Left, window, cx)
8913 }
8914
8915 pub fn move_to_snippet_tabstop(
8916 &mut self,
8917 bias: Bias,
8918 window: &mut Window,
8919 cx: &mut Context<Self>,
8920 ) -> bool {
8921 if let Some(mut snippet) = self.snippet_stack.pop() {
8922 match bias {
8923 Bias::Left => {
8924 if snippet.active_index > 0 {
8925 snippet.active_index -= 1;
8926 } else {
8927 self.snippet_stack.push(snippet);
8928 return false;
8929 }
8930 }
8931 Bias::Right => {
8932 if snippet.active_index + 1 < snippet.ranges.len() {
8933 snippet.active_index += 1;
8934 } else {
8935 self.snippet_stack.push(snippet);
8936 return false;
8937 }
8938 }
8939 }
8940 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
8941 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
8942 s.select_anchor_ranges(current_ranges.iter().cloned())
8943 });
8944
8945 if let Some(choices) = &snippet.choices[snippet.active_index] {
8946 if let Some(selection) = current_ranges.first() {
8947 self.show_snippet_choices(&choices, selection.clone(), cx);
8948 }
8949 }
8950
8951 // If snippet state is not at the last tabstop, push it back on the stack
8952 if snippet.active_index + 1 < snippet.ranges.len() {
8953 self.snippet_stack.push(snippet);
8954 }
8955 return true;
8956 }
8957 }
8958
8959 false
8960 }
8961
8962 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
8963 self.transact(window, cx, |this, window, cx| {
8964 this.select_all(&SelectAll, window, cx);
8965 this.insert("", window, cx);
8966 });
8967 }
8968
8969 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
8970 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
8971 self.transact(window, cx, |this, window, cx| {
8972 this.select_autoclose_pair(window, cx);
8973 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
8974 if !this.linked_edit_ranges.is_empty() {
8975 let selections = this.selections.all::<MultiBufferPoint>(cx);
8976 let snapshot = this.buffer.read(cx).snapshot(cx);
8977
8978 for selection in selections.iter() {
8979 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
8980 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
8981 if selection_start.buffer_id != selection_end.buffer_id {
8982 continue;
8983 }
8984 if let Some(ranges) =
8985 this.linked_editing_ranges_for(selection_start..selection_end, cx)
8986 {
8987 for (buffer, entries) in ranges {
8988 linked_ranges.entry(buffer).or_default().extend(entries);
8989 }
8990 }
8991 }
8992 }
8993
8994 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
8995 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
8996 for selection in &mut selections {
8997 if selection.is_empty() {
8998 let old_head = selection.head();
8999 let mut new_head =
9000 movement::left(&display_map, old_head.to_display_point(&display_map))
9001 .to_point(&display_map);
9002 if let Some((buffer, line_buffer_range)) = display_map
9003 .buffer_snapshot
9004 .buffer_line_for_row(MultiBufferRow(old_head.row))
9005 {
9006 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9007 let indent_len = match indent_size.kind {
9008 IndentKind::Space => {
9009 buffer.settings_at(line_buffer_range.start, cx).tab_size
9010 }
9011 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9012 };
9013 if old_head.column <= indent_size.len && old_head.column > 0 {
9014 let indent_len = indent_len.get();
9015 new_head = cmp::min(
9016 new_head,
9017 MultiBufferPoint::new(
9018 old_head.row,
9019 ((old_head.column - 1) / indent_len) * indent_len,
9020 ),
9021 );
9022 }
9023 }
9024
9025 selection.set_head(new_head, SelectionGoal::None);
9026 }
9027 }
9028
9029 this.signature_help_state.set_backspace_pressed(true);
9030 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9031 s.select(selections)
9032 });
9033 this.insert("", window, cx);
9034 let empty_str: Arc<str> = Arc::from("");
9035 for (buffer, edits) in linked_ranges {
9036 let snapshot = buffer.read(cx).snapshot();
9037 use text::ToPoint as TP;
9038
9039 let edits = edits
9040 .into_iter()
9041 .map(|range| {
9042 let end_point = TP::to_point(&range.end, &snapshot);
9043 let mut start_point = TP::to_point(&range.start, &snapshot);
9044
9045 if end_point == start_point {
9046 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9047 .saturating_sub(1);
9048 start_point =
9049 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9050 };
9051
9052 (start_point..end_point, empty_str.clone())
9053 })
9054 .sorted_by_key(|(range, _)| range.start)
9055 .collect::<Vec<_>>();
9056 buffer.update(cx, |this, cx| {
9057 this.edit(edits, None, cx);
9058 })
9059 }
9060 this.refresh_inline_completion(true, false, window, cx);
9061 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9062 });
9063 }
9064
9065 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9066 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9067 self.transact(window, cx, |this, window, cx| {
9068 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9069 s.move_with(|map, selection| {
9070 if selection.is_empty() {
9071 let cursor = movement::right(map, selection.head());
9072 selection.end = cursor;
9073 selection.reversed = true;
9074 selection.goal = SelectionGoal::None;
9075 }
9076 })
9077 });
9078 this.insert("", window, cx);
9079 this.refresh_inline_completion(true, false, window, cx);
9080 });
9081 }
9082
9083 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9084 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9085 if self.move_to_prev_snippet_tabstop(window, cx) {
9086 return;
9087 }
9088 self.outdent(&Outdent, window, cx);
9089 }
9090
9091 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9092 if self.move_to_next_snippet_tabstop(window, cx) {
9093 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9094 return;
9095 }
9096 if self.read_only(cx) {
9097 return;
9098 }
9099 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9100 let mut selections = self.selections.all_adjusted(cx);
9101 let buffer = self.buffer.read(cx);
9102 let snapshot = buffer.snapshot(cx);
9103 let rows_iter = selections.iter().map(|s| s.head().row);
9104 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9105
9106 let has_some_cursor_in_whitespace = selections
9107 .iter()
9108 .filter(|selection| selection.is_empty())
9109 .any(|selection| {
9110 let cursor = selection.head();
9111 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9112 cursor.column < current_indent.len
9113 });
9114
9115 let mut edits = Vec::new();
9116 let mut prev_edited_row = 0;
9117 let mut row_delta = 0;
9118 for selection in &mut selections {
9119 if selection.start.row != prev_edited_row {
9120 row_delta = 0;
9121 }
9122 prev_edited_row = selection.end.row;
9123
9124 // If the selection is non-empty, then increase the indentation of the selected lines.
9125 if !selection.is_empty() {
9126 row_delta =
9127 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9128 continue;
9129 }
9130
9131 let cursor = selection.head();
9132 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9133 if let Some(suggested_indent) =
9134 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9135 {
9136 // Don't do anything if already at suggested indent
9137 // and there is any other cursor which is not
9138 if has_some_cursor_in_whitespace
9139 && cursor.column == current_indent.len
9140 && current_indent.len == suggested_indent.len
9141 {
9142 continue;
9143 }
9144
9145 // Adjust line and move cursor to suggested indent
9146 // if cursor is not at suggested indent
9147 if cursor.column < suggested_indent.len
9148 && cursor.column <= current_indent.len
9149 && current_indent.len <= suggested_indent.len
9150 {
9151 selection.start = Point::new(cursor.row, suggested_indent.len);
9152 selection.end = selection.start;
9153 if row_delta == 0 {
9154 edits.extend(Buffer::edit_for_indent_size_adjustment(
9155 cursor.row,
9156 current_indent,
9157 suggested_indent,
9158 ));
9159 row_delta = suggested_indent.len - current_indent.len;
9160 }
9161 continue;
9162 }
9163
9164 // If current indent is more than suggested indent
9165 // only move cursor to current indent and skip indent
9166 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9167 selection.start = Point::new(cursor.row, current_indent.len);
9168 selection.end = selection.start;
9169 continue;
9170 }
9171 }
9172
9173 // Otherwise, insert a hard or soft tab.
9174 let settings = buffer.language_settings_at(cursor, cx);
9175 let tab_size = if settings.hard_tabs {
9176 IndentSize::tab()
9177 } else {
9178 let tab_size = settings.tab_size.get();
9179 let indent_remainder = snapshot
9180 .text_for_range(Point::new(cursor.row, 0)..cursor)
9181 .flat_map(str::chars)
9182 .fold(row_delta % tab_size, |counter: u32, c| {
9183 if c == '\t' {
9184 0
9185 } else {
9186 (counter + 1) % tab_size
9187 }
9188 });
9189
9190 let chars_to_next_tab_stop = tab_size - indent_remainder;
9191 IndentSize::spaces(chars_to_next_tab_stop)
9192 };
9193 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9194 selection.end = selection.start;
9195 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9196 row_delta += tab_size.len;
9197 }
9198
9199 self.transact(window, cx, |this, window, cx| {
9200 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9201 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9202 s.select(selections)
9203 });
9204 this.refresh_inline_completion(true, false, window, cx);
9205 });
9206 }
9207
9208 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9209 if self.read_only(cx) {
9210 return;
9211 }
9212 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9213 let mut selections = self.selections.all::<Point>(cx);
9214 let mut prev_edited_row = 0;
9215 let mut row_delta = 0;
9216 let mut edits = Vec::new();
9217 let buffer = self.buffer.read(cx);
9218 let snapshot = buffer.snapshot(cx);
9219 for selection in &mut selections {
9220 if selection.start.row != prev_edited_row {
9221 row_delta = 0;
9222 }
9223 prev_edited_row = selection.end.row;
9224
9225 row_delta =
9226 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9227 }
9228
9229 self.transact(window, cx, |this, window, cx| {
9230 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9231 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9232 s.select(selections)
9233 });
9234 });
9235 }
9236
9237 fn indent_selection(
9238 buffer: &MultiBuffer,
9239 snapshot: &MultiBufferSnapshot,
9240 selection: &mut Selection<Point>,
9241 edits: &mut Vec<(Range<Point>, String)>,
9242 delta_for_start_row: u32,
9243 cx: &App,
9244 ) -> u32 {
9245 let settings = buffer.language_settings_at(selection.start, cx);
9246 let tab_size = settings.tab_size.get();
9247 let indent_kind = if settings.hard_tabs {
9248 IndentKind::Tab
9249 } else {
9250 IndentKind::Space
9251 };
9252 let mut start_row = selection.start.row;
9253 let mut end_row = selection.end.row + 1;
9254
9255 // If a selection ends at the beginning of a line, don't indent
9256 // that last line.
9257 if selection.end.column == 0 && selection.end.row > selection.start.row {
9258 end_row -= 1;
9259 }
9260
9261 // Avoid re-indenting a row that has already been indented by a
9262 // previous selection, but still update this selection's column
9263 // to reflect that indentation.
9264 if delta_for_start_row > 0 {
9265 start_row += 1;
9266 selection.start.column += delta_for_start_row;
9267 if selection.end.row == selection.start.row {
9268 selection.end.column += delta_for_start_row;
9269 }
9270 }
9271
9272 let mut delta_for_end_row = 0;
9273 let has_multiple_rows = start_row + 1 != end_row;
9274 for row in start_row..end_row {
9275 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9276 let indent_delta = match (current_indent.kind, indent_kind) {
9277 (IndentKind::Space, IndentKind::Space) => {
9278 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9279 IndentSize::spaces(columns_to_next_tab_stop)
9280 }
9281 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9282 (_, IndentKind::Tab) => IndentSize::tab(),
9283 };
9284
9285 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9286 0
9287 } else {
9288 selection.start.column
9289 };
9290 let row_start = Point::new(row, start);
9291 edits.push((
9292 row_start..row_start,
9293 indent_delta.chars().collect::<String>(),
9294 ));
9295
9296 // Update this selection's endpoints to reflect the indentation.
9297 if row == selection.start.row {
9298 selection.start.column += indent_delta.len;
9299 }
9300 if row == selection.end.row {
9301 selection.end.column += indent_delta.len;
9302 delta_for_end_row = indent_delta.len;
9303 }
9304 }
9305
9306 if selection.start.row == selection.end.row {
9307 delta_for_start_row + delta_for_end_row
9308 } else {
9309 delta_for_end_row
9310 }
9311 }
9312
9313 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9314 if self.read_only(cx) {
9315 return;
9316 }
9317 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9318 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9319 let selections = self.selections.all::<Point>(cx);
9320 let mut deletion_ranges = Vec::new();
9321 let mut last_outdent = None;
9322 {
9323 let buffer = self.buffer.read(cx);
9324 let snapshot = buffer.snapshot(cx);
9325 for selection in &selections {
9326 let settings = buffer.language_settings_at(selection.start, cx);
9327 let tab_size = settings.tab_size.get();
9328 let mut rows = selection.spanned_rows(false, &display_map);
9329
9330 // Avoid re-outdenting a row that has already been outdented by a
9331 // previous selection.
9332 if let Some(last_row) = last_outdent {
9333 if last_row == rows.start {
9334 rows.start = rows.start.next_row();
9335 }
9336 }
9337 let has_multiple_rows = rows.len() > 1;
9338 for row in rows.iter_rows() {
9339 let indent_size = snapshot.indent_size_for_line(row);
9340 if indent_size.len > 0 {
9341 let deletion_len = match indent_size.kind {
9342 IndentKind::Space => {
9343 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9344 if columns_to_prev_tab_stop == 0 {
9345 tab_size
9346 } else {
9347 columns_to_prev_tab_stop
9348 }
9349 }
9350 IndentKind::Tab => 1,
9351 };
9352 let start = if has_multiple_rows
9353 || deletion_len > selection.start.column
9354 || indent_size.len < selection.start.column
9355 {
9356 0
9357 } else {
9358 selection.start.column - deletion_len
9359 };
9360 deletion_ranges.push(
9361 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9362 );
9363 last_outdent = Some(row);
9364 }
9365 }
9366 }
9367 }
9368
9369 self.transact(window, cx, |this, window, cx| {
9370 this.buffer.update(cx, |buffer, cx| {
9371 let empty_str: Arc<str> = Arc::default();
9372 buffer.edit(
9373 deletion_ranges
9374 .into_iter()
9375 .map(|range| (range, empty_str.clone())),
9376 None,
9377 cx,
9378 );
9379 });
9380 let selections = this.selections.all::<usize>(cx);
9381 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9382 s.select(selections)
9383 });
9384 });
9385 }
9386
9387 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9388 if self.read_only(cx) {
9389 return;
9390 }
9391 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9392 let selections = self
9393 .selections
9394 .all::<usize>(cx)
9395 .into_iter()
9396 .map(|s| s.range());
9397
9398 self.transact(window, cx, |this, window, cx| {
9399 this.buffer.update(cx, |buffer, cx| {
9400 buffer.autoindent_ranges(selections, cx);
9401 });
9402 let selections = this.selections.all::<usize>(cx);
9403 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9404 s.select(selections)
9405 });
9406 });
9407 }
9408
9409 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9410 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9411 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9412 let selections = self.selections.all::<Point>(cx);
9413
9414 let mut new_cursors = Vec::new();
9415 let mut edit_ranges = Vec::new();
9416 let mut selections = selections.iter().peekable();
9417 while let Some(selection) = selections.next() {
9418 let mut rows = selection.spanned_rows(false, &display_map);
9419 let goal_display_column = selection.head().to_display_point(&display_map).column();
9420
9421 // Accumulate contiguous regions of rows that we want to delete.
9422 while let Some(next_selection) = selections.peek() {
9423 let next_rows = next_selection.spanned_rows(false, &display_map);
9424 if next_rows.start <= rows.end {
9425 rows.end = next_rows.end;
9426 selections.next().unwrap();
9427 } else {
9428 break;
9429 }
9430 }
9431
9432 let buffer = &display_map.buffer_snapshot;
9433 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9434 let edit_end;
9435 let cursor_buffer_row;
9436 if buffer.max_point().row >= rows.end.0 {
9437 // If there's a line after the range, delete the \n from the end of the row range
9438 // and position the cursor on the next line.
9439 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9440 cursor_buffer_row = rows.end;
9441 } else {
9442 // If there isn't a line after the range, delete the \n from the line before the
9443 // start of the row range and position the cursor there.
9444 edit_start = edit_start.saturating_sub(1);
9445 edit_end = buffer.len();
9446 cursor_buffer_row = rows.start.previous_row();
9447 }
9448
9449 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9450 *cursor.column_mut() =
9451 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9452
9453 new_cursors.push((
9454 selection.id,
9455 buffer.anchor_after(cursor.to_point(&display_map)),
9456 ));
9457 edit_ranges.push(edit_start..edit_end);
9458 }
9459
9460 self.transact(window, cx, |this, window, cx| {
9461 let buffer = this.buffer.update(cx, |buffer, cx| {
9462 let empty_str: Arc<str> = Arc::default();
9463 buffer.edit(
9464 edit_ranges
9465 .into_iter()
9466 .map(|range| (range, empty_str.clone())),
9467 None,
9468 cx,
9469 );
9470 buffer.snapshot(cx)
9471 });
9472 let new_selections = new_cursors
9473 .into_iter()
9474 .map(|(id, cursor)| {
9475 let cursor = cursor.to_point(&buffer);
9476 Selection {
9477 id,
9478 start: cursor,
9479 end: cursor,
9480 reversed: false,
9481 goal: SelectionGoal::None,
9482 }
9483 })
9484 .collect();
9485
9486 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9487 s.select(new_selections);
9488 });
9489 });
9490 }
9491
9492 pub fn join_lines_impl(
9493 &mut self,
9494 insert_whitespace: bool,
9495 window: &mut Window,
9496 cx: &mut Context<Self>,
9497 ) {
9498 if self.read_only(cx) {
9499 return;
9500 }
9501 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9502 for selection in self.selections.all::<Point>(cx) {
9503 let start = MultiBufferRow(selection.start.row);
9504 // Treat single line selections as if they include the next line. Otherwise this action
9505 // would do nothing for single line selections individual cursors.
9506 let end = if selection.start.row == selection.end.row {
9507 MultiBufferRow(selection.start.row + 1)
9508 } else {
9509 MultiBufferRow(selection.end.row)
9510 };
9511
9512 if let Some(last_row_range) = row_ranges.last_mut() {
9513 if start <= last_row_range.end {
9514 last_row_range.end = end;
9515 continue;
9516 }
9517 }
9518 row_ranges.push(start..end);
9519 }
9520
9521 let snapshot = self.buffer.read(cx).snapshot(cx);
9522 let mut cursor_positions = Vec::new();
9523 for row_range in &row_ranges {
9524 let anchor = snapshot.anchor_before(Point::new(
9525 row_range.end.previous_row().0,
9526 snapshot.line_len(row_range.end.previous_row()),
9527 ));
9528 cursor_positions.push(anchor..anchor);
9529 }
9530
9531 self.transact(window, cx, |this, window, cx| {
9532 for row_range in row_ranges.into_iter().rev() {
9533 for row in row_range.iter_rows().rev() {
9534 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9535 let next_line_row = row.next_row();
9536 let indent = snapshot.indent_size_for_line(next_line_row);
9537 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9538
9539 let replace =
9540 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9541 " "
9542 } else {
9543 ""
9544 };
9545
9546 this.buffer.update(cx, |buffer, cx| {
9547 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9548 });
9549 }
9550 }
9551
9552 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9553 s.select_anchor_ranges(cursor_positions)
9554 });
9555 });
9556 }
9557
9558 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9559 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9560 self.join_lines_impl(true, window, cx);
9561 }
9562
9563 pub fn sort_lines_case_sensitive(
9564 &mut self,
9565 _: &SortLinesCaseSensitive,
9566 window: &mut Window,
9567 cx: &mut Context<Self>,
9568 ) {
9569 self.manipulate_lines(window, cx, |lines| lines.sort())
9570 }
9571
9572 pub fn sort_lines_case_insensitive(
9573 &mut self,
9574 _: &SortLinesCaseInsensitive,
9575 window: &mut Window,
9576 cx: &mut Context<Self>,
9577 ) {
9578 self.manipulate_lines(window, cx, |lines| {
9579 lines.sort_by_key(|line| line.to_lowercase())
9580 })
9581 }
9582
9583 pub fn unique_lines_case_insensitive(
9584 &mut self,
9585 _: &UniqueLinesCaseInsensitive,
9586 window: &mut Window,
9587 cx: &mut Context<Self>,
9588 ) {
9589 self.manipulate_lines(window, cx, |lines| {
9590 let mut seen = HashSet::default();
9591 lines.retain(|line| seen.insert(line.to_lowercase()));
9592 })
9593 }
9594
9595 pub fn unique_lines_case_sensitive(
9596 &mut self,
9597 _: &UniqueLinesCaseSensitive,
9598 window: &mut Window,
9599 cx: &mut Context<Self>,
9600 ) {
9601 self.manipulate_lines(window, cx, |lines| {
9602 let mut seen = HashSet::default();
9603 lines.retain(|line| seen.insert(*line));
9604 })
9605 }
9606
9607 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9608 let Some(project) = self.project.clone() else {
9609 return;
9610 };
9611 self.reload(project, window, cx)
9612 .detach_and_notify_err(window, cx);
9613 }
9614
9615 pub fn restore_file(
9616 &mut self,
9617 _: &::git::RestoreFile,
9618 window: &mut Window,
9619 cx: &mut Context<Self>,
9620 ) {
9621 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9622 let mut buffer_ids = HashSet::default();
9623 let snapshot = self.buffer().read(cx).snapshot(cx);
9624 for selection in self.selections.all::<usize>(cx) {
9625 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9626 }
9627
9628 let buffer = self.buffer().read(cx);
9629 let ranges = buffer_ids
9630 .into_iter()
9631 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9632 .collect::<Vec<_>>();
9633
9634 self.restore_hunks_in_ranges(ranges, window, cx);
9635 }
9636
9637 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9638 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9639 let selections = self
9640 .selections
9641 .all(cx)
9642 .into_iter()
9643 .map(|s| s.range())
9644 .collect();
9645 self.restore_hunks_in_ranges(selections, window, cx);
9646 }
9647
9648 pub fn restore_hunks_in_ranges(
9649 &mut self,
9650 ranges: Vec<Range<Point>>,
9651 window: &mut Window,
9652 cx: &mut Context<Editor>,
9653 ) {
9654 let mut revert_changes = HashMap::default();
9655 let chunk_by = self
9656 .snapshot(window, cx)
9657 .hunks_for_ranges(ranges)
9658 .into_iter()
9659 .chunk_by(|hunk| hunk.buffer_id);
9660 for (buffer_id, hunks) in &chunk_by {
9661 let hunks = hunks.collect::<Vec<_>>();
9662 for hunk in &hunks {
9663 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9664 }
9665 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9666 }
9667 drop(chunk_by);
9668 if !revert_changes.is_empty() {
9669 self.transact(window, cx, |editor, window, cx| {
9670 editor.restore(revert_changes, window, cx);
9671 });
9672 }
9673 }
9674
9675 pub fn open_active_item_in_terminal(
9676 &mut self,
9677 _: &OpenInTerminal,
9678 window: &mut Window,
9679 cx: &mut Context<Self>,
9680 ) {
9681 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9682 let project_path = buffer.read(cx).project_path(cx)?;
9683 let project = self.project.as_ref()?.read(cx);
9684 let entry = project.entry_for_path(&project_path, cx)?;
9685 let parent = match &entry.canonical_path {
9686 Some(canonical_path) => canonical_path.to_path_buf(),
9687 None => project.absolute_path(&project_path, cx)?,
9688 }
9689 .parent()?
9690 .to_path_buf();
9691 Some(parent)
9692 }) {
9693 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
9694 }
9695 }
9696
9697 fn set_breakpoint_context_menu(
9698 &mut self,
9699 display_row: DisplayRow,
9700 position: Option<Anchor>,
9701 clicked_point: gpui::Point<Pixels>,
9702 window: &mut Window,
9703 cx: &mut Context<Self>,
9704 ) {
9705 if !cx.has_flag::<DebuggerFeatureFlag>() {
9706 return;
9707 }
9708 let source = self
9709 .buffer
9710 .read(cx)
9711 .snapshot(cx)
9712 .anchor_before(Point::new(display_row.0, 0u32));
9713
9714 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
9715
9716 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
9717 self,
9718 source,
9719 clicked_point,
9720 context_menu,
9721 window,
9722 cx,
9723 );
9724 }
9725
9726 fn add_edit_breakpoint_block(
9727 &mut self,
9728 anchor: Anchor,
9729 breakpoint: &Breakpoint,
9730 edit_action: BreakpointPromptEditAction,
9731 window: &mut Window,
9732 cx: &mut Context<Self>,
9733 ) {
9734 let weak_editor = cx.weak_entity();
9735 let bp_prompt = cx.new(|cx| {
9736 BreakpointPromptEditor::new(
9737 weak_editor,
9738 anchor,
9739 breakpoint.clone(),
9740 edit_action,
9741 window,
9742 cx,
9743 )
9744 });
9745
9746 let height = bp_prompt.update(cx, |this, cx| {
9747 this.prompt
9748 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
9749 });
9750 let cloned_prompt = bp_prompt.clone();
9751 let blocks = vec![BlockProperties {
9752 style: BlockStyle::Sticky,
9753 placement: BlockPlacement::Above(anchor),
9754 height: Some(height),
9755 render: Arc::new(move |cx| {
9756 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
9757 cloned_prompt.clone().into_any_element()
9758 }),
9759 priority: 0,
9760 render_in_minimap: true,
9761 }];
9762
9763 let focus_handle = bp_prompt.focus_handle(cx);
9764 window.focus(&focus_handle);
9765
9766 let block_ids = self.insert_blocks(blocks, None, cx);
9767 bp_prompt.update(cx, |prompt, _| {
9768 prompt.add_block_ids(block_ids);
9769 });
9770 }
9771
9772 pub(crate) fn breakpoint_at_row(
9773 &self,
9774 row: u32,
9775 window: &mut Window,
9776 cx: &mut Context<Self>,
9777 ) -> Option<(Anchor, Breakpoint)> {
9778 let snapshot = self.snapshot(window, cx);
9779 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
9780
9781 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9782 }
9783
9784 pub(crate) fn breakpoint_at_anchor(
9785 &self,
9786 breakpoint_position: Anchor,
9787 snapshot: &EditorSnapshot,
9788 cx: &mut Context<Self>,
9789 ) -> Option<(Anchor, Breakpoint)> {
9790 let project = self.project.clone()?;
9791
9792 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
9793 snapshot
9794 .buffer_snapshot
9795 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
9796 })?;
9797
9798 let enclosing_excerpt = breakpoint_position.excerpt_id;
9799 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
9800 let buffer_snapshot = buffer.read(cx).snapshot();
9801
9802 let row = buffer_snapshot
9803 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
9804 .row;
9805
9806 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
9807 let anchor_end = snapshot
9808 .buffer_snapshot
9809 .anchor_after(Point::new(row, line_len));
9810
9811 let bp = self
9812 .breakpoint_store
9813 .as_ref()?
9814 .read_with(cx, |breakpoint_store, cx| {
9815 breakpoint_store
9816 .breakpoints(
9817 &buffer,
9818 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
9819 &buffer_snapshot,
9820 cx,
9821 )
9822 .next()
9823 .and_then(|(bp, _)| {
9824 let breakpoint_row = buffer_snapshot
9825 .summary_for_anchor::<text::PointUtf16>(&bp.position)
9826 .row;
9827
9828 if breakpoint_row == row {
9829 snapshot
9830 .buffer_snapshot
9831 .anchor_in_excerpt(enclosing_excerpt, bp.position)
9832 .map(|position| (position, bp.bp.clone()))
9833 } else {
9834 None
9835 }
9836 })
9837 });
9838 bp
9839 }
9840
9841 pub fn edit_log_breakpoint(
9842 &mut self,
9843 _: &EditLogBreakpoint,
9844 window: &mut Window,
9845 cx: &mut Context<Self>,
9846 ) {
9847 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9848 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
9849 message: None,
9850 state: BreakpointState::Enabled,
9851 condition: None,
9852 hit_condition: None,
9853 });
9854
9855 self.add_edit_breakpoint_block(
9856 anchor,
9857 &breakpoint,
9858 BreakpointPromptEditAction::Log,
9859 window,
9860 cx,
9861 );
9862 }
9863 }
9864
9865 fn breakpoints_at_cursors(
9866 &self,
9867 window: &mut Window,
9868 cx: &mut Context<Self>,
9869 ) -> Vec<(Anchor, Option<Breakpoint>)> {
9870 let snapshot = self.snapshot(window, cx);
9871 let cursors = self
9872 .selections
9873 .disjoint_anchors()
9874 .into_iter()
9875 .map(|selection| {
9876 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
9877
9878 let breakpoint_position = self
9879 .breakpoint_at_row(cursor_position.row, window, cx)
9880 .map(|bp| bp.0)
9881 .unwrap_or_else(|| {
9882 snapshot
9883 .display_snapshot
9884 .buffer_snapshot
9885 .anchor_after(Point::new(cursor_position.row, 0))
9886 });
9887
9888 let breakpoint = self
9889 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
9890 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
9891
9892 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
9893 })
9894 // 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.
9895 .collect::<HashMap<Anchor, _>>();
9896
9897 cursors.into_iter().collect()
9898 }
9899
9900 pub fn enable_breakpoint(
9901 &mut self,
9902 _: &crate::actions::EnableBreakpoint,
9903 window: &mut Window,
9904 cx: &mut Context<Self>,
9905 ) {
9906 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9907 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
9908 continue;
9909 };
9910 self.edit_breakpoint_at_anchor(
9911 anchor,
9912 breakpoint,
9913 BreakpointEditAction::InvertState,
9914 cx,
9915 );
9916 }
9917 }
9918
9919 pub fn disable_breakpoint(
9920 &mut self,
9921 _: &crate::actions::DisableBreakpoint,
9922 window: &mut Window,
9923 cx: &mut Context<Self>,
9924 ) {
9925 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9926 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
9927 continue;
9928 };
9929 self.edit_breakpoint_at_anchor(
9930 anchor,
9931 breakpoint,
9932 BreakpointEditAction::InvertState,
9933 cx,
9934 );
9935 }
9936 }
9937
9938 pub fn toggle_breakpoint(
9939 &mut self,
9940 _: &crate::actions::ToggleBreakpoint,
9941 window: &mut Window,
9942 cx: &mut Context<Self>,
9943 ) {
9944 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
9945 if let Some(breakpoint) = breakpoint {
9946 self.edit_breakpoint_at_anchor(
9947 anchor,
9948 breakpoint,
9949 BreakpointEditAction::Toggle,
9950 cx,
9951 );
9952 } else {
9953 self.edit_breakpoint_at_anchor(
9954 anchor,
9955 Breakpoint::new_standard(),
9956 BreakpointEditAction::Toggle,
9957 cx,
9958 );
9959 }
9960 }
9961 }
9962
9963 pub fn edit_breakpoint_at_anchor(
9964 &mut self,
9965 breakpoint_position: Anchor,
9966 breakpoint: Breakpoint,
9967 edit_action: BreakpointEditAction,
9968 cx: &mut Context<Self>,
9969 ) {
9970 let Some(breakpoint_store) = &self.breakpoint_store else {
9971 return;
9972 };
9973
9974 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
9975 if breakpoint_position == Anchor::min() {
9976 self.buffer()
9977 .read(cx)
9978 .excerpt_buffer_ids()
9979 .into_iter()
9980 .next()
9981 } else {
9982 None
9983 }
9984 }) else {
9985 return;
9986 };
9987
9988 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
9989 return;
9990 };
9991
9992 breakpoint_store.update(cx, |breakpoint_store, cx| {
9993 breakpoint_store.toggle_breakpoint(
9994 buffer,
9995 BreakpointWithPosition {
9996 position: breakpoint_position.text_anchor,
9997 bp: breakpoint,
9998 },
9999 edit_action,
10000 cx,
10001 );
10002 });
10003
10004 cx.notify();
10005 }
10006
10007 #[cfg(any(test, feature = "test-support"))]
10008 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10009 self.breakpoint_store.clone()
10010 }
10011
10012 pub fn prepare_restore_change(
10013 &self,
10014 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10015 hunk: &MultiBufferDiffHunk,
10016 cx: &mut App,
10017 ) -> Option<()> {
10018 if hunk.is_created_file() {
10019 return None;
10020 }
10021 let buffer = self.buffer.read(cx);
10022 let diff = buffer.diff_for(hunk.buffer_id)?;
10023 let buffer = buffer.buffer(hunk.buffer_id)?;
10024 let buffer = buffer.read(cx);
10025 let original_text = diff
10026 .read(cx)
10027 .base_text()
10028 .as_rope()
10029 .slice(hunk.diff_base_byte_range.clone());
10030 let buffer_snapshot = buffer.snapshot();
10031 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10032 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10033 probe
10034 .0
10035 .start
10036 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10037 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10038 }) {
10039 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10040 Some(())
10041 } else {
10042 None
10043 }
10044 }
10045
10046 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10047 self.manipulate_lines(window, cx, |lines| lines.reverse())
10048 }
10049
10050 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10051 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10052 }
10053
10054 fn manipulate_lines<Fn>(
10055 &mut self,
10056 window: &mut Window,
10057 cx: &mut Context<Self>,
10058 mut callback: Fn,
10059 ) where
10060 Fn: FnMut(&mut Vec<&str>),
10061 {
10062 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10063
10064 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10065 let buffer = self.buffer.read(cx).snapshot(cx);
10066
10067 let mut edits = Vec::new();
10068
10069 let selections = self.selections.all::<Point>(cx);
10070 let mut selections = selections.iter().peekable();
10071 let mut contiguous_row_selections = Vec::new();
10072 let mut new_selections = Vec::new();
10073 let mut added_lines = 0;
10074 let mut removed_lines = 0;
10075
10076 while let Some(selection) = selections.next() {
10077 let (start_row, end_row) = consume_contiguous_rows(
10078 &mut contiguous_row_selections,
10079 selection,
10080 &display_map,
10081 &mut selections,
10082 );
10083
10084 let start_point = Point::new(start_row.0, 0);
10085 let end_point = Point::new(
10086 end_row.previous_row().0,
10087 buffer.line_len(end_row.previous_row()),
10088 );
10089 let text = buffer
10090 .text_for_range(start_point..end_point)
10091 .collect::<String>();
10092
10093 let mut lines = text.split('\n').collect_vec();
10094
10095 let lines_before = lines.len();
10096 callback(&mut lines);
10097 let lines_after = lines.len();
10098
10099 edits.push((start_point..end_point, lines.join("\n")));
10100
10101 // Selections must change based on added and removed line count
10102 let start_row =
10103 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10104 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10105 new_selections.push(Selection {
10106 id: selection.id,
10107 start: start_row,
10108 end: end_row,
10109 goal: SelectionGoal::None,
10110 reversed: selection.reversed,
10111 });
10112
10113 if lines_after > lines_before {
10114 added_lines += lines_after - lines_before;
10115 } else if lines_before > lines_after {
10116 removed_lines += lines_before - lines_after;
10117 }
10118 }
10119
10120 self.transact(window, cx, |this, window, cx| {
10121 let buffer = this.buffer.update(cx, |buffer, cx| {
10122 buffer.edit(edits, None, cx);
10123 buffer.snapshot(cx)
10124 });
10125
10126 // Recalculate offsets on newly edited buffer
10127 let new_selections = new_selections
10128 .iter()
10129 .map(|s| {
10130 let start_point = Point::new(s.start.0, 0);
10131 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10132 Selection {
10133 id: s.id,
10134 start: buffer.point_to_offset(start_point),
10135 end: buffer.point_to_offset(end_point),
10136 goal: s.goal,
10137 reversed: s.reversed,
10138 }
10139 })
10140 .collect();
10141
10142 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10143 s.select(new_selections);
10144 });
10145
10146 this.request_autoscroll(Autoscroll::fit(), cx);
10147 });
10148 }
10149
10150 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10151 self.manipulate_text(window, cx, |text| {
10152 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10153 if has_upper_case_characters {
10154 text.to_lowercase()
10155 } else {
10156 text.to_uppercase()
10157 }
10158 })
10159 }
10160
10161 pub fn convert_to_upper_case(
10162 &mut self,
10163 _: &ConvertToUpperCase,
10164 window: &mut Window,
10165 cx: &mut Context<Self>,
10166 ) {
10167 self.manipulate_text(window, cx, |text| text.to_uppercase())
10168 }
10169
10170 pub fn convert_to_lower_case(
10171 &mut self,
10172 _: &ConvertToLowerCase,
10173 window: &mut Window,
10174 cx: &mut Context<Self>,
10175 ) {
10176 self.manipulate_text(window, cx, |text| text.to_lowercase())
10177 }
10178
10179 pub fn convert_to_title_case(
10180 &mut self,
10181 _: &ConvertToTitleCase,
10182 window: &mut Window,
10183 cx: &mut Context<Self>,
10184 ) {
10185 self.manipulate_text(window, cx, |text| {
10186 text.split('\n')
10187 .map(|line| line.to_case(Case::Title))
10188 .join("\n")
10189 })
10190 }
10191
10192 pub fn convert_to_snake_case(
10193 &mut self,
10194 _: &ConvertToSnakeCase,
10195 window: &mut Window,
10196 cx: &mut Context<Self>,
10197 ) {
10198 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10199 }
10200
10201 pub fn convert_to_kebab_case(
10202 &mut self,
10203 _: &ConvertToKebabCase,
10204 window: &mut Window,
10205 cx: &mut Context<Self>,
10206 ) {
10207 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10208 }
10209
10210 pub fn convert_to_upper_camel_case(
10211 &mut self,
10212 _: &ConvertToUpperCamelCase,
10213 window: &mut Window,
10214 cx: &mut Context<Self>,
10215 ) {
10216 self.manipulate_text(window, cx, |text| {
10217 text.split('\n')
10218 .map(|line| line.to_case(Case::UpperCamel))
10219 .join("\n")
10220 })
10221 }
10222
10223 pub fn convert_to_lower_camel_case(
10224 &mut self,
10225 _: &ConvertToLowerCamelCase,
10226 window: &mut Window,
10227 cx: &mut Context<Self>,
10228 ) {
10229 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10230 }
10231
10232 pub fn convert_to_opposite_case(
10233 &mut self,
10234 _: &ConvertToOppositeCase,
10235 window: &mut Window,
10236 cx: &mut Context<Self>,
10237 ) {
10238 self.manipulate_text(window, cx, |text| {
10239 text.chars()
10240 .fold(String::with_capacity(text.len()), |mut t, c| {
10241 if c.is_uppercase() {
10242 t.extend(c.to_lowercase());
10243 } else {
10244 t.extend(c.to_uppercase());
10245 }
10246 t
10247 })
10248 })
10249 }
10250
10251 pub fn convert_to_rot13(
10252 &mut self,
10253 _: &ConvertToRot13,
10254 window: &mut Window,
10255 cx: &mut Context<Self>,
10256 ) {
10257 self.manipulate_text(window, cx, |text| {
10258 text.chars()
10259 .map(|c| match c {
10260 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10261 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10262 _ => c,
10263 })
10264 .collect()
10265 })
10266 }
10267
10268 pub fn convert_to_rot47(
10269 &mut self,
10270 _: &ConvertToRot47,
10271 window: &mut Window,
10272 cx: &mut Context<Self>,
10273 ) {
10274 self.manipulate_text(window, cx, |text| {
10275 text.chars()
10276 .map(|c| {
10277 let code_point = c as u32;
10278 if code_point >= 33 && code_point <= 126 {
10279 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10280 }
10281 c
10282 })
10283 .collect()
10284 })
10285 }
10286
10287 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10288 where
10289 Fn: FnMut(&str) -> String,
10290 {
10291 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10292 let buffer = self.buffer.read(cx).snapshot(cx);
10293
10294 let mut new_selections = Vec::new();
10295 let mut edits = Vec::new();
10296 let mut selection_adjustment = 0i32;
10297
10298 for selection in self.selections.all::<usize>(cx) {
10299 let selection_is_empty = selection.is_empty();
10300
10301 let (start, end) = if selection_is_empty {
10302 let word_range = movement::surrounding_word(
10303 &display_map,
10304 selection.start.to_display_point(&display_map),
10305 );
10306 let start = word_range.start.to_offset(&display_map, Bias::Left);
10307 let end = word_range.end.to_offset(&display_map, Bias::Left);
10308 (start, end)
10309 } else {
10310 (selection.start, selection.end)
10311 };
10312
10313 let text = buffer.text_for_range(start..end).collect::<String>();
10314 let old_length = text.len() as i32;
10315 let text = callback(&text);
10316
10317 new_selections.push(Selection {
10318 start: (start as i32 - selection_adjustment) as usize,
10319 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10320 goal: SelectionGoal::None,
10321 ..selection
10322 });
10323
10324 selection_adjustment += old_length - text.len() as i32;
10325
10326 edits.push((start..end, text));
10327 }
10328
10329 self.transact(window, cx, |this, window, cx| {
10330 this.buffer.update(cx, |buffer, cx| {
10331 buffer.edit(edits, None, cx);
10332 });
10333
10334 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10335 s.select(new_selections);
10336 });
10337
10338 this.request_autoscroll(Autoscroll::fit(), cx);
10339 });
10340 }
10341
10342 pub fn duplicate(
10343 &mut self,
10344 upwards: bool,
10345 whole_lines: bool,
10346 window: &mut Window,
10347 cx: &mut Context<Self>,
10348 ) {
10349 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10350
10351 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10352 let buffer = &display_map.buffer_snapshot;
10353 let selections = self.selections.all::<Point>(cx);
10354
10355 let mut edits = Vec::new();
10356 let mut selections_iter = selections.iter().peekable();
10357 while let Some(selection) = selections_iter.next() {
10358 let mut rows = selection.spanned_rows(false, &display_map);
10359 // duplicate line-wise
10360 if whole_lines || selection.start == selection.end {
10361 // Avoid duplicating the same lines twice.
10362 while let Some(next_selection) = selections_iter.peek() {
10363 let next_rows = next_selection.spanned_rows(false, &display_map);
10364 if next_rows.start < rows.end {
10365 rows.end = next_rows.end;
10366 selections_iter.next().unwrap();
10367 } else {
10368 break;
10369 }
10370 }
10371
10372 // Copy the text from the selected row region and splice it either at the start
10373 // or end of the region.
10374 let start = Point::new(rows.start.0, 0);
10375 let end = Point::new(
10376 rows.end.previous_row().0,
10377 buffer.line_len(rows.end.previous_row()),
10378 );
10379 let text = buffer
10380 .text_for_range(start..end)
10381 .chain(Some("\n"))
10382 .collect::<String>();
10383 let insert_location = if upwards {
10384 Point::new(rows.end.0, 0)
10385 } else {
10386 start
10387 };
10388 edits.push((insert_location..insert_location, text));
10389 } else {
10390 // duplicate character-wise
10391 let start = selection.start;
10392 let end = selection.end;
10393 let text = buffer.text_for_range(start..end).collect::<String>();
10394 edits.push((selection.end..selection.end, text));
10395 }
10396 }
10397
10398 self.transact(window, cx, |this, _, cx| {
10399 this.buffer.update(cx, |buffer, cx| {
10400 buffer.edit(edits, None, cx);
10401 });
10402
10403 this.request_autoscroll(Autoscroll::fit(), cx);
10404 });
10405 }
10406
10407 pub fn duplicate_line_up(
10408 &mut self,
10409 _: &DuplicateLineUp,
10410 window: &mut Window,
10411 cx: &mut Context<Self>,
10412 ) {
10413 self.duplicate(true, true, window, cx);
10414 }
10415
10416 pub fn duplicate_line_down(
10417 &mut self,
10418 _: &DuplicateLineDown,
10419 window: &mut Window,
10420 cx: &mut Context<Self>,
10421 ) {
10422 self.duplicate(false, true, window, cx);
10423 }
10424
10425 pub fn duplicate_selection(
10426 &mut self,
10427 _: &DuplicateSelection,
10428 window: &mut Window,
10429 cx: &mut Context<Self>,
10430 ) {
10431 self.duplicate(false, false, window, cx);
10432 }
10433
10434 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10435 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10436
10437 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10438 let buffer = self.buffer.read(cx).snapshot(cx);
10439
10440 let mut edits = Vec::new();
10441 let mut unfold_ranges = Vec::new();
10442 let mut refold_creases = Vec::new();
10443
10444 let selections = self.selections.all::<Point>(cx);
10445 let mut selections = selections.iter().peekable();
10446 let mut contiguous_row_selections = Vec::new();
10447 let mut new_selections = Vec::new();
10448
10449 while let Some(selection) = selections.next() {
10450 // Find all the selections that span a contiguous row range
10451 let (start_row, end_row) = consume_contiguous_rows(
10452 &mut contiguous_row_selections,
10453 selection,
10454 &display_map,
10455 &mut selections,
10456 );
10457
10458 // Move the text spanned by the row range to be before the line preceding the row range
10459 if start_row.0 > 0 {
10460 let range_to_move = Point::new(
10461 start_row.previous_row().0,
10462 buffer.line_len(start_row.previous_row()),
10463 )
10464 ..Point::new(
10465 end_row.previous_row().0,
10466 buffer.line_len(end_row.previous_row()),
10467 );
10468 let insertion_point = display_map
10469 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10470 .0;
10471
10472 // Don't move lines across excerpts
10473 if buffer
10474 .excerpt_containing(insertion_point..range_to_move.end)
10475 .is_some()
10476 {
10477 let text = buffer
10478 .text_for_range(range_to_move.clone())
10479 .flat_map(|s| s.chars())
10480 .skip(1)
10481 .chain(['\n'])
10482 .collect::<String>();
10483
10484 edits.push((
10485 buffer.anchor_after(range_to_move.start)
10486 ..buffer.anchor_before(range_to_move.end),
10487 String::new(),
10488 ));
10489 let insertion_anchor = buffer.anchor_after(insertion_point);
10490 edits.push((insertion_anchor..insertion_anchor, text));
10491
10492 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10493
10494 // Move selections up
10495 new_selections.extend(contiguous_row_selections.drain(..).map(
10496 |mut selection| {
10497 selection.start.row -= row_delta;
10498 selection.end.row -= row_delta;
10499 selection
10500 },
10501 ));
10502
10503 // Move folds up
10504 unfold_ranges.push(range_to_move.clone());
10505 for fold in display_map.folds_in_range(
10506 buffer.anchor_before(range_to_move.start)
10507 ..buffer.anchor_after(range_to_move.end),
10508 ) {
10509 let mut start = fold.range.start.to_point(&buffer);
10510 let mut end = fold.range.end.to_point(&buffer);
10511 start.row -= row_delta;
10512 end.row -= row_delta;
10513 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10514 }
10515 }
10516 }
10517
10518 // If we didn't move line(s), preserve the existing selections
10519 new_selections.append(&mut contiguous_row_selections);
10520 }
10521
10522 self.transact(window, cx, |this, window, cx| {
10523 this.unfold_ranges(&unfold_ranges, true, true, cx);
10524 this.buffer.update(cx, |buffer, cx| {
10525 for (range, text) in edits {
10526 buffer.edit([(range, text)], None, cx);
10527 }
10528 });
10529 this.fold_creases(refold_creases, true, window, cx);
10530 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10531 s.select(new_selections);
10532 })
10533 });
10534 }
10535
10536 pub fn move_line_down(
10537 &mut self,
10538 _: &MoveLineDown,
10539 window: &mut Window,
10540 cx: &mut Context<Self>,
10541 ) {
10542 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10543
10544 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10545 let buffer = self.buffer.read(cx).snapshot(cx);
10546
10547 let mut edits = Vec::new();
10548 let mut unfold_ranges = Vec::new();
10549 let mut refold_creases = Vec::new();
10550
10551 let selections = self.selections.all::<Point>(cx);
10552 let mut selections = selections.iter().peekable();
10553 let mut contiguous_row_selections = Vec::new();
10554 let mut new_selections = Vec::new();
10555
10556 while let Some(selection) = selections.next() {
10557 // Find all the selections that span a contiguous row range
10558 let (start_row, end_row) = consume_contiguous_rows(
10559 &mut contiguous_row_selections,
10560 selection,
10561 &display_map,
10562 &mut selections,
10563 );
10564
10565 // Move the text spanned by the row range to be after the last line of the row range
10566 if end_row.0 <= buffer.max_point().row {
10567 let range_to_move =
10568 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10569 let insertion_point = display_map
10570 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10571 .0;
10572
10573 // Don't move lines across excerpt boundaries
10574 if buffer
10575 .excerpt_containing(range_to_move.start..insertion_point)
10576 .is_some()
10577 {
10578 let mut text = String::from("\n");
10579 text.extend(buffer.text_for_range(range_to_move.clone()));
10580 text.pop(); // Drop trailing newline
10581 edits.push((
10582 buffer.anchor_after(range_to_move.start)
10583 ..buffer.anchor_before(range_to_move.end),
10584 String::new(),
10585 ));
10586 let insertion_anchor = buffer.anchor_after(insertion_point);
10587 edits.push((insertion_anchor..insertion_anchor, text));
10588
10589 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10590
10591 // Move selections down
10592 new_selections.extend(contiguous_row_selections.drain(..).map(
10593 |mut selection| {
10594 selection.start.row += row_delta;
10595 selection.end.row += row_delta;
10596 selection
10597 },
10598 ));
10599
10600 // Move folds down
10601 unfold_ranges.push(range_to_move.clone());
10602 for fold in display_map.folds_in_range(
10603 buffer.anchor_before(range_to_move.start)
10604 ..buffer.anchor_after(range_to_move.end),
10605 ) {
10606 let mut start = fold.range.start.to_point(&buffer);
10607 let mut end = fold.range.end.to_point(&buffer);
10608 start.row += row_delta;
10609 end.row += row_delta;
10610 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10611 }
10612 }
10613 }
10614
10615 // If we didn't move line(s), preserve the existing selections
10616 new_selections.append(&mut contiguous_row_selections);
10617 }
10618
10619 self.transact(window, cx, |this, window, cx| {
10620 this.unfold_ranges(&unfold_ranges, true, true, cx);
10621 this.buffer.update(cx, |buffer, cx| {
10622 for (range, text) in edits {
10623 buffer.edit([(range, text)], None, cx);
10624 }
10625 });
10626 this.fold_creases(refold_creases, true, window, cx);
10627 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10628 s.select(new_selections)
10629 });
10630 });
10631 }
10632
10633 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10634 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10635 let text_layout_details = &self.text_layout_details(window);
10636 self.transact(window, cx, |this, window, cx| {
10637 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10638 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10639 s.move_with(|display_map, selection| {
10640 if !selection.is_empty() {
10641 return;
10642 }
10643
10644 let mut head = selection.head();
10645 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10646 if head.column() == display_map.line_len(head.row()) {
10647 transpose_offset = display_map
10648 .buffer_snapshot
10649 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10650 }
10651
10652 if transpose_offset == 0 {
10653 return;
10654 }
10655
10656 *head.column_mut() += 1;
10657 head = display_map.clip_point(head, Bias::Right);
10658 let goal = SelectionGoal::HorizontalPosition(
10659 display_map
10660 .x_for_display_point(head, text_layout_details)
10661 .into(),
10662 );
10663 selection.collapse_to(head, goal);
10664
10665 let transpose_start = display_map
10666 .buffer_snapshot
10667 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10668 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
10669 let transpose_end = display_map
10670 .buffer_snapshot
10671 .clip_offset(transpose_offset + 1, Bias::Right);
10672 if let Some(ch) =
10673 display_map.buffer_snapshot.chars_at(transpose_start).next()
10674 {
10675 edits.push((transpose_start..transpose_offset, String::new()));
10676 edits.push((transpose_end..transpose_end, ch.to_string()));
10677 }
10678 }
10679 });
10680 edits
10681 });
10682 this.buffer
10683 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10684 let selections = this.selections.all::<usize>(cx);
10685 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10686 s.select(selections);
10687 });
10688 });
10689 }
10690
10691 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
10692 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10693 self.rewrap_impl(RewrapOptions::default(), cx)
10694 }
10695
10696 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
10697 let buffer = self.buffer.read(cx).snapshot(cx);
10698 let selections = self.selections.all::<Point>(cx);
10699 let mut selections = selections.iter().peekable();
10700
10701 let mut edits = Vec::new();
10702 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
10703
10704 while let Some(selection) = selections.next() {
10705 let mut start_row = selection.start.row;
10706 let mut end_row = selection.end.row;
10707
10708 // Skip selections that overlap with a range that has already been rewrapped.
10709 let selection_range = start_row..end_row;
10710 if rewrapped_row_ranges
10711 .iter()
10712 .any(|range| range.overlaps(&selection_range))
10713 {
10714 continue;
10715 }
10716
10717 let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
10718
10719 // Since not all lines in the selection may be at the same indent
10720 // level, choose the indent size that is the most common between all
10721 // of the lines.
10722 //
10723 // If there is a tie, we use the deepest indent.
10724 let (indent_size, indent_end) = {
10725 let mut indent_size_occurrences = HashMap::default();
10726 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
10727
10728 for row in start_row..=end_row {
10729 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
10730 rows_by_indent_size.entry(indent).or_default().push(row);
10731 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
10732 }
10733
10734 let indent_size = indent_size_occurrences
10735 .into_iter()
10736 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
10737 .map(|(indent, _)| indent)
10738 .unwrap_or_default();
10739 let row = rows_by_indent_size[&indent_size][0];
10740 let indent_end = Point::new(row, indent_size.len);
10741
10742 (indent_size, indent_end)
10743 };
10744
10745 let mut line_prefix = indent_size.chars().collect::<String>();
10746
10747 let mut inside_comment = false;
10748 if let Some(comment_prefix) =
10749 buffer
10750 .language_scope_at(selection.head())
10751 .and_then(|language| {
10752 language
10753 .line_comment_prefixes()
10754 .iter()
10755 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
10756 .cloned()
10757 })
10758 {
10759 line_prefix.push_str(&comment_prefix);
10760 inside_comment = true;
10761 }
10762
10763 let language_settings = buffer.language_settings_at(selection.head(), cx);
10764 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
10765 RewrapBehavior::InComments => inside_comment,
10766 RewrapBehavior::InSelections => !selection.is_empty(),
10767 RewrapBehavior::Anywhere => true,
10768 };
10769
10770 let should_rewrap = options.override_language_settings
10771 || allow_rewrap_based_on_language
10772 || self.hard_wrap.is_some();
10773 if !should_rewrap {
10774 continue;
10775 }
10776
10777 if selection.is_empty() {
10778 'expand_upwards: while start_row > 0 {
10779 let prev_row = start_row - 1;
10780 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
10781 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
10782 {
10783 start_row = prev_row;
10784 } else {
10785 break 'expand_upwards;
10786 }
10787 }
10788
10789 'expand_downwards: while end_row < buffer.max_point().row {
10790 let next_row = end_row + 1;
10791 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
10792 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
10793 {
10794 end_row = next_row;
10795 } else {
10796 break 'expand_downwards;
10797 }
10798 }
10799 }
10800
10801 let start = Point::new(start_row, 0);
10802 let start_offset = start.to_offset(&buffer);
10803 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
10804 let selection_text = buffer.text_for_range(start..end).collect::<String>();
10805 let Some(lines_without_prefixes) = selection_text
10806 .lines()
10807 .map(|line| {
10808 line.strip_prefix(&line_prefix)
10809 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
10810 .with_context(|| {
10811 format!("line did not start with prefix {line_prefix:?}: {line:?}")
10812 })
10813 })
10814 .collect::<Result<Vec<_>, _>>()
10815 .log_err()
10816 else {
10817 continue;
10818 };
10819
10820 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
10821 buffer
10822 .language_settings_at(Point::new(start_row, 0), cx)
10823 .preferred_line_length as usize
10824 });
10825 let wrapped_text = wrap_with_prefix(
10826 line_prefix,
10827 lines_without_prefixes.join("\n"),
10828 wrap_column,
10829 tab_size,
10830 options.preserve_existing_whitespace,
10831 );
10832
10833 // TODO: should always use char-based diff while still supporting cursor behavior that
10834 // matches vim.
10835 let mut diff_options = DiffOptions::default();
10836 if options.override_language_settings {
10837 diff_options.max_word_diff_len = 0;
10838 diff_options.max_word_diff_line_count = 0;
10839 } else {
10840 diff_options.max_word_diff_len = usize::MAX;
10841 diff_options.max_word_diff_line_count = usize::MAX;
10842 }
10843
10844 for (old_range, new_text) in
10845 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
10846 {
10847 let edit_start = buffer.anchor_after(start_offset + old_range.start);
10848 let edit_end = buffer.anchor_after(start_offset + old_range.end);
10849 edits.push((edit_start..edit_end, new_text));
10850 }
10851
10852 rewrapped_row_ranges.push(start_row..=end_row);
10853 }
10854
10855 self.buffer
10856 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
10857 }
10858
10859 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
10860 let mut text = String::new();
10861 let buffer = self.buffer.read(cx).snapshot(cx);
10862 let mut selections = self.selections.all::<Point>(cx);
10863 let mut clipboard_selections = Vec::with_capacity(selections.len());
10864 {
10865 let max_point = buffer.max_point();
10866 let mut is_first = true;
10867 for selection in &mut selections {
10868 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10869 if is_entire_line {
10870 selection.start = Point::new(selection.start.row, 0);
10871 if !selection.is_empty() && selection.end.column == 0 {
10872 selection.end = cmp::min(max_point, selection.end);
10873 } else {
10874 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
10875 }
10876 selection.goal = SelectionGoal::None;
10877 }
10878 if is_first {
10879 is_first = false;
10880 } else {
10881 text += "\n";
10882 }
10883 let mut len = 0;
10884 for chunk in buffer.text_for_range(selection.start..selection.end) {
10885 text.push_str(chunk);
10886 len += chunk.len();
10887 }
10888 clipboard_selections.push(ClipboardSelection {
10889 len,
10890 is_entire_line,
10891 first_line_indent: buffer
10892 .indent_size_for_line(MultiBufferRow(selection.start.row))
10893 .len,
10894 });
10895 }
10896 }
10897
10898 self.transact(window, cx, |this, window, cx| {
10899 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10900 s.select(selections);
10901 });
10902 this.insert("", window, cx);
10903 });
10904 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
10905 }
10906
10907 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
10908 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10909 let item = self.cut_common(window, cx);
10910 cx.write_to_clipboard(item);
10911 }
10912
10913 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
10914 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10915 self.change_selections(None, window, cx, |s| {
10916 s.move_with(|snapshot, sel| {
10917 if sel.is_empty() {
10918 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
10919 }
10920 });
10921 });
10922 let item = self.cut_common(window, cx);
10923 cx.set_global(KillRing(item))
10924 }
10925
10926 pub fn kill_ring_yank(
10927 &mut self,
10928 _: &KillRingYank,
10929 window: &mut Window,
10930 cx: &mut Context<Self>,
10931 ) {
10932 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10933 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
10934 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
10935 (kill_ring.text().to_string(), kill_ring.metadata_json())
10936 } else {
10937 return;
10938 }
10939 } else {
10940 return;
10941 };
10942 self.do_paste(&text, metadata, false, window, cx);
10943 }
10944
10945 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
10946 self.do_copy(true, cx);
10947 }
10948
10949 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
10950 self.do_copy(false, cx);
10951 }
10952
10953 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
10954 let selections = self.selections.all::<Point>(cx);
10955 let buffer = self.buffer.read(cx).read(cx);
10956 let mut text = String::new();
10957
10958 let mut clipboard_selections = Vec::with_capacity(selections.len());
10959 {
10960 let max_point = buffer.max_point();
10961 let mut is_first = true;
10962 for selection in &selections {
10963 let mut start = selection.start;
10964 let mut end = selection.end;
10965 let is_entire_line = selection.is_empty() || self.selections.line_mode;
10966 if is_entire_line {
10967 start = Point::new(start.row, 0);
10968 end = cmp::min(max_point, Point::new(end.row + 1, 0));
10969 }
10970
10971 let mut trimmed_selections = Vec::new();
10972 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
10973 let row = MultiBufferRow(start.row);
10974 let first_indent = buffer.indent_size_for_line(row);
10975 if first_indent.len == 0 || start.column > first_indent.len {
10976 trimmed_selections.push(start..end);
10977 } else {
10978 trimmed_selections.push(
10979 Point::new(row.0, first_indent.len)
10980 ..Point::new(row.0, buffer.line_len(row)),
10981 );
10982 for row in start.row + 1..=end.row {
10983 let mut line_len = buffer.line_len(MultiBufferRow(row));
10984 if row == end.row {
10985 line_len = end.column;
10986 }
10987 if line_len == 0 {
10988 trimmed_selections
10989 .push(Point::new(row, 0)..Point::new(row, line_len));
10990 continue;
10991 }
10992 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
10993 if row_indent_size.len >= first_indent.len {
10994 trimmed_selections.push(
10995 Point::new(row, first_indent.len)..Point::new(row, line_len),
10996 );
10997 } else {
10998 trimmed_selections.clear();
10999 trimmed_selections.push(start..end);
11000 break;
11001 }
11002 }
11003 }
11004 } else {
11005 trimmed_selections.push(start..end);
11006 }
11007
11008 for trimmed_range in trimmed_selections {
11009 if is_first {
11010 is_first = false;
11011 } else {
11012 text += "\n";
11013 }
11014 let mut len = 0;
11015 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11016 text.push_str(chunk);
11017 len += chunk.len();
11018 }
11019 clipboard_selections.push(ClipboardSelection {
11020 len,
11021 is_entire_line,
11022 first_line_indent: buffer
11023 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11024 .len,
11025 });
11026 }
11027 }
11028 }
11029
11030 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11031 text,
11032 clipboard_selections,
11033 ));
11034 }
11035
11036 pub fn do_paste(
11037 &mut self,
11038 text: &String,
11039 clipboard_selections: Option<Vec<ClipboardSelection>>,
11040 handle_entire_lines: bool,
11041 window: &mut Window,
11042 cx: &mut Context<Self>,
11043 ) {
11044 if self.read_only(cx) {
11045 return;
11046 }
11047
11048 let clipboard_text = Cow::Borrowed(text);
11049
11050 self.transact(window, cx, |this, window, cx| {
11051 if let Some(mut clipboard_selections) = clipboard_selections {
11052 let old_selections = this.selections.all::<usize>(cx);
11053 let all_selections_were_entire_line =
11054 clipboard_selections.iter().all(|s| s.is_entire_line);
11055 let first_selection_indent_column =
11056 clipboard_selections.first().map(|s| s.first_line_indent);
11057 if clipboard_selections.len() != old_selections.len() {
11058 clipboard_selections.drain(..);
11059 }
11060 let cursor_offset = this.selections.last::<usize>(cx).head();
11061 let mut auto_indent_on_paste = true;
11062
11063 this.buffer.update(cx, |buffer, cx| {
11064 let snapshot = buffer.read(cx);
11065 auto_indent_on_paste = snapshot
11066 .language_settings_at(cursor_offset, cx)
11067 .auto_indent_on_paste;
11068
11069 let mut start_offset = 0;
11070 let mut edits = Vec::new();
11071 let mut original_indent_columns = Vec::new();
11072 for (ix, selection) in old_selections.iter().enumerate() {
11073 let to_insert;
11074 let entire_line;
11075 let original_indent_column;
11076 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11077 let end_offset = start_offset + clipboard_selection.len;
11078 to_insert = &clipboard_text[start_offset..end_offset];
11079 entire_line = clipboard_selection.is_entire_line;
11080 start_offset = end_offset + 1;
11081 original_indent_column = Some(clipboard_selection.first_line_indent);
11082 } else {
11083 to_insert = clipboard_text.as_str();
11084 entire_line = all_selections_were_entire_line;
11085 original_indent_column = first_selection_indent_column
11086 }
11087
11088 // If the corresponding selection was empty when this slice of the
11089 // clipboard text was written, then the entire line containing the
11090 // selection was copied. If this selection is also currently empty,
11091 // then paste the line before the current line of the buffer.
11092 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11093 let column = selection.start.to_point(&snapshot).column as usize;
11094 let line_start = selection.start - column;
11095 line_start..line_start
11096 } else {
11097 selection.range()
11098 };
11099
11100 edits.push((range, to_insert));
11101 original_indent_columns.push(original_indent_column);
11102 }
11103 drop(snapshot);
11104
11105 buffer.edit(
11106 edits,
11107 if auto_indent_on_paste {
11108 Some(AutoindentMode::Block {
11109 original_indent_columns,
11110 })
11111 } else {
11112 None
11113 },
11114 cx,
11115 );
11116 });
11117
11118 let selections = this.selections.all::<usize>(cx);
11119 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11120 s.select(selections)
11121 });
11122 } else {
11123 this.insert(&clipboard_text, window, cx);
11124 }
11125 });
11126 }
11127
11128 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11129 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11130 if let Some(item) = cx.read_from_clipboard() {
11131 let entries = item.entries();
11132
11133 match entries.first() {
11134 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11135 // of all the pasted entries.
11136 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11137 .do_paste(
11138 clipboard_string.text(),
11139 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11140 true,
11141 window,
11142 cx,
11143 ),
11144 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11145 }
11146 }
11147 }
11148
11149 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11150 if self.read_only(cx) {
11151 return;
11152 }
11153
11154 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11155
11156 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11157 if let Some((selections, _)) =
11158 self.selection_history.transaction(transaction_id).cloned()
11159 {
11160 self.change_selections(None, window, cx, |s| {
11161 s.select_anchors(selections.to_vec());
11162 });
11163 } else {
11164 log::error!(
11165 "No entry in selection_history found for undo. \
11166 This may correspond to a bug where undo does not update the selection. \
11167 If this is occurring, please add details to \
11168 https://github.com/zed-industries/zed/issues/22692"
11169 );
11170 }
11171 self.request_autoscroll(Autoscroll::fit(), cx);
11172 self.unmark_text(window, cx);
11173 self.refresh_inline_completion(true, false, window, cx);
11174 cx.emit(EditorEvent::Edited { transaction_id });
11175 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11176 }
11177 }
11178
11179 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11180 if self.read_only(cx) {
11181 return;
11182 }
11183
11184 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11185
11186 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11187 if let Some((_, Some(selections))) =
11188 self.selection_history.transaction(transaction_id).cloned()
11189 {
11190 self.change_selections(None, window, cx, |s| {
11191 s.select_anchors(selections.to_vec());
11192 });
11193 } else {
11194 log::error!(
11195 "No entry in selection_history found for redo. \
11196 This may correspond to a bug where undo does not update the selection. \
11197 If this is occurring, please add details to \
11198 https://github.com/zed-industries/zed/issues/22692"
11199 );
11200 }
11201 self.request_autoscroll(Autoscroll::fit(), cx);
11202 self.unmark_text(window, cx);
11203 self.refresh_inline_completion(true, false, window, cx);
11204 cx.emit(EditorEvent::Edited { transaction_id });
11205 }
11206 }
11207
11208 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11209 self.buffer
11210 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11211 }
11212
11213 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11214 self.buffer
11215 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11216 }
11217
11218 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11219 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11220 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11221 s.move_with(|map, selection| {
11222 let cursor = if selection.is_empty() {
11223 movement::left(map, selection.start)
11224 } else {
11225 selection.start
11226 };
11227 selection.collapse_to(cursor, SelectionGoal::None);
11228 });
11229 })
11230 }
11231
11232 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11233 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11234 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11235 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11236 })
11237 }
11238
11239 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11240 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11241 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11242 s.move_with(|map, selection| {
11243 let cursor = if selection.is_empty() {
11244 movement::right(map, selection.end)
11245 } else {
11246 selection.end
11247 };
11248 selection.collapse_to(cursor, SelectionGoal::None)
11249 });
11250 })
11251 }
11252
11253 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11254 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11255 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11256 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11257 })
11258 }
11259
11260 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11261 if self.take_rename(true, window, cx).is_some() {
11262 return;
11263 }
11264
11265 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11266 cx.propagate();
11267 return;
11268 }
11269
11270 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11271
11272 let text_layout_details = &self.text_layout_details(window);
11273 let selection_count = self.selections.count();
11274 let first_selection = self.selections.first_anchor();
11275
11276 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11277 s.move_with(|map, selection| {
11278 if !selection.is_empty() {
11279 selection.goal = SelectionGoal::None;
11280 }
11281 let (cursor, goal) = movement::up(
11282 map,
11283 selection.start,
11284 selection.goal,
11285 false,
11286 text_layout_details,
11287 );
11288 selection.collapse_to(cursor, goal);
11289 });
11290 });
11291
11292 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11293 {
11294 cx.propagate();
11295 }
11296 }
11297
11298 pub fn move_up_by_lines(
11299 &mut self,
11300 action: &MoveUpByLines,
11301 window: &mut Window,
11302 cx: &mut Context<Self>,
11303 ) {
11304 if self.take_rename(true, window, cx).is_some() {
11305 return;
11306 }
11307
11308 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11309 cx.propagate();
11310 return;
11311 }
11312
11313 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11314
11315 let text_layout_details = &self.text_layout_details(window);
11316
11317 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11318 s.move_with(|map, selection| {
11319 if !selection.is_empty() {
11320 selection.goal = SelectionGoal::None;
11321 }
11322 let (cursor, goal) = movement::up_by_rows(
11323 map,
11324 selection.start,
11325 action.lines,
11326 selection.goal,
11327 false,
11328 text_layout_details,
11329 );
11330 selection.collapse_to(cursor, goal);
11331 });
11332 })
11333 }
11334
11335 pub fn move_down_by_lines(
11336 &mut self,
11337 action: &MoveDownByLines,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) {
11341 if self.take_rename(true, window, cx).is_some() {
11342 return;
11343 }
11344
11345 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11346 cx.propagate();
11347 return;
11348 }
11349
11350 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11351
11352 let text_layout_details = &self.text_layout_details(window);
11353
11354 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11355 s.move_with(|map, selection| {
11356 if !selection.is_empty() {
11357 selection.goal = SelectionGoal::None;
11358 }
11359 let (cursor, goal) = movement::down_by_rows(
11360 map,
11361 selection.start,
11362 action.lines,
11363 selection.goal,
11364 false,
11365 text_layout_details,
11366 );
11367 selection.collapse_to(cursor, goal);
11368 });
11369 })
11370 }
11371
11372 pub fn select_down_by_lines(
11373 &mut self,
11374 action: &SelectDownByLines,
11375 window: &mut Window,
11376 cx: &mut Context<Self>,
11377 ) {
11378 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11379 let text_layout_details = &self.text_layout_details(window);
11380 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11381 s.move_heads_with(|map, head, goal| {
11382 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11383 })
11384 })
11385 }
11386
11387 pub fn select_up_by_lines(
11388 &mut self,
11389 action: &SelectUpByLines,
11390 window: &mut Window,
11391 cx: &mut Context<Self>,
11392 ) {
11393 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11394 let text_layout_details = &self.text_layout_details(window);
11395 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11396 s.move_heads_with(|map, head, goal| {
11397 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11398 })
11399 })
11400 }
11401
11402 pub fn select_page_up(
11403 &mut self,
11404 _: &SelectPageUp,
11405 window: &mut Window,
11406 cx: &mut Context<Self>,
11407 ) {
11408 let Some(row_count) = self.visible_row_count() else {
11409 return;
11410 };
11411
11412 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11413
11414 let text_layout_details = &self.text_layout_details(window);
11415
11416 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11417 s.move_heads_with(|map, head, goal| {
11418 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11419 })
11420 })
11421 }
11422
11423 pub fn move_page_up(
11424 &mut self,
11425 action: &MovePageUp,
11426 window: &mut Window,
11427 cx: &mut Context<Self>,
11428 ) {
11429 if self.take_rename(true, window, cx).is_some() {
11430 return;
11431 }
11432
11433 if self
11434 .context_menu
11435 .borrow_mut()
11436 .as_mut()
11437 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11438 .unwrap_or(false)
11439 {
11440 return;
11441 }
11442
11443 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11444 cx.propagate();
11445 return;
11446 }
11447
11448 let Some(row_count) = self.visible_row_count() else {
11449 return;
11450 };
11451
11452 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11453
11454 let autoscroll = if action.center_cursor {
11455 Autoscroll::center()
11456 } else {
11457 Autoscroll::fit()
11458 };
11459
11460 let text_layout_details = &self.text_layout_details(window);
11461
11462 self.change_selections(Some(autoscroll), window, cx, |s| {
11463 s.move_with(|map, selection| {
11464 if !selection.is_empty() {
11465 selection.goal = SelectionGoal::None;
11466 }
11467 let (cursor, goal) = movement::up_by_rows(
11468 map,
11469 selection.end,
11470 row_count,
11471 selection.goal,
11472 false,
11473 text_layout_details,
11474 );
11475 selection.collapse_to(cursor, goal);
11476 });
11477 });
11478 }
11479
11480 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11481 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11482 let text_layout_details = &self.text_layout_details(window);
11483 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11484 s.move_heads_with(|map, head, goal| {
11485 movement::up(map, head, goal, false, text_layout_details)
11486 })
11487 })
11488 }
11489
11490 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11491 self.take_rename(true, window, cx);
11492
11493 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11494 cx.propagate();
11495 return;
11496 }
11497
11498 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11499
11500 let text_layout_details = &self.text_layout_details(window);
11501 let selection_count = self.selections.count();
11502 let first_selection = self.selections.first_anchor();
11503
11504 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11505 s.move_with(|map, selection| {
11506 if !selection.is_empty() {
11507 selection.goal = SelectionGoal::None;
11508 }
11509 let (cursor, goal) = movement::down(
11510 map,
11511 selection.end,
11512 selection.goal,
11513 false,
11514 text_layout_details,
11515 );
11516 selection.collapse_to(cursor, goal);
11517 });
11518 });
11519
11520 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11521 {
11522 cx.propagate();
11523 }
11524 }
11525
11526 pub fn select_page_down(
11527 &mut self,
11528 _: &SelectPageDown,
11529 window: &mut Window,
11530 cx: &mut Context<Self>,
11531 ) {
11532 let Some(row_count) = self.visible_row_count() else {
11533 return;
11534 };
11535
11536 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11537
11538 let text_layout_details = &self.text_layout_details(window);
11539
11540 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11541 s.move_heads_with(|map, head, goal| {
11542 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11543 })
11544 })
11545 }
11546
11547 pub fn move_page_down(
11548 &mut self,
11549 action: &MovePageDown,
11550 window: &mut Window,
11551 cx: &mut Context<Self>,
11552 ) {
11553 if self.take_rename(true, window, cx).is_some() {
11554 return;
11555 }
11556
11557 if self
11558 .context_menu
11559 .borrow_mut()
11560 .as_mut()
11561 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11562 .unwrap_or(false)
11563 {
11564 return;
11565 }
11566
11567 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11568 cx.propagate();
11569 return;
11570 }
11571
11572 let Some(row_count) = self.visible_row_count() else {
11573 return;
11574 };
11575
11576 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11577
11578 let autoscroll = if action.center_cursor {
11579 Autoscroll::center()
11580 } else {
11581 Autoscroll::fit()
11582 };
11583
11584 let text_layout_details = &self.text_layout_details(window);
11585 self.change_selections(Some(autoscroll), window, cx, |s| {
11586 s.move_with(|map, selection| {
11587 if !selection.is_empty() {
11588 selection.goal = SelectionGoal::None;
11589 }
11590 let (cursor, goal) = movement::down_by_rows(
11591 map,
11592 selection.end,
11593 row_count,
11594 selection.goal,
11595 false,
11596 text_layout_details,
11597 );
11598 selection.collapse_to(cursor, goal);
11599 });
11600 });
11601 }
11602
11603 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11604 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11605 let text_layout_details = &self.text_layout_details(window);
11606 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11607 s.move_heads_with(|map, head, goal| {
11608 movement::down(map, head, goal, false, text_layout_details)
11609 })
11610 });
11611 }
11612
11613 pub fn context_menu_first(
11614 &mut self,
11615 _: &ContextMenuFirst,
11616 window: &mut Window,
11617 cx: &mut Context<Self>,
11618 ) {
11619 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11620 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
11621 }
11622 }
11623
11624 pub fn context_menu_prev(
11625 &mut self,
11626 _: &ContextMenuPrevious,
11627 window: &mut Window,
11628 cx: &mut Context<Self>,
11629 ) {
11630 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11631 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
11632 }
11633 }
11634
11635 pub fn context_menu_next(
11636 &mut self,
11637 _: &ContextMenuNext,
11638 window: &mut Window,
11639 cx: &mut Context<Self>,
11640 ) {
11641 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11642 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
11643 }
11644 }
11645
11646 pub fn context_menu_last(
11647 &mut self,
11648 _: &ContextMenuLast,
11649 window: &mut Window,
11650 cx: &mut Context<Self>,
11651 ) {
11652 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
11653 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
11654 }
11655 }
11656
11657 pub fn move_to_previous_word_start(
11658 &mut self,
11659 _: &MoveToPreviousWordStart,
11660 window: &mut Window,
11661 cx: &mut Context<Self>,
11662 ) {
11663 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11664 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11665 s.move_cursors_with(|map, head, _| {
11666 (
11667 movement::previous_word_start(map, head),
11668 SelectionGoal::None,
11669 )
11670 });
11671 })
11672 }
11673
11674 pub fn move_to_previous_subword_start(
11675 &mut self,
11676 _: &MoveToPreviousSubwordStart,
11677 window: &mut Window,
11678 cx: &mut Context<Self>,
11679 ) {
11680 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11681 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11682 s.move_cursors_with(|map, head, _| {
11683 (
11684 movement::previous_subword_start(map, head),
11685 SelectionGoal::None,
11686 )
11687 });
11688 })
11689 }
11690
11691 pub fn select_to_previous_word_start(
11692 &mut self,
11693 _: &SelectToPreviousWordStart,
11694 window: &mut Window,
11695 cx: &mut Context<Self>,
11696 ) {
11697 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11698 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11699 s.move_heads_with(|map, head, _| {
11700 (
11701 movement::previous_word_start(map, head),
11702 SelectionGoal::None,
11703 )
11704 });
11705 })
11706 }
11707
11708 pub fn select_to_previous_subword_start(
11709 &mut self,
11710 _: &SelectToPreviousSubwordStart,
11711 window: &mut Window,
11712 cx: &mut Context<Self>,
11713 ) {
11714 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11715 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11716 s.move_heads_with(|map, head, _| {
11717 (
11718 movement::previous_subword_start(map, head),
11719 SelectionGoal::None,
11720 )
11721 });
11722 })
11723 }
11724
11725 pub fn delete_to_previous_word_start(
11726 &mut self,
11727 action: &DeleteToPreviousWordStart,
11728 window: &mut Window,
11729 cx: &mut Context<Self>,
11730 ) {
11731 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11732 self.transact(window, cx, |this, window, cx| {
11733 this.select_autoclose_pair(window, cx);
11734 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11735 s.move_with(|map, selection| {
11736 if selection.is_empty() {
11737 let cursor = if action.ignore_newlines {
11738 movement::previous_word_start(map, selection.head())
11739 } else {
11740 movement::previous_word_start_or_newline(map, selection.head())
11741 };
11742 selection.set_head(cursor, SelectionGoal::None);
11743 }
11744 });
11745 });
11746 this.insert("", window, cx);
11747 });
11748 }
11749
11750 pub fn delete_to_previous_subword_start(
11751 &mut self,
11752 _: &DeleteToPreviousSubwordStart,
11753 window: &mut Window,
11754 cx: &mut Context<Self>,
11755 ) {
11756 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11757 self.transact(window, cx, |this, window, cx| {
11758 this.select_autoclose_pair(window, cx);
11759 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11760 s.move_with(|map, selection| {
11761 if selection.is_empty() {
11762 let cursor = movement::previous_subword_start(map, selection.head());
11763 selection.set_head(cursor, SelectionGoal::None);
11764 }
11765 });
11766 });
11767 this.insert("", window, cx);
11768 });
11769 }
11770
11771 pub fn move_to_next_word_end(
11772 &mut self,
11773 _: &MoveToNextWordEnd,
11774 window: &mut Window,
11775 cx: &mut Context<Self>,
11776 ) {
11777 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11778 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11779 s.move_cursors_with(|map, head, _| {
11780 (movement::next_word_end(map, head), SelectionGoal::None)
11781 });
11782 })
11783 }
11784
11785 pub fn move_to_next_subword_end(
11786 &mut self,
11787 _: &MoveToNextSubwordEnd,
11788 window: &mut Window,
11789 cx: &mut Context<Self>,
11790 ) {
11791 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11792 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11793 s.move_cursors_with(|map, head, _| {
11794 (movement::next_subword_end(map, head), SelectionGoal::None)
11795 });
11796 })
11797 }
11798
11799 pub fn select_to_next_word_end(
11800 &mut self,
11801 _: &SelectToNextWordEnd,
11802 window: &mut Window,
11803 cx: &mut Context<Self>,
11804 ) {
11805 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11806 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11807 s.move_heads_with(|map, head, _| {
11808 (movement::next_word_end(map, head), SelectionGoal::None)
11809 });
11810 })
11811 }
11812
11813 pub fn select_to_next_subword_end(
11814 &mut self,
11815 _: &SelectToNextSubwordEnd,
11816 window: &mut Window,
11817 cx: &mut Context<Self>,
11818 ) {
11819 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11820 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11821 s.move_heads_with(|map, head, _| {
11822 (movement::next_subword_end(map, head), SelectionGoal::None)
11823 });
11824 })
11825 }
11826
11827 pub fn delete_to_next_word_end(
11828 &mut self,
11829 action: &DeleteToNextWordEnd,
11830 window: &mut Window,
11831 cx: &mut Context<Self>,
11832 ) {
11833 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11834 self.transact(window, cx, |this, window, cx| {
11835 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11836 s.move_with(|map, selection| {
11837 if selection.is_empty() {
11838 let cursor = if action.ignore_newlines {
11839 movement::next_word_end(map, selection.head())
11840 } else {
11841 movement::next_word_end_or_newline(map, selection.head())
11842 };
11843 selection.set_head(cursor, SelectionGoal::None);
11844 }
11845 });
11846 });
11847 this.insert("", window, cx);
11848 });
11849 }
11850
11851 pub fn delete_to_next_subword_end(
11852 &mut self,
11853 _: &DeleteToNextSubwordEnd,
11854 window: &mut Window,
11855 cx: &mut Context<Self>,
11856 ) {
11857 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11858 self.transact(window, cx, |this, window, cx| {
11859 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11860 s.move_with(|map, selection| {
11861 if selection.is_empty() {
11862 let cursor = movement::next_subword_end(map, selection.head());
11863 selection.set_head(cursor, SelectionGoal::None);
11864 }
11865 });
11866 });
11867 this.insert("", window, cx);
11868 });
11869 }
11870
11871 pub fn move_to_beginning_of_line(
11872 &mut self,
11873 action: &MoveToBeginningOfLine,
11874 window: &mut Window,
11875 cx: &mut Context<Self>,
11876 ) {
11877 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11878 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11879 s.move_cursors_with(|map, head, _| {
11880 (
11881 movement::indented_line_beginning(
11882 map,
11883 head,
11884 action.stop_at_soft_wraps,
11885 action.stop_at_indent,
11886 ),
11887 SelectionGoal::None,
11888 )
11889 });
11890 })
11891 }
11892
11893 pub fn select_to_beginning_of_line(
11894 &mut self,
11895 action: &SelectToBeginningOfLine,
11896 window: &mut Window,
11897 cx: &mut Context<Self>,
11898 ) {
11899 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11900 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11901 s.move_heads_with(|map, head, _| {
11902 (
11903 movement::indented_line_beginning(
11904 map,
11905 head,
11906 action.stop_at_soft_wraps,
11907 action.stop_at_indent,
11908 ),
11909 SelectionGoal::None,
11910 )
11911 });
11912 });
11913 }
11914
11915 pub fn delete_to_beginning_of_line(
11916 &mut self,
11917 action: &DeleteToBeginningOfLine,
11918 window: &mut Window,
11919 cx: &mut Context<Self>,
11920 ) {
11921 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11922 self.transact(window, cx, |this, window, cx| {
11923 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11924 s.move_with(|_, selection| {
11925 selection.reversed = true;
11926 });
11927 });
11928
11929 this.select_to_beginning_of_line(
11930 &SelectToBeginningOfLine {
11931 stop_at_soft_wraps: false,
11932 stop_at_indent: action.stop_at_indent,
11933 },
11934 window,
11935 cx,
11936 );
11937 this.backspace(&Backspace, window, cx);
11938 });
11939 }
11940
11941 pub fn move_to_end_of_line(
11942 &mut self,
11943 action: &MoveToEndOfLine,
11944 window: &mut Window,
11945 cx: &mut Context<Self>,
11946 ) {
11947 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11948 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11949 s.move_cursors_with(|map, head, _| {
11950 (
11951 movement::line_end(map, head, action.stop_at_soft_wraps),
11952 SelectionGoal::None,
11953 )
11954 });
11955 })
11956 }
11957
11958 pub fn select_to_end_of_line(
11959 &mut self,
11960 action: &SelectToEndOfLine,
11961 window: &mut Window,
11962 cx: &mut Context<Self>,
11963 ) {
11964 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11965 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11966 s.move_heads_with(|map, head, _| {
11967 (
11968 movement::line_end(map, head, action.stop_at_soft_wraps),
11969 SelectionGoal::None,
11970 )
11971 });
11972 })
11973 }
11974
11975 pub fn delete_to_end_of_line(
11976 &mut self,
11977 _: &DeleteToEndOfLine,
11978 window: &mut Window,
11979 cx: &mut Context<Self>,
11980 ) {
11981 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11982 self.transact(window, cx, |this, window, cx| {
11983 this.select_to_end_of_line(
11984 &SelectToEndOfLine {
11985 stop_at_soft_wraps: false,
11986 },
11987 window,
11988 cx,
11989 );
11990 this.delete(&Delete, window, cx);
11991 });
11992 }
11993
11994 pub fn cut_to_end_of_line(
11995 &mut self,
11996 _: &CutToEndOfLine,
11997 window: &mut Window,
11998 cx: &mut Context<Self>,
11999 ) {
12000 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12001 self.transact(window, cx, |this, window, cx| {
12002 this.select_to_end_of_line(
12003 &SelectToEndOfLine {
12004 stop_at_soft_wraps: false,
12005 },
12006 window,
12007 cx,
12008 );
12009 this.cut(&Cut, window, cx);
12010 });
12011 }
12012
12013 pub fn move_to_start_of_paragraph(
12014 &mut self,
12015 _: &MoveToStartOfParagraph,
12016 window: &mut Window,
12017 cx: &mut Context<Self>,
12018 ) {
12019 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12020 cx.propagate();
12021 return;
12022 }
12023 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12024 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12025 s.move_with(|map, selection| {
12026 selection.collapse_to(
12027 movement::start_of_paragraph(map, selection.head(), 1),
12028 SelectionGoal::None,
12029 )
12030 });
12031 })
12032 }
12033
12034 pub fn move_to_end_of_paragraph(
12035 &mut self,
12036 _: &MoveToEndOfParagraph,
12037 window: &mut Window,
12038 cx: &mut Context<Self>,
12039 ) {
12040 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12041 cx.propagate();
12042 return;
12043 }
12044 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12045 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12046 s.move_with(|map, selection| {
12047 selection.collapse_to(
12048 movement::end_of_paragraph(map, selection.head(), 1),
12049 SelectionGoal::None,
12050 )
12051 });
12052 })
12053 }
12054
12055 pub fn select_to_start_of_paragraph(
12056 &mut self,
12057 _: &SelectToStartOfParagraph,
12058 window: &mut Window,
12059 cx: &mut Context<Self>,
12060 ) {
12061 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12062 cx.propagate();
12063 return;
12064 }
12065 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12066 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12067 s.move_heads_with(|map, head, _| {
12068 (
12069 movement::start_of_paragraph(map, head, 1),
12070 SelectionGoal::None,
12071 )
12072 });
12073 })
12074 }
12075
12076 pub fn select_to_end_of_paragraph(
12077 &mut self,
12078 _: &SelectToEndOfParagraph,
12079 window: &mut Window,
12080 cx: &mut Context<Self>,
12081 ) {
12082 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12083 cx.propagate();
12084 return;
12085 }
12086 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12087 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12088 s.move_heads_with(|map, head, _| {
12089 (
12090 movement::end_of_paragraph(map, head, 1),
12091 SelectionGoal::None,
12092 )
12093 });
12094 })
12095 }
12096
12097 pub fn move_to_start_of_excerpt(
12098 &mut self,
12099 _: &MoveToStartOfExcerpt,
12100 window: &mut Window,
12101 cx: &mut Context<Self>,
12102 ) {
12103 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12104 cx.propagate();
12105 return;
12106 }
12107 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12108 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12109 s.move_with(|map, selection| {
12110 selection.collapse_to(
12111 movement::start_of_excerpt(
12112 map,
12113 selection.head(),
12114 workspace::searchable::Direction::Prev,
12115 ),
12116 SelectionGoal::None,
12117 )
12118 });
12119 })
12120 }
12121
12122 pub fn move_to_start_of_next_excerpt(
12123 &mut self,
12124 _: &MoveToStartOfNextExcerpt,
12125 window: &mut Window,
12126 cx: &mut Context<Self>,
12127 ) {
12128 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12129 cx.propagate();
12130 return;
12131 }
12132
12133 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12134 s.move_with(|map, selection| {
12135 selection.collapse_to(
12136 movement::start_of_excerpt(
12137 map,
12138 selection.head(),
12139 workspace::searchable::Direction::Next,
12140 ),
12141 SelectionGoal::None,
12142 )
12143 });
12144 })
12145 }
12146
12147 pub fn move_to_end_of_excerpt(
12148 &mut self,
12149 _: &MoveToEndOfExcerpt,
12150 window: &mut Window,
12151 cx: &mut Context<Self>,
12152 ) {
12153 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12154 cx.propagate();
12155 return;
12156 }
12157 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12158 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12159 s.move_with(|map, selection| {
12160 selection.collapse_to(
12161 movement::end_of_excerpt(
12162 map,
12163 selection.head(),
12164 workspace::searchable::Direction::Next,
12165 ),
12166 SelectionGoal::None,
12167 )
12168 });
12169 })
12170 }
12171
12172 pub fn move_to_end_of_previous_excerpt(
12173 &mut self,
12174 _: &MoveToEndOfPreviousExcerpt,
12175 window: &mut Window,
12176 cx: &mut Context<Self>,
12177 ) {
12178 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12179 cx.propagate();
12180 return;
12181 }
12182 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12183 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12184 s.move_with(|map, selection| {
12185 selection.collapse_to(
12186 movement::end_of_excerpt(
12187 map,
12188 selection.head(),
12189 workspace::searchable::Direction::Prev,
12190 ),
12191 SelectionGoal::None,
12192 )
12193 });
12194 })
12195 }
12196
12197 pub fn select_to_start_of_excerpt(
12198 &mut self,
12199 _: &SelectToStartOfExcerpt,
12200 window: &mut Window,
12201 cx: &mut Context<Self>,
12202 ) {
12203 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12204 cx.propagate();
12205 return;
12206 }
12207 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12208 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12209 s.move_heads_with(|map, head, _| {
12210 (
12211 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12212 SelectionGoal::None,
12213 )
12214 });
12215 })
12216 }
12217
12218 pub fn select_to_start_of_next_excerpt(
12219 &mut self,
12220 _: &SelectToStartOfNextExcerpt,
12221 window: &mut Window,
12222 cx: &mut Context<Self>,
12223 ) {
12224 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12225 cx.propagate();
12226 return;
12227 }
12228 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12229 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12230 s.move_heads_with(|map, head, _| {
12231 (
12232 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12233 SelectionGoal::None,
12234 )
12235 });
12236 })
12237 }
12238
12239 pub fn select_to_end_of_excerpt(
12240 &mut self,
12241 _: &SelectToEndOfExcerpt,
12242 window: &mut Window,
12243 cx: &mut Context<Self>,
12244 ) {
12245 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12246 cx.propagate();
12247 return;
12248 }
12249 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12250 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12251 s.move_heads_with(|map, head, _| {
12252 (
12253 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12254 SelectionGoal::None,
12255 )
12256 });
12257 })
12258 }
12259
12260 pub fn select_to_end_of_previous_excerpt(
12261 &mut self,
12262 _: &SelectToEndOfPreviousExcerpt,
12263 window: &mut Window,
12264 cx: &mut Context<Self>,
12265 ) {
12266 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12267 cx.propagate();
12268 return;
12269 }
12270 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12271 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12272 s.move_heads_with(|map, head, _| {
12273 (
12274 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12275 SelectionGoal::None,
12276 )
12277 });
12278 })
12279 }
12280
12281 pub fn move_to_beginning(
12282 &mut self,
12283 _: &MoveToBeginning,
12284 window: &mut Window,
12285 cx: &mut Context<Self>,
12286 ) {
12287 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12288 cx.propagate();
12289 return;
12290 }
12291 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12292 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12293 s.select_ranges(vec![0..0]);
12294 });
12295 }
12296
12297 pub fn select_to_beginning(
12298 &mut self,
12299 _: &SelectToBeginning,
12300 window: &mut Window,
12301 cx: &mut Context<Self>,
12302 ) {
12303 let mut selection = self.selections.last::<Point>(cx);
12304 selection.set_head(Point::zero(), SelectionGoal::None);
12305 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12306 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12307 s.select(vec![selection]);
12308 });
12309 }
12310
12311 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12312 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12313 cx.propagate();
12314 return;
12315 }
12316 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12317 let cursor = self.buffer.read(cx).read(cx).len();
12318 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12319 s.select_ranges(vec![cursor..cursor])
12320 });
12321 }
12322
12323 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12324 self.nav_history = nav_history;
12325 }
12326
12327 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12328 self.nav_history.as_ref()
12329 }
12330
12331 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12332 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12333 }
12334
12335 fn push_to_nav_history(
12336 &mut self,
12337 cursor_anchor: Anchor,
12338 new_position: Option<Point>,
12339 is_deactivate: bool,
12340 cx: &mut Context<Self>,
12341 ) {
12342 if let Some(nav_history) = self.nav_history.as_mut() {
12343 let buffer = self.buffer.read(cx).read(cx);
12344 let cursor_position = cursor_anchor.to_point(&buffer);
12345 let scroll_state = self.scroll_manager.anchor();
12346 let scroll_top_row = scroll_state.top_row(&buffer);
12347 drop(buffer);
12348
12349 if let Some(new_position) = new_position {
12350 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12351 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12352 return;
12353 }
12354 }
12355
12356 nav_history.push(
12357 Some(NavigationData {
12358 cursor_anchor,
12359 cursor_position,
12360 scroll_anchor: scroll_state,
12361 scroll_top_row,
12362 }),
12363 cx,
12364 );
12365 cx.emit(EditorEvent::PushedToNavHistory {
12366 anchor: cursor_anchor,
12367 is_deactivate,
12368 })
12369 }
12370 }
12371
12372 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12373 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12374 let buffer = self.buffer.read(cx).snapshot(cx);
12375 let mut selection = self.selections.first::<usize>(cx);
12376 selection.set_head(buffer.len(), SelectionGoal::None);
12377 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12378 s.select(vec![selection]);
12379 });
12380 }
12381
12382 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12383 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12384 let end = self.buffer.read(cx).read(cx).len();
12385 self.change_selections(None, window, cx, |s| {
12386 s.select_ranges(vec![0..end]);
12387 });
12388 }
12389
12390 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12391 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12392 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12393 let mut selections = self.selections.all::<Point>(cx);
12394 let max_point = display_map.buffer_snapshot.max_point();
12395 for selection in &mut selections {
12396 let rows = selection.spanned_rows(true, &display_map);
12397 selection.start = Point::new(rows.start.0, 0);
12398 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12399 selection.reversed = false;
12400 }
12401 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12402 s.select(selections);
12403 });
12404 }
12405
12406 pub fn split_selection_into_lines(
12407 &mut self,
12408 _: &SplitSelectionIntoLines,
12409 window: &mut Window,
12410 cx: &mut Context<Self>,
12411 ) {
12412 let selections = self
12413 .selections
12414 .all::<Point>(cx)
12415 .into_iter()
12416 .map(|selection| selection.start..selection.end)
12417 .collect::<Vec<_>>();
12418 self.unfold_ranges(&selections, true, true, cx);
12419
12420 let mut new_selection_ranges = Vec::new();
12421 {
12422 let buffer = self.buffer.read(cx).read(cx);
12423 for selection in selections {
12424 for row in selection.start.row..selection.end.row {
12425 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12426 new_selection_ranges.push(cursor..cursor);
12427 }
12428
12429 let is_multiline_selection = selection.start.row != selection.end.row;
12430 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12431 // so this action feels more ergonomic when paired with other selection operations
12432 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12433 if !should_skip_last {
12434 new_selection_ranges.push(selection.end..selection.end);
12435 }
12436 }
12437 }
12438 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12439 s.select_ranges(new_selection_ranges);
12440 });
12441 }
12442
12443 pub fn add_selection_above(
12444 &mut self,
12445 _: &AddSelectionAbove,
12446 window: &mut Window,
12447 cx: &mut Context<Self>,
12448 ) {
12449 self.add_selection(true, window, cx);
12450 }
12451
12452 pub fn add_selection_below(
12453 &mut self,
12454 _: &AddSelectionBelow,
12455 window: &mut Window,
12456 cx: &mut Context<Self>,
12457 ) {
12458 self.add_selection(false, window, cx);
12459 }
12460
12461 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12462 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12463
12464 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12465 let mut selections = self.selections.all::<Point>(cx);
12466 let text_layout_details = self.text_layout_details(window);
12467 let mut state = self.add_selections_state.take().unwrap_or_else(|| {
12468 let oldest_selection = selections.iter().min_by_key(|s| s.id).unwrap().clone();
12469 let range = oldest_selection.display_range(&display_map).sorted();
12470
12471 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12472 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12473 let positions = start_x.min(end_x)..start_x.max(end_x);
12474
12475 selections.clear();
12476 let mut stack = Vec::new();
12477 for row in range.start.row().0..=range.end.row().0 {
12478 if let Some(selection) = self.selections.build_columnar_selection(
12479 &display_map,
12480 DisplayRow(row),
12481 &positions,
12482 oldest_selection.reversed,
12483 &text_layout_details,
12484 ) {
12485 stack.push(selection.id);
12486 selections.push(selection);
12487 }
12488 }
12489
12490 if above {
12491 stack.reverse();
12492 }
12493
12494 AddSelectionsState { above, stack }
12495 });
12496
12497 let last_added_selection = *state.stack.last().unwrap();
12498 let mut new_selections = Vec::new();
12499 if above == state.above {
12500 let end_row = if above {
12501 DisplayRow(0)
12502 } else {
12503 display_map.max_point().row()
12504 };
12505
12506 'outer: for selection in selections {
12507 if selection.id == last_added_selection {
12508 let range = selection.display_range(&display_map).sorted();
12509 debug_assert_eq!(range.start.row(), range.end.row());
12510 let mut row = range.start.row();
12511 let positions =
12512 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12513 px(start)..px(end)
12514 } else {
12515 let start_x =
12516 display_map.x_for_display_point(range.start, &text_layout_details);
12517 let end_x =
12518 display_map.x_for_display_point(range.end, &text_layout_details);
12519 start_x.min(end_x)..start_x.max(end_x)
12520 };
12521
12522 while row != end_row {
12523 if above {
12524 row.0 -= 1;
12525 } else {
12526 row.0 += 1;
12527 }
12528
12529 if let Some(new_selection) = self.selections.build_columnar_selection(
12530 &display_map,
12531 row,
12532 &positions,
12533 selection.reversed,
12534 &text_layout_details,
12535 ) {
12536 state.stack.push(new_selection.id);
12537 if above {
12538 new_selections.push(new_selection);
12539 new_selections.push(selection);
12540 } else {
12541 new_selections.push(selection);
12542 new_selections.push(new_selection);
12543 }
12544
12545 continue 'outer;
12546 }
12547 }
12548 }
12549
12550 new_selections.push(selection);
12551 }
12552 } else {
12553 new_selections = selections;
12554 new_selections.retain(|s| s.id != last_added_selection);
12555 state.stack.pop();
12556 }
12557
12558 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12559 s.select(new_selections);
12560 });
12561 if state.stack.len() > 1 {
12562 self.add_selections_state = Some(state);
12563 }
12564 }
12565
12566 fn select_match_ranges(
12567 &mut self,
12568 range: Range<usize>,
12569 reversed: bool,
12570 replace_newest: bool,
12571 auto_scroll: Option<Autoscroll>,
12572 window: &mut Window,
12573 cx: &mut Context<Editor>,
12574 ) {
12575 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
12576 self.change_selections(auto_scroll, window, cx, |s| {
12577 if replace_newest {
12578 s.delete(s.newest_anchor().id);
12579 }
12580 if reversed {
12581 s.insert_range(range.end..range.start);
12582 } else {
12583 s.insert_range(range);
12584 }
12585 });
12586 }
12587
12588 pub fn select_next_match_internal(
12589 &mut self,
12590 display_map: &DisplaySnapshot,
12591 replace_newest: bool,
12592 autoscroll: Option<Autoscroll>,
12593 window: &mut Window,
12594 cx: &mut Context<Self>,
12595 ) -> Result<()> {
12596 let buffer = &display_map.buffer_snapshot;
12597 let mut selections = self.selections.all::<usize>(cx);
12598 if let Some(mut select_next_state) = self.select_next_state.take() {
12599 let query = &select_next_state.query;
12600 if !select_next_state.done {
12601 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12602 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12603 let mut next_selected_range = None;
12604
12605 let bytes_after_last_selection =
12606 buffer.bytes_in_range(last_selection.end..buffer.len());
12607 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
12608 let query_matches = query
12609 .stream_find_iter(bytes_after_last_selection)
12610 .map(|result| (last_selection.end, result))
12611 .chain(
12612 query
12613 .stream_find_iter(bytes_before_first_selection)
12614 .map(|result| (0, result)),
12615 );
12616
12617 for (start_offset, query_match) in query_matches {
12618 let query_match = query_match.unwrap(); // can only fail due to I/O
12619 let offset_range =
12620 start_offset + query_match.start()..start_offset + query_match.end();
12621 let display_range = offset_range.start.to_display_point(display_map)
12622 ..offset_range.end.to_display_point(display_map);
12623
12624 if !select_next_state.wordwise
12625 || (!movement::is_inside_word(display_map, display_range.start)
12626 && !movement::is_inside_word(display_map, display_range.end))
12627 {
12628 // TODO: This is n^2, because we might check all the selections
12629 if !selections
12630 .iter()
12631 .any(|selection| selection.range().overlaps(&offset_range))
12632 {
12633 next_selected_range = Some(offset_range);
12634 break;
12635 }
12636 }
12637 }
12638
12639 if let Some(next_selected_range) = next_selected_range {
12640 self.select_match_ranges(
12641 next_selected_range,
12642 last_selection.reversed,
12643 replace_newest,
12644 autoscroll,
12645 window,
12646 cx,
12647 );
12648 } else {
12649 select_next_state.done = true;
12650 }
12651 }
12652
12653 self.select_next_state = Some(select_next_state);
12654 } else {
12655 let mut only_carets = true;
12656 let mut same_text_selected = true;
12657 let mut selected_text = None;
12658
12659 let mut selections_iter = selections.iter().peekable();
12660 while let Some(selection) = selections_iter.next() {
12661 if selection.start != selection.end {
12662 only_carets = false;
12663 }
12664
12665 if same_text_selected {
12666 if selected_text.is_none() {
12667 selected_text =
12668 Some(buffer.text_for_range(selection.range()).collect::<String>());
12669 }
12670
12671 if let Some(next_selection) = selections_iter.peek() {
12672 if next_selection.range().len() == selection.range().len() {
12673 let next_selected_text = buffer
12674 .text_for_range(next_selection.range())
12675 .collect::<String>();
12676 if Some(next_selected_text) != selected_text {
12677 same_text_selected = false;
12678 selected_text = None;
12679 }
12680 } else {
12681 same_text_selected = false;
12682 selected_text = None;
12683 }
12684 }
12685 }
12686 }
12687
12688 if only_carets {
12689 for selection in &mut selections {
12690 let word_range = movement::surrounding_word(
12691 display_map,
12692 selection.start.to_display_point(display_map),
12693 );
12694 selection.start = word_range.start.to_offset(display_map, Bias::Left);
12695 selection.end = word_range.end.to_offset(display_map, Bias::Left);
12696 selection.goal = SelectionGoal::None;
12697 selection.reversed = false;
12698 self.select_match_ranges(
12699 selection.start..selection.end,
12700 selection.reversed,
12701 replace_newest,
12702 autoscroll,
12703 window,
12704 cx,
12705 );
12706 }
12707
12708 if selections.len() == 1 {
12709 let selection = selections
12710 .last()
12711 .expect("ensured that there's only one selection");
12712 let query = buffer
12713 .text_for_range(selection.start..selection.end)
12714 .collect::<String>();
12715 let is_empty = query.is_empty();
12716 let select_state = SelectNextState {
12717 query: AhoCorasick::new(&[query])?,
12718 wordwise: true,
12719 done: is_empty,
12720 };
12721 self.select_next_state = Some(select_state);
12722 } else {
12723 self.select_next_state = None;
12724 }
12725 } else if let Some(selected_text) = selected_text {
12726 self.select_next_state = Some(SelectNextState {
12727 query: AhoCorasick::new(&[selected_text])?,
12728 wordwise: false,
12729 done: false,
12730 });
12731 self.select_next_match_internal(
12732 display_map,
12733 replace_newest,
12734 autoscroll,
12735 window,
12736 cx,
12737 )?;
12738 }
12739 }
12740 Ok(())
12741 }
12742
12743 pub fn select_all_matches(
12744 &mut self,
12745 _action: &SelectAllMatches,
12746 window: &mut Window,
12747 cx: &mut Context<Self>,
12748 ) -> Result<()> {
12749 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12750
12751 self.push_to_selection_history();
12752 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12753
12754 self.select_next_match_internal(&display_map, false, None, window, cx)?;
12755 let Some(select_next_state) = self.select_next_state.as_mut() else {
12756 return Ok(());
12757 };
12758 if select_next_state.done {
12759 return Ok(());
12760 }
12761
12762 let mut new_selections = Vec::new();
12763
12764 let reversed = self.selections.oldest::<usize>(cx).reversed;
12765 let buffer = &display_map.buffer_snapshot;
12766 let query_matches = select_next_state
12767 .query
12768 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
12769
12770 for query_match in query_matches.into_iter() {
12771 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
12772 let offset_range = if reversed {
12773 query_match.end()..query_match.start()
12774 } else {
12775 query_match.start()..query_match.end()
12776 };
12777 let display_range = offset_range.start.to_display_point(&display_map)
12778 ..offset_range.end.to_display_point(&display_map);
12779
12780 if !select_next_state.wordwise
12781 || (!movement::is_inside_word(&display_map, display_range.start)
12782 && !movement::is_inside_word(&display_map, display_range.end))
12783 {
12784 new_selections.push(offset_range.start..offset_range.end);
12785 }
12786 }
12787
12788 select_next_state.done = true;
12789 self.unfold_ranges(&new_selections.clone(), false, false, cx);
12790 self.change_selections(None, window, cx, |selections| {
12791 selections.select_ranges(new_selections)
12792 });
12793
12794 Ok(())
12795 }
12796
12797 pub fn select_next(
12798 &mut self,
12799 action: &SelectNext,
12800 window: &mut Window,
12801 cx: &mut Context<Self>,
12802 ) -> Result<()> {
12803 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12804 self.push_to_selection_history();
12805 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12806 self.select_next_match_internal(
12807 &display_map,
12808 action.replace_newest,
12809 Some(Autoscroll::newest()),
12810 window,
12811 cx,
12812 )?;
12813 Ok(())
12814 }
12815
12816 pub fn select_previous(
12817 &mut self,
12818 action: &SelectPrevious,
12819 window: &mut Window,
12820 cx: &mut Context<Self>,
12821 ) -> Result<()> {
12822 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12823 self.push_to_selection_history();
12824 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12825 let buffer = &display_map.buffer_snapshot;
12826 let mut selections = self.selections.all::<usize>(cx);
12827 if let Some(mut select_prev_state) = self.select_prev_state.take() {
12828 let query = &select_prev_state.query;
12829 if !select_prev_state.done {
12830 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
12831 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
12832 let mut next_selected_range = None;
12833 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
12834 let bytes_before_last_selection =
12835 buffer.reversed_bytes_in_range(0..last_selection.start);
12836 let bytes_after_first_selection =
12837 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
12838 let query_matches = query
12839 .stream_find_iter(bytes_before_last_selection)
12840 .map(|result| (last_selection.start, result))
12841 .chain(
12842 query
12843 .stream_find_iter(bytes_after_first_selection)
12844 .map(|result| (buffer.len(), result)),
12845 );
12846 for (end_offset, query_match) in query_matches {
12847 let query_match = query_match.unwrap(); // can only fail due to I/O
12848 let offset_range =
12849 end_offset - query_match.end()..end_offset - query_match.start();
12850 let display_range = offset_range.start.to_display_point(&display_map)
12851 ..offset_range.end.to_display_point(&display_map);
12852
12853 if !select_prev_state.wordwise
12854 || (!movement::is_inside_word(&display_map, display_range.start)
12855 && !movement::is_inside_word(&display_map, display_range.end))
12856 {
12857 next_selected_range = Some(offset_range);
12858 break;
12859 }
12860 }
12861
12862 if let Some(next_selected_range) = next_selected_range {
12863 self.select_match_ranges(
12864 next_selected_range,
12865 last_selection.reversed,
12866 action.replace_newest,
12867 Some(Autoscroll::newest()),
12868 window,
12869 cx,
12870 );
12871 } else {
12872 select_prev_state.done = true;
12873 }
12874 }
12875
12876 self.select_prev_state = Some(select_prev_state);
12877 } else {
12878 let mut only_carets = true;
12879 let mut same_text_selected = true;
12880 let mut selected_text = None;
12881
12882 let mut selections_iter = selections.iter().peekable();
12883 while let Some(selection) = selections_iter.next() {
12884 if selection.start != selection.end {
12885 only_carets = false;
12886 }
12887
12888 if same_text_selected {
12889 if selected_text.is_none() {
12890 selected_text =
12891 Some(buffer.text_for_range(selection.range()).collect::<String>());
12892 }
12893
12894 if let Some(next_selection) = selections_iter.peek() {
12895 if next_selection.range().len() == selection.range().len() {
12896 let next_selected_text = buffer
12897 .text_for_range(next_selection.range())
12898 .collect::<String>();
12899 if Some(next_selected_text) != selected_text {
12900 same_text_selected = false;
12901 selected_text = None;
12902 }
12903 } else {
12904 same_text_selected = false;
12905 selected_text = None;
12906 }
12907 }
12908 }
12909 }
12910
12911 if only_carets {
12912 for selection in &mut selections {
12913 let word_range = movement::surrounding_word(
12914 &display_map,
12915 selection.start.to_display_point(&display_map),
12916 );
12917 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
12918 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
12919 selection.goal = SelectionGoal::None;
12920 selection.reversed = false;
12921 self.select_match_ranges(
12922 selection.start..selection.end,
12923 selection.reversed,
12924 action.replace_newest,
12925 Some(Autoscroll::newest()),
12926 window,
12927 cx,
12928 );
12929 }
12930 if selections.len() == 1 {
12931 let selection = selections
12932 .last()
12933 .expect("ensured that there's only one selection");
12934 let query = buffer
12935 .text_for_range(selection.start..selection.end)
12936 .collect::<String>();
12937 let is_empty = query.is_empty();
12938 let select_state = SelectNextState {
12939 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
12940 wordwise: true,
12941 done: is_empty,
12942 };
12943 self.select_prev_state = Some(select_state);
12944 } else {
12945 self.select_prev_state = None;
12946 }
12947 } else if let Some(selected_text) = selected_text {
12948 self.select_prev_state = Some(SelectNextState {
12949 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
12950 wordwise: false,
12951 done: false,
12952 });
12953 self.select_previous(action, window, cx)?;
12954 }
12955 }
12956 Ok(())
12957 }
12958
12959 pub fn find_next_match(
12960 &mut self,
12961 _: &FindNextMatch,
12962 window: &mut Window,
12963 cx: &mut Context<Self>,
12964 ) -> Result<()> {
12965 let selections = self.selections.disjoint_anchors();
12966 match selections.first() {
12967 Some(first) if selections.len() >= 2 => {
12968 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12969 s.select_ranges([first.range()]);
12970 });
12971 }
12972 _ => self.select_next(
12973 &SelectNext {
12974 replace_newest: true,
12975 },
12976 window,
12977 cx,
12978 )?,
12979 }
12980 Ok(())
12981 }
12982
12983 pub fn find_previous_match(
12984 &mut self,
12985 _: &FindPreviousMatch,
12986 window: &mut Window,
12987 cx: &mut Context<Self>,
12988 ) -> Result<()> {
12989 let selections = self.selections.disjoint_anchors();
12990 match selections.last() {
12991 Some(last) if selections.len() >= 2 => {
12992 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12993 s.select_ranges([last.range()]);
12994 });
12995 }
12996 _ => self.select_previous(
12997 &SelectPrevious {
12998 replace_newest: true,
12999 },
13000 window,
13001 cx,
13002 )?,
13003 }
13004 Ok(())
13005 }
13006
13007 pub fn toggle_comments(
13008 &mut self,
13009 action: &ToggleComments,
13010 window: &mut Window,
13011 cx: &mut Context<Self>,
13012 ) {
13013 if self.read_only(cx) {
13014 return;
13015 }
13016 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13017 let text_layout_details = &self.text_layout_details(window);
13018 self.transact(window, cx, |this, window, cx| {
13019 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13020 let mut edits = Vec::new();
13021 let mut selection_edit_ranges = Vec::new();
13022 let mut last_toggled_row = None;
13023 let snapshot = this.buffer.read(cx).read(cx);
13024 let empty_str: Arc<str> = Arc::default();
13025 let mut suffixes_inserted = Vec::new();
13026 let ignore_indent = action.ignore_indent;
13027
13028 fn comment_prefix_range(
13029 snapshot: &MultiBufferSnapshot,
13030 row: MultiBufferRow,
13031 comment_prefix: &str,
13032 comment_prefix_whitespace: &str,
13033 ignore_indent: bool,
13034 ) -> Range<Point> {
13035 let indent_size = if ignore_indent {
13036 0
13037 } else {
13038 snapshot.indent_size_for_line(row).len
13039 };
13040
13041 let start = Point::new(row.0, indent_size);
13042
13043 let mut line_bytes = snapshot
13044 .bytes_in_range(start..snapshot.max_point())
13045 .flatten()
13046 .copied();
13047
13048 // If this line currently begins with the line comment prefix, then record
13049 // the range containing the prefix.
13050 if line_bytes
13051 .by_ref()
13052 .take(comment_prefix.len())
13053 .eq(comment_prefix.bytes())
13054 {
13055 // Include any whitespace that matches the comment prefix.
13056 let matching_whitespace_len = line_bytes
13057 .zip(comment_prefix_whitespace.bytes())
13058 .take_while(|(a, b)| a == b)
13059 .count() as u32;
13060 let end = Point::new(
13061 start.row,
13062 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13063 );
13064 start..end
13065 } else {
13066 start..start
13067 }
13068 }
13069
13070 fn comment_suffix_range(
13071 snapshot: &MultiBufferSnapshot,
13072 row: MultiBufferRow,
13073 comment_suffix: &str,
13074 comment_suffix_has_leading_space: bool,
13075 ) -> Range<Point> {
13076 let end = Point::new(row.0, snapshot.line_len(row));
13077 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13078
13079 let mut line_end_bytes = snapshot
13080 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13081 .flatten()
13082 .copied();
13083
13084 let leading_space_len = if suffix_start_column > 0
13085 && line_end_bytes.next() == Some(b' ')
13086 && comment_suffix_has_leading_space
13087 {
13088 1
13089 } else {
13090 0
13091 };
13092
13093 // If this line currently begins with the line comment prefix, then record
13094 // the range containing the prefix.
13095 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13096 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13097 start..end
13098 } else {
13099 end..end
13100 }
13101 }
13102
13103 // TODO: Handle selections that cross excerpts
13104 for selection in &mut selections {
13105 let start_column = snapshot
13106 .indent_size_for_line(MultiBufferRow(selection.start.row))
13107 .len;
13108 let language = if let Some(language) =
13109 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13110 {
13111 language
13112 } else {
13113 continue;
13114 };
13115
13116 selection_edit_ranges.clear();
13117
13118 // If multiple selections contain a given row, avoid processing that
13119 // row more than once.
13120 let mut start_row = MultiBufferRow(selection.start.row);
13121 if last_toggled_row == Some(start_row) {
13122 start_row = start_row.next_row();
13123 }
13124 let end_row =
13125 if selection.end.row > selection.start.row && selection.end.column == 0 {
13126 MultiBufferRow(selection.end.row - 1)
13127 } else {
13128 MultiBufferRow(selection.end.row)
13129 };
13130 last_toggled_row = Some(end_row);
13131
13132 if start_row > end_row {
13133 continue;
13134 }
13135
13136 // If the language has line comments, toggle those.
13137 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13138
13139 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13140 if ignore_indent {
13141 full_comment_prefixes = full_comment_prefixes
13142 .into_iter()
13143 .map(|s| Arc::from(s.trim_end()))
13144 .collect();
13145 }
13146
13147 if !full_comment_prefixes.is_empty() {
13148 let first_prefix = full_comment_prefixes
13149 .first()
13150 .expect("prefixes is non-empty");
13151 let prefix_trimmed_lengths = full_comment_prefixes
13152 .iter()
13153 .map(|p| p.trim_end_matches(' ').len())
13154 .collect::<SmallVec<[usize; 4]>>();
13155
13156 let mut all_selection_lines_are_comments = true;
13157
13158 for row in start_row.0..=end_row.0 {
13159 let row = MultiBufferRow(row);
13160 if start_row < end_row && snapshot.is_line_blank(row) {
13161 continue;
13162 }
13163
13164 let prefix_range = full_comment_prefixes
13165 .iter()
13166 .zip(prefix_trimmed_lengths.iter().copied())
13167 .map(|(prefix, trimmed_prefix_len)| {
13168 comment_prefix_range(
13169 snapshot.deref(),
13170 row,
13171 &prefix[..trimmed_prefix_len],
13172 &prefix[trimmed_prefix_len..],
13173 ignore_indent,
13174 )
13175 })
13176 .max_by_key(|range| range.end.column - range.start.column)
13177 .expect("prefixes is non-empty");
13178
13179 if prefix_range.is_empty() {
13180 all_selection_lines_are_comments = false;
13181 }
13182
13183 selection_edit_ranges.push(prefix_range);
13184 }
13185
13186 if all_selection_lines_are_comments {
13187 edits.extend(
13188 selection_edit_ranges
13189 .iter()
13190 .cloned()
13191 .map(|range| (range, empty_str.clone())),
13192 );
13193 } else {
13194 let min_column = selection_edit_ranges
13195 .iter()
13196 .map(|range| range.start.column)
13197 .min()
13198 .unwrap_or(0);
13199 edits.extend(selection_edit_ranges.iter().map(|range| {
13200 let position = Point::new(range.start.row, min_column);
13201 (position..position, first_prefix.clone())
13202 }));
13203 }
13204 } else if let Some((full_comment_prefix, comment_suffix)) =
13205 language.block_comment_delimiters()
13206 {
13207 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13208 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13209 let prefix_range = comment_prefix_range(
13210 snapshot.deref(),
13211 start_row,
13212 comment_prefix,
13213 comment_prefix_whitespace,
13214 ignore_indent,
13215 );
13216 let suffix_range = comment_suffix_range(
13217 snapshot.deref(),
13218 end_row,
13219 comment_suffix.trim_start_matches(' '),
13220 comment_suffix.starts_with(' '),
13221 );
13222
13223 if prefix_range.is_empty() || suffix_range.is_empty() {
13224 edits.push((
13225 prefix_range.start..prefix_range.start,
13226 full_comment_prefix.clone(),
13227 ));
13228 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13229 suffixes_inserted.push((end_row, comment_suffix.len()));
13230 } else {
13231 edits.push((prefix_range, empty_str.clone()));
13232 edits.push((suffix_range, empty_str.clone()));
13233 }
13234 } else {
13235 continue;
13236 }
13237 }
13238
13239 drop(snapshot);
13240 this.buffer.update(cx, |buffer, cx| {
13241 buffer.edit(edits, None, cx);
13242 });
13243
13244 // Adjust selections so that they end before any comment suffixes that
13245 // were inserted.
13246 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13247 let mut selections = this.selections.all::<Point>(cx);
13248 let snapshot = this.buffer.read(cx).read(cx);
13249 for selection in &mut selections {
13250 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13251 match row.cmp(&MultiBufferRow(selection.end.row)) {
13252 Ordering::Less => {
13253 suffixes_inserted.next();
13254 continue;
13255 }
13256 Ordering::Greater => break,
13257 Ordering::Equal => {
13258 if selection.end.column == snapshot.line_len(row) {
13259 if selection.is_empty() {
13260 selection.start.column -= suffix_len as u32;
13261 }
13262 selection.end.column -= suffix_len as u32;
13263 }
13264 break;
13265 }
13266 }
13267 }
13268 }
13269
13270 drop(snapshot);
13271 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13272 s.select(selections)
13273 });
13274
13275 let selections = this.selections.all::<Point>(cx);
13276 let selections_on_single_row = selections.windows(2).all(|selections| {
13277 selections[0].start.row == selections[1].start.row
13278 && selections[0].end.row == selections[1].end.row
13279 && selections[0].start.row == selections[0].end.row
13280 });
13281 let selections_selecting = selections
13282 .iter()
13283 .any(|selection| selection.start != selection.end);
13284 let advance_downwards = action.advance_downwards
13285 && selections_on_single_row
13286 && !selections_selecting
13287 && !matches!(this.mode, EditorMode::SingleLine { .. });
13288
13289 if advance_downwards {
13290 let snapshot = this.buffer.read(cx).snapshot(cx);
13291
13292 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13293 s.move_cursors_with(|display_snapshot, display_point, _| {
13294 let mut point = display_point.to_point(display_snapshot);
13295 point.row += 1;
13296 point = snapshot.clip_point(point, Bias::Left);
13297 let display_point = point.to_display_point(display_snapshot);
13298 let goal = SelectionGoal::HorizontalPosition(
13299 display_snapshot
13300 .x_for_display_point(display_point, text_layout_details)
13301 .into(),
13302 );
13303 (display_point, goal)
13304 })
13305 });
13306 }
13307 });
13308 }
13309
13310 pub fn select_enclosing_symbol(
13311 &mut self,
13312 _: &SelectEnclosingSymbol,
13313 window: &mut Window,
13314 cx: &mut Context<Self>,
13315 ) {
13316 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13317
13318 let buffer = self.buffer.read(cx).snapshot(cx);
13319 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13320
13321 fn update_selection(
13322 selection: &Selection<usize>,
13323 buffer_snap: &MultiBufferSnapshot,
13324 ) -> Option<Selection<usize>> {
13325 let cursor = selection.head();
13326 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13327 for symbol in symbols.iter().rev() {
13328 let start = symbol.range.start.to_offset(buffer_snap);
13329 let end = symbol.range.end.to_offset(buffer_snap);
13330 let new_range = start..end;
13331 if start < selection.start || end > selection.end {
13332 return Some(Selection {
13333 id: selection.id,
13334 start: new_range.start,
13335 end: new_range.end,
13336 goal: SelectionGoal::None,
13337 reversed: selection.reversed,
13338 });
13339 }
13340 }
13341 None
13342 }
13343
13344 let mut selected_larger_symbol = false;
13345 let new_selections = old_selections
13346 .iter()
13347 .map(|selection| match update_selection(selection, &buffer) {
13348 Some(new_selection) => {
13349 if new_selection.range() != selection.range() {
13350 selected_larger_symbol = true;
13351 }
13352 new_selection
13353 }
13354 None => selection.clone(),
13355 })
13356 .collect::<Vec<_>>();
13357
13358 if selected_larger_symbol {
13359 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13360 s.select(new_selections);
13361 });
13362 }
13363 }
13364
13365 pub fn select_larger_syntax_node(
13366 &mut self,
13367 _: &SelectLargerSyntaxNode,
13368 window: &mut Window,
13369 cx: &mut Context<Self>,
13370 ) {
13371 let Some(visible_row_count) = self.visible_row_count() else {
13372 return;
13373 };
13374 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13375 if old_selections.is_empty() {
13376 return;
13377 }
13378
13379 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13380
13381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13382 let buffer = self.buffer.read(cx).snapshot(cx);
13383
13384 let mut selected_larger_node = false;
13385 let mut new_selections = old_selections
13386 .iter()
13387 .map(|selection| {
13388 let old_range = selection.start..selection.end;
13389
13390 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13391 // manually select word at selection
13392 if ["string_content", "inline"].contains(&node.kind()) {
13393 let word_range = {
13394 let display_point = buffer
13395 .offset_to_point(old_range.start)
13396 .to_display_point(&display_map);
13397 let Range { start, end } =
13398 movement::surrounding_word(&display_map, display_point);
13399 start.to_point(&display_map).to_offset(&buffer)
13400 ..end.to_point(&display_map).to_offset(&buffer)
13401 };
13402 // ignore if word is already selected
13403 if !word_range.is_empty() && old_range != word_range {
13404 let last_word_range = {
13405 let display_point = buffer
13406 .offset_to_point(old_range.end)
13407 .to_display_point(&display_map);
13408 let Range { start, end } =
13409 movement::surrounding_word(&display_map, display_point);
13410 start.to_point(&display_map).to_offset(&buffer)
13411 ..end.to_point(&display_map).to_offset(&buffer)
13412 };
13413 // only select word if start and end point belongs to same word
13414 if word_range == last_word_range {
13415 selected_larger_node = true;
13416 return Selection {
13417 id: selection.id,
13418 start: word_range.start,
13419 end: word_range.end,
13420 goal: SelectionGoal::None,
13421 reversed: selection.reversed,
13422 };
13423 }
13424 }
13425 }
13426 }
13427
13428 let mut new_range = old_range.clone();
13429 while let Some((_node, containing_range)) =
13430 buffer.syntax_ancestor(new_range.clone())
13431 {
13432 new_range = match containing_range {
13433 MultiOrSingleBufferOffsetRange::Single(_) => break,
13434 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13435 };
13436 if !display_map.intersects_fold(new_range.start)
13437 && !display_map.intersects_fold(new_range.end)
13438 {
13439 break;
13440 }
13441 }
13442
13443 selected_larger_node |= new_range != old_range;
13444 Selection {
13445 id: selection.id,
13446 start: new_range.start,
13447 end: new_range.end,
13448 goal: SelectionGoal::None,
13449 reversed: selection.reversed,
13450 }
13451 })
13452 .collect::<Vec<_>>();
13453
13454 if !selected_larger_node {
13455 return; // don't put this call in the history
13456 }
13457
13458 // scroll based on transformation done to the last selection created by the user
13459 let (last_old, last_new) = old_selections
13460 .last()
13461 .zip(new_selections.last().cloned())
13462 .expect("old_selections isn't empty");
13463
13464 // revert selection
13465 let is_selection_reversed = {
13466 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13467 new_selections.last_mut().expect("checked above").reversed =
13468 should_newest_selection_be_reversed;
13469 should_newest_selection_be_reversed
13470 };
13471
13472 if selected_larger_node {
13473 self.select_syntax_node_history.disable_clearing = true;
13474 self.change_selections(None, window, cx, |s| {
13475 s.select(new_selections.clone());
13476 });
13477 self.select_syntax_node_history.disable_clearing = false;
13478 }
13479
13480 let start_row = last_new.start.to_display_point(&display_map).row().0;
13481 let end_row = last_new.end.to_display_point(&display_map).row().0;
13482 let selection_height = end_row - start_row + 1;
13483 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13484
13485 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13486 let scroll_behavior = if fits_on_the_screen {
13487 self.request_autoscroll(Autoscroll::fit(), cx);
13488 SelectSyntaxNodeScrollBehavior::FitSelection
13489 } else if is_selection_reversed {
13490 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13491 SelectSyntaxNodeScrollBehavior::CursorTop
13492 } else {
13493 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13494 SelectSyntaxNodeScrollBehavior::CursorBottom
13495 };
13496
13497 self.select_syntax_node_history.push((
13498 old_selections,
13499 scroll_behavior,
13500 is_selection_reversed,
13501 ));
13502 }
13503
13504 pub fn select_smaller_syntax_node(
13505 &mut self,
13506 _: &SelectSmallerSyntaxNode,
13507 window: &mut Window,
13508 cx: &mut Context<Self>,
13509 ) {
13510 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13511
13512 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13513 self.select_syntax_node_history.pop()
13514 {
13515 if let Some(selection) = selections.last_mut() {
13516 selection.reversed = is_selection_reversed;
13517 }
13518
13519 self.select_syntax_node_history.disable_clearing = true;
13520 self.change_selections(None, window, cx, |s| {
13521 s.select(selections.to_vec());
13522 });
13523 self.select_syntax_node_history.disable_clearing = false;
13524
13525 match scroll_behavior {
13526 SelectSyntaxNodeScrollBehavior::CursorTop => {
13527 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13528 }
13529 SelectSyntaxNodeScrollBehavior::FitSelection => {
13530 self.request_autoscroll(Autoscroll::fit(), cx);
13531 }
13532 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13533 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13534 }
13535 }
13536 }
13537 }
13538
13539 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13540 if !EditorSettings::get_global(cx).gutter.runnables {
13541 self.clear_tasks();
13542 return Task::ready(());
13543 }
13544 let project = self.project.as_ref().map(Entity::downgrade);
13545 let task_sources = self.lsp_task_sources(cx);
13546 cx.spawn_in(window, async move |editor, cx| {
13547 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13548 let Some(project) = project.and_then(|p| p.upgrade()) else {
13549 return;
13550 };
13551 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13552 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13553 }) else {
13554 return;
13555 };
13556
13557 let hide_runnables = project
13558 .update(cx, |project, cx| {
13559 // Do not display any test indicators in non-dev server remote projects.
13560 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13561 })
13562 .unwrap_or(true);
13563 if hide_runnables {
13564 return;
13565 }
13566 let new_rows =
13567 cx.background_spawn({
13568 let snapshot = display_snapshot.clone();
13569 async move {
13570 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13571 }
13572 })
13573 .await;
13574 let Ok(lsp_tasks) =
13575 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13576 else {
13577 return;
13578 };
13579 let lsp_tasks = lsp_tasks.await;
13580
13581 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
13582 lsp_tasks
13583 .into_iter()
13584 .flat_map(|(kind, tasks)| {
13585 tasks.into_iter().filter_map(move |(location, task)| {
13586 Some((kind.clone(), location?, task))
13587 })
13588 })
13589 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
13590 let buffer = location.target.buffer;
13591 let buffer_snapshot = buffer.read(cx).snapshot();
13592 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
13593 |(excerpt_id, snapshot, _)| {
13594 if snapshot.remote_id() == buffer_snapshot.remote_id() {
13595 display_snapshot
13596 .buffer_snapshot
13597 .anchor_in_excerpt(excerpt_id, location.target.range.start)
13598 } else {
13599 None
13600 }
13601 },
13602 );
13603 if let Some(offset) = offset {
13604 let task_buffer_range =
13605 location.target.range.to_point(&buffer_snapshot);
13606 let context_buffer_range =
13607 task_buffer_range.to_offset(&buffer_snapshot);
13608 let context_range = BufferOffset(context_buffer_range.start)
13609 ..BufferOffset(context_buffer_range.end);
13610
13611 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
13612 .or_insert_with(|| RunnableTasks {
13613 templates: Vec::new(),
13614 offset,
13615 column: task_buffer_range.start.column,
13616 extra_variables: HashMap::default(),
13617 context_range,
13618 })
13619 .templates
13620 .push((kind, task.original_task().clone()));
13621 }
13622
13623 acc
13624 })
13625 }) else {
13626 return;
13627 };
13628
13629 let rows = Self::runnable_rows(project, display_snapshot, new_rows, cx.clone());
13630 editor
13631 .update(cx, |editor, _| {
13632 editor.clear_tasks();
13633 for (key, mut value) in rows {
13634 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
13635 value.templates.extend(lsp_tasks.templates);
13636 }
13637
13638 editor.insert_tasks(key, value);
13639 }
13640 for (key, value) in lsp_tasks_by_rows {
13641 editor.insert_tasks(key, value);
13642 }
13643 })
13644 .ok();
13645 })
13646 }
13647 fn fetch_runnable_ranges(
13648 snapshot: &DisplaySnapshot,
13649 range: Range<Anchor>,
13650 ) -> Vec<language::RunnableRange> {
13651 snapshot.buffer_snapshot.runnable_ranges(range).collect()
13652 }
13653
13654 fn runnable_rows(
13655 project: Entity<Project>,
13656 snapshot: DisplaySnapshot,
13657 runnable_ranges: Vec<RunnableRange>,
13658 mut cx: AsyncWindowContext,
13659 ) -> Vec<((BufferId, BufferRow), RunnableTasks)> {
13660 runnable_ranges
13661 .into_iter()
13662 .filter_map(|mut runnable| {
13663 let tasks = cx
13664 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
13665 .ok()?;
13666 if tasks.is_empty() {
13667 return None;
13668 }
13669
13670 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
13671
13672 let row = snapshot
13673 .buffer_snapshot
13674 .buffer_line_for_row(MultiBufferRow(point.row))?
13675 .1
13676 .start
13677 .row;
13678
13679 let context_range =
13680 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
13681 Some((
13682 (runnable.buffer_id, row),
13683 RunnableTasks {
13684 templates: tasks,
13685 offset: snapshot
13686 .buffer_snapshot
13687 .anchor_before(runnable.run_range.start),
13688 context_range,
13689 column: point.column,
13690 extra_variables: runnable.extra_captures,
13691 },
13692 ))
13693 })
13694 .collect()
13695 }
13696
13697 fn templates_with_tags(
13698 project: &Entity<Project>,
13699 runnable: &mut Runnable,
13700 cx: &mut App,
13701 ) -> Vec<(TaskSourceKind, TaskTemplate)> {
13702 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
13703 let (worktree_id, file) = project
13704 .buffer_for_id(runnable.buffer, cx)
13705 .and_then(|buffer| buffer.read(cx).file())
13706 .map(|file| (file.worktree_id(cx), file.clone()))
13707 .unzip();
13708
13709 (
13710 project.task_store().read(cx).task_inventory().cloned(),
13711 worktree_id,
13712 file,
13713 )
13714 });
13715
13716 let mut templates_with_tags = mem::take(&mut runnable.tags)
13717 .into_iter()
13718 .flat_map(|RunnableTag(tag)| {
13719 inventory
13720 .as_ref()
13721 .into_iter()
13722 .flat_map(|inventory| {
13723 inventory.read(cx).list_tasks(
13724 file.clone(),
13725 Some(runnable.language.clone()),
13726 worktree_id,
13727 cx,
13728 )
13729 })
13730 .filter(move |(_, template)| {
13731 template.tags.iter().any(|source_tag| source_tag == &tag)
13732 })
13733 })
13734 .sorted_by_key(|(kind, _)| kind.to_owned())
13735 .collect::<Vec<_>>();
13736 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
13737 // Strongest source wins; if we have worktree tag binding, prefer that to
13738 // global and language bindings;
13739 // if we have a global binding, prefer that to language binding.
13740 let first_mismatch = templates_with_tags
13741 .iter()
13742 .position(|(tag_source, _)| tag_source != leading_tag_source);
13743 if let Some(index) = first_mismatch {
13744 templates_with_tags.truncate(index);
13745 }
13746 }
13747
13748 templates_with_tags
13749 }
13750
13751 pub fn move_to_enclosing_bracket(
13752 &mut self,
13753 _: &MoveToEnclosingBracket,
13754 window: &mut Window,
13755 cx: &mut Context<Self>,
13756 ) {
13757 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13758 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13759 s.move_offsets_with(|snapshot, selection| {
13760 let Some(enclosing_bracket_ranges) =
13761 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
13762 else {
13763 return;
13764 };
13765
13766 let mut best_length = usize::MAX;
13767 let mut best_inside = false;
13768 let mut best_in_bracket_range = false;
13769 let mut best_destination = None;
13770 for (open, close) in enclosing_bracket_ranges {
13771 let close = close.to_inclusive();
13772 let length = close.end() - open.start;
13773 let inside = selection.start >= open.end && selection.end <= *close.start();
13774 let in_bracket_range = open.to_inclusive().contains(&selection.head())
13775 || close.contains(&selection.head());
13776
13777 // If best is next to a bracket and current isn't, skip
13778 if !in_bracket_range && best_in_bracket_range {
13779 continue;
13780 }
13781
13782 // Prefer smaller lengths unless best is inside and current isn't
13783 if length > best_length && (best_inside || !inside) {
13784 continue;
13785 }
13786
13787 best_length = length;
13788 best_inside = inside;
13789 best_in_bracket_range = in_bracket_range;
13790 best_destination = Some(
13791 if close.contains(&selection.start) && close.contains(&selection.end) {
13792 if inside { open.end } else { open.start }
13793 } else if inside {
13794 *close.start()
13795 } else {
13796 *close.end()
13797 },
13798 );
13799 }
13800
13801 if let Some(destination) = best_destination {
13802 selection.collapse_to(destination, SelectionGoal::None);
13803 }
13804 })
13805 });
13806 }
13807
13808 pub fn undo_selection(
13809 &mut self,
13810 _: &UndoSelection,
13811 window: &mut Window,
13812 cx: &mut Context<Self>,
13813 ) {
13814 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13815 self.end_selection(window, cx);
13816 self.selection_history.mode = SelectionHistoryMode::Undoing;
13817 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
13818 self.change_selections(None, window, cx, |s| {
13819 s.select_anchors(entry.selections.to_vec())
13820 });
13821 self.select_next_state = entry.select_next_state;
13822 self.select_prev_state = entry.select_prev_state;
13823 self.add_selections_state = entry.add_selections_state;
13824 self.request_autoscroll(Autoscroll::newest(), cx);
13825 }
13826 self.selection_history.mode = SelectionHistoryMode::Normal;
13827 }
13828
13829 pub fn redo_selection(
13830 &mut self,
13831 _: &RedoSelection,
13832 window: &mut Window,
13833 cx: &mut Context<Self>,
13834 ) {
13835 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13836 self.end_selection(window, cx);
13837 self.selection_history.mode = SelectionHistoryMode::Redoing;
13838 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
13839 self.change_selections(None, window, cx, |s| {
13840 s.select_anchors(entry.selections.to_vec())
13841 });
13842 self.select_next_state = entry.select_next_state;
13843 self.select_prev_state = entry.select_prev_state;
13844 self.add_selections_state = entry.add_selections_state;
13845 self.request_autoscroll(Autoscroll::newest(), cx);
13846 }
13847 self.selection_history.mode = SelectionHistoryMode::Normal;
13848 }
13849
13850 pub fn expand_excerpts(
13851 &mut self,
13852 action: &ExpandExcerpts,
13853 _: &mut Window,
13854 cx: &mut Context<Self>,
13855 ) {
13856 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
13857 }
13858
13859 pub fn expand_excerpts_down(
13860 &mut self,
13861 action: &ExpandExcerptsDown,
13862 _: &mut Window,
13863 cx: &mut Context<Self>,
13864 ) {
13865 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
13866 }
13867
13868 pub fn expand_excerpts_up(
13869 &mut self,
13870 action: &ExpandExcerptsUp,
13871 _: &mut Window,
13872 cx: &mut Context<Self>,
13873 ) {
13874 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
13875 }
13876
13877 pub fn expand_excerpts_for_direction(
13878 &mut self,
13879 lines: u32,
13880 direction: ExpandExcerptDirection,
13881
13882 cx: &mut Context<Self>,
13883 ) {
13884 let selections = self.selections.disjoint_anchors();
13885
13886 let lines = if lines == 0 {
13887 EditorSettings::get_global(cx).expand_excerpt_lines
13888 } else {
13889 lines
13890 };
13891
13892 self.buffer.update(cx, |buffer, cx| {
13893 let snapshot = buffer.snapshot(cx);
13894 let mut excerpt_ids = selections
13895 .iter()
13896 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
13897 .collect::<Vec<_>>();
13898 excerpt_ids.sort();
13899 excerpt_ids.dedup();
13900 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
13901 })
13902 }
13903
13904 pub fn expand_excerpt(
13905 &mut self,
13906 excerpt: ExcerptId,
13907 direction: ExpandExcerptDirection,
13908 window: &mut Window,
13909 cx: &mut Context<Self>,
13910 ) {
13911 let current_scroll_position = self.scroll_position(cx);
13912 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
13913 let mut should_scroll_up = false;
13914
13915 if direction == ExpandExcerptDirection::Down {
13916 let multi_buffer = self.buffer.read(cx);
13917 let snapshot = multi_buffer.snapshot(cx);
13918 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
13919 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
13920 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
13921 let buffer_snapshot = buffer.read(cx).snapshot();
13922 let excerpt_end_row =
13923 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
13924 let last_row = buffer_snapshot.max_point().row;
13925 let lines_below = last_row.saturating_sub(excerpt_end_row);
13926 should_scroll_up = lines_below >= lines_to_expand;
13927 }
13928 }
13929 }
13930 }
13931
13932 self.buffer.update(cx, |buffer, cx| {
13933 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
13934 });
13935
13936 if should_scroll_up {
13937 let new_scroll_position =
13938 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
13939 self.set_scroll_position(new_scroll_position, window, cx);
13940 }
13941 }
13942
13943 pub fn go_to_singleton_buffer_point(
13944 &mut self,
13945 point: Point,
13946 window: &mut Window,
13947 cx: &mut Context<Self>,
13948 ) {
13949 self.go_to_singleton_buffer_range(point..point, window, cx);
13950 }
13951
13952 pub fn go_to_singleton_buffer_range(
13953 &mut self,
13954 range: Range<Point>,
13955 window: &mut Window,
13956 cx: &mut Context<Self>,
13957 ) {
13958 let multibuffer = self.buffer().read(cx);
13959 let Some(buffer) = multibuffer.as_singleton() else {
13960 return;
13961 };
13962 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
13963 return;
13964 };
13965 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
13966 return;
13967 };
13968 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
13969 s.select_anchor_ranges([start..end])
13970 });
13971 }
13972
13973 pub fn go_to_diagnostic(
13974 &mut self,
13975 _: &GoToDiagnostic,
13976 window: &mut Window,
13977 cx: &mut Context<Self>,
13978 ) {
13979 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13980 self.go_to_diagnostic_impl(Direction::Next, window, cx)
13981 }
13982
13983 pub fn go_to_prev_diagnostic(
13984 &mut self,
13985 _: &GoToPreviousDiagnostic,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) {
13989 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13990 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
13991 }
13992
13993 pub fn go_to_diagnostic_impl(
13994 &mut self,
13995 direction: Direction,
13996 window: &mut Window,
13997 cx: &mut Context<Self>,
13998 ) {
13999 let buffer = self.buffer.read(cx).snapshot(cx);
14000 let selection = self.selections.newest::<usize>(cx);
14001
14002 let mut active_group_id = None;
14003 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14004 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14005 active_group_id = Some(active_group.group_id);
14006 }
14007 }
14008
14009 fn filtered(
14010 snapshot: EditorSnapshot,
14011 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14012 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14013 diagnostics
14014 .filter(|entry| entry.range.start != entry.range.end)
14015 .filter(|entry| !entry.diagnostic.is_unnecessary)
14016 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14017 }
14018
14019 let snapshot = self.snapshot(window, cx);
14020 let before = filtered(
14021 snapshot.clone(),
14022 buffer
14023 .diagnostics_in_range(0..selection.start)
14024 .filter(|entry| entry.range.start <= selection.start),
14025 );
14026 let after = filtered(
14027 snapshot,
14028 buffer
14029 .diagnostics_in_range(selection.start..buffer.len())
14030 .filter(|entry| entry.range.start >= selection.start),
14031 );
14032
14033 let mut found: Option<DiagnosticEntry<usize>> = None;
14034 if direction == Direction::Prev {
14035 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14036 {
14037 for diagnostic in prev_diagnostics.into_iter().rev() {
14038 if diagnostic.range.start != selection.start
14039 || active_group_id
14040 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14041 {
14042 found = Some(diagnostic);
14043 break 'outer;
14044 }
14045 }
14046 }
14047 } else {
14048 for diagnostic in after.chain(before) {
14049 if diagnostic.range.start != selection.start
14050 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14051 {
14052 found = Some(diagnostic);
14053 break;
14054 }
14055 }
14056 }
14057 let Some(next_diagnostic) = found else {
14058 return;
14059 };
14060
14061 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14062 return;
14063 };
14064 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14065 s.select_ranges(vec![
14066 next_diagnostic.range.start..next_diagnostic.range.start,
14067 ])
14068 });
14069 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14070 self.refresh_inline_completion(false, true, window, cx);
14071 }
14072
14073 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14074 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14075 let snapshot = self.snapshot(window, cx);
14076 let selection = self.selections.newest::<Point>(cx);
14077 self.go_to_hunk_before_or_after_position(
14078 &snapshot,
14079 selection.head(),
14080 Direction::Next,
14081 window,
14082 cx,
14083 );
14084 }
14085
14086 pub fn go_to_hunk_before_or_after_position(
14087 &mut self,
14088 snapshot: &EditorSnapshot,
14089 position: Point,
14090 direction: Direction,
14091 window: &mut Window,
14092 cx: &mut Context<Editor>,
14093 ) {
14094 let row = if direction == Direction::Next {
14095 self.hunk_after_position(snapshot, position)
14096 .map(|hunk| hunk.row_range.start)
14097 } else {
14098 self.hunk_before_position(snapshot, position)
14099 };
14100
14101 if let Some(row) = row {
14102 let destination = Point::new(row.0, 0);
14103 let autoscroll = Autoscroll::center();
14104
14105 self.unfold_ranges(&[destination..destination], false, false, cx);
14106 self.change_selections(Some(autoscroll), window, cx, |s| {
14107 s.select_ranges([destination..destination]);
14108 });
14109 }
14110 }
14111
14112 fn hunk_after_position(
14113 &mut self,
14114 snapshot: &EditorSnapshot,
14115 position: Point,
14116 ) -> Option<MultiBufferDiffHunk> {
14117 snapshot
14118 .buffer_snapshot
14119 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14120 .find(|hunk| hunk.row_range.start.0 > position.row)
14121 .or_else(|| {
14122 snapshot
14123 .buffer_snapshot
14124 .diff_hunks_in_range(Point::zero()..position)
14125 .find(|hunk| hunk.row_range.end.0 < position.row)
14126 })
14127 }
14128
14129 fn go_to_prev_hunk(
14130 &mut self,
14131 _: &GoToPreviousHunk,
14132 window: &mut Window,
14133 cx: &mut Context<Self>,
14134 ) {
14135 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14136 let snapshot = self.snapshot(window, cx);
14137 let selection = self.selections.newest::<Point>(cx);
14138 self.go_to_hunk_before_or_after_position(
14139 &snapshot,
14140 selection.head(),
14141 Direction::Prev,
14142 window,
14143 cx,
14144 );
14145 }
14146
14147 fn hunk_before_position(
14148 &mut self,
14149 snapshot: &EditorSnapshot,
14150 position: Point,
14151 ) -> Option<MultiBufferRow> {
14152 snapshot
14153 .buffer_snapshot
14154 .diff_hunk_before(position)
14155 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14156 }
14157
14158 fn go_to_next_change(
14159 &mut self,
14160 _: &GoToNextChange,
14161 window: &mut Window,
14162 cx: &mut Context<Self>,
14163 ) {
14164 if let Some(selections) = self
14165 .change_list
14166 .next_change(1, Direction::Next)
14167 .map(|s| s.to_vec())
14168 {
14169 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14170 let map = s.display_map();
14171 s.select_display_ranges(selections.iter().map(|a| {
14172 let point = a.to_display_point(&map);
14173 point..point
14174 }))
14175 })
14176 }
14177 }
14178
14179 fn go_to_previous_change(
14180 &mut self,
14181 _: &GoToPreviousChange,
14182 window: &mut Window,
14183 cx: &mut Context<Self>,
14184 ) {
14185 if let Some(selections) = self
14186 .change_list
14187 .next_change(1, Direction::Prev)
14188 .map(|s| s.to_vec())
14189 {
14190 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14191 let map = s.display_map();
14192 s.select_display_ranges(selections.iter().map(|a| {
14193 let point = a.to_display_point(&map);
14194 point..point
14195 }))
14196 })
14197 }
14198 }
14199
14200 fn go_to_line<T: 'static>(
14201 &mut self,
14202 position: Anchor,
14203 highlight_color: Option<Hsla>,
14204 window: &mut Window,
14205 cx: &mut Context<Self>,
14206 ) {
14207 let snapshot = self.snapshot(window, cx).display_snapshot;
14208 let position = position.to_point(&snapshot.buffer_snapshot);
14209 let start = snapshot
14210 .buffer_snapshot
14211 .clip_point(Point::new(position.row, 0), Bias::Left);
14212 let end = start + Point::new(1, 0);
14213 let start = snapshot.buffer_snapshot.anchor_before(start);
14214 let end = snapshot.buffer_snapshot.anchor_before(end);
14215
14216 self.highlight_rows::<T>(
14217 start..end,
14218 highlight_color
14219 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14220 Default::default(),
14221 cx,
14222 );
14223
14224 if self.buffer.read(cx).is_singleton() {
14225 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14226 }
14227 }
14228
14229 pub fn go_to_definition(
14230 &mut self,
14231 _: &GoToDefinition,
14232 window: &mut Window,
14233 cx: &mut Context<Self>,
14234 ) -> Task<Result<Navigated>> {
14235 let definition =
14236 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14237 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14238 cx.spawn_in(window, async move |editor, cx| {
14239 if definition.await? == Navigated::Yes {
14240 return Ok(Navigated::Yes);
14241 }
14242 match fallback_strategy {
14243 GoToDefinitionFallback::None => Ok(Navigated::No),
14244 GoToDefinitionFallback::FindAllReferences => {
14245 match editor.update_in(cx, |editor, window, cx| {
14246 editor.find_all_references(&FindAllReferences, window, cx)
14247 })? {
14248 Some(references) => references.await,
14249 None => Ok(Navigated::No),
14250 }
14251 }
14252 }
14253 })
14254 }
14255
14256 pub fn go_to_declaration(
14257 &mut self,
14258 _: &GoToDeclaration,
14259 window: &mut Window,
14260 cx: &mut Context<Self>,
14261 ) -> Task<Result<Navigated>> {
14262 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14263 }
14264
14265 pub fn go_to_declaration_split(
14266 &mut self,
14267 _: &GoToDeclaration,
14268 window: &mut Window,
14269 cx: &mut Context<Self>,
14270 ) -> Task<Result<Navigated>> {
14271 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14272 }
14273
14274 pub fn go_to_implementation(
14275 &mut self,
14276 _: &GoToImplementation,
14277 window: &mut Window,
14278 cx: &mut Context<Self>,
14279 ) -> Task<Result<Navigated>> {
14280 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14281 }
14282
14283 pub fn go_to_implementation_split(
14284 &mut self,
14285 _: &GoToImplementationSplit,
14286 window: &mut Window,
14287 cx: &mut Context<Self>,
14288 ) -> Task<Result<Navigated>> {
14289 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14290 }
14291
14292 pub fn go_to_type_definition(
14293 &mut self,
14294 _: &GoToTypeDefinition,
14295 window: &mut Window,
14296 cx: &mut Context<Self>,
14297 ) -> Task<Result<Navigated>> {
14298 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14299 }
14300
14301 pub fn go_to_definition_split(
14302 &mut self,
14303 _: &GoToDefinitionSplit,
14304 window: &mut Window,
14305 cx: &mut Context<Self>,
14306 ) -> Task<Result<Navigated>> {
14307 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14308 }
14309
14310 pub fn go_to_type_definition_split(
14311 &mut self,
14312 _: &GoToTypeDefinitionSplit,
14313 window: &mut Window,
14314 cx: &mut Context<Self>,
14315 ) -> Task<Result<Navigated>> {
14316 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14317 }
14318
14319 fn go_to_definition_of_kind(
14320 &mut self,
14321 kind: GotoDefinitionKind,
14322 split: bool,
14323 window: &mut Window,
14324 cx: &mut Context<Self>,
14325 ) -> Task<Result<Navigated>> {
14326 let Some(provider) = self.semantics_provider.clone() else {
14327 return Task::ready(Ok(Navigated::No));
14328 };
14329 let head = self.selections.newest::<usize>(cx).head();
14330 let buffer = self.buffer.read(cx);
14331 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14332 text_anchor
14333 } else {
14334 return Task::ready(Ok(Navigated::No));
14335 };
14336
14337 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14338 return Task::ready(Ok(Navigated::No));
14339 };
14340
14341 cx.spawn_in(window, async move |editor, cx| {
14342 let definitions = definitions.await?;
14343 let navigated = editor
14344 .update_in(cx, |editor, window, cx| {
14345 editor.navigate_to_hover_links(
14346 Some(kind),
14347 definitions
14348 .into_iter()
14349 .filter(|location| {
14350 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14351 })
14352 .map(HoverLink::Text)
14353 .collect::<Vec<_>>(),
14354 split,
14355 window,
14356 cx,
14357 )
14358 })?
14359 .await?;
14360 anyhow::Ok(navigated)
14361 })
14362 }
14363
14364 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14365 let selection = self.selections.newest_anchor();
14366 let head = selection.head();
14367 let tail = selection.tail();
14368
14369 let Some((buffer, start_position)) =
14370 self.buffer.read(cx).text_anchor_for_position(head, cx)
14371 else {
14372 return;
14373 };
14374
14375 let end_position = if head != tail {
14376 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14377 return;
14378 };
14379 Some(pos)
14380 } else {
14381 None
14382 };
14383
14384 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14385 let url = if let Some(end_pos) = end_position {
14386 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14387 } else {
14388 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14389 };
14390
14391 if let Some(url) = url {
14392 editor.update(cx, |_, cx| {
14393 cx.open_url(&url);
14394 })
14395 } else {
14396 Ok(())
14397 }
14398 });
14399
14400 url_finder.detach();
14401 }
14402
14403 pub fn open_selected_filename(
14404 &mut self,
14405 _: &OpenSelectedFilename,
14406 window: &mut Window,
14407 cx: &mut Context<Self>,
14408 ) {
14409 let Some(workspace) = self.workspace() else {
14410 return;
14411 };
14412
14413 let position = self.selections.newest_anchor().head();
14414
14415 let Some((buffer, buffer_position)) =
14416 self.buffer.read(cx).text_anchor_for_position(position, cx)
14417 else {
14418 return;
14419 };
14420
14421 let project = self.project.clone();
14422
14423 cx.spawn_in(window, async move |_, cx| {
14424 let result = find_file(&buffer, project, buffer_position, cx).await;
14425
14426 if let Some((_, path)) = result {
14427 workspace
14428 .update_in(cx, |workspace, window, cx| {
14429 workspace.open_resolved_path(path, window, cx)
14430 })?
14431 .await?;
14432 }
14433 anyhow::Ok(())
14434 })
14435 .detach();
14436 }
14437
14438 pub(crate) fn navigate_to_hover_links(
14439 &mut self,
14440 kind: Option<GotoDefinitionKind>,
14441 mut definitions: Vec<HoverLink>,
14442 split: bool,
14443 window: &mut Window,
14444 cx: &mut Context<Editor>,
14445 ) -> Task<Result<Navigated>> {
14446 // If there is one definition, just open it directly
14447 if definitions.len() == 1 {
14448 let definition = definitions.pop().unwrap();
14449
14450 enum TargetTaskResult {
14451 Location(Option<Location>),
14452 AlreadyNavigated,
14453 }
14454
14455 let target_task = match definition {
14456 HoverLink::Text(link) => {
14457 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14458 }
14459 HoverLink::InlayHint(lsp_location, server_id) => {
14460 let computation =
14461 self.compute_target_location(lsp_location, server_id, window, cx);
14462 cx.background_spawn(async move {
14463 let location = computation.await?;
14464 Ok(TargetTaskResult::Location(location))
14465 })
14466 }
14467 HoverLink::Url(url) => {
14468 cx.open_url(&url);
14469 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14470 }
14471 HoverLink::File(path) => {
14472 if let Some(workspace) = self.workspace() {
14473 cx.spawn_in(window, async move |_, cx| {
14474 workspace
14475 .update_in(cx, |workspace, window, cx| {
14476 workspace.open_resolved_path(path, window, cx)
14477 })?
14478 .await
14479 .map(|_| TargetTaskResult::AlreadyNavigated)
14480 })
14481 } else {
14482 Task::ready(Ok(TargetTaskResult::Location(None)))
14483 }
14484 }
14485 };
14486 cx.spawn_in(window, async move |editor, cx| {
14487 let target = match target_task.await.context("target resolution task")? {
14488 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14489 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14490 TargetTaskResult::Location(Some(target)) => target,
14491 };
14492
14493 editor.update_in(cx, |editor, window, cx| {
14494 let Some(workspace) = editor.workspace() else {
14495 return Navigated::No;
14496 };
14497 let pane = workspace.read(cx).active_pane().clone();
14498
14499 let range = target.range.to_point(target.buffer.read(cx));
14500 let range = editor.range_for_match(&range);
14501 let range = collapse_multiline_range(range);
14502
14503 if !split
14504 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14505 {
14506 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14507 } else {
14508 window.defer(cx, move |window, cx| {
14509 let target_editor: Entity<Self> =
14510 workspace.update(cx, |workspace, cx| {
14511 let pane = if split {
14512 workspace.adjacent_pane(window, cx)
14513 } else {
14514 workspace.active_pane().clone()
14515 };
14516
14517 workspace.open_project_item(
14518 pane,
14519 target.buffer.clone(),
14520 true,
14521 true,
14522 window,
14523 cx,
14524 )
14525 });
14526 target_editor.update(cx, |target_editor, cx| {
14527 // When selecting a definition in a different buffer, disable the nav history
14528 // to avoid creating a history entry at the previous cursor location.
14529 pane.update(cx, |pane, _| pane.disable_history());
14530 target_editor.go_to_singleton_buffer_range(range, window, cx);
14531 pane.update(cx, |pane, _| pane.enable_history());
14532 });
14533 });
14534 }
14535 Navigated::Yes
14536 })
14537 })
14538 } else if !definitions.is_empty() {
14539 cx.spawn_in(window, async move |editor, cx| {
14540 let (title, location_tasks, workspace) = editor
14541 .update_in(cx, |editor, window, cx| {
14542 let tab_kind = match kind {
14543 Some(GotoDefinitionKind::Implementation) => "Implementations",
14544 _ => "Definitions",
14545 };
14546 let title = definitions
14547 .iter()
14548 .find_map(|definition| match definition {
14549 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
14550 let buffer = origin.buffer.read(cx);
14551 format!(
14552 "{} for {}",
14553 tab_kind,
14554 buffer
14555 .text_for_range(origin.range.clone())
14556 .collect::<String>()
14557 )
14558 }),
14559 HoverLink::InlayHint(_, _) => None,
14560 HoverLink::Url(_) => None,
14561 HoverLink::File(_) => None,
14562 })
14563 .unwrap_or(tab_kind.to_string());
14564 let location_tasks = definitions
14565 .into_iter()
14566 .map(|definition| match definition {
14567 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
14568 HoverLink::InlayHint(lsp_location, server_id) => editor
14569 .compute_target_location(lsp_location, server_id, window, cx),
14570 HoverLink::Url(_) => Task::ready(Ok(None)),
14571 HoverLink::File(_) => Task::ready(Ok(None)),
14572 })
14573 .collect::<Vec<_>>();
14574 (title, location_tasks, editor.workspace().clone())
14575 })
14576 .context("location tasks preparation")?;
14577
14578 let locations = future::join_all(location_tasks)
14579 .await
14580 .into_iter()
14581 .filter_map(|location| location.transpose())
14582 .collect::<Result<_>>()
14583 .context("location tasks")?;
14584
14585 let Some(workspace) = workspace else {
14586 return Ok(Navigated::No);
14587 };
14588 let opened = workspace
14589 .update_in(cx, |workspace, window, cx| {
14590 Self::open_locations_in_multibuffer(
14591 workspace,
14592 locations,
14593 title,
14594 split,
14595 MultibufferSelectionMode::First,
14596 window,
14597 cx,
14598 )
14599 })
14600 .ok();
14601
14602 anyhow::Ok(Navigated::from_bool(opened.is_some()))
14603 })
14604 } else {
14605 Task::ready(Ok(Navigated::No))
14606 }
14607 }
14608
14609 fn compute_target_location(
14610 &self,
14611 lsp_location: lsp::Location,
14612 server_id: LanguageServerId,
14613 window: &mut Window,
14614 cx: &mut Context<Self>,
14615 ) -> Task<anyhow::Result<Option<Location>>> {
14616 let Some(project) = self.project.clone() else {
14617 return Task::ready(Ok(None));
14618 };
14619
14620 cx.spawn_in(window, async move |editor, cx| {
14621 let location_task = editor.update(cx, |_, cx| {
14622 project.update(cx, |project, cx| {
14623 let language_server_name = project
14624 .language_server_statuses(cx)
14625 .find(|(id, _)| server_id == *id)
14626 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
14627 language_server_name.map(|language_server_name| {
14628 project.open_local_buffer_via_lsp(
14629 lsp_location.uri.clone(),
14630 server_id,
14631 language_server_name,
14632 cx,
14633 )
14634 })
14635 })
14636 })?;
14637 let location = match location_task {
14638 Some(task) => Some({
14639 let target_buffer_handle = task.await.context("open local buffer")?;
14640 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
14641 let target_start = target_buffer
14642 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
14643 let target_end = target_buffer
14644 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
14645 target_buffer.anchor_after(target_start)
14646 ..target_buffer.anchor_before(target_end)
14647 })?;
14648 Location {
14649 buffer: target_buffer_handle,
14650 range,
14651 }
14652 }),
14653 None => None,
14654 };
14655 Ok(location)
14656 })
14657 }
14658
14659 pub fn find_all_references(
14660 &mut self,
14661 _: &FindAllReferences,
14662 window: &mut Window,
14663 cx: &mut Context<Self>,
14664 ) -> Option<Task<Result<Navigated>>> {
14665 let selection = self.selections.newest::<usize>(cx);
14666 let multi_buffer = self.buffer.read(cx);
14667 let head = selection.head();
14668
14669 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
14670 let head_anchor = multi_buffer_snapshot.anchor_at(
14671 head,
14672 if head < selection.tail() {
14673 Bias::Right
14674 } else {
14675 Bias::Left
14676 },
14677 );
14678
14679 match self
14680 .find_all_references_task_sources
14681 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14682 {
14683 Ok(_) => {
14684 log::info!(
14685 "Ignoring repeated FindAllReferences invocation with the position of already running task"
14686 );
14687 return None;
14688 }
14689 Err(i) => {
14690 self.find_all_references_task_sources.insert(i, head_anchor);
14691 }
14692 }
14693
14694 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
14695 let workspace = self.workspace()?;
14696 let project = workspace.read(cx).project().clone();
14697 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
14698 Some(cx.spawn_in(window, async move |editor, cx| {
14699 let _cleanup = cx.on_drop(&editor, move |editor, _| {
14700 if let Ok(i) = editor
14701 .find_all_references_task_sources
14702 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
14703 {
14704 editor.find_all_references_task_sources.remove(i);
14705 }
14706 });
14707
14708 let locations = references.await?;
14709 if locations.is_empty() {
14710 return anyhow::Ok(Navigated::No);
14711 }
14712
14713 workspace.update_in(cx, |workspace, window, cx| {
14714 let title = locations
14715 .first()
14716 .as_ref()
14717 .map(|location| {
14718 let buffer = location.buffer.read(cx);
14719 format!(
14720 "References to `{}`",
14721 buffer
14722 .text_for_range(location.range.clone())
14723 .collect::<String>()
14724 )
14725 })
14726 .unwrap();
14727 Self::open_locations_in_multibuffer(
14728 workspace,
14729 locations,
14730 title,
14731 false,
14732 MultibufferSelectionMode::First,
14733 window,
14734 cx,
14735 );
14736 Navigated::Yes
14737 })
14738 }))
14739 }
14740
14741 /// Opens a multibuffer with the given project locations in it
14742 pub fn open_locations_in_multibuffer(
14743 workspace: &mut Workspace,
14744 mut locations: Vec<Location>,
14745 title: String,
14746 split: bool,
14747 multibuffer_selection_mode: MultibufferSelectionMode,
14748 window: &mut Window,
14749 cx: &mut Context<Workspace>,
14750 ) {
14751 // If there are multiple definitions, open them in a multibuffer
14752 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
14753 let mut locations = locations.into_iter().peekable();
14754 let mut ranges: Vec<Range<Anchor>> = Vec::new();
14755 let capability = workspace.project().read(cx).capability();
14756
14757 let excerpt_buffer = cx.new(|cx| {
14758 let mut multibuffer = MultiBuffer::new(capability);
14759 while let Some(location) = locations.next() {
14760 let buffer = location.buffer.read(cx);
14761 let mut ranges_for_buffer = Vec::new();
14762 let range = location.range.to_point(buffer);
14763 ranges_for_buffer.push(range.clone());
14764
14765 while let Some(next_location) = locations.peek() {
14766 if next_location.buffer == location.buffer {
14767 ranges_for_buffer.push(next_location.range.to_point(buffer));
14768 locations.next();
14769 } else {
14770 break;
14771 }
14772 }
14773
14774 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
14775 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
14776 PathKey::for_buffer(&location.buffer, cx),
14777 location.buffer.clone(),
14778 ranges_for_buffer,
14779 DEFAULT_MULTIBUFFER_CONTEXT,
14780 cx,
14781 );
14782 ranges.extend(new_ranges)
14783 }
14784
14785 multibuffer.with_title(title)
14786 });
14787
14788 let editor = cx.new(|cx| {
14789 Editor::for_multibuffer(
14790 excerpt_buffer,
14791 Some(workspace.project().clone()),
14792 window,
14793 cx,
14794 )
14795 });
14796 editor.update(cx, |editor, cx| {
14797 match multibuffer_selection_mode {
14798 MultibufferSelectionMode::First => {
14799 if let Some(first_range) = ranges.first() {
14800 editor.change_selections(None, window, cx, |selections| {
14801 selections.clear_disjoint();
14802 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
14803 });
14804 }
14805 editor.highlight_background::<Self>(
14806 &ranges,
14807 |theme| theme.editor_highlighted_line_background,
14808 cx,
14809 );
14810 }
14811 MultibufferSelectionMode::All => {
14812 editor.change_selections(None, window, cx, |selections| {
14813 selections.clear_disjoint();
14814 selections.select_anchor_ranges(ranges);
14815 });
14816 }
14817 }
14818 editor.register_buffers_with_language_servers(cx);
14819 });
14820
14821 let item = Box::new(editor);
14822 let item_id = item.item_id();
14823
14824 if split {
14825 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
14826 } else {
14827 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
14828 let (preview_item_id, preview_item_idx) =
14829 workspace.active_pane().read_with(cx, |pane, _| {
14830 (pane.preview_item_id(), pane.preview_item_idx())
14831 });
14832
14833 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
14834
14835 if let Some(preview_item_id) = preview_item_id {
14836 workspace.active_pane().update(cx, |pane, cx| {
14837 pane.remove_item(preview_item_id, false, false, window, cx);
14838 });
14839 }
14840 } else {
14841 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
14842 }
14843 }
14844 workspace.active_pane().update(cx, |pane, cx| {
14845 pane.set_preview_item_id(Some(item_id), cx);
14846 });
14847 }
14848
14849 pub fn rename(
14850 &mut self,
14851 _: &Rename,
14852 window: &mut Window,
14853 cx: &mut Context<Self>,
14854 ) -> Option<Task<Result<()>>> {
14855 use language::ToOffset as _;
14856
14857 let provider = self.semantics_provider.clone()?;
14858 let selection = self.selections.newest_anchor().clone();
14859 let (cursor_buffer, cursor_buffer_position) = self
14860 .buffer
14861 .read(cx)
14862 .text_anchor_for_position(selection.head(), cx)?;
14863 let (tail_buffer, cursor_buffer_position_end) = self
14864 .buffer
14865 .read(cx)
14866 .text_anchor_for_position(selection.tail(), cx)?;
14867 if tail_buffer != cursor_buffer {
14868 return None;
14869 }
14870
14871 let snapshot = cursor_buffer.read(cx).snapshot();
14872 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
14873 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
14874 let prepare_rename = provider
14875 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
14876 .unwrap_or_else(|| Task::ready(Ok(None)));
14877 drop(snapshot);
14878
14879 Some(cx.spawn_in(window, async move |this, cx| {
14880 let rename_range = if let Some(range) = prepare_rename.await? {
14881 Some(range)
14882 } else {
14883 this.update(cx, |this, cx| {
14884 let buffer = this.buffer.read(cx).snapshot(cx);
14885 let mut buffer_highlights = this
14886 .document_highlights_for_position(selection.head(), &buffer)
14887 .filter(|highlight| {
14888 highlight.start.excerpt_id == selection.head().excerpt_id
14889 && highlight.end.excerpt_id == selection.head().excerpt_id
14890 });
14891 buffer_highlights
14892 .next()
14893 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
14894 })?
14895 };
14896 if let Some(rename_range) = rename_range {
14897 this.update_in(cx, |this, window, cx| {
14898 let snapshot = cursor_buffer.read(cx).snapshot();
14899 let rename_buffer_range = rename_range.to_offset(&snapshot);
14900 let cursor_offset_in_rename_range =
14901 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
14902 let cursor_offset_in_rename_range_end =
14903 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
14904
14905 this.take_rename(false, window, cx);
14906 let buffer = this.buffer.read(cx).read(cx);
14907 let cursor_offset = selection.head().to_offset(&buffer);
14908 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
14909 let rename_end = rename_start + rename_buffer_range.len();
14910 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
14911 let mut old_highlight_id = None;
14912 let old_name: Arc<str> = buffer
14913 .chunks(rename_start..rename_end, true)
14914 .map(|chunk| {
14915 if old_highlight_id.is_none() {
14916 old_highlight_id = chunk.syntax_highlight_id;
14917 }
14918 chunk.text
14919 })
14920 .collect::<String>()
14921 .into();
14922
14923 drop(buffer);
14924
14925 // Position the selection in the rename editor so that it matches the current selection.
14926 this.show_local_selections = false;
14927 let rename_editor = cx.new(|cx| {
14928 let mut editor = Editor::single_line(window, cx);
14929 editor.buffer.update(cx, |buffer, cx| {
14930 buffer.edit([(0..0, old_name.clone())], None, cx)
14931 });
14932 let rename_selection_range = match cursor_offset_in_rename_range
14933 .cmp(&cursor_offset_in_rename_range_end)
14934 {
14935 Ordering::Equal => {
14936 editor.select_all(&SelectAll, window, cx);
14937 return editor;
14938 }
14939 Ordering::Less => {
14940 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
14941 }
14942 Ordering::Greater => {
14943 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
14944 }
14945 };
14946 if rename_selection_range.end > old_name.len() {
14947 editor.select_all(&SelectAll, window, cx);
14948 } else {
14949 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14950 s.select_ranges([rename_selection_range]);
14951 });
14952 }
14953 editor
14954 });
14955 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
14956 if e == &EditorEvent::Focused {
14957 cx.emit(EditorEvent::FocusedIn)
14958 }
14959 })
14960 .detach();
14961
14962 let write_highlights =
14963 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
14964 let read_highlights =
14965 this.clear_background_highlights::<DocumentHighlightRead>(cx);
14966 let ranges = write_highlights
14967 .iter()
14968 .flat_map(|(_, ranges)| ranges.iter())
14969 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
14970 .cloned()
14971 .collect();
14972
14973 this.highlight_text::<Rename>(
14974 ranges,
14975 HighlightStyle {
14976 fade_out: Some(0.6),
14977 ..Default::default()
14978 },
14979 cx,
14980 );
14981 let rename_focus_handle = rename_editor.focus_handle(cx);
14982 window.focus(&rename_focus_handle);
14983 let block_id = this.insert_blocks(
14984 [BlockProperties {
14985 style: BlockStyle::Flex,
14986 placement: BlockPlacement::Below(range.start),
14987 height: Some(1),
14988 render: Arc::new({
14989 let rename_editor = rename_editor.clone();
14990 move |cx: &mut BlockContext| {
14991 let mut text_style = cx.editor_style.text.clone();
14992 if let Some(highlight_style) = old_highlight_id
14993 .and_then(|h| h.style(&cx.editor_style.syntax))
14994 {
14995 text_style = text_style.highlight(highlight_style);
14996 }
14997 div()
14998 .block_mouse_down()
14999 .pl(cx.anchor_x)
15000 .child(EditorElement::new(
15001 &rename_editor,
15002 EditorStyle {
15003 background: cx.theme().system().transparent,
15004 local_player: cx.editor_style.local_player,
15005 text: text_style,
15006 scrollbar_width: cx.editor_style.scrollbar_width,
15007 syntax: cx.editor_style.syntax.clone(),
15008 status: cx.editor_style.status.clone(),
15009 inlay_hints_style: HighlightStyle {
15010 font_weight: Some(FontWeight::BOLD),
15011 ..make_inlay_hints_style(cx.app)
15012 },
15013 inline_completion_styles: make_suggestion_styles(
15014 cx.app,
15015 ),
15016 ..EditorStyle::default()
15017 },
15018 ))
15019 .into_any_element()
15020 }
15021 }),
15022 priority: 0,
15023 render_in_minimap: true,
15024 }],
15025 Some(Autoscroll::fit()),
15026 cx,
15027 )[0];
15028 this.pending_rename = Some(RenameState {
15029 range,
15030 old_name,
15031 editor: rename_editor,
15032 block_id,
15033 });
15034 })?;
15035 }
15036
15037 Ok(())
15038 }))
15039 }
15040
15041 pub fn confirm_rename(
15042 &mut self,
15043 _: &ConfirmRename,
15044 window: &mut Window,
15045 cx: &mut Context<Self>,
15046 ) -> Option<Task<Result<()>>> {
15047 let rename = self.take_rename(false, window, cx)?;
15048 let workspace = self.workspace()?.downgrade();
15049 let (buffer, start) = self
15050 .buffer
15051 .read(cx)
15052 .text_anchor_for_position(rename.range.start, cx)?;
15053 let (end_buffer, _) = self
15054 .buffer
15055 .read(cx)
15056 .text_anchor_for_position(rename.range.end, cx)?;
15057 if buffer != end_buffer {
15058 return None;
15059 }
15060
15061 let old_name = rename.old_name;
15062 let new_name = rename.editor.read(cx).text(cx);
15063
15064 let rename = self.semantics_provider.as_ref()?.perform_rename(
15065 &buffer,
15066 start,
15067 new_name.clone(),
15068 cx,
15069 )?;
15070
15071 Some(cx.spawn_in(window, async move |editor, cx| {
15072 let project_transaction = rename.await?;
15073 Self::open_project_transaction(
15074 &editor,
15075 workspace,
15076 project_transaction,
15077 format!("Rename: {} → {}", old_name, new_name),
15078 cx,
15079 )
15080 .await?;
15081
15082 editor.update(cx, |editor, cx| {
15083 editor.refresh_document_highlights(cx);
15084 })?;
15085 Ok(())
15086 }))
15087 }
15088
15089 fn take_rename(
15090 &mut self,
15091 moving_cursor: bool,
15092 window: &mut Window,
15093 cx: &mut Context<Self>,
15094 ) -> Option<RenameState> {
15095 let rename = self.pending_rename.take()?;
15096 if rename.editor.focus_handle(cx).is_focused(window) {
15097 window.focus(&self.focus_handle);
15098 }
15099
15100 self.remove_blocks(
15101 [rename.block_id].into_iter().collect(),
15102 Some(Autoscroll::fit()),
15103 cx,
15104 );
15105 self.clear_highlights::<Rename>(cx);
15106 self.show_local_selections = true;
15107
15108 if moving_cursor {
15109 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15110 editor.selections.newest::<usize>(cx).head()
15111 });
15112
15113 // Update the selection to match the position of the selection inside
15114 // the rename editor.
15115 let snapshot = self.buffer.read(cx).read(cx);
15116 let rename_range = rename.range.to_offset(&snapshot);
15117 let cursor_in_editor = snapshot
15118 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15119 .min(rename_range.end);
15120 drop(snapshot);
15121
15122 self.change_selections(None, window, cx, |s| {
15123 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15124 });
15125 } else {
15126 self.refresh_document_highlights(cx);
15127 }
15128
15129 Some(rename)
15130 }
15131
15132 pub fn pending_rename(&self) -> Option<&RenameState> {
15133 self.pending_rename.as_ref()
15134 }
15135
15136 fn format(
15137 &mut self,
15138 _: &Format,
15139 window: &mut Window,
15140 cx: &mut Context<Self>,
15141 ) -> Option<Task<Result<()>>> {
15142 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15143
15144 let project = match &self.project {
15145 Some(project) => project.clone(),
15146 None => return None,
15147 };
15148
15149 Some(self.perform_format(
15150 project,
15151 FormatTrigger::Manual,
15152 FormatTarget::Buffers,
15153 window,
15154 cx,
15155 ))
15156 }
15157
15158 fn format_selections(
15159 &mut self,
15160 _: &FormatSelections,
15161 window: &mut Window,
15162 cx: &mut Context<Self>,
15163 ) -> Option<Task<Result<()>>> {
15164 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15165
15166 let project = match &self.project {
15167 Some(project) => project.clone(),
15168 None => return None,
15169 };
15170
15171 let ranges = self
15172 .selections
15173 .all_adjusted(cx)
15174 .into_iter()
15175 .map(|selection| selection.range())
15176 .collect_vec();
15177
15178 Some(self.perform_format(
15179 project,
15180 FormatTrigger::Manual,
15181 FormatTarget::Ranges(ranges),
15182 window,
15183 cx,
15184 ))
15185 }
15186
15187 fn perform_format(
15188 &mut self,
15189 project: Entity<Project>,
15190 trigger: FormatTrigger,
15191 target: FormatTarget,
15192 window: &mut Window,
15193 cx: &mut Context<Self>,
15194 ) -> Task<Result<()>> {
15195 let buffer = self.buffer.clone();
15196 let (buffers, target) = match target {
15197 FormatTarget::Buffers => {
15198 let mut buffers = buffer.read(cx).all_buffers();
15199 if trigger == FormatTrigger::Save {
15200 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15201 }
15202 (buffers, LspFormatTarget::Buffers)
15203 }
15204 FormatTarget::Ranges(selection_ranges) => {
15205 let multi_buffer = buffer.read(cx);
15206 let snapshot = multi_buffer.read(cx);
15207 let mut buffers = HashSet::default();
15208 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15209 BTreeMap::new();
15210 for selection_range in selection_ranges {
15211 for (buffer, buffer_range, _) in
15212 snapshot.range_to_buffer_ranges(selection_range)
15213 {
15214 let buffer_id = buffer.remote_id();
15215 let start = buffer.anchor_before(buffer_range.start);
15216 let end = buffer.anchor_after(buffer_range.end);
15217 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15218 buffer_id_to_ranges
15219 .entry(buffer_id)
15220 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15221 .or_insert_with(|| vec![start..end]);
15222 }
15223 }
15224 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15225 }
15226 };
15227
15228 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15229 let selections_prev = transaction_id_prev
15230 .and_then(|transaction_id_prev| {
15231 // default to selections as they were after the last edit, if we have them,
15232 // instead of how they are now.
15233 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15234 // will take you back to where you made the last edit, instead of staying where you scrolled
15235 self.selection_history
15236 .transaction(transaction_id_prev)
15237 .map(|t| t.0.clone())
15238 })
15239 .unwrap_or_else(|| {
15240 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15241 self.selections.disjoint_anchors()
15242 });
15243
15244 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15245 let format = project.update(cx, |project, cx| {
15246 project.format(buffers, target, true, trigger, cx)
15247 });
15248
15249 cx.spawn_in(window, async move |editor, cx| {
15250 let transaction = futures::select_biased! {
15251 transaction = format.log_err().fuse() => transaction,
15252 () = timeout => {
15253 log::warn!("timed out waiting for formatting");
15254 None
15255 }
15256 };
15257
15258 buffer
15259 .update(cx, |buffer, cx| {
15260 if let Some(transaction) = transaction {
15261 if !buffer.is_singleton() {
15262 buffer.push_transaction(&transaction.0, cx);
15263 }
15264 }
15265 cx.notify();
15266 })
15267 .ok();
15268
15269 if let Some(transaction_id_now) =
15270 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15271 {
15272 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15273 if has_new_transaction {
15274 _ = editor.update(cx, |editor, _| {
15275 editor
15276 .selection_history
15277 .insert_transaction(transaction_id_now, selections_prev);
15278 });
15279 }
15280 }
15281
15282 Ok(())
15283 })
15284 }
15285
15286 fn organize_imports(
15287 &mut self,
15288 _: &OrganizeImports,
15289 window: &mut Window,
15290 cx: &mut Context<Self>,
15291 ) -> Option<Task<Result<()>>> {
15292 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15293 let project = match &self.project {
15294 Some(project) => project.clone(),
15295 None => return None,
15296 };
15297 Some(self.perform_code_action_kind(
15298 project,
15299 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15300 window,
15301 cx,
15302 ))
15303 }
15304
15305 fn perform_code_action_kind(
15306 &mut self,
15307 project: Entity<Project>,
15308 kind: CodeActionKind,
15309 window: &mut Window,
15310 cx: &mut Context<Self>,
15311 ) -> Task<Result<()>> {
15312 let buffer = self.buffer.clone();
15313 let buffers = buffer.read(cx).all_buffers();
15314 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15315 let apply_action = project.update(cx, |project, cx| {
15316 project.apply_code_action_kind(buffers, kind, true, cx)
15317 });
15318 cx.spawn_in(window, async move |_, cx| {
15319 let transaction = futures::select_biased! {
15320 () = timeout => {
15321 log::warn!("timed out waiting for executing code action");
15322 None
15323 }
15324 transaction = apply_action.log_err().fuse() => transaction,
15325 };
15326 buffer
15327 .update(cx, |buffer, cx| {
15328 // check if we need this
15329 if let Some(transaction) = transaction {
15330 if !buffer.is_singleton() {
15331 buffer.push_transaction(&transaction.0, cx);
15332 }
15333 }
15334 cx.notify();
15335 })
15336 .ok();
15337 Ok(())
15338 })
15339 }
15340
15341 fn restart_language_server(
15342 &mut self,
15343 _: &RestartLanguageServer,
15344 _: &mut Window,
15345 cx: &mut Context<Self>,
15346 ) {
15347 if let Some(project) = self.project.clone() {
15348 self.buffer.update(cx, |multi_buffer, cx| {
15349 project.update(cx, |project, cx| {
15350 project.restart_language_servers_for_buffers(
15351 multi_buffer.all_buffers().into_iter().collect(),
15352 cx,
15353 );
15354 });
15355 })
15356 }
15357 }
15358
15359 fn stop_language_server(
15360 &mut self,
15361 _: &StopLanguageServer,
15362 _: &mut Window,
15363 cx: &mut Context<Self>,
15364 ) {
15365 if let Some(project) = self.project.clone() {
15366 self.buffer.update(cx, |multi_buffer, cx| {
15367 project.update(cx, |project, cx| {
15368 project.stop_language_servers_for_buffers(
15369 multi_buffer.all_buffers().into_iter().collect(),
15370 cx,
15371 );
15372 cx.emit(project::Event::RefreshInlayHints);
15373 });
15374 });
15375 }
15376 }
15377
15378 fn cancel_language_server_work(
15379 workspace: &mut Workspace,
15380 _: &actions::CancelLanguageServerWork,
15381 _: &mut Window,
15382 cx: &mut Context<Workspace>,
15383 ) {
15384 let project = workspace.project();
15385 let buffers = workspace
15386 .active_item(cx)
15387 .and_then(|item| item.act_as::<Editor>(cx))
15388 .map_or(HashSet::default(), |editor| {
15389 editor.read(cx).buffer.read(cx).all_buffers()
15390 });
15391 project.update(cx, |project, cx| {
15392 project.cancel_language_server_work_for_buffers(buffers, cx);
15393 });
15394 }
15395
15396 fn show_character_palette(
15397 &mut self,
15398 _: &ShowCharacterPalette,
15399 window: &mut Window,
15400 _: &mut Context<Self>,
15401 ) {
15402 window.show_character_palette();
15403 }
15404
15405 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15406 if self.mode.is_minimap() {
15407 return;
15408 }
15409
15410 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15411 let buffer = self.buffer.read(cx).snapshot(cx);
15412 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15413 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15414 let is_valid = buffer
15415 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15416 .any(|entry| {
15417 entry.diagnostic.is_primary
15418 && !entry.range.is_empty()
15419 && entry.range.start == primary_range_start
15420 && entry.diagnostic.message == active_diagnostics.active_message
15421 });
15422
15423 if !is_valid {
15424 self.dismiss_diagnostics(cx);
15425 }
15426 }
15427 }
15428
15429 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15430 match &self.active_diagnostics {
15431 ActiveDiagnostic::Group(group) => Some(group),
15432 _ => None,
15433 }
15434 }
15435
15436 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15437 self.dismiss_diagnostics(cx);
15438 self.active_diagnostics = ActiveDiagnostic::All;
15439 }
15440
15441 fn activate_diagnostics(
15442 &mut self,
15443 buffer_id: BufferId,
15444 diagnostic: DiagnosticEntry<usize>,
15445 window: &mut Window,
15446 cx: &mut Context<Self>,
15447 ) {
15448 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15449 return;
15450 }
15451 self.dismiss_diagnostics(cx);
15452 let snapshot = self.snapshot(window, cx);
15453 let buffer = self.buffer.read(cx).snapshot(cx);
15454 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15455 return;
15456 };
15457
15458 let diagnostic_group = buffer
15459 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15460 .collect::<Vec<_>>();
15461
15462 let blocks =
15463 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15464
15465 let blocks = self.display_map.update(cx, |display_map, cx| {
15466 display_map.insert_blocks(blocks, cx).into_iter().collect()
15467 });
15468 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15469 active_range: buffer.anchor_before(diagnostic.range.start)
15470 ..buffer.anchor_after(diagnostic.range.end),
15471 active_message: diagnostic.diagnostic.message.clone(),
15472 group_id: diagnostic.diagnostic.group_id,
15473 blocks,
15474 });
15475 cx.notify();
15476 }
15477
15478 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15479 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15480 return;
15481 };
15482
15483 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15484 if let ActiveDiagnostic::Group(group) = prev {
15485 self.display_map.update(cx, |display_map, cx| {
15486 display_map.remove_blocks(group.blocks, cx);
15487 });
15488 cx.notify();
15489 }
15490 }
15491
15492 /// Disable inline diagnostics rendering for this editor.
15493 pub fn disable_inline_diagnostics(&mut self) {
15494 self.inline_diagnostics_enabled = false;
15495 self.inline_diagnostics_update = Task::ready(());
15496 self.inline_diagnostics.clear();
15497 }
15498
15499 pub fn diagnostics_enabled(&self) -> bool {
15500 self.mode.is_full()
15501 }
15502
15503 pub fn inline_diagnostics_enabled(&self) -> bool {
15504 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15505 }
15506
15507 pub fn show_inline_diagnostics(&self) -> bool {
15508 self.show_inline_diagnostics
15509 }
15510
15511 pub fn toggle_inline_diagnostics(
15512 &mut self,
15513 _: &ToggleInlineDiagnostics,
15514 window: &mut Window,
15515 cx: &mut Context<Editor>,
15516 ) {
15517 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15518 self.refresh_inline_diagnostics(false, window, cx);
15519 }
15520
15521 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15522 self.diagnostics_max_severity = severity;
15523 self.display_map.update(cx, |display_map, _| {
15524 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15525 });
15526 }
15527
15528 pub fn toggle_diagnostics(
15529 &mut self,
15530 _: &ToggleDiagnostics,
15531 window: &mut Window,
15532 cx: &mut Context<Editor>,
15533 ) {
15534 if !self.diagnostics_enabled() {
15535 return;
15536 }
15537
15538 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15539 EditorSettings::get_global(cx)
15540 .diagnostics_max_severity
15541 .filter(|severity| severity != &DiagnosticSeverity::Off)
15542 .unwrap_or(DiagnosticSeverity::Hint)
15543 } else {
15544 DiagnosticSeverity::Off
15545 };
15546 self.set_max_diagnostics_severity(new_severity, cx);
15547 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15548 self.active_diagnostics = ActiveDiagnostic::None;
15549 self.inline_diagnostics_update = Task::ready(());
15550 self.inline_diagnostics.clear();
15551 } else {
15552 self.refresh_inline_diagnostics(false, window, cx);
15553 }
15554
15555 cx.notify();
15556 }
15557
15558 pub fn toggle_minimap(
15559 &mut self,
15560 _: &ToggleMinimap,
15561 window: &mut Window,
15562 cx: &mut Context<Editor>,
15563 ) {
15564 if self.supports_minimap(cx) {
15565 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
15566 }
15567 }
15568
15569 fn refresh_inline_diagnostics(
15570 &mut self,
15571 debounce: bool,
15572 window: &mut Window,
15573 cx: &mut Context<Self>,
15574 ) {
15575 let max_severity = ProjectSettings::get_global(cx)
15576 .diagnostics
15577 .inline
15578 .max_severity
15579 .unwrap_or(self.diagnostics_max_severity);
15580
15581 if !self.inline_diagnostics_enabled()
15582 || !self.show_inline_diagnostics
15583 || max_severity == DiagnosticSeverity::Off
15584 {
15585 self.inline_diagnostics_update = Task::ready(());
15586 self.inline_diagnostics.clear();
15587 return;
15588 }
15589
15590 let debounce_ms = ProjectSettings::get_global(cx)
15591 .diagnostics
15592 .inline
15593 .update_debounce_ms;
15594 let debounce = if debounce && debounce_ms > 0 {
15595 Some(Duration::from_millis(debounce_ms))
15596 } else {
15597 None
15598 };
15599 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
15600 let editor = editor.upgrade().unwrap();
15601
15602 if let Some(debounce) = debounce {
15603 cx.background_executor().timer(debounce).await;
15604 }
15605 let Some(snapshot) = editor
15606 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
15607 .ok()
15608 else {
15609 return;
15610 };
15611
15612 let new_inline_diagnostics = cx
15613 .background_spawn(async move {
15614 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
15615 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
15616 let message = diagnostic_entry
15617 .diagnostic
15618 .message
15619 .split_once('\n')
15620 .map(|(line, _)| line)
15621 .map(SharedString::new)
15622 .unwrap_or_else(|| {
15623 SharedString::from(diagnostic_entry.diagnostic.message)
15624 });
15625 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
15626 let (Ok(i) | Err(i)) = inline_diagnostics
15627 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
15628 inline_diagnostics.insert(
15629 i,
15630 (
15631 start_anchor,
15632 InlineDiagnostic {
15633 message,
15634 group_id: diagnostic_entry.diagnostic.group_id,
15635 start: diagnostic_entry.range.start.to_point(&snapshot),
15636 is_primary: diagnostic_entry.diagnostic.is_primary,
15637 severity: diagnostic_entry.diagnostic.severity,
15638 },
15639 ),
15640 );
15641 }
15642 inline_diagnostics
15643 })
15644 .await;
15645
15646 editor
15647 .update(cx, |editor, cx| {
15648 editor.inline_diagnostics = new_inline_diagnostics;
15649 cx.notify();
15650 })
15651 .ok();
15652 });
15653 }
15654
15655 pub fn set_selections_from_remote(
15656 &mut self,
15657 selections: Vec<Selection<Anchor>>,
15658 pending_selection: Option<Selection<Anchor>>,
15659 window: &mut Window,
15660 cx: &mut Context<Self>,
15661 ) {
15662 let old_cursor_position = self.selections.newest_anchor().head();
15663 self.selections.change_with(cx, |s| {
15664 s.select_anchors(selections);
15665 if let Some(pending_selection) = pending_selection {
15666 s.set_pending(pending_selection, SelectMode::Character);
15667 } else {
15668 s.clear_pending();
15669 }
15670 });
15671 self.selections_did_change(false, &old_cursor_position, true, window, cx);
15672 }
15673
15674 fn push_to_selection_history(&mut self) {
15675 self.selection_history.push(SelectionHistoryEntry {
15676 selections: self.selections.disjoint_anchors(),
15677 select_next_state: self.select_next_state.clone(),
15678 select_prev_state: self.select_prev_state.clone(),
15679 add_selections_state: self.add_selections_state.clone(),
15680 });
15681 }
15682
15683 pub fn transact(
15684 &mut self,
15685 window: &mut Window,
15686 cx: &mut Context<Self>,
15687 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
15688 ) -> Option<TransactionId> {
15689 self.start_transaction_at(Instant::now(), window, cx);
15690 update(self, window, cx);
15691 self.end_transaction_at(Instant::now(), cx)
15692 }
15693
15694 pub fn start_transaction_at(
15695 &mut self,
15696 now: Instant,
15697 window: &mut Window,
15698 cx: &mut Context<Self>,
15699 ) {
15700 self.end_selection(window, cx);
15701 if let Some(tx_id) = self
15702 .buffer
15703 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
15704 {
15705 self.selection_history
15706 .insert_transaction(tx_id, self.selections.disjoint_anchors());
15707 cx.emit(EditorEvent::TransactionBegun {
15708 transaction_id: tx_id,
15709 })
15710 }
15711 }
15712
15713 pub fn end_transaction_at(
15714 &mut self,
15715 now: Instant,
15716 cx: &mut Context<Self>,
15717 ) -> Option<TransactionId> {
15718 if let Some(transaction_id) = self
15719 .buffer
15720 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
15721 {
15722 if let Some((_, end_selections)) =
15723 self.selection_history.transaction_mut(transaction_id)
15724 {
15725 *end_selections = Some(self.selections.disjoint_anchors());
15726 } else {
15727 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
15728 }
15729
15730 cx.emit(EditorEvent::Edited { transaction_id });
15731 Some(transaction_id)
15732 } else {
15733 None
15734 }
15735 }
15736
15737 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
15738 if self.selection_mark_mode {
15739 self.change_selections(None, window, cx, |s| {
15740 s.move_with(|_, sel| {
15741 sel.collapse_to(sel.head(), SelectionGoal::None);
15742 });
15743 })
15744 }
15745 self.selection_mark_mode = true;
15746 cx.notify();
15747 }
15748
15749 pub fn swap_selection_ends(
15750 &mut self,
15751 _: &actions::SwapSelectionEnds,
15752 window: &mut Window,
15753 cx: &mut Context<Self>,
15754 ) {
15755 self.change_selections(None, window, cx, |s| {
15756 s.move_with(|_, sel| {
15757 if sel.start != sel.end {
15758 sel.reversed = !sel.reversed
15759 }
15760 });
15761 });
15762 self.request_autoscroll(Autoscroll::newest(), cx);
15763 cx.notify();
15764 }
15765
15766 pub fn toggle_fold(
15767 &mut self,
15768 _: &actions::ToggleFold,
15769 window: &mut Window,
15770 cx: &mut Context<Self>,
15771 ) {
15772 if self.is_singleton(cx) {
15773 let selection = self.selections.newest::<Point>(cx);
15774
15775 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15776 let range = if selection.is_empty() {
15777 let point = selection.head().to_display_point(&display_map);
15778 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15779 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15780 .to_point(&display_map);
15781 start..end
15782 } else {
15783 selection.range()
15784 };
15785 if display_map.folds_in_range(range).next().is_some() {
15786 self.unfold_lines(&Default::default(), window, cx)
15787 } else {
15788 self.fold(&Default::default(), window, cx)
15789 }
15790 } else {
15791 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15792 let buffer_ids: HashSet<_> = self
15793 .selections
15794 .disjoint_anchor_ranges()
15795 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15796 .collect();
15797
15798 let should_unfold = buffer_ids
15799 .iter()
15800 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
15801
15802 for buffer_id in buffer_ids {
15803 if should_unfold {
15804 self.unfold_buffer(buffer_id, cx);
15805 } else {
15806 self.fold_buffer(buffer_id, cx);
15807 }
15808 }
15809 }
15810 }
15811
15812 pub fn toggle_fold_recursive(
15813 &mut self,
15814 _: &actions::ToggleFoldRecursive,
15815 window: &mut Window,
15816 cx: &mut Context<Self>,
15817 ) {
15818 let selection = self.selections.newest::<Point>(cx);
15819
15820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15821 let range = if selection.is_empty() {
15822 let point = selection.head().to_display_point(&display_map);
15823 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
15824 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
15825 .to_point(&display_map);
15826 start..end
15827 } else {
15828 selection.range()
15829 };
15830 if display_map.folds_in_range(range).next().is_some() {
15831 self.unfold_recursive(&Default::default(), window, cx)
15832 } else {
15833 self.fold_recursive(&Default::default(), window, cx)
15834 }
15835 }
15836
15837 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
15838 if self.is_singleton(cx) {
15839 let mut to_fold = Vec::new();
15840 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15841 let selections = self.selections.all_adjusted(cx);
15842
15843 for selection in selections {
15844 let range = selection.range().sorted();
15845 let buffer_start_row = range.start.row;
15846
15847 if range.start.row != range.end.row {
15848 let mut found = false;
15849 let mut row = range.start.row;
15850 while row <= range.end.row {
15851 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
15852 {
15853 found = true;
15854 row = crease.range().end.row + 1;
15855 to_fold.push(crease);
15856 } else {
15857 row += 1
15858 }
15859 }
15860 if found {
15861 continue;
15862 }
15863 }
15864
15865 for row in (0..=range.start.row).rev() {
15866 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15867 if crease.range().end.row >= buffer_start_row {
15868 to_fold.push(crease);
15869 if row <= range.start.row {
15870 break;
15871 }
15872 }
15873 }
15874 }
15875 }
15876
15877 self.fold_creases(to_fold, true, window, cx);
15878 } else {
15879 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
15880 let buffer_ids = self
15881 .selections
15882 .disjoint_anchor_ranges()
15883 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
15884 .collect::<HashSet<_>>();
15885 for buffer_id in buffer_ids {
15886 self.fold_buffer(buffer_id, cx);
15887 }
15888 }
15889 }
15890
15891 fn fold_at_level(
15892 &mut self,
15893 fold_at: &FoldAtLevel,
15894 window: &mut Window,
15895 cx: &mut Context<Self>,
15896 ) {
15897 if !self.buffer.read(cx).is_singleton() {
15898 return;
15899 }
15900
15901 let fold_at_level = fold_at.0;
15902 let snapshot = self.buffer.read(cx).snapshot(cx);
15903 let mut to_fold = Vec::new();
15904 let mut stack = vec![(0, snapshot.max_row().0, 1)];
15905
15906 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
15907 while start_row < end_row {
15908 match self
15909 .snapshot(window, cx)
15910 .crease_for_buffer_row(MultiBufferRow(start_row))
15911 {
15912 Some(crease) => {
15913 let nested_start_row = crease.range().start.row + 1;
15914 let nested_end_row = crease.range().end.row;
15915
15916 if current_level < fold_at_level {
15917 stack.push((nested_start_row, nested_end_row, current_level + 1));
15918 } else if current_level == fold_at_level {
15919 to_fold.push(crease);
15920 }
15921
15922 start_row = nested_end_row + 1;
15923 }
15924 None => start_row += 1,
15925 }
15926 }
15927 }
15928
15929 self.fold_creases(to_fold, true, window, cx);
15930 }
15931
15932 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
15933 if self.buffer.read(cx).is_singleton() {
15934 let mut fold_ranges = Vec::new();
15935 let snapshot = self.buffer.read(cx).snapshot(cx);
15936
15937 for row in 0..snapshot.max_row().0 {
15938 if let Some(foldable_range) = self
15939 .snapshot(window, cx)
15940 .crease_for_buffer_row(MultiBufferRow(row))
15941 {
15942 fold_ranges.push(foldable_range);
15943 }
15944 }
15945
15946 self.fold_creases(fold_ranges, true, window, cx);
15947 } else {
15948 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
15949 editor
15950 .update_in(cx, |editor, _, cx| {
15951 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
15952 editor.fold_buffer(buffer_id, cx);
15953 }
15954 })
15955 .ok();
15956 });
15957 }
15958 }
15959
15960 pub fn fold_function_bodies(
15961 &mut self,
15962 _: &actions::FoldFunctionBodies,
15963 window: &mut Window,
15964 cx: &mut Context<Self>,
15965 ) {
15966 let snapshot = self.buffer.read(cx).snapshot(cx);
15967
15968 let ranges = snapshot
15969 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
15970 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
15971 .collect::<Vec<_>>();
15972
15973 let creases = ranges
15974 .into_iter()
15975 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
15976 .collect();
15977
15978 self.fold_creases(creases, true, window, cx);
15979 }
15980
15981 pub fn fold_recursive(
15982 &mut self,
15983 _: &actions::FoldRecursive,
15984 window: &mut Window,
15985 cx: &mut Context<Self>,
15986 ) {
15987 let mut to_fold = Vec::new();
15988 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15989 let selections = self.selections.all_adjusted(cx);
15990
15991 for selection in selections {
15992 let range = selection.range().sorted();
15993 let buffer_start_row = range.start.row;
15994
15995 if range.start.row != range.end.row {
15996 let mut found = false;
15997 for row in range.start.row..=range.end.row {
15998 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
15999 found = true;
16000 to_fold.push(crease);
16001 }
16002 }
16003 if found {
16004 continue;
16005 }
16006 }
16007
16008 for row in (0..=range.start.row).rev() {
16009 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16010 if crease.range().end.row >= buffer_start_row {
16011 to_fold.push(crease);
16012 } else {
16013 break;
16014 }
16015 }
16016 }
16017 }
16018
16019 self.fold_creases(to_fold, true, window, cx);
16020 }
16021
16022 pub fn fold_at(
16023 &mut self,
16024 buffer_row: MultiBufferRow,
16025 window: &mut Window,
16026 cx: &mut Context<Self>,
16027 ) {
16028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16029
16030 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16031 let autoscroll = self
16032 .selections
16033 .all::<Point>(cx)
16034 .iter()
16035 .any(|selection| crease.range().overlaps(&selection.range()));
16036
16037 self.fold_creases(vec![crease], autoscroll, window, cx);
16038 }
16039 }
16040
16041 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16042 if self.is_singleton(cx) {
16043 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16044 let buffer = &display_map.buffer_snapshot;
16045 let selections = self.selections.all::<Point>(cx);
16046 let ranges = selections
16047 .iter()
16048 .map(|s| {
16049 let range = s.display_range(&display_map).sorted();
16050 let mut start = range.start.to_point(&display_map);
16051 let mut end = range.end.to_point(&display_map);
16052 start.column = 0;
16053 end.column = buffer.line_len(MultiBufferRow(end.row));
16054 start..end
16055 })
16056 .collect::<Vec<_>>();
16057
16058 self.unfold_ranges(&ranges, true, true, cx);
16059 } else {
16060 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16061 let buffer_ids = self
16062 .selections
16063 .disjoint_anchor_ranges()
16064 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16065 .collect::<HashSet<_>>();
16066 for buffer_id in buffer_ids {
16067 self.unfold_buffer(buffer_id, cx);
16068 }
16069 }
16070 }
16071
16072 pub fn unfold_recursive(
16073 &mut self,
16074 _: &UnfoldRecursive,
16075 _window: &mut Window,
16076 cx: &mut Context<Self>,
16077 ) {
16078 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16079 let selections = self.selections.all::<Point>(cx);
16080 let ranges = selections
16081 .iter()
16082 .map(|s| {
16083 let mut range = s.display_range(&display_map).sorted();
16084 *range.start.column_mut() = 0;
16085 *range.end.column_mut() = display_map.line_len(range.end.row());
16086 let start = range.start.to_point(&display_map);
16087 let end = range.end.to_point(&display_map);
16088 start..end
16089 })
16090 .collect::<Vec<_>>();
16091
16092 self.unfold_ranges(&ranges, true, true, cx);
16093 }
16094
16095 pub fn unfold_at(
16096 &mut self,
16097 buffer_row: MultiBufferRow,
16098 _window: &mut Window,
16099 cx: &mut Context<Self>,
16100 ) {
16101 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16102
16103 let intersection_range = Point::new(buffer_row.0, 0)
16104 ..Point::new(
16105 buffer_row.0,
16106 display_map.buffer_snapshot.line_len(buffer_row),
16107 );
16108
16109 let autoscroll = self
16110 .selections
16111 .all::<Point>(cx)
16112 .iter()
16113 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16114
16115 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16116 }
16117
16118 pub fn unfold_all(
16119 &mut self,
16120 _: &actions::UnfoldAll,
16121 _window: &mut Window,
16122 cx: &mut Context<Self>,
16123 ) {
16124 if self.buffer.read(cx).is_singleton() {
16125 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16126 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16127 } else {
16128 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16129 editor
16130 .update(cx, |editor, cx| {
16131 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16132 editor.unfold_buffer(buffer_id, cx);
16133 }
16134 })
16135 .ok();
16136 });
16137 }
16138 }
16139
16140 pub fn fold_selected_ranges(
16141 &mut self,
16142 _: &FoldSelectedRanges,
16143 window: &mut Window,
16144 cx: &mut Context<Self>,
16145 ) {
16146 let selections = self.selections.all_adjusted(cx);
16147 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16148 let ranges = selections
16149 .into_iter()
16150 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16151 .collect::<Vec<_>>();
16152 self.fold_creases(ranges, true, window, cx);
16153 }
16154
16155 pub fn fold_ranges<T: ToOffset + Clone>(
16156 &mut self,
16157 ranges: Vec<Range<T>>,
16158 auto_scroll: bool,
16159 window: &mut Window,
16160 cx: &mut Context<Self>,
16161 ) {
16162 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16163 let ranges = ranges
16164 .into_iter()
16165 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16166 .collect::<Vec<_>>();
16167 self.fold_creases(ranges, auto_scroll, window, cx);
16168 }
16169
16170 pub fn fold_creases<T: ToOffset + Clone>(
16171 &mut self,
16172 creases: Vec<Crease<T>>,
16173 auto_scroll: bool,
16174 _window: &mut Window,
16175 cx: &mut Context<Self>,
16176 ) {
16177 if creases.is_empty() {
16178 return;
16179 }
16180
16181 let mut buffers_affected = HashSet::default();
16182 let multi_buffer = self.buffer().read(cx);
16183 for crease in &creases {
16184 if let Some((_, buffer, _)) =
16185 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16186 {
16187 buffers_affected.insert(buffer.read(cx).remote_id());
16188 };
16189 }
16190
16191 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16192
16193 if auto_scroll {
16194 self.request_autoscroll(Autoscroll::fit(), cx);
16195 }
16196
16197 cx.notify();
16198
16199 self.scrollbar_marker_state.dirty = true;
16200 self.folds_did_change(cx);
16201 }
16202
16203 /// Removes any folds whose ranges intersect any of the given ranges.
16204 pub fn unfold_ranges<T: ToOffset + Clone>(
16205 &mut self,
16206 ranges: &[Range<T>],
16207 inclusive: bool,
16208 auto_scroll: bool,
16209 cx: &mut Context<Self>,
16210 ) {
16211 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16212 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16213 });
16214 self.folds_did_change(cx);
16215 }
16216
16217 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16218 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16219 return;
16220 }
16221 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16222 self.display_map.update(cx, |display_map, cx| {
16223 display_map.fold_buffers([buffer_id], cx)
16224 });
16225 cx.emit(EditorEvent::BufferFoldToggled {
16226 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16227 folded: true,
16228 });
16229 cx.notify();
16230 }
16231
16232 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16233 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16234 return;
16235 }
16236 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16237 self.display_map.update(cx, |display_map, cx| {
16238 display_map.unfold_buffers([buffer_id], cx);
16239 });
16240 cx.emit(EditorEvent::BufferFoldToggled {
16241 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16242 folded: false,
16243 });
16244 cx.notify();
16245 }
16246
16247 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16248 self.display_map.read(cx).is_buffer_folded(buffer)
16249 }
16250
16251 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16252 self.display_map.read(cx).folded_buffers()
16253 }
16254
16255 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16256 self.display_map.update(cx, |display_map, cx| {
16257 display_map.disable_header_for_buffer(buffer_id, cx);
16258 });
16259 cx.notify();
16260 }
16261
16262 /// Removes any folds with the given ranges.
16263 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16264 &mut self,
16265 ranges: &[Range<T>],
16266 type_id: TypeId,
16267 auto_scroll: bool,
16268 cx: &mut Context<Self>,
16269 ) {
16270 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16271 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16272 });
16273 self.folds_did_change(cx);
16274 }
16275
16276 fn remove_folds_with<T: ToOffset + Clone>(
16277 &mut self,
16278 ranges: &[Range<T>],
16279 auto_scroll: bool,
16280 cx: &mut Context<Self>,
16281 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16282 ) {
16283 if ranges.is_empty() {
16284 return;
16285 }
16286
16287 let mut buffers_affected = HashSet::default();
16288 let multi_buffer = self.buffer().read(cx);
16289 for range in ranges {
16290 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16291 buffers_affected.insert(buffer.read(cx).remote_id());
16292 };
16293 }
16294
16295 self.display_map.update(cx, update);
16296
16297 if auto_scroll {
16298 self.request_autoscroll(Autoscroll::fit(), cx);
16299 }
16300
16301 cx.notify();
16302 self.scrollbar_marker_state.dirty = true;
16303 self.active_indent_guides_state.dirty = true;
16304 }
16305
16306 pub fn update_fold_widths(
16307 &mut self,
16308 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16309 cx: &mut Context<Self>,
16310 ) -> bool {
16311 self.display_map
16312 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16313 }
16314
16315 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16316 self.display_map.read(cx).fold_placeholder.clone()
16317 }
16318
16319 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16320 self.buffer.update(cx, |buffer, cx| {
16321 buffer.set_all_diff_hunks_expanded(cx);
16322 });
16323 }
16324
16325 pub fn expand_all_diff_hunks(
16326 &mut self,
16327 _: &ExpandAllDiffHunks,
16328 _window: &mut Window,
16329 cx: &mut Context<Self>,
16330 ) {
16331 self.buffer.update(cx, |buffer, cx| {
16332 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16333 });
16334 }
16335
16336 pub fn toggle_selected_diff_hunks(
16337 &mut self,
16338 _: &ToggleSelectedDiffHunks,
16339 _window: &mut Window,
16340 cx: &mut Context<Self>,
16341 ) {
16342 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16343 self.toggle_diff_hunks_in_ranges(ranges, cx);
16344 }
16345
16346 pub fn diff_hunks_in_ranges<'a>(
16347 &'a self,
16348 ranges: &'a [Range<Anchor>],
16349 buffer: &'a MultiBufferSnapshot,
16350 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16351 ranges.iter().flat_map(move |range| {
16352 let end_excerpt_id = range.end.excerpt_id;
16353 let range = range.to_point(buffer);
16354 let mut peek_end = range.end;
16355 if range.end.row < buffer.max_row().0 {
16356 peek_end = Point::new(range.end.row + 1, 0);
16357 }
16358 buffer
16359 .diff_hunks_in_range(range.start..peek_end)
16360 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16361 })
16362 }
16363
16364 pub fn has_stageable_diff_hunks_in_ranges(
16365 &self,
16366 ranges: &[Range<Anchor>],
16367 snapshot: &MultiBufferSnapshot,
16368 ) -> bool {
16369 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16370 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16371 }
16372
16373 pub fn toggle_staged_selected_diff_hunks(
16374 &mut self,
16375 _: &::git::ToggleStaged,
16376 _: &mut Window,
16377 cx: &mut Context<Self>,
16378 ) {
16379 let snapshot = self.buffer.read(cx).snapshot(cx);
16380 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16381 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16382 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16383 }
16384
16385 pub fn set_render_diff_hunk_controls(
16386 &mut self,
16387 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16388 cx: &mut Context<Self>,
16389 ) {
16390 self.render_diff_hunk_controls = render_diff_hunk_controls;
16391 cx.notify();
16392 }
16393
16394 pub fn stage_and_next(
16395 &mut self,
16396 _: &::git::StageAndNext,
16397 window: &mut Window,
16398 cx: &mut Context<Self>,
16399 ) {
16400 self.do_stage_or_unstage_and_next(true, window, cx);
16401 }
16402
16403 pub fn unstage_and_next(
16404 &mut self,
16405 _: &::git::UnstageAndNext,
16406 window: &mut Window,
16407 cx: &mut Context<Self>,
16408 ) {
16409 self.do_stage_or_unstage_and_next(false, window, cx);
16410 }
16411
16412 pub fn stage_or_unstage_diff_hunks(
16413 &mut self,
16414 stage: bool,
16415 ranges: Vec<Range<Anchor>>,
16416 cx: &mut Context<Self>,
16417 ) {
16418 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16419 cx.spawn(async move |this, cx| {
16420 task.await?;
16421 this.update(cx, |this, cx| {
16422 let snapshot = this.buffer.read(cx).snapshot(cx);
16423 let chunk_by = this
16424 .diff_hunks_in_ranges(&ranges, &snapshot)
16425 .chunk_by(|hunk| hunk.buffer_id);
16426 for (buffer_id, hunks) in &chunk_by {
16427 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16428 }
16429 })
16430 })
16431 .detach_and_log_err(cx);
16432 }
16433
16434 fn save_buffers_for_ranges_if_needed(
16435 &mut self,
16436 ranges: &[Range<Anchor>],
16437 cx: &mut Context<Editor>,
16438 ) -> Task<Result<()>> {
16439 let multibuffer = self.buffer.read(cx);
16440 let snapshot = multibuffer.read(cx);
16441 let buffer_ids: HashSet<_> = ranges
16442 .iter()
16443 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16444 .collect();
16445 drop(snapshot);
16446
16447 let mut buffers = HashSet::default();
16448 for buffer_id in buffer_ids {
16449 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16450 let buffer = buffer_entity.read(cx);
16451 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16452 {
16453 buffers.insert(buffer_entity);
16454 }
16455 }
16456 }
16457
16458 if let Some(project) = &self.project {
16459 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16460 } else {
16461 Task::ready(Ok(()))
16462 }
16463 }
16464
16465 fn do_stage_or_unstage_and_next(
16466 &mut self,
16467 stage: bool,
16468 window: &mut Window,
16469 cx: &mut Context<Self>,
16470 ) {
16471 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16472
16473 if ranges.iter().any(|range| range.start != range.end) {
16474 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16475 return;
16476 }
16477
16478 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16479 let snapshot = self.snapshot(window, cx);
16480 let position = self.selections.newest::<Point>(cx).head();
16481 let mut row = snapshot
16482 .buffer_snapshot
16483 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16484 .find(|hunk| hunk.row_range.start.0 > position.row)
16485 .map(|hunk| hunk.row_range.start);
16486
16487 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16488 // Outside of the project diff editor, wrap around to the beginning.
16489 if !all_diff_hunks_expanded {
16490 row = row.or_else(|| {
16491 snapshot
16492 .buffer_snapshot
16493 .diff_hunks_in_range(Point::zero()..position)
16494 .find(|hunk| hunk.row_range.end.0 < position.row)
16495 .map(|hunk| hunk.row_range.start)
16496 });
16497 }
16498
16499 if let Some(row) = row {
16500 let destination = Point::new(row.0, 0);
16501 let autoscroll = Autoscroll::center();
16502
16503 self.unfold_ranges(&[destination..destination], false, false, cx);
16504 self.change_selections(Some(autoscroll), window, cx, |s| {
16505 s.select_ranges([destination..destination]);
16506 });
16507 }
16508 }
16509
16510 fn do_stage_or_unstage(
16511 &self,
16512 stage: bool,
16513 buffer_id: BufferId,
16514 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
16515 cx: &mut App,
16516 ) -> Option<()> {
16517 let project = self.project.as_ref()?;
16518 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
16519 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
16520 let buffer_snapshot = buffer.read(cx).snapshot();
16521 let file_exists = buffer_snapshot
16522 .file()
16523 .is_some_and(|file| file.disk_state().exists());
16524 diff.update(cx, |diff, cx| {
16525 diff.stage_or_unstage_hunks(
16526 stage,
16527 &hunks
16528 .map(|hunk| buffer_diff::DiffHunk {
16529 buffer_range: hunk.buffer_range,
16530 diff_base_byte_range: hunk.diff_base_byte_range,
16531 secondary_status: hunk.secondary_status,
16532 range: Point::zero()..Point::zero(), // unused
16533 })
16534 .collect::<Vec<_>>(),
16535 &buffer_snapshot,
16536 file_exists,
16537 cx,
16538 )
16539 });
16540 None
16541 }
16542
16543 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
16544 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16545 self.buffer
16546 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
16547 }
16548
16549 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
16550 self.buffer.update(cx, |buffer, cx| {
16551 let ranges = vec![Anchor::min()..Anchor::max()];
16552 if !buffer.all_diff_hunks_expanded()
16553 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
16554 {
16555 buffer.collapse_diff_hunks(ranges, cx);
16556 true
16557 } else {
16558 false
16559 }
16560 })
16561 }
16562
16563 fn toggle_diff_hunks_in_ranges(
16564 &mut self,
16565 ranges: Vec<Range<Anchor>>,
16566 cx: &mut Context<Editor>,
16567 ) {
16568 self.buffer.update(cx, |buffer, cx| {
16569 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
16570 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
16571 })
16572 }
16573
16574 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
16575 self.buffer.update(cx, |buffer, cx| {
16576 let snapshot = buffer.snapshot(cx);
16577 let excerpt_id = range.end.excerpt_id;
16578 let point_range = range.to_point(&snapshot);
16579 let expand = !buffer.single_hunk_is_expanded(range, cx);
16580 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
16581 })
16582 }
16583
16584 pub(crate) fn apply_all_diff_hunks(
16585 &mut self,
16586 _: &ApplyAllDiffHunks,
16587 window: &mut Window,
16588 cx: &mut Context<Self>,
16589 ) {
16590 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16591
16592 let buffers = self.buffer.read(cx).all_buffers();
16593 for branch_buffer in buffers {
16594 branch_buffer.update(cx, |branch_buffer, cx| {
16595 branch_buffer.merge_into_base(Vec::new(), cx);
16596 });
16597 }
16598
16599 if let Some(project) = self.project.clone() {
16600 self.save(true, project, window, cx).detach_and_log_err(cx);
16601 }
16602 }
16603
16604 pub(crate) fn apply_selected_diff_hunks(
16605 &mut self,
16606 _: &ApplyDiffHunk,
16607 window: &mut Window,
16608 cx: &mut Context<Self>,
16609 ) {
16610 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
16611 let snapshot = self.snapshot(window, cx);
16612 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
16613 let mut ranges_by_buffer = HashMap::default();
16614 self.transact(window, cx, |editor, _window, cx| {
16615 for hunk in hunks {
16616 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
16617 ranges_by_buffer
16618 .entry(buffer.clone())
16619 .or_insert_with(Vec::new)
16620 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
16621 }
16622 }
16623
16624 for (buffer, ranges) in ranges_by_buffer {
16625 buffer.update(cx, |buffer, cx| {
16626 buffer.merge_into_base(ranges, cx);
16627 });
16628 }
16629 });
16630
16631 if let Some(project) = self.project.clone() {
16632 self.save(true, project, window, cx).detach_and_log_err(cx);
16633 }
16634 }
16635
16636 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
16637 if hovered != self.gutter_hovered {
16638 self.gutter_hovered = hovered;
16639 cx.notify();
16640 }
16641 }
16642
16643 pub fn insert_blocks(
16644 &mut self,
16645 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
16646 autoscroll: Option<Autoscroll>,
16647 cx: &mut Context<Self>,
16648 ) -> Vec<CustomBlockId> {
16649 let blocks = self
16650 .display_map
16651 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
16652 if let Some(autoscroll) = autoscroll {
16653 self.request_autoscroll(autoscroll, cx);
16654 }
16655 cx.notify();
16656 blocks
16657 }
16658
16659 pub fn resize_blocks(
16660 &mut self,
16661 heights: HashMap<CustomBlockId, u32>,
16662 autoscroll: Option<Autoscroll>,
16663 cx: &mut Context<Self>,
16664 ) {
16665 self.display_map
16666 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
16667 if let Some(autoscroll) = autoscroll {
16668 self.request_autoscroll(autoscroll, cx);
16669 }
16670 cx.notify();
16671 }
16672
16673 pub fn replace_blocks(
16674 &mut self,
16675 renderers: HashMap<CustomBlockId, RenderBlock>,
16676 autoscroll: Option<Autoscroll>,
16677 cx: &mut Context<Self>,
16678 ) {
16679 self.display_map
16680 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
16681 if let Some(autoscroll) = autoscroll {
16682 self.request_autoscroll(autoscroll, cx);
16683 }
16684 cx.notify();
16685 }
16686
16687 pub fn remove_blocks(
16688 &mut self,
16689 block_ids: HashSet<CustomBlockId>,
16690 autoscroll: Option<Autoscroll>,
16691 cx: &mut Context<Self>,
16692 ) {
16693 self.display_map.update(cx, |display_map, cx| {
16694 display_map.remove_blocks(block_ids, cx)
16695 });
16696 if let Some(autoscroll) = autoscroll {
16697 self.request_autoscroll(autoscroll, cx);
16698 }
16699 cx.notify();
16700 }
16701
16702 pub fn row_for_block(
16703 &self,
16704 block_id: CustomBlockId,
16705 cx: &mut Context<Self>,
16706 ) -> Option<DisplayRow> {
16707 self.display_map
16708 .update(cx, |map, cx| map.row_for_block(block_id, cx))
16709 }
16710
16711 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
16712 self.focused_block = Some(focused_block);
16713 }
16714
16715 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
16716 self.focused_block.take()
16717 }
16718
16719 pub fn insert_creases(
16720 &mut self,
16721 creases: impl IntoIterator<Item = Crease<Anchor>>,
16722 cx: &mut Context<Self>,
16723 ) -> Vec<CreaseId> {
16724 self.display_map
16725 .update(cx, |map, cx| map.insert_creases(creases, cx))
16726 }
16727
16728 pub fn remove_creases(
16729 &mut self,
16730 ids: impl IntoIterator<Item = CreaseId>,
16731 cx: &mut Context<Self>,
16732 ) -> Vec<(CreaseId, Range<Anchor>)> {
16733 self.display_map
16734 .update(cx, |map, cx| map.remove_creases(ids, cx))
16735 }
16736
16737 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
16738 self.display_map
16739 .update(cx, |map, cx| map.snapshot(cx))
16740 .longest_row()
16741 }
16742
16743 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
16744 self.display_map
16745 .update(cx, |map, cx| map.snapshot(cx))
16746 .max_point()
16747 }
16748
16749 pub fn text(&self, cx: &App) -> String {
16750 self.buffer.read(cx).read(cx).text()
16751 }
16752
16753 pub fn is_empty(&self, cx: &App) -> bool {
16754 self.buffer.read(cx).read(cx).is_empty()
16755 }
16756
16757 pub fn text_option(&self, cx: &App) -> Option<String> {
16758 let text = self.text(cx);
16759 let text = text.trim();
16760
16761 if text.is_empty() {
16762 return None;
16763 }
16764
16765 Some(text.to_string())
16766 }
16767
16768 pub fn set_text(
16769 &mut self,
16770 text: impl Into<Arc<str>>,
16771 window: &mut Window,
16772 cx: &mut Context<Self>,
16773 ) {
16774 self.transact(window, cx, |this, _, cx| {
16775 this.buffer
16776 .read(cx)
16777 .as_singleton()
16778 .expect("you can only call set_text on editors for singleton buffers")
16779 .update(cx, |buffer, cx| buffer.set_text(text, cx));
16780 });
16781 }
16782
16783 pub fn display_text(&self, cx: &mut App) -> String {
16784 self.display_map
16785 .update(cx, |map, cx| map.snapshot(cx))
16786 .text()
16787 }
16788
16789 fn create_minimap(
16790 &self,
16791 minimap_settings: MinimapSettings,
16792 window: &mut Window,
16793 cx: &mut Context<Self>,
16794 ) -> Option<Entity<Self>> {
16795 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
16796 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
16797 }
16798
16799 fn initialize_new_minimap(
16800 &self,
16801 minimap_settings: MinimapSettings,
16802 window: &mut Window,
16803 cx: &mut Context<Self>,
16804 ) -> Entity<Self> {
16805 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
16806
16807 let mut minimap = Editor::new_internal(
16808 EditorMode::Minimap {
16809 parent: cx.weak_entity(),
16810 },
16811 self.buffer.clone(),
16812 self.project.clone(),
16813 Some(self.display_map.clone()),
16814 window,
16815 cx,
16816 );
16817 minimap.scroll_manager.clone_state(&self.scroll_manager);
16818 minimap.set_text_style_refinement(TextStyleRefinement {
16819 font_size: Some(MINIMAP_FONT_SIZE),
16820 font_weight: Some(MINIMAP_FONT_WEIGHT),
16821 ..Default::default()
16822 });
16823 minimap.update_minimap_configuration(minimap_settings, cx);
16824 cx.new(|_| minimap)
16825 }
16826
16827 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
16828 let current_line_highlight = minimap_settings
16829 .current_line_highlight
16830 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
16831 self.set_current_line_highlight(Some(current_line_highlight));
16832 }
16833
16834 pub fn minimap(&self) -> Option<&Entity<Self>> {
16835 self.minimap
16836 .as_ref()
16837 .filter(|_| self.minimap_visibility.visible())
16838 }
16839
16840 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
16841 let mut wrap_guides = smallvec![];
16842
16843 if self.show_wrap_guides == Some(false) {
16844 return wrap_guides;
16845 }
16846
16847 let settings = self.buffer.read(cx).language_settings(cx);
16848 if settings.show_wrap_guides {
16849 match self.soft_wrap_mode(cx) {
16850 SoftWrap::Column(soft_wrap) => {
16851 wrap_guides.push((soft_wrap as usize, true));
16852 }
16853 SoftWrap::Bounded(soft_wrap) => {
16854 wrap_guides.push((soft_wrap as usize, true));
16855 }
16856 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
16857 }
16858 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
16859 }
16860
16861 wrap_guides
16862 }
16863
16864 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
16865 let settings = self.buffer.read(cx).language_settings(cx);
16866 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
16867 match mode {
16868 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
16869 SoftWrap::None
16870 }
16871 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
16872 language_settings::SoftWrap::PreferredLineLength => {
16873 SoftWrap::Column(settings.preferred_line_length)
16874 }
16875 language_settings::SoftWrap::Bounded => {
16876 SoftWrap::Bounded(settings.preferred_line_length)
16877 }
16878 }
16879 }
16880
16881 pub fn set_soft_wrap_mode(
16882 &mut self,
16883 mode: language_settings::SoftWrap,
16884
16885 cx: &mut Context<Self>,
16886 ) {
16887 self.soft_wrap_mode_override = Some(mode);
16888 cx.notify();
16889 }
16890
16891 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
16892 self.hard_wrap = hard_wrap;
16893 cx.notify();
16894 }
16895
16896 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
16897 self.text_style_refinement = Some(style);
16898 }
16899
16900 /// called by the Element so we know what style we were most recently rendered with.
16901 pub(crate) fn set_style(
16902 &mut self,
16903 style: EditorStyle,
16904 window: &mut Window,
16905 cx: &mut Context<Self>,
16906 ) {
16907 // We intentionally do not inform the display map about the minimap style
16908 // so that wrapping is not recalculated and stays consistent for the editor
16909 // and its linked minimap.
16910 if !self.mode.is_minimap() {
16911 let rem_size = window.rem_size();
16912 self.display_map.update(cx, |map, cx| {
16913 map.set_font(
16914 style.text.font(),
16915 style.text.font_size.to_pixels(rem_size),
16916 cx,
16917 )
16918 });
16919 }
16920 self.style = Some(style);
16921 }
16922
16923 pub fn style(&self) -> Option<&EditorStyle> {
16924 self.style.as_ref()
16925 }
16926
16927 // Called by the element. This method is not designed to be called outside of the editor
16928 // element's layout code because it does not notify when rewrapping is computed synchronously.
16929 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
16930 self.display_map
16931 .update(cx, |map, cx| map.set_wrap_width(width, cx))
16932 }
16933
16934 pub fn set_soft_wrap(&mut self) {
16935 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
16936 }
16937
16938 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
16939 if self.soft_wrap_mode_override.is_some() {
16940 self.soft_wrap_mode_override.take();
16941 } else {
16942 let soft_wrap = match self.soft_wrap_mode(cx) {
16943 SoftWrap::GitDiff => return,
16944 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
16945 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
16946 language_settings::SoftWrap::None
16947 }
16948 };
16949 self.soft_wrap_mode_override = Some(soft_wrap);
16950 }
16951 cx.notify();
16952 }
16953
16954 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
16955 let Some(workspace) = self.workspace() else {
16956 return;
16957 };
16958 let fs = workspace.read(cx).app_state().fs.clone();
16959 let current_show = TabBarSettings::get_global(cx).show;
16960 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
16961 setting.show = Some(!current_show);
16962 });
16963 }
16964
16965 pub fn toggle_indent_guides(
16966 &mut self,
16967 _: &ToggleIndentGuides,
16968 _: &mut Window,
16969 cx: &mut Context<Self>,
16970 ) {
16971 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
16972 self.buffer
16973 .read(cx)
16974 .language_settings(cx)
16975 .indent_guides
16976 .enabled
16977 });
16978 self.show_indent_guides = Some(!currently_enabled);
16979 cx.notify();
16980 }
16981
16982 fn should_show_indent_guides(&self) -> Option<bool> {
16983 self.show_indent_guides
16984 }
16985
16986 pub fn toggle_line_numbers(
16987 &mut self,
16988 _: &ToggleLineNumbers,
16989 _: &mut Window,
16990 cx: &mut Context<Self>,
16991 ) {
16992 let mut editor_settings = EditorSettings::get_global(cx).clone();
16993 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
16994 EditorSettings::override_global(editor_settings, cx);
16995 }
16996
16997 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
16998 if let Some(show_line_numbers) = self.show_line_numbers {
16999 return show_line_numbers;
17000 }
17001 EditorSettings::get_global(cx).gutter.line_numbers
17002 }
17003
17004 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17005 self.use_relative_line_numbers
17006 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17007 }
17008
17009 pub fn toggle_relative_line_numbers(
17010 &mut self,
17011 _: &ToggleRelativeLineNumbers,
17012 _: &mut Window,
17013 cx: &mut Context<Self>,
17014 ) {
17015 let is_relative = self.should_use_relative_line_numbers(cx);
17016 self.set_relative_line_number(Some(!is_relative), cx)
17017 }
17018
17019 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17020 self.use_relative_line_numbers = is_relative;
17021 cx.notify();
17022 }
17023
17024 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17025 self.show_gutter = show_gutter;
17026 cx.notify();
17027 }
17028
17029 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17030 self.show_scrollbars = ScrollbarAxes {
17031 horizontal: show,
17032 vertical: show,
17033 };
17034 cx.notify();
17035 }
17036
17037 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17038 self.show_scrollbars.vertical = show;
17039 cx.notify();
17040 }
17041
17042 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17043 self.show_scrollbars.horizontal = show;
17044 cx.notify();
17045 }
17046
17047 pub fn set_minimap_visibility(
17048 &mut self,
17049 minimap_visibility: MinimapVisibility,
17050 window: &mut Window,
17051 cx: &mut Context<Self>,
17052 ) {
17053 if self.minimap_visibility != minimap_visibility {
17054 if minimap_visibility.visible() && self.minimap.is_none() {
17055 let minimap_settings = EditorSettings::get_global(cx).minimap;
17056 self.minimap =
17057 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17058 }
17059 self.minimap_visibility = minimap_visibility;
17060 cx.notify();
17061 }
17062 }
17063
17064 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17065 self.set_show_scrollbars(false, cx);
17066 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17067 }
17068
17069 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17070 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17071 }
17072
17073 /// Normally the text in full mode and auto height editors is padded on the
17074 /// left side by roughly half a character width for improved hit testing.
17075 ///
17076 /// Use this method to disable this for cases where this is not wanted (e.g.
17077 /// if you want to align the editor text with some other text above or below)
17078 /// or if you want to add this padding to single-line editors.
17079 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17080 self.offset_content = offset_content;
17081 cx.notify();
17082 }
17083
17084 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17085 self.show_line_numbers = Some(show_line_numbers);
17086 cx.notify();
17087 }
17088
17089 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17090 self.disable_expand_excerpt_buttons = true;
17091 cx.notify();
17092 }
17093
17094 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17095 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17096 cx.notify();
17097 }
17098
17099 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17100 self.show_code_actions = Some(show_code_actions);
17101 cx.notify();
17102 }
17103
17104 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17105 self.show_runnables = Some(show_runnables);
17106 cx.notify();
17107 }
17108
17109 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17110 self.show_breakpoints = Some(show_breakpoints);
17111 cx.notify();
17112 }
17113
17114 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17115 if self.display_map.read(cx).masked != masked {
17116 self.display_map.update(cx, |map, _| map.masked = masked);
17117 }
17118 cx.notify()
17119 }
17120
17121 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17122 self.show_wrap_guides = Some(show_wrap_guides);
17123 cx.notify();
17124 }
17125
17126 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17127 self.show_indent_guides = Some(show_indent_guides);
17128 cx.notify();
17129 }
17130
17131 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17132 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17133 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17134 if let Some(dir) = file.abs_path(cx).parent() {
17135 return Some(dir.to_owned());
17136 }
17137 }
17138
17139 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17140 return Some(project_path.path.to_path_buf());
17141 }
17142 }
17143
17144 None
17145 }
17146
17147 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17148 self.active_excerpt(cx)?
17149 .1
17150 .read(cx)
17151 .file()
17152 .and_then(|f| f.as_local())
17153 }
17154
17155 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17156 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17157 let buffer = buffer.read(cx);
17158 if let Some(project_path) = buffer.project_path(cx) {
17159 let project = self.project.as_ref()?.read(cx);
17160 project.absolute_path(&project_path, cx)
17161 } else {
17162 buffer
17163 .file()
17164 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17165 }
17166 })
17167 }
17168
17169 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17170 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17171 let project_path = buffer.read(cx).project_path(cx)?;
17172 let project = self.project.as_ref()?.read(cx);
17173 let entry = project.entry_for_path(&project_path, cx)?;
17174 let path = entry.path.to_path_buf();
17175 Some(path)
17176 })
17177 }
17178
17179 pub fn reveal_in_finder(
17180 &mut self,
17181 _: &RevealInFileManager,
17182 _window: &mut Window,
17183 cx: &mut Context<Self>,
17184 ) {
17185 if let Some(target) = self.target_file(cx) {
17186 cx.reveal_path(&target.abs_path(cx));
17187 }
17188 }
17189
17190 pub fn copy_path(
17191 &mut self,
17192 _: &zed_actions::workspace::CopyPath,
17193 _window: &mut Window,
17194 cx: &mut Context<Self>,
17195 ) {
17196 if let Some(path) = self.target_file_abs_path(cx) {
17197 if let Some(path) = path.to_str() {
17198 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17199 }
17200 }
17201 }
17202
17203 pub fn copy_relative_path(
17204 &mut self,
17205 _: &zed_actions::workspace::CopyRelativePath,
17206 _window: &mut Window,
17207 cx: &mut Context<Self>,
17208 ) {
17209 if let Some(path) = self.target_file_path(cx) {
17210 if let Some(path) = path.to_str() {
17211 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17212 }
17213 }
17214 }
17215
17216 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17217 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17218 buffer.read(cx).project_path(cx)
17219 } else {
17220 None
17221 }
17222 }
17223
17224 // Returns true if the editor handled a go-to-line request
17225 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17226 maybe!({
17227 let breakpoint_store = self.breakpoint_store.as_ref()?;
17228
17229 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17230 else {
17231 self.clear_row_highlights::<ActiveDebugLine>();
17232 return None;
17233 };
17234
17235 let position = active_stack_frame.position;
17236 let buffer_id = position.buffer_id?;
17237 let snapshot = self
17238 .project
17239 .as_ref()?
17240 .read(cx)
17241 .buffer_for_id(buffer_id, cx)?
17242 .read(cx)
17243 .snapshot();
17244
17245 let mut handled = false;
17246 for (id, ExcerptRange { context, .. }) in
17247 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17248 {
17249 if context.start.cmp(&position, &snapshot).is_ge()
17250 || context.end.cmp(&position, &snapshot).is_lt()
17251 {
17252 continue;
17253 }
17254 let snapshot = self.buffer.read(cx).snapshot(cx);
17255 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17256
17257 handled = true;
17258 self.clear_row_highlights::<ActiveDebugLine>();
17259
17260 self.go_to_line::<ActiveDebugLine>(
17261 multibuffer_anchor,
17262 Some(cx.theme().colors().editor_debugger_active_line_background),
17263 window,
17264 cx,
17265 );
17266
17267 cx.notify();
17268 }
17269
17270 handled.then_some(())
17271 })
17272 .is_some()
17273 }
17274
17275 pub fn copy_file_name_without_extension(
17276 &mut self,
17277 _: &CopyFileNameWithoutExtension,
17278 _: &mut Window,
17279 cx: &mut Context<Self>,
17280 ) {
17281 if let Some(file) = self.target_file(cx) {
17282 if let Some(file_stem) = file.path().file_stem() {
17283 if let Some(name) = file_stem.to_str() {
17284 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17285 }
17286 }
17287 }
17288 }
17289
17290 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17291 if let Some(file) = self.target_file(cx) {
17292 if let Some(file_name) = file.path().file_name() {
17293 if let Some(name) = file_name.to_str() {
17294 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17295 }
17296 }
17297 }
17298 }
17299
17300 pub fn toggle_git_blame(
17301 &mut self,
17302 _: &::git::Blame,
17303 window: &mut Window,
17304 cx: &mut Context<Self>,
17305 ) {
17306 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17307
17308 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17309 self.start_git_blame(true, window, cx);
17310 }
17311
17312 cx.notify();
17313 }
17314
17315 pub fn toggle_git_blame_inline(
17316 &mut self,
17317 _: &ToggleGitBlameInline,
17318 window: &mut Window,
17319 cx: &mut Context<Self>,
17320 ) {
17321 self.toggle_git_blame_inline_internal(true, window, cx);
17322 cx.notify();
17323 }
17324
17325 pub fn open_git_blame_commit(
17326 &mut self,
17327 _: &OpenGitBlameCommit,
17328 window: &mut Window,
17329 cx: &mut Context<Self>,
17330 ) {
17331 self.open_git_blame_commit_internal(window, cx);
17332 }
17333
17334 fn open_git_blame_commit_internal(
17335 &mut self,
17336 window: &mut Window,
17337 cx: &mut Context<Self>,
17338 ) -> Option<()> {
17339 let blame = self.blame.as_ref()?;
17340 let snapshot = self.snapshot(window, cx);
17341 let cursor = self.selections.newest::<Point>(cx).head();
17342 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17343 let blame_entry = blame
17344 .update(cx, |blame, cx| {
17345 blame
17346 .blame_for_rows(
17347 &[RowInfo {
17348 buffer_id: Some(buffer.remote_id()),
17349 buffer_row: Some(point.row),
17350 ..Default::default()
17351 }],
17352 cx,
17353 )
17354 .next()
17355 })
17356 .flatten()?;
17357 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17358 let repo = blame.read(cx).repository(cx)?;
17359 let workspace = self.workspace()?.downgrade();
17360 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17361 None
17362 }
17363
17364 pub fn git_blame_inline_enabled(&self) -> bool {
17365 self.git_blame_inline_enabled
17366 }
17367
17368 pub fn toggle_selection_menu(
17369 &mut self,
17370 _: &ToggleSelectionMenu,
17371 _: &mut Window,
17372 cx: &mut Context<Self>,
17373 ) {
17374 self.show_selection_menu = self
17375 .show_selection_menu
17376 .map(|show_selections_menu| !show_selections_menu)
17377 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17378
17379 cx.notify();
17380 }
17381
17382 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17383 self.show_selection_menu
17384 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17385 }
17386
17387 fn start_git_blame(
17388 &mut self,
17389 user_triggered: bool,
17390 window: &mut Window,
17391 cx: &mut Context<Self>,
17392 ) {
17393 if let Some(project) = self.project.as_ref() {
17394 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17395 return;
17396 };
17397
17398 if buffer.read(cx).file().is_none() {
17399 return;
17400 }
17401
17402 let focused = self.focus_handle(cx).contains_focused(window, cx);
17403
17404 let project = project.clone();
17405 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17406 self.blame_subscription =
17407 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17408 self.blame = Some(blame);
17409 }
17410 }
17411
17412 fn toggle_git_blame_inline_internal(
17413 &mut self,
17414 user_triggered: bool,
17415 window: &mut Window,
17416 cx: &mut Context<Self>,
17417 ) {
17418 if self.git_blame_inline_enabled {
17419 self.git_blame_inline_enabled = false;
17420 self.show_git_blame_inline = false;
17421 self.show_git_blame_inline_delay_task.take();
17422 } else {
17423 self.git_blame_inline_enabled = true;
17424 self.start_git_blame_inline(user_triggered, window, cx);
17425 }
17426
17427 cx.notify();
17428 }
17429
17430 fn start_git_blame_inline(
17431 &mut self,
17432 user_triggered: bool,
17433 window: &mut Window,
17434 cx: &mut Context<Self>,
17435 ) {
17436 self.start_git_blame(user_triggered, window, cx);
17437
17438 if ProjectSettings::get_global(cx)
17439 .git
17440 .inline_blame_delay()
17441 .is_some()
17442 {
17443 self.start_inline_blame_timer(window, cx);
17444 } else {
17445 self.show_git_blame_inline = true
17446 }
17447 }
17448
17449 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17450 self.blame.as_ref()
17451 }
17452
17453 pub fn show_git_blame_gutter(&self) -> bool {
17454 self.show_git_blame_gutter
17455 }
17456
17457 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17458 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17459 }
17460
17461 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17462 self.show_git_blame_inline
17463 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17464 && !self.newest_selection_head_on_empty_line(cx)
17465 && self.has_blame_entries(cx)
17466 }
17467
17468 fn has_blame_entries(&self, cx: &App) -> bool {
17469 self.blame()
17470 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17471 }
17472
17473 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17474 let cursor_anchor = self.selections.newest_anchor().head();
17475
17476 let snapshot = self.buffer.read(cx).snapshot(cx);
17477 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17478
17479 snapshot.line_len(buffer_row) == 0
17480 }
17481
17482 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17483 let buffer_and_selection = maybe!({
17484 let selection = self.selections.newest::<Point>(cx);
17485 let selection_range = selection.range();
17486
17487 let multi_buffer = self.buffer().read(cx);
17488 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17489 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17490
17491 let (buffer, range, _) = if selection.reversed {
17492 buffer_ranges.first()
17493 } else {
17494 buffer_ranges.last()
17495 }?;
17496
17497 let selection = text::ToPoint::to_point(&range.start, &buffer).row
17498 ..text::ToPoint::to_point(&range.end, &buffer).row;
17499 Some((
17500 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
17501 selection,
17502 ))
17503 });
17504
17505 let Some((buffer, selection)) = buffer_and_selection else {
17506 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
17507 };
17508
17509 let Some(project) = self.project.as_ref() else {
17510 return Task::ready(Err(anyhow!("editor does not have project")));
17511 };
17512
17513 project.update(cx, |project, cx| {
17514 project.get_permalink_to_line(&buffer, selection, cx)
17515 })
17516 }
17517
17518 pub fn copy_permalink_to_line(
17519 &mut self,
17520 _: &CopyPermalinkToLine,
17521 window: &mut Window,
17522 cx: &mut Context<Self>,
17523 ) {
17524 let permalink_task = self.get_permalink_to_line(cx);
17525 let workspace = self.workspace();
17526
17527 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17528 Ok(permalink) => {
17529 cx.update(|_, cx| {
17530 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
17531 })
17532 .ok();
17533 }
17534 Err(err) => {
17535 let message = format!("Failed to copy permalink: {err}");
17536
17537 anyhow::Result::<()>::Err(err).log_err();
17538
17539 if let Some(workspace) = workspace {
17540 workspace
17541 .update_in(cx, |workspace, _, cx| {
17542 struct CopyPermalinkToLine;
17543
17544 workspace.show_toast(
17545 Toast::new(
17546 NotificationId::unique::<CopyPermalinkToLine>(),
17547 message,
17548 ),
17549 cx,
17550 )
17551 })
17552 .ok();
17553 }
17554 }
17555 })
17556 .detach();
17557 }
17558
17559 pub fn copy_file_location(
17560 &mut self,
17561 _: &CopyFileLocation,
17562 _: &mut Window,
17563 cx: &mut Context<Self>,
17564 ) {
17565 let selection = self.selections.newest::<Point>(cx).start.row + 1;
17566 if let Some(file) = self.target_file(cx) {
17567 if let Some(path) = file.path().to_str() {
17568 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
17569 }
17570 }
17571 }
17572
17573 pub fn open_permalink_to_line(
17574 &mut self,
17575 _: &OpenPermalinkToLine,
17576 window: &mut Window,
17577 cx: &mut Context<Self>,
17578 ) {
17579 let permalink_task = self.get_permalink_to_line(cx);
17580 let workspace = self.workspace();
17581
17582 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
17583 Ok(permalink) => {
17584 cx.update(|_, cx| {
17585 cx.open_url(permalink.as_ref());
17586 })
17587 .ok();
17588 }
17589 Err(err) => {
17590 let message = format!("Failed to open permalink: {err}");
17591
17592 anyhow::Result::<()>::Err(err).log_err();
17593
17594 if let Some(workspace) = workspace {
17595 workspace
17596 .update(cx, |workspace, cx| {
17597 struct OpenPermalinkToLine;
17598
17599 workspace.show_toast(
17600 Toast::new(
17601 NotificationId::unique::<OpenPermalinkToLine>(),
17602 message,
17603 ),
17604 cx,
17605 )
17606 })
17607 .ok();
17608 }
17609 }
17610 })
17611 .detach();
17612 }
17613
17614 pub fn insert_uuid_v4(
17615 &mut self,
17616 _: &InsertUuidV4,
17617 window: &mut Window,
17618 cx: &mut Context<Self>,
17619 ) {
17620 self.insert_uuid(UuidVersion::V4, window, cx);
17621 }
17622
17623 pub fn insert_uuid_v7(
17624 &mut self,
17625 _: &InsertUuidV7,
17626 window: &mut Window,
17627 cx: &mut Context<Self>,
17628 ) {
17629 self.insert_uuid(UuidVersion::V7, window, cx);
17630 }
17631
17632 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
17633 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17634 self.transact(window, cx, |this, window, cx| {
17635 let edits = this
17636 .selections
17637 .all::<Point>(cx)
17638 .into_iter()
17639 .map(|selection| {
17640 let uuid = match version {
17641 UuidVersion::V4 => uuid::Uuid::new_v4(),
17642 UuidVersion::V7 => uuid::Uuid::now_v7(),
17643 };
17644
17645 (selection.range(), uuid.to_string())
17646 });
17647 this.edit(edits, cx);
17648 this.refresh_inline_completion(true, false, window, cx);
17649 });
17650 }
17651
17652 pub fn open_selections_in_multibuffer(
17653 &mut self,
17654 _: &OpenSelectionsInMultibuffer,
17655 window: &mut Window,
17656 cx: &mut Context<Self>,
17657 ) {
17658 let multibuffer = self.buffer.read(cx);
17659
17660 let Some(buffer) = multibuffer.as_singleton() else {
17661 return;
17662 };
17663
17664 let Some(workspace) = self.workspace() else {
17665 return;
17666 };
17667
17668 let locations = self
17669 .selections
17670 .disjoint_anchors()
17671 .iter()
17672 .map(|selection| {
17673 let range = if selection.reversed {
17674 selection.end.text_anchor..selection.start.text_anchor
17675 } else {
17676 selection.start.text_anchor..selection.end.text_anchor
17677 };
17678 Location {
17679 buffer: buffer.clone(),
17680 range,
17681 }
17682 })
17683 .collect::<Vec<_>>();
17684
17685 let title = multibuffer.title(cx).to_string();
17686
17687 cx.spawn_in(window, async move |_, cx| {
17688 workspace.update_in(cx, |workspace, window, cx| {
17689 Self::open_locations_in_multibuffer(
17690 workspace,
17691 locations,
17692 format!("Selections for '{title}'"),
17693 false,
17694 MultibufferSelectionMode::All,
17695 window,
17696 cx,
17697 );
17698 })
17699 })
17700 .detach();
17701 }
17702
17703 /// Adds a row highlight for the given range. If a row has multiple highlights, the
17704 /// last highlight added will be used.
17705 ///
17706 /// If the range ends at the beginning of a line, then that line will not be highlighted.
17707 pub fn highlight_rows<T: 'static>(
17708 &mut self,
17709 range: Range<Anchor>,
17710 color: Hsla,
17711 options: RowHighlightOptions,
17712 cx: &mut Context<Self>,
17713 ) {
17714 let snapshot = self.buffer().read(cx).snapshot(cx);
17715 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17716 let ix = row_highlights.binary_search_by(|highlight| {
17717 Ordering::Equal
17718 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
17719 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
17720 });
17721
17722 if let Err(mut ix) = ix {
17723 let index = post_inc(&mut self.highlight_order);
17724
17725 // If this range intersects with the preceding highlight, then merge it with
17726 // the preceding highlight. Otherwise insert a new highlight.
17727 let mut merged = false;
17728 if ix > 0 {
17729 let prev_highlight = &mut row_highlights[ix - 1];
17730 if prev_highlight
17731 .range
17732 .end
17733 .cmp(&range.start, &snapshot)
17734 .is_ge()
17735 {
17736 ix -= 1;
17737 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
17738 prev_highlight.range.end = range.end;
17739 }
17740 merged = true;
17741 prev_highlight.index = index;
17742 prev_highlight.color = color;
17743 prev_highlight.options = options;
17744 }
17745 }
17746
17747 if !merged {
17748 row_highlights.insert(
17749 ix,
17750 RowHighlight {
17751 range: range.clone(),
17752 index,
17753 color,
17754 options,
17755 type_id: TypeId::of::<T>(),
17756 },
17757 );
17758 }
17759
17760 // If any of the following highlights intersect with this one, merge them.
17761 while let Some(next_highlight) = row_highlights.get(ix + 1) {
17762 let highlight = &row_highlights[ix];
17763 if next_highlight
17764 .range
17765 .start
17766 .cmp(&highlight.range.end, &snapshot)
17767 .is_le()
17768 {
17769 if next_highlight
17770 .range
17771 .end
17772 .cmp(&highlight.range.end, &snapshot)
17773 .is_gt()
17774 {
17775 row_highlights[ix].range.end = next_highlight.range.end;
17776 }
17777 row_highlights.remove(ix + 1);
17778 } else {
17779 break;
17780 }
17781 }
17782 }
17783 }
17784
17785 /// Remove any highlighted row ranges of the given type that intersect the
17786 /// given ranges.
17787 pub fn remove_highlighted_rows<T: 'static>(
17788 &mut self,
17789 ranges_to_remove: Vec<Range<Anchor>>,
17790 cx: &mut Context<Self>,
17791 ) {
17792 let snapshot = self.buffer().read(cx).snapshot(cx);
17793 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
17794 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
17795 row_highlights.retain(|highlight| {
17796 while let Some(range_to_remove) = ranges_to_remove.peek() {
17797 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
17798 Ordering::Less | Ordering::Equal => {
17799 ranges_to_remove.next();
17800 }
17801 Ordering::Greater => {
17802 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
17803 Ordering::Less | Ordering::Equal => {
17804 return false;
17805 }
17806 Ordering::Greater => break,
17807 }
17808 }
17809 }
17810 }
17811
17812 true
17813 })
17814 }
17815
17816 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
17817 pub fn clear_row_highlights<T: 'static>(&mut self) {
17818 self.highlighted_rows.remove(&TypeId::of::<T>());
17819 }
17820
17821 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
17822 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
17823 self.highlighted_rows
17824 .get(&TypeId::of::<T>())
17825 .map_or(&[] as &[_], |vec| vec.as_slice())
17826 .iter()
17827 .map(|highlight| (highlight.range.clone(), highlight.color))
17828 }
17829
17830 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
17831 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
17832 /// Allows to ignore certain kinds of highlights.
17833 pub fn highlighted_display_rows(
17834 &self,
17835 window: &mut Window,
17836 cx: &mut App,
17837 ) -> BTreeMap<DisplayRow, LineHighlight> {
17838 let snapshot = self.snapshot(window, cx);
17839 let mut used_highlight_orders = HashMap::default();
17840 self.highlighted_rows
17841 .iter()
17842 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
17843 .fold(
17844 BTreeMap::<DisplayRow, LineHighlight>::new(),
17845 |mut unique_rows, highlight| {
17846 let start = highlight.range.start.to_display_point(&snapshot);
17847 let end = highlight.range.end.to_display_point(&snapshot);
17848 let start_row = start.row().0;
17849 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
17850 && end.column() == 0
17851 {
17852 end.row().0.saturating_sub(1)
17853 } else {
17854 end.row().0
17855 };
17856 for row in start_row..=end_row {
17857 let used_index =
17858 used_highlight_orders.entry(row).or_insert(highlight.index);
17859 if highlight.index >= *used_index {
17860 *used_index = highlight.index;
17861 unique_rows.insert(
17862 DisplayRow(row),
17863 LineHighlight {
17864 include_gutter: highlight.options.include_gutter,
17865 border: None,
17866 background: highlight.color.into(),
17867 type_id: Some(highlight.type_id),
17868 },
17869 );
17870 }
17871 }
17872 unique_rows
17873 },
17874 )
17875 }
17876
17877 pub fn highlighted_display_row_for_autoscroll(
17878 &self,
17879 snapshot: &DisplaySnapshot,
17880 ) -> Option<DisplayRow> {
17881 self.highlighted_rows
17882 .values()
17883 .flat_map(|highlighted_rows| highlighted_rows.iter())
17884 .filter_map(|highlight| {
17885 if highlight.options.autoscroll {
17886 Some(highlight.range.start.to_display_point(snapshot).row())
17887 } else {
17888 None
17889 }
17890 })
17891 .min()
17892 }
17893
17894 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
17895 self.highlight_background::<SearchWithinRange>(
17896 ranges,
17897 |colors| colors.editor_document_highlight_read_background,
17898 cx,
17899 )
17900 }
17901
17902 pub fn set_breadcrumb_header(&mut self, new_header: String) {
17903 self.breadcrumb_header = Some(new_header);
17904 }
17905
17906 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
17907 self.clear_background_highlights::<SearchWithinRange>(cx);
17908 }
17909
17910 pub fn highlight_background<T: 'static>(
17911 &mut self,
17912 ranges: &[Range<Anchor>],
17913 color_fetcher: fn(&ThemeColors) -> Hsla,
17914 cx: &mut Context<Self>,
17915 ) {
17916 self.background_highlights
17917 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17918 self.scrollbar_marker_state.dirty = true;
17919 cx.notify();
17920 }
17921
17922 pub fn clear_background_highlights<T: 'static>(
17923 &mut self,
17924 cx: &mut Context<Self>,
17925 ) -> Option<BackgroundHighlight> {
17926 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
17927 if !text_highlights.1.is_empty() {
17928 self.scrollbar_marker_state.dirty = true;
17929 cx.notify();
17930 }
17931 Some(text_highlights)
17932 }
17933
17934 pub fn highlight_gutter<T: 'static>(
17935 &mut self,
17936 ranges: &[Range<Anchor>],
17937 color_fetcher: fn(&App) -> Hsla,
17938 cx: &mut Context<Self>,
17939 ) {
17940 self.gutter_highlights
17941 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
17942 cx.notify();
17943 }
17944
17945 pub fn clear_gutter_highlights<T: 'static>(
17946 &mut self,
17947 cx: &mut Context<Self>,
17948 ) -> Option<GutterHighlight> {
17949 cx.notify();
17950 self.gutter_highlights.remove(&TypeId::of::<T>())
17951 }
17952
17953 #[cfg(feature = "test-support")]
17954 pub fn all_text_background_highlights(
17955 &self,
17956 window: &mut Window,
17957 cx: &mut Context<Self>,
17958 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
17959 let snapshot = self.snapshot(window, cx);
17960 let buffer = &snapshot.buffer_snapshot;
17961 let start = buffer.anchor_before(0);
17962 let end = buffer.anchor_after(buffer.len());
17963 let theme = cx.theme().colors();
17964 self.background_highlights_in_range(start..end, &snapshot, theme)
17965 }
17966
17967 #[cfg(feature = "test-support")]
17968 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
17969 let snapshot = self.buffer().read(cx).snapshot(cx);
17970
17971 let highlights = self
17972 .background_highlights
17973 .get(&TypeId::of::<items::BufferSearchHighlights>());
17974
17975 if let Some((_color, ranges)) = highlights {
17976 ranges
17977 .iter()
17978 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
17979 .collect_vec()
17980 } else {
17981 vec![]
17982 }
17983 }
17984
17985 fn document_highlights_for_position<'a>(
17986 &'a self,
17987 position: Anchor,
17988 buffer: &'a MultiBufferSnapshot,
17989 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
17990 let read_highlights = self
17991 .background_highlights
17992 .get(&TypeId::of::<DocumentHighlightRead>())
17993 .map(|h| &h.1);
17994 let write_highlights = self
17995 .background_highlights
17996 .get(&TypeId::of::<DocumentHighlightWrite>())
17997 .map(|h| &h.1);
17998 let left_position = position.bias_left(buffer);
17999 let right_position = position.bias_right(buffer);
18000 read_highlights
18001 .into_iter()
18002 .chain(write_highlights)
18003 .flat_map(move |ranges| {
18004 let start_ix = match ranges.binary_search_by(|probe| {
18005 let cmp = probe.end.cmp(&left_position, buffer);
18006 if cmp.is_ge() {
18007 Ordering::Greater
18008 } else {
18009 Ordering::Less
18010 }
18011 }) {
18012 Ok(i) | Err(i) => i,
18013 };
18014
18015 ranges[start_ix..]
18016 .iter()
18017 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18018 })
18019 }
18020
18021 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18022 self.background_highlights
18023 .get(&TypeId::of::<T>())
18024 .map_or(false, |(_, highlights)| !highlights.is_empty())
18025 }
18026
18027 pub fn background_highlights_in_range(
18028 &self,
18029 search_range: Range<Anchor>,
18030 display_snapshot: &DisplaySnapshot,
18031 theme: &ThemeColors,
18032 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18033 let mut results = Vec::new();
18034 for (color_fetcher, ranges) in self.background_highlights.values() {
18035 let color = color_fetcher(theme);
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 for range in &ranges[start_ix..] {
18049 if range
18050 .start
18051 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18052 .is_ge()
18053 {
18054 break;
18055 }
18056
18057 let start = range.start.to_display_point(display_snapshot);
18058 let end = range.end.to_display_point(display_snapshot);
18059 results.push((start..end, color))
18060 }
18061 }
18062 results
18063 }
18064
18065 pub fn background_highlight_row_ranges<T: 'static>(
18066 &self,
18067 search_range: Range<Anchor>,
18068 display_snapshot: &DisplaySnapshot,
18069 count: usize,
18070 ) -> Vec<RangeInclusive<DisplayPoint>> {
18071 let mut results = Vec::new();
18072 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18073 return vec![];
18074 };
18075
18076 let start_ix = match ranges.binary_search_by(|probe| {
18077 let cmp = probe
18078 .end
18079 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18080 if cmp.is_gt() {
18081 Ordering::Greater
18082 } else {
18083 Ordering::Less
18084 }
18085 }) {
18086 Ok(i) | Err(i) => i,
18087 };
18088 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18089 if let (Some(start_display), Some(end_display)) = (start, end) {
18090 results.push(
18091 start_display.to_display_point(display_snapshot)
18092 ..=end_display.to_display_point(display_snapshot),
18093 );
18094 }
18095 };
18096 let mut start_row: Option<Point> = None;
18097 let mut end_row: Option<Point> = None;
18098 if ranges.len() > count {
18099 return Vec::new();
18100 }
18101 for range in &ranges[start_ix..] {
18102 if range
18103 .start
18104 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18105 .is_ge()
18106 {
18107 break;
18108 }
18109 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18110 if let Some(current_row) = &end_row {
18111 if end.row == current_row.row {
18112 continue;
18113 }
18114 }
18115 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18116 if start_row.is_none() {
18117 assert_eq!(end_row, None);
18118 start_row = Some(start);
18119 end_row = Some(end);
18120 continue;
18121 }
18122 if let Some(current_end) = end_row.as_mut() {
18123 if start.row > current_end.row + 1 {
18124 push_region(start_row, end_row);
18125 start_row = Some(start);
18126 end_row = Some(end);
18127 } else {
18128 // Merge two hunks.
18129 *current_end = end;
18130 }
18131 } else {
18132 unreachable!();
18133 }
18134 }
18135 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18136 push_region(start_row, end_row);
18137 results
18138 }
18139
18140 pub fn gutter_highlights_in_range(
18141 &self,
18142 search_range: Range<Anchor>,
18143 display_snapshot: &DisplaySnapshot,
18144 cx: &App,
18145 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18146 let mut results = Vec::new();
18147 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18148 let color = color_fetcher(cx);
18149 let start_ix = match ranges.binary_search_by(|probe| {
18150 let cmp = probe
18151 .end
18152 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18153 if cmp.is_gt() {
18154 Ordering::Greater
18155 } else {
18156 Ordering::Less
18157 }
18158 }) {
18159 Ok(i) | Err(i) => i,
18160 };
18161 for range in &ranges[start_ix..] {
18162 if range
18163 .start
18164 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18165 .is_ge()
18166 {
18167 break;
18168 }
18169
18170 let start = range.start.to_display_point(display_snapshot);
18171 let end = range.end.to_display_point(display_snapshot);
18172 results.push((start..end, color))
18173 }
18174 }
18175 results
18176 }
18177
18178 /// Get the text ranges corresponding to the redaction query
18179 pub fn redacted_ranges(
18180 &self,
18181 search_range: Range<Anchor>,
18182 display_snapshot: &DisplaySnapshot,
18183 cx: &App,
18184 ) -> Vec<Range<DisplayPoint>> {
18185 display_snapshot
18186 .buffer_snapshot
18187 .redacted_ranges(search_range, |file| {
18188 if let Some(file) = file {
18189 file.is_private()
18190 && EditorSettings::get(
18191 Some(SettingsLocation {
18192 worktree_id: file.worktree_id(cx),
18193 path: file.path().as_ref(),
18194 }),
18195 cx,
18196 )
18197 .redact_private_values
18198 } else {
18199 false
18200 }
18201 })
18202 .map(|range| {
18203 range.start.to_display_point(display_snapshot)
18204 ..range.end.to_display_point(display_snapshot)
18205 })
18206 .collect()
18207 }
18208
18209 pub fn highlight_text<T: 'static>(
18210 &mut self,
18211 ranges: Vec<Range<Anchor>>,
18212 style: HighlightStyle,
18213 cx: &mut Context<Self>,
18214 ) {
18215 self.display_map.update(cx, |map, _| {
18216 map.highlight_text(TypeId::of::<T>(), ranges, style)
18217 });
18218 cx.notify();
18219 }
18220
18221 pub(crate) fn highlight_inlays<T: 'static>(
18222 &mut self,
18223 highlights: Vec<InlayHighlight>,
18224 style: HighlightStyle,
18225 cx: &mut Context<Self>,
18226 ) {
18227 self.display_map.update(cx, |map, _| {
18228 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18229 });
18230 cx.notify();
18231 }
18232
18233 pub fn text_highlights<'a, T: 'static>(
18234 &'a self,
18235 cx: &'a App,
18236 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18237 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18238 }
18239
18240 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18241 let cleared = self
18242 .display_map
18243 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18244 if cleared {
18245 cx.notify();
18246 }
18247 }
18248
18249 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18250 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18251 && self.focus_handle.is_focused(window)
18252 }
18253
18254 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18255 self.show_cursor_when_unfocused = is_enabled;
18256 cx.notify();
18257 }
18258
18259 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18260 cx.notify();
18261 }
18262
18263 fn on_debug_session_event(
18264 &mut self,
18265 _session: Entity<Session>,
18266 event: &SessionEvent,
18267 cx: &mut Context<Self>,
18268 ) {
18269 match event {
18270 SessionEvent::InvalidateInlineValue => {
18271 self.refresh_inline_values(cx);
18272 }
18273 _ => {}
18274 }
18275 }
18276
18277 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18278 let Some(project) = self.project.clone() else {
18279 return;
18280 };
18281
18282 if !self.inline_value_cache.enabled {
18283 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18284 self.splice_inlays(&inlays, Vec::new(), cx);
18285 return;
18286 }
18287
18288 let current_execution_position = self
18289 .highlighted_rows
18290 .get(&TypeId::of::<ActiveDebugLine>())
18291 .and_then(|lines| lines.last().map(|line| line.range.start));
18292
18293 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18294 let inline_values = editor
18295 .update(cx, |editor, cx| {
18296 let Some(current_execution_position) = current_execution_position else {
18297 return Some(Task::ready(Ok(Vec::new())));
18298 };
18299
18300 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18301 let snapshot = buffer.snapshot(cx);
18302
18303 let excerpt = snapshot.excerpt_containing(
18304 current_execution_position..current_execution_position,
18305 )?;
18306
18307 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18308 })?;
18309
18310 let range =
18311 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18312
18313 project.inline_values(buffer, range, cx)
18314 })
18315 .ok()
18316 .flatten()?
18317 .await
18318 .context("refreshing debugger inlays")
18319 .log_err()?;
18320
18321 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18322
18323 for (buffer_id, inline_value) in inline_values
18324 .into_iter()
18325 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18326 {
18327 buffer_inline_values
18328 .entry(buffer_id)
18329 .or_default()
18330 .push(inline_value);
18331 }
18332
18333 editor
18334 .update(cx, |editor, cx| {
18335 let snapshot = editor.buffer.read(cx).snapshot(cx);
18336 let mut new_inlays = Vec::default();
18337
18338 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18339 let buffer_id = buffer_snapshot.remote_id();
18340 buffer_inline_values
18341 .get(&buffer_id)
18342 .into_iter()
18343 .flatten()
18344 .for_each(|hint| {
18345 let inlay = Inlay::debugger_hint(
18346 post_inc(&mut editor.next_inlay_id),
18347 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18348 hint.text(),
18349 );
18350
18351 new_inlays.push(inlay);
18352 });
18353 }
18354
18355 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18356 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18357
18358 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18359 })
18360 .ok()?;
18361 Some(())
18362 });
18363 }
18364
18365 fn on_buffer_event(
18366 &mut self,
18367 multibuffer: &Entity<MultiBuffer>,
18368 event: &multi_buffer::Event,
18369 window: &mut Window,
18370 cx: &mut Context<Self>,
18371 ) {
18372 match event {
18373 multi_buffer::Event::Edited {
18374 singleton_buffer_edited,
18375 edited_buffer: buffer_edited,
18376 } => {
18377 self.scrollbar_marker_state.dirty = true;
18378 self.active_indent_guides_state.dirty = true;
18379 self.refresh_active_diagnostics(cx);
18380 self.refresh_code_actions(window, cx);
18381 self.refresh_selected_text_highlights(true, window, cx);
18382 refresh_matching_bracket_highlights(self, window, cx);
18383 if self.has_active_inline_completion() {
18384 self.update_visible_inline_completion(window, cx);
18385 }
18386 if let Some(buffer) = buffer_edited {
18387 let buffer_id = buffer.read(cx).remote_id();
18388 if !self.registered_buffers.contains_key(&buffer_id) {
18389 if let Some(project) = self.project.as_ref() {
18390 project.update(cx, |project, cx| {
18391 self.registered_buffers.insert(
18392 buffer_id,
18393 project.register_buffer_with_language_servers(&buffer, cx),
18394 );
18395 })
18396 }
18397 }
18398 }
18399 cx.emit(EditorEvent::BufferEdited);
18400 cx.emit(SearchEvent::MatchesInvalidated);
18401 if *singleton_buffer_edited {
18402 if let Some(project) = &self.project {
18403 #[allow(clippy::mutable_key_type)]
18404 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18405 multibuffer
18406 .all_buffers()
18407 .into_iter()
18408 .filter_map(|buffer| {
18409 buffer.update(cx, |buffer, cx| {
18410 let language = buffer.language()?;
18411 let should_discard = project.update(cx, |project, cx| {
18412 project.is_local()
18413 && !project.has_language_servers_for(buffer, cx)
18414 });
18415 should_discard.not().then_some(language.clone())
18416 })
18417 })
18418 .collect::<HashSet<_>>()
18419 });
18420 if !languages_affected.is_empty() {
18421 self.refresh_inlay_hints(
18422 InlayHintRefreshReason::BufferEdited(languages_affected),
18423 cx,
18424 );
18425 }
18426 }
18427 }
18428
18429 let Some(project) = &self.project else { return };
18430 let (telemetry, is_via_ssh) = {
18431 let project = project.read(cx);
18432 let telemetry = project.client().telemetry().clone();
18433 let is_via_ssh = project.is_via_ssh();
18434 (telemetry, is_via_ssh)
18435 };
18436 refresh_linked_ranges(self, window, cx);
18437 telemetry.log_edit_event("editor", is_via_ssh);
18438 }
18439 multi_buffer::Event::ExcerptsAdded {
18440 buffer,
18441 predecessor,
18442 excerpts,
18443 } => {
18444 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18445 let buffer_id = buffer.read(cx).remote_id();
18446 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
18447 if let Some(project) = &self.project {
18448 update_uncommitted_diff_for_buffer(
18449 cx.entity(),
18450 project,
18451 [buffer.clone()],
18452 self.buffer.clone(),
18453 cx,
18454 )
18455 .detach();
18456 }
18457 }
18458 cx.emit(EditorEvent::ExcerptsAdded {
18459 buffer: buffer.clone(),
18460 predecessor: *predecessor,
18461 excerpts: excerpts.clone(),
18462 });
18463 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18464 }
18465 multi_buffer::Event::ExcerptsRemoved {
18466 ids,
18467 removed_buffer_ids,
18468 } => {
18469 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
18470 let buffer = self.buffer.read(cx);
18471 self.registered_buffers
18472 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
18473 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18474 cx.emit(EditorEvent::ExcerptsRemoved {
18475 ids: ids.clone(),
18476 removed_buffer_ids: removed_buffer_ids.clone(),
18477 })
18478 }
18479 multi_buffer::Event::ExcerptsEdited {
18480 excerpt_ids,
18481 buffer_ids,
18482 } => {
18483 self.display_map.update(cx, |map, cx| {
18484 map.unfold_buffers(buffer_ids.iter().copied(), cx)
18485 });
18486 cx.emit(EditorEvent::ExcerptsEdited {
18487 ids: excerpt_ids.clone(),
18488 })
18489 }
18490 multi_buffer::Event::ExcerptsExpanded { ids } => {
18491 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18492 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
18493 }
18494 multi_buffer::Event::Reparsed(buffer_id) => {
18495 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18496 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18497
18498 cx.emit(EditorEvent::Reparsed(*buffer_id));
18499 }
18500 multi_buffer::Event::DiffHunksToggled => {
18501 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18502 }
18503 multi_buffer::Event::LanguageChanged(buffer_id) => {
18504 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
18505 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
18506 cx.emit(EditorEvent::Reparsed(*buffer_id));
18507 cx.notify();
18508 }
18509 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
18510 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
18511 multi_buffer::Event::FileHandleChanged
18512 | multi_buffer::Event::Reloaded
18513 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
18514 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
18515 multi_buffer::Event::DiagnosticsUpdated => {
18516 self.refresh_active_diagnostics(cx);
18517 self.refresh_inline_diagnostics(true, window, cx);
18518 self.scrollbar_marker_state.dirty = true;
18519 cx.notify();
18520 }
18521 _ => {}
18522 };
18523 }
18524
18525 pub fn start_temporary_diff_override(&mut self) {
18526 self.load_diff_task.take();
18527 self.temporary_diff_override = true;
18528 }
18529
18530 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
18531 self.temporary_diff_override = false;
18532 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
18533 self.buffer.update(cx, |buffer, cx| {
18534 buffer.set_all_diff_hunks_collapsed(cx);
18535 });
18536
18537 if let Some(project) = self.project.clone() {
18538 self.load_diff_task = Some(
18539 update_uncommitted_diff_for_buffer(
18540 cx.entity(),
18541 &project,
18542 self.buffer.read(cx).all_buffers(),
18543 self.buffer.clone(),
18544 cx,
18545 )
18546 .shared(),
18547 );
18548 }
18549 }
18550
18551 fn on_display_map_changed(
18552 &mut self,
18553 _: Entity<DisplayMap>,
18554 _: &mut Window,
18555 cx: &mut Context<Self>,
18556 ) {
18557 cx.notify();
18558 }
18559
18560 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
18561 let new_severity = if self.diagnostics_enabled() {
18562 EditorSettings::get_global(cx)
18563 .diagnostics_max_severity
18564 .unwrap_or(DiagnosticSeverity::Hint)
18565 } else {
18566 DiagnosticSeverity::Off
18567 };
18568 self.set_max_diagnostics_severity(new_severity, cx);
18569 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
18570 self.update_edit_prediction_settings(cx);
18571 self.refresh_inline_completion(true, false, window, cx);
18572 self.refresh_inlay_hints(
18573 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
18574 self.selections.newest_anchor().head(),
18575 &self.buffer.read(cx).snapshot(cx),
18576 cx,
18577 )),
18578 cx,
18579 );
18580
18581 let old_cursor_shape = self.cursor_shape;
18582
18583 {
18584 let editor_settings = EditorSettings::get_global(cx);
18585 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
18586 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
18587 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
18588 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
18589 }
18590
18591 if old_cursor_shape != self.cursor_shape {
18592 cx.emit(EditorEvent::CursorShapeChanged);
18593 }
18594
18595 let project_settings = ProjectSettings::get_global(cx);
18596 self.serialize_dirty_buffers =
18597 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
18598
18599 if self.mode.is_full() {
18600 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
18601 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
18602 if self.show_inline_diagnostics != show_inline_diagnostics {
18603 self.show_inline_diagnostics = show_inline_diagnostics;
18604 self.refresh_inline_diagnostics(false, window, cx);
18605 }
18606
18607 if self.git_blame_inline_enabled != inline_blame_enabled {
18608 self.toggle_git_blame_inline_internal(false, window, cx);
18609 }
18610
18611 let minimap_settings = EditorSettings::get_global(cx).minimap;
18612 if self.minimap_visibility.settings_visibility() != minimap_settings.minimap_enabled() {
18613 self.set_minimap_visibility(
18614 MinimapVisibility::for_mode(self.mode(), cx),
18615 window,
18616 cx,
18617 );
18618 } else if let Some(minimap_entity) = self.minimap.as_ref() {
18619 minimap_entity.update(cx, |minimap_editor, cx| {
18620 minimap_editor.update_minimap_configuration(minimap_settings, cx)
18621 })
18622 }
18623 }
18624
18625 cx.notify();
18626 }
18627
18628 pub fn set_searchable(&mut self, searchable: bool) {
18629 self.searchable = searchable;
18630 }
18631
18632 pub fn searchable(&self) -> bool {
18633 self.searchable
18634 }
18635
18636 fn open_proposed_changes_editor(
18637 &mut self,
18638 _: &OpenProposedChangesEditor,
18639 window: &mut Window,
18640 cx: &mut Context<Self>,
18641 ) {
18642 let Some(workspace) = self.workspace() else {
18643 cx.propagate();
18644 return;
18645 };
18646
18647 let selections = self.selections.all::<usize>(cx);
18648 let multi_buffer = self.buffer.read(cx);
18649 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
18650 let mut new_selections_by_buffer = HashMap::default();
18651 for selection in selections {
18652 for (buffer, range, _) in
18653 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
18654 {
18655 let mut range = range.to_point(buffer);
18656 range.start.column = 0;
18657 range.end.column = buffer.line_len(range.end.row);
18658 new_selections_by_buffer
18659 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
18660 .or_insert(Vec::new())
18661 .push(range)
18662 }
18663 }
18664
18665 let proposed_changes_buffers = new_selections_by_buffer
18666 .into_iter()
18667 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
18668 .collect::<Vec<_>>();
18669 let proposed_changes_editor = cx.new(|cx| {
18670 ProposedChangesEditor::new(
18671 "Proposed changes",
18672 proposed_changes_buffers,
18673 self.project.clone(),
18674 window,
18675 cx,
18676 )
18677 });
18678
18679 window.defer(cx, move |window, cx| {
18680 workspace.update(cx, |workspace, cx| {
18681 workspace.active_pane().update(cx, |pane, cx| {
18682 pane.add_item(
18683 Box::new(proposed_changes_editor),
18684 true,
18685 true,
18686 None,
18687 window,
18688 cx,
18689 );
18690 });
18691 });
18692 });
18693 }
18694
18695 pub fn open_excerpts_in_split(
18696 &mut self,
18697 _: &OpenExcerptsSplit,
18698 window: &mut Window,
18699 cx: &mut Context<Self>,
18700 ) {
18701 self.open_excerpts_common(None, true, window, cx)
18702 }
18703
18704 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
18705 self.open_excerpts_common(None, false, window, cx)
18706 }
18707
18708 fn open_excerpts_common(
18709 &mut self,
18710 jump_data: Option<JumpData>,
18711 split: bool,
18712 window: &mut Window,
18713 cx: &mut Context<Self>,
18714 ) {
18715 let Some(workspace) = self.workspace() else {
18716 cx.propagate();
18717 return;
18718 };
18719
18720 if self.buffer.read(cx).is_singleton() {
18721 cx.propagate();
18722 return;
18723 }
18724
18725 let mut new_selections_by_buffer = HashMap::default();
18726 match &jump_data {
18727 Some(JumpData::MultiBufferPoint {
18728 excerpt_id,
18729 position,
18730 anchor,
18731 line_offset_from_top,
18732 }) => {
18733 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18734 if let Some(buffer) = multi_buffer_snapshot
18735 .buffer_id_for_excerpt(*excerpt_id)
18736 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
18737 {
18738 let buffer_snapshot = buffer.read(cx).snapshot();
18739 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
18740 language::ToPoint::to_point(anchor, &buffer_snapshot)
18741 } else {
18742 buffer_snapshot.clip_point(*position, Bias::Left)
18743 };
18744 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
18745 new_selections_by_buffer.insert(
18746 buffer,
18747 (
18748 vec![jump_to_offset..jump_to_offset],
18749 Some(*line_offset_from_top),
18750 ),
18751 );
18752 }
18753 }
18754 Some(JumpData::MultiBufferRow {
18755 row,
18756 line_offset_from_top,
18757 }) => {
18758 let point = MultiBufferPoint::new(row.0, 0);
18759 if let Some((buffer, buffer_point, _)) =
18760 self.buffer.read(cx).point_to_buffer_point(point, cx)
18761 {
18762 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
18763 new_selections_by_buffer
18764 .entry(buffer)
18765 .or_insert((Vec::new(), Some(*line_offset_from_top)))
18766 .0
18767 .push(buffer_offset..buffer_offset)
18768 }
18769 }
18770 None => {
18771 let selections = self.selections.all::<usize>(cx);
18772 let multi_buffer = self.buffer.read(cx);
18773 for selection in selections {
18774 for (snapshot, range, _, anchor) in multi_buffer
18775 .snapshot(cx)
18776 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
18777 {
18778 if let Some(anchor) = anchor {
18779 // selection is in a deleted hunk
18780 let Some(buffer_id) = anchor.buffer_id else {
18781 continue;
18782 };
18783 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
18784 continue;
18785 };
18786 let offset = text::ToOffset::to_offset(
18787 &anchor.text_anchor,
18788 &buffer_handle.read(cx).snapshot(),
18789 );
18790 let range = offset..offset;
18791 new_selections_by_buffer
18792 .entry(buffer_handle)
18793 .or_insert((Vec::new(), None))
18794 .0
18795 .push(range)
18796 } else {
18797 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
18798 else {
18799 continue;
18800 };
18801 new_selections_by_buffer
18802 .entry(buffer_handle)
18803 .or_insert((Vec::new(), None))
18804 .0
18805 .push(range)
18806 }
18807 }
18808 }
18809 }
18810 }
18811
18812 new_selections_by_buffer
18813 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
18814
18815 if new_selections_by_buffer.is_empty() {
18816 return;
18817 }
18818
18819 // We defer the pane interaction because we ourselves are a workspace item
18820 // and activating a new item causes the pane to call a method on us reentrantly,
18821 // which panics if we're on the stack.
18822 window.defer(cx, move |window, cx| {
18823 workspace.update(cx, |workspace, cx| {
18824 let pane = if split {
18825 workspace.adjacent_pane(window, cx)
18826 } else {
18827 workspace.active_pane().clone()
18828 };
18829
18830 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
18831 let editor = buffer
18832 .read(cx)
18833 .file()
18834 .is_none()
18835 .then(|| {
18836 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
18837 // so `workspace.open_project_item` will never find them, always opening a new editor.
18838 // Instead, we try to activate the existing editor in the pane first.
18839 let (editor, pane_item_index) =
18840 pane.read(cx).items().enumerate().find_map(|(i, item)| {
18841 let editor = item.downcast::<Editor>()?;
18842 let singleton_buffer =
18843 editor.read(cx).buffer().read(cx).as_singleton()?;
18844 if singleton_buffer == buffer {
18845 Some((editor, i))
18846 } else {
18847 None
18848 }
18849 })?;
18850 pane.update(cx, |pane, cx| {
18851 pane.activate_item(pane_item_index, true, true, window, cx)
18852 });
18853 Some(editor)
18854 })
18855 .flatten()
18856 .unwrap_or_else(|| {
18857 workspace.open_project_item::<Self>(
18858 pane.clone(),
18859 buffer,
18860 true,
18861 true,
18862 window,
18863 cx,
18864 )
18865 });
18866
18867 editor.update(cx, |editor, cx| {
18868 let autoscroll = match scroll_offset {
18869 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
18870 None => Autoscroll::newest(),
18871 };
18872 let nav_history = editor.nav_history.take();
18873 editor.change_selections(Some(autoscroll), window, cx, |s| {
18874 s.select_ranges(ranges);
18875 });
18876 editor.nav_history = nav_history;
18877 });
18878 }
18879 })
18880 });
18881 }
18882
18883 // For now, don't allow opening excerpts in buffers that aren't backed by
18884 // regular project files.
18885 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
18886 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
18887 }
18888
18889 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
18890 let snapshot = self.buffer.read(cx).read(cx);
18891 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
18892 Some(
18893 ranges
18894 .iter()
18895 .map(move |range| {
18896 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
18897 })
18898 .collect(),
18899 )
18900 }
18901
18902 fn selection_replacement_ranges(
18903 &self,
18904 range: Range<OffsetUtf16>,
18905 cx: &mut App,
18906 ) -> Vec<Range<OffsetUtf16>> {
18907 let selections = self.selections.all::<OffsetUtf16>(cx);
18908 let newest_selection = selections
18909 .iter()
18910 .max_by_key(|selection| selection.id)
18911 .unwrap();
18912 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
18913 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
18914 let snapshot = self.buffer.read(cx).read(cx);
18915 selections
18916 .into_iter()
18917 .map(|mut selection| {
18918 selection.start.0 =
18919 (selection.start.0 as isize).saturating_add(start_delta) as usize;
18920 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
18921 snapshot.clip_offset_utf16(selection.start, Bias::Left)
18922 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
18923 })
18924 .collect()
18925 }
18926
18927 fn report_editor_event(
18928 &self,
18929 event_type: &'static str,
18930 file_extension: Option<String>,
18931 cx: &App,
18932 ) {
18933 if cfg!(any(test, feature = "test-support")) {
18934 return;
18935 }
18936
18937 let Some(project) = &self.project else { return };
18938
18939 // If None, we are in a file without an extension
18940 let file = self
18941 .buffer
18942 .read(cx)
18943 .as_singleton()
18944 .and_then(|b| b.read(cx).file());
18945 let file_extension = file_extension.or(file
18946 .as_ref()
18947 .and_then(|file| Path::new(file.file_name(cx)).extension())
18948 .and_then(|e| e.to_str())
18949 .map(|a| a.to_string()));
18950
18951 let vim_mode = vim_enabled(cx);
18952
18953 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
18954 let copilot_enabled = edit_predictions_provider
18955 == language::language_settings::EditPredictionProvider::Copilot;
18956 let copilot_enabled_for_language = self
18957 .buffer
18958 .read(cx)
18959 .language_settings(cx)
18960 .show_edit_predictions;
18961
18962 let project = project.read(cx);
18963 telemetry::event!(
18964 event_type,
18965 file_extension,
18966 vim_mode,
18967 copilot_enabled,
18968 copilot_enabled_for_language,
18969 edit_predictions_provider,
18970 is_via_ssh = project.is_via_ssh(),
18971 );
18972 }
18973
18974 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
18975 /// with each line being an array of {text, highlight} objects.
18976 fn copy_highlight_json(
18977 &mut self,
18978 _: &CopyHighlightJson,
18979 window: &mut Window,
18980 cx: &mut Context<Self>,
18981 ) {
18982 #[derive(Serialize)]
18983 struct Chunk<'a> {
18984 text: String,
18985 highlight: Option<&'a str>,
18986 }
18987
18988 let snapshot = self.buffer.read(cx).snapshot(cx);
18989 let range = self
18990 .selected_text_range(false, window, cx)
18991 .and_then(|selection| {
18992 if selection.range.is_empty() {
18993 None
18994 } else {
18995 Some(selection.range)
18996 }
18997 })
18998 .unwrap_or_else(|| 0..snapshot.len());
18999
19000 let chunks = snapshot.chunks(range, true);
19001 let mut lines = Vec::new();
19002 let mut line: VecDeque<Chunk> = VecDeque::new();
19003
19004 let Some(style) = self.style.as_ref() else {
19005 return;
19006 };
19007
19008 for chunk in chunks {
19009 let highlight = chunk
19010 .syntax_highlight_id
19011 .and_then(|id| id.name(&style.syntax));
19012 let mut chunk_lines = chunk.text.split('\n').peekable();
19013 while let Some(text) = chunk_lines.next() {
19014 let mut merged_with_last_token = false;
19015 if let Some(last_token) = line.back_mut() {
19016 if last_token.highlight == highlight {
19017 last_token.text.push_str(text);
19018 merged_with_last_token = true;
19019 }
19020 }
19021
19022 if !merged_with_last_token {
19023 line.push_back(Chunk {
19024 text: text.into(),
19025 highlight,
19026 });
19027 }
19028
19029 if chunk_lines.peek().is_some() {
19030 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19031 line.pop_front();
19032 }
19033 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19034 line.pop_back();
19035 }
19036
19037 lines.push(mem::take(&mut line));
19038 }
19039 }
19040 }
19041
19042 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19043 return;
19044 };
19045 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19046 }
19047
19048 pub fn open_context_menu(
19049 &mut self,
19050 _: &OpenContextMenu,
19051 window: &mut Window,
19052 cx: &mut Context<Self>,
19053 ) {
19054 self.request_autoscroll(Autoscroll::newest(), cx);
19055 let position = self.selections.newest_display(cx).start;
19056 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19057 }
19058
19059 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19060 &self.inlay_hint_cache
19061 }
19062
19063 pub fn replay_insert_event(
19064 &mut self,
19065 text: &str,
19066 relative_utf16_range: Option<Range<isize>>,
19067 window: &mut Window,
19068 cx: &mut Context<Self>,
19069 ) {
19070 if !self.input_enabled {
19071 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19072 return;
19073 }
19074 if let Some(relative_utf16_range) = relative_utf16_range {
19075 let selections = self.selections.all::<OffsetUtf16>(cx);
19076 self.change_selections(None, window, cx, |s| {
19077 let new_ranges = selections.into_iter().map(|range| {
19078 let start = OffsetUtf16(
19079 range
19080 .head()
19081 .0
19082 .saturating_add_signed(relative_utf16_range.start),
19083 );
19084 let end = OffsetUtf16(
19085 range
19086 .head()
19087 .0
19088 .saturating_add_signed(relative_utf16_range.end),
19089 );
19090 start..end
19091 });
19092 s.select_ranges(new_ranges);
19093 });
19094 }
19095
19096 self.handle_input(text, window, cx);
19097 }
19098
19099 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19100 let Some(provider) = self.semantics_provider.as_ref() else {
19101 return false;
19102 };
19103
19104 let mut supports = false;
19105 self.buffer().update(cx, |this, cx| {
19106 this.for_each_buffer(|buffer| {
19107 supports |= provider.supports_inlay_hints(buffer, cx);
19108 });
19109 });
19110
19111 supports
19112 }
19113
19114 pub fn is_focused(&self, window: &Window) -> bool {
19115 self.focus_handle.is_focused(window)
19116 }
19117
19118 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19119 cx.emit(EditorEvent::Focused);
19120
19121 if let Some(descendant) = self
19122 .last_focused_descendant
19123 .take()
19124 .and_then(|descendant| descendant.upgrade())
19125 {
19126 window.focus(&descendant);
19127 } else {
19128 if let Some(blame) = self.blame.as_ref() {
19129 blame.update(cx, GitBlame::focus)
19130 }
19131
19132 self.blink_manager.update(cx, BlinkManager::enable);
19133 self.show_cursor_names(window, cx);
19134 self.buffer.update(cx, |buffer, cx| {
19135 buffer.finalize_last_transaction(cx);
19136 if self.leader_id.is_none() {
19137 buffer.set_active_selections(
19138 &self.selections.disjoint_anchors(),
19139 self.selections.line_mode,
19140 self.cursor_shape,
19141 cx,
19142 );
19143 }
19144 });
19145 }
19146 }
19147
19148 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19149 cx.emit(EditorEvent::FocusedIn)
19150 }
19151
19152 fn handle_focus_out(
19153 &mut self,
19154 event: FocusOutEvent,
19155 _window: &mut Window,
19156 cx: &mut Context<Self>,
19157 ) {
19158 if event.blurred != self.focus_handle {
19159 self.last_focused_descendant = Some(event.blurred);
19160 }
19161 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19162 }
19163
19164 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19165 self.blink_manager.update(cx, BlinkManager::disable);
19166 self.buffer
19167 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19168
19169 if let Some(blame) = self.blame.as_ref() {
19170 blame.update(cx, GitBlame::blur)
19171 }
19172 if !self.hover_state.focused(window, cx) {
19173 hide_hover(self, cx);
19174 }
19175 if !self
19176 .context_menu
19177 .borrow()
19178 .as_ref()
19179 .is_some_and(|context_menu| context_menu.focused(window, cx))
19180 {
19181 self.hide_context_menu(window, cx);
19182 }
19183 self.discard_inline_completion(false, cx);
19184 cx.emit(EditorEvent::Blurred);
19185 cx.notify();
19186 }
19187
19188 pub fn register_action<A: Action>(
19189 &mut self,
19190 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19191 ) -> Subscription {
19192 let id = self.next_editor_action_id.post_inc();
19193 let listener = Arc::new(listener);
19194 self.editor_actions.borrow_mut().insert(
19195 id,
19196 Box::new(move |window, _| {
19197 let listener = listener.clone();
19198 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19199 let action = action.downcast_ref().unwrap();
19200 if phase == DispatchPhase::Bubble {
19201 listener(action, window, cx)
19202 }
19203 })
19204 }),
19205 );
19206
19207 let editor_actions = self.editor_actions.clone();
19208 Subscription::new(move || {
19209 editor_actions.borrow_mut().remove(&id);
19210 })
19211 }
19212
19213 pub fn file_header_size(&self) -> u32 {
19214 FILE_HEADER_HEIGHT
19215 }
19216
19217 pub fn restore(
19218 &mut self,
19219 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19220 window: &mut Window,
19221 cx: &mut Context<Self>,
19222 ) {
19223 let workspace = self.workspace();
19224 let project = self.project.as_ref();
19225 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19226 let mut tasks = Vec::new();
19227 for (buffer_id, changes) in revert_changes {
19228 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19229 buffer.update(cx, |buffer, cx| {
19230 buffer.edit(
19231 changes
19232 .into_iter()
19233 .map(|(range, text)| (range, text.to_string())),
19234 None,
19235 cx,
19236 );
19237 });
19238
19239 if let Some(project) =
19240 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19241 {
19242 project.update(cx, |project, cx| {
19243 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19244 })
19245 }
19246 }
19247 }
19248 tasks
19249 });
19250 cx.spawn_in(window, async move |_, cx| {
19251 for (buffer, task) in save_tasks {
19252 let result = task.await;
19253 if result.is_err() {
19254 let Some(path) = buffer
19255 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19256 .ok()
19257 else {
19258 continue;
19259 };
19260 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19261 let Some(task) = cx
19262 .update_window_entity(&workspace, |workspace, window, cx| {
19263 workspace
19264 .open_path_preview(path, None, false, false, false, window, cx)
19265 })
19266 .ok()
19267 else {
19268 continue;
19269 };
19270 task.await.log_err();
19271 }
19272 }
19273 }
19274 })
19275 .detach();
19276 self.change_selections(None, window, cx, |selections| selections.refresh());
19277 }
19278
19279 pub fn to_pixel_point(
19280 &self,
19281 source: multi_buffer::Anchor,
19282 editor_snapshot: &EditorSnapshot,
19283 window: &mut Window,
19284 ) -> Option<gpui::Point<Pixels>> {
19285 let source_point = source.to_display_point(editor_snapshot);
19286 self.display_to_pixel_point(source_point, editor_snapshot, window)
19287 }
19288
19289 pub fn display_to_pixel_point(
19290 &self,
19291 source: DisplayPoint,
19292 editor_snapshot: &EditorSnapshot,
19293 window: &mut Window,
19294 ) -> Option<gpui::Point<Pixels>> {
19295 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19296 let text_layout_details = self.text_layout_details(window);
19297 let scroll_top = text_layout_details
19298 .scroll_anchor
19299 .scroll_position(editor_snapshot)
19300 .y;
19301
19302 if source.row().as_f32() < scroll_top.floor() {
19303 return None;
19304 }
19305 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19306 let source_y = line_height * (source.row().as_f32() - scroll_top);
19307 Some(gpui::Point::new(source_x, source_y))
19308 }
19309
19310 pub fn has_visible_completions_menu(&self) -> bool {
19311 !self.edit_prediction_preview_is_active()
19312 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19313 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19314 })
19315 }
19316
19317 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19318 if self.mode.is_minimap() {
19319 return;
19320 }
19321 self.addons
19322 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19323 }
19324
19325 pub fn unregister_addon<T: Addon>(&mut self) {
19326 self.addons.remove(&std::any::TypeId::of::<T>());
19327 }
19328
19329 pub fn addon<T: Addon>(&self) -> Option<&T> {
19330 let type_id = std::any::TypeId::of::<T>();
19331 self.addons
19332 .get(&type_id)
19333 .and_then(|item| item.to_any().downcast_ref::<T>())
19334 }
19335
19336 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
19337 let type_id = std::any::TypeId::of::<T>();
19338 self.addons
19339 .get_mut(&type_id)
19340 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
19341 }
19342
19343 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
19344 let text_layout_details = self.text_layout_details(window);
19345 let style = &text_layout_details.editor_style;
19346 let font_id = window.text_system().resolve_font(&style.text.font());
19347 let font_size = style.text.font_size.to_pixels(window.rem_size());
19348 let line_height = style.text.line_height_in_pixels(window.rem_size());
19349 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
19350
19351 gpui::Size::new(em_width, line_height)
19352 }
19353
19354 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
19355 self.load_diff_task.clone()
19356 }
19357
19358 fn read_metadata_from_db(
19359 &mut self,
19360 item_id: u64,
19361 workspace_id: WorkspaceId,
19362 window: &mut Window,
19363 cx: &mut Context<Editor>,
19364 ) {
19365 if self.is_singleton(cx)
19366 && !self.mode.is_minimap()
19367 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
19368 {
19369 let buffer_snapshot = OnceCell::new();
19370
19371 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
19372 if !folds.is_empty() {
19373 let snapshot =
19374 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19375 self.fold_ranges(
19376 folds
19377 .into_iter()
19378 .map(|(start, end)| {
19379 snapshot.clip_offset(start, Bias::Left)
19380 ..snapshot.clip_offset(end, Bias::Right)
19381 })
19382 .collect(),
19383 false,
19384 window,
19385 cx,
19386 );
19387 }
19388 }
19389
19390 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
19391 if !selections.is_empty() {
19392 let snapshot =
19393 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
19394 self.change_selections(None, window, cx, |s| {
19395 s.select_ranges(selections.into_iter().map(|(start, end)| {
19396 snapshot.clip_offset(start, Bias::Left)
19397 ..snapshot.clip_offset(end, Bias::Right)
19398 }));
19399 });
19400 }
19401 };
19402 }
19403
19404 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
19405 }
19406}
19407
19408fn vim_enabled(cx: &App) -> bool {
19409 cx.global::<SettingsStore>()
19410 .raw_user_settings()
19411 .get("vim_mode")
19412 == Some(&serde_json::Value::Bool(true))
19413}
19414
19415// Consider user intent and default settings
19416fn choose_completion_range(
19417 completion: &Completion,
19418 intent: CompletionIntent,
19419 buffer: &Entity<Buffer>,
19420 cx: &mut Context<Editor>,
19421) -> Range<usize> {
19422 fn should_replace(
19423 completion: &Completion,
19424 insert_range: &Range<text::Anchor>,
19425 intent: CompletionIntent,
19426 completion_mode_setting: LspInsertMode,
19427 buffer: &Buffer,
19428 ) -> bool {
19429 // specific actions take precedence over settings
19430 match intent {
19431 CompletionIntent::CompleteWithInsert => return false,
19432 CompletionIntent::CompleteWithReplace => return true,
19433 CompletionIntent::Complete | CompletionIntent::Compose => {}
19434 }
19435
19436 match completion_mode_setting {
19437 LspInsertMode::Insert => false,
19438 LspInsertMode::Replace => true,
19439 LspInsertMode::ReplaceSubsequence => {
19440 let mut text_to_replace = buffer.chars_for_range(
19441 buffer.anchor_before(completion.replace_range.start)
19442 ..buffer.anchor_after(completion.replace_range.end),
19443 );
19444 let mut completion_text = completion.new_text.chars();
19445
19446 // is `text_to_replace` a subsequence of `completion_text`
19447 text_to_replace
19448 .all(|needle_ch| completion_text.any(|haystack_ch| haystack_ch == needle_ch))
19449 }
19450 LspInsertMode::ReplaceSuffix => {
19451 let range_after_cursor = insert_range.end..completion.replace_range.end;
19452
19453 let text_after_cursor = buffer
19454 .text_for_range(
19455 buffer.anchor_before(range_after_cursor.start)
19456 ..buffer.anchor_after(range_after_cursor.end),
19457 )
19458 .collect::<String>();
19459 completion.new_text.ends_with(&text_after_cursor)
19460 }
19461 }
19462 }
19463
19464 let buffer = buffer.read(cx);
19465
19466 if let CompletionSource::Lsp {
19467 insert_range: Some(insert_range),
19468 ..
19469 } = &completion.source
19470 {
19471 let completion_mode_setting =
19472 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
19473 .completions
19474 .lsp_insert_mode;
19475
19476 if !should_replace(
19477 completion,
19478 &insert_range,
19479 intent,
19480 completion_mode_setting,
19481 buffer,
19482 ) {
19483 return insert_range.to_offset(buffer);
19484 }
19485 }
19486
19487 completion.replace_range.to_offset(buffer)
19488}
19489
19490fn insert_extra_newline_brackets(
19491 buffer: &MultiBufferSnapshot,
19492 range: Range<usize>,
19493 language: &language::LanguageScope,
19494) -> bool {
19495 let leading_whitespace_len = buffer
19496 .reversed_chars_at(range.start)
19497 .take_while(|c| c.is_whitespace() && *c != '\n')
19498 .map(|c| c.len_utf8())
19499 .sum::<usize>();
19500 let trailing_whitespace_len = buffer
19501 .chars_at(range.end)
19502 .take_while(|c| c.is_whitespace() && *c != '\n')
19503 .map(|c| c.len_utf8())
19504 .sum::<usize>();
19505 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
19506
19507 language.brackets().any(|(pair, enabled)| {
19508 let pair_start = pair.start.trim_end();
19509 let pair_end = pair.end.trim_start();
19510
19511 enabled
19512 && pair.newline
19513 && buffer.contains_str_at(range.end, pair_end)
19514 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
19515 })
19516}
19517
19518fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
19519 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
19520 [(buffer, range, _)] => (*buffer, range.clone()),
19521 _ => return false,
19522 };
19523 let pair = {
19524 let mut result: Option<BracketMatch> = None;
19525
19526 for pair in buffer
19527 .all_bracket_ranges(range.clone())
19528 .filter(move |pair| {
19529 pair.open_range.start <= range.start && pair.close_range.end >= range.end
19530 })
19531 {
19532 let len = pair.close_range.end - pair.open_range.start;
19533
19534 if let Some(existing) = &result {
19535 let existing_len = existing.close_range.end - existing.open_range.start;
19536 if len > existing_len {
19537 continue;
19538 }
19539 }
19540
19541 result = Some(pair);
19542 }
19543
19544 result
19545 };
19546 let Some(pair) = pair else {
19547 return false;
19548 };
19549 pair.newline_only
19550 && buffer
19551 .chars_for_range(pair.open_range.end..range.start)
19552 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
19553 .all(|c| c.is_whitespace() && c != '\n')
19554}
19555
19556fn update_uncommitted_diff_for_buffer(
19557 editor: Entity<Editor>,
19558 project: &Entity<Project>,
19559 buffers: impl IntoIterator<Item = Entity<Buffer>>,
19560 buffer: Entity<MultiBuffer>,
19561 cx: &mut App,
19562) -> Task<()> {
19563 let mut tasks = Vec::new();
19564 project.update(cx, |project, cx| {
19565 for buffer in buffers {
19566 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
19567 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
19568 }
19569 }
19570 });
19571 cx.spawn(async move |cx| {
19572 let diffs = future::join_all(tasks).await;
19573 if editor
19574 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
19575 .unwrap_or(false)
19576 {
19577 return;
19578 }
19579
19580 buffer
19581 .update(cx, |buffer, cx| {
19582 for diff in diffs.into_iter().flatten() {
19583 buffer.add_diff(diff, cx);
19584 }
19585 })
19586 .ok();
19587 })
19588}
19589
19590pub trait CollaborationHub {
19591 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
19592 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
19593 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
19594}
19595
19596impl CollaborationHub for Entity<Project> {
19597 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
19598 self.read(cx).collaborators()
19599 }
19600
19601 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
19602 self.read(cx).user_store().read(cx).participant_indices()
19603 }
19604
19605 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
19606 let this = self.read(cx);
19607 let user_ids = this.collaborators().values().map(|c| c.user_id);
19608 this.user_store().read(cx).participant_names(user_ids, cx)
19609 }
19610}
19611
19612pub trait SemanticsProvider {
19613 fn hover(
19614 &self,
19615 buffer: &Entity<Buffer>,
19616 position: text::Anchor,
19617 cx: &mut App,
19618 ) -> Option<Task<Vec<project::Hover>>>;
19619
19620 fn inline_values(
19621 &self,
19622 buffer_handle: Entity<Buffer>,
19623 range: Range<text::Anchor>,
19624 cx: &mut App,
19625 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19626
19627 fn inlay_hints(
19628 &self,
19629 buffer_handle: Entity<Buffer>,
19630 range: Range<text::Anchor>,
19631 cx: &mut App,
19632 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
19633
19634 fn resolve_inlay_hint(
19635 &self,
19636 hint: InlayHint,
19637 buffer_handle: Entity<Buffer>,
19638 server_id: LanguageServerId,
19639 cx: &mut App,
19640 ) -> Option<Task<anyhow::Result<InlayHint>>>;
19641
19642 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
19643
19644 fn document_highlights(
19645 &self,
19646 buffer: &Entity<Buffer>,
19647 position: text::Anchor,
19648 cx: &mut App,
19649 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
19650
19651 fn definitions(
19652 &self,
19653 buffer: &Entity<Buffer>,
19654 position: text::Anchor,
19655 kind: GotoDefinitionKind,
19656 cx: &mut App,
19657 ) -> Option<Task<Result<Vec<LocationLink>>>>;
19658
19659 fn range_for_rename(
19660 &self,
19661 buffer: &Entity<Buffer>,
19662 position: text::Anchor,
19663 cx: &mut App,
19664 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
19665
19666 fn perform_rename(
19667 &self,
19668 buffer: &Entity<Buffer>,
19669 position: text::Anchor,
19670 new_name: String,
19671 cx: &mut App,
19672 ) -> Option<Task<Result<ProjectTransaction>>>;
19673}
19674
19675pub trait CompletionProvider {
19676 fn completions(
19677 &self,
19678 excerpt_id: ExcerptId,
19679 buffer: &Entity<Buffer>,
19680 buffer_position: text::Anchor,
19681 trigger: CompletionContext,
19682 window: &mut Window,
19683 cx: &mut Context<Editor>,
19684 ) -> Task<Result<Option<Vec<Completion>>>>;
19685
19686 fn resolve_completions(
19687 &self,
19688 buffer: Entity<Buffer>,
19689 completion_indices: Vec<usize>,
19690 completions: Rc<RefCell<Box<[Completion]>>>,
19691 cx: &mut Context<Editor>,
19692 ) -> Task<Result<bool>>;
19693
19694 fn apply_additional_edits_for_completion(
19695 &self,
19696 _buffer: Entity<Buffer>,
19697 _completions: Rc<RefCell<Box<[Completion]>>>,
19698 _completion_index: usize,
19699 _push_to_history: bool,
19700 _cx: &mut Context<Editor>,
19701 ) -> Task<Result<Option<language::Transaction>>> {
19702 Task::ready(Ok(None))
19703 }
19704
19705 fn is_completion_trigger(
19706 &self,
19707 buffer: &Entity<Buffer>,
19708 position: language::Anchor,
19709 text: &str,
19710 trigger_in_words: bool,
19711 cx: &mut Context<Editor>,
19712 ) -> bool;
19713
19714 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
19715
19716 fn sort_completions(&self) -> bool {
19717 true
19718 }
19719
19720 fn filter_completions(&self) -> bool {
19721 true
19722 }
19723}
19724
19725pub trait CodeActionProvider {
19726 fn id(&self) -> Arc<str>;
19727
19728 fn code_actions(
19729 &self,
19730 buffer: &Entity<Buffer>,
19731 range: Range<text::Anchor>,
19732 window: &mut Window,
19733 cx: &mut App,
19734 ) -> Task<Result<Vec<CodeAction>>>;
19735
19736 fn apply_code_action(
19737 &self,
19738 buffer_handle: Entity<Buffer>,
19739 action: CodeAction,
19740 excerpt_id: ExcerptId,
19741 push_to_history: bool,
19742 window: &mut Window,
19743 cx: &mut App,
19744 ) -> Task<Result<ProjectTransaction>>;
19745}
19746
19747impl CodeActionProvider for Entity<Project> {
19748 fn id(&self) -> Arc<str> {
19749 "project".into()
19750 }
19751
19752 fn code_actions(
19753 &self,
19754 buffer: &Entity<Buffer>,
19755 range: Range<text::Anchor>,
19756 _window: &mut Window,
19757 cx: &mut App,
19758 ) -> Task<Result<Vec<CodeAction>>> {
19759 self.update(cx, |project, cx| {
19760 let code_lens = project.code_lens(buffer, range.clone(), cx);
19761 let code_actions = project.code_actions(buffer, range, None, cx);
19762 cx.background_spawn(async move {
19763 let (code_lens, code_actions) = join(code_lens, code_actions).await;
19764 Ok(code_lens
19765 .context("code lens fetch")?
19766 .into_iter()
19767 .chain(code_actions.context("code action fetch")?)
19768 .collect())
19769 })
19770 })
19771 }
19772
19773 fn apply_code_action(
19774 &self,
19775 buffer_handle: Entity<Buffer>,
19776 action: CodeAction,
19777 _excerpt_id: ExcerptId,
19778 push_to_history: bool,
19779 _window: &mut Window,
19780 cx: &mut App,
19781 ) -> Task<Result<ProjectTransaction>> {
19782 self.update(cx, |project, cx| {
19783 project.apply_code_action(buffer_handle, action, push_to_history, cx)
19784 })
19785 }
19786}
19787
19788fn snippet_completions(
19789 project: &Project,
19790 buffer: &Entity<Buffer>,
19791 buffer_position: text::Anchor,
19792 cx: &mut App,
19793) -> Task<Result<Vec<Completion>>> {
19794 let languages = buffer.read(cx).languages_at(buffer_position);
19795 let snippet_store = project.snippets().read(cx);
19796
19797 let scopes: Vec<_> = languages
19798 .iter()
19799 .filter_map(|language| {
19800 let language_name = language.lsp_id();
19801 let snippets = snippet_store.snippets_for(Some(language_name), cx);
19802
19803 if snippets.is_empty() {
19804 None
19805 } else {
19806 Some((language.default_scope(), snippets))
19807 }
19808 })
19809 .collect();
19810
19811 if scopes.is_empty() {
19812 return Task::ready(Ok(vec![]));
19813 }
19814
19815 let snapshot = buffer.read(cx).text_snapshot();
19816 let chars: String = snapshot
19817 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
19818 .collect();
19819 let executor = cx.background_executor().clone();
19820
19821 cx.background_spawn(async move {
19822 let mut all_results: Vec<Completion> = Vec::new();
19823 for (scope, snippets) in scopes.into_iter() {
19824 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
19825 let mut last_word = chars
19826 .chars()
19827 .take_while(|c| classifier.is_word(*c))
19828 .collect::<String>();
19829 last_word = last_word.chars().rev().collect();
19830
19831 if last_word.is_empty() {
19832 return Ok(vec![]);
19833 }
19834
19835 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
19836 let to_lsp = |point: &text::Anchor| {
19837 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
19838 point_to_lsp(end)
19839 };
19840 let lsp_end = to_lsp(&buffer_position);
19841
19842 let candidates = snippets
19843 .iter()
19844 .enumerate()
19845 .flat_map(|(ix, snippet)| {
19846 snippet
19847 .prefix
19848 .iter()
19849 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
19850 })
19851 .collect::<Vec<StringMatchCandidate>>();
19852
19853 let mut matches = fuzzy::match_strings(
19854 &candidates,
19855 &last_word,
19856 last_word.chars().any(|c| c.is_uppercase()),
19857 100,
19858 &Default::default(),
19859 executor.clone(),
19860 )
19861 .await;
19862
19863 // Remove all candidates where the query's start does not match the start of any word in the candidate
19864 if let Some(query_start) = last_word.chars().next() {
19865 matches.retain(|string_match| {
19866 split_words(&string_match.string).any(|word| {
19867 // Check that the first codepoint of the word as lowercase matches the first
19868 // codepoint of the query as lowercase
19869 word.chars()
19870 .flat_map(|codepoint| codepoint.to_lowercase())
19871 .zip(query_start.to_lowercase())
19872 .all(|(word_cp, query_cp)| word_cp == query_cp)
19873 })
19874 });
19875 }
19876
19877 let matched_strings = matches
19878 .into_iter()
19879 .map(|m| m.string)
19880 .collect::<HashSet<_>>();
19881
19882 let mut result: Vec<Completion> = snippets
19883 .iter()
19884 .filter_map(|snippet| {
19885 let matching_prefix = snippet
19886 .prefix
19887 .iter()
19888 .find(|prefix| matched_strings.contains(*prefix))?;
19889 let start = as_offset - last_word.len();
19890 let start = snapshot.anchor_before(start);
19891 let range = start..buffer_position;
19892 let lsp_start = to_lsp(&start);
19893 let lsp_range = lsp::Range {
19894 start: lsp_start,
19895 end: lsp_end,
19896 };
19897 Some(Completion {
19898 replace_range: range,
19899 new_text: snippet.body.clone(),
19900 source: CompletionSource::Lsp {
19901 insert_range: None,
19902 server_id: LanguageServerId(usize::MAX),
19903 resolved: true,
19904 lsp_completion: Box::new(lsp::CompletionItem {
19905 label: snippet.prefix.first().unwrap().clone(),
19906 kind: Some(CompletionItemKind::SNIPPET),
19907 label_details: snippet.description.as_ref().map(|description| {
19908 lsp::CompletionItemLabelDetails {
19909 detail: Some(description.clone()),
19910 description: None,
19911 }
19912 }),
19913 insert_text_format: Some(InsertTextFormat::SNIPPET),
19914 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19915 lsp::InsertReplaceEdit {
19916 new_text: snippet.body.clone(),
19917 insert: lsp_range,
19918 replace: lsp_range,
19919 },
19920 )),
19921 filter_text: Some(snippet.body.clone()),
19922 sort_text: Some(char::MAX.to_string()),
19923 ..lsp::CompletionItem::default()
19924 }),
19925 lsp_defaults: None,
19926 },
19927 label: CodeLabel {
19928 text: matching_prefix.clone(),
19929 runs: Vec::new(),
19930 filter_range: 0..matching_prefix.len(),
19931 },
19932 icon_path: None,
19933 documentation: Some(
19934 CompletionDocumentation::SingleLineAndMultiLinePlainText {
19935 single_line: snippet.name.clone().into(),
19936 plain_text: snippet
19937 .description
19938 .clone()
19939 .map(|description| description.into()),
19940 },
19941 ),
19942 insert_text_mode: None,
19943 confirm: None,
19944 })
19945 })
19946 .collect();
19947
19948 all_results.append(&mut result);
19949 }
19950
19951 Ok(all_results)
19952 })
19953}
19954
19955impl CompletionProvider for Entity<Project> {
19956 fn completions(
19957 &self,
19958 _excerpt_id: ExcerptId,
19959 buffer: &Entity<Buffer>,
19960 buffer_position: text::Anchor,
19961 options: CompletionContext,
19962 _window: &mut Window,
19963 cx: &mut Context<Editor>,
19964 ) -> Task<Result<Option<Vec<Completion>>>> {
19965 self.update(cx, |project, cx| {
19966 let snippets = snippet_completions(project, buffer, buffer_position, cx);
19967 let project_completions = project.completions(buffer, buffer_position, options, cx);
19968 cx.background_spawn(async move {
19969 let snippets_completions = snippets.await?;
19970 match project_completions.await? {
19971 Some(mut completions) => {
19972 completions.extend(snippets_completions);
19973 Ok(Some(completions))
19974 }
19975 None => {
19976 if snippets_completions.is_empty() {
19977 Ok(None)
19978 } else {
19979 Ok(Some(snippets_completions))
19980 }
19981 }
19982 }
19983 })
19984 })
19985 }
19986
19987 fn resolve_completions(
19988 &self,
19989 buffer: Entity<Buffer>,
19990 completion_indices: Vec<usize>,
19991 completions: Rc<RefCell<Box<[Completion]>>>,
19992 cx: &mut Context<Editor>,
19993 ) -> Task<Result<bool>> {
19994 self.update(cx, |project, cx| {
19995 project.lsp_store().update(cx, |lsp_store, cx| {
19996 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
19997 })
19998 })
19999 }
20000
20001 fn apply_additional_edits_for_completion(
20002 &self,
20003 buffer: Entity<Buffer>,
20004 completions: Rc<RefCell<Box<[Completion]>>>,
20005 completion_index: usize,
20006 push_to_history: bool,
20007 cx: &mut Context<Editor>,
20008 ) -> Task<Result<Option<language::Transaction>>> {
20009 self.update(cx, |project, cx| {
20010 project.lsp_store().update(cx, |lsp_store, cx| {
20011 lsp_store.apply_additional_edits_for_completion(
20012 buffer,
20013 completions,
20014 completion_index,
20015 push_to_history,
20016 cx,
20017 )
20018 })
20019 })
20020 }
20021
20022 fn is_completion_trigger(
20023 &self,
20024 buffer: &Entity<Buffer>,
20025 position: language::Anchor,
20026 text: &str,
20027 trigger_in_words: bool,
20028 cx: &mut Context<Editor>,
20029 ) -> bool {
20030 let mut chars = text.chars();
20031 let char = if let Some(char) = chars.next() {
20032 char
20033 } else {
20034 return false;
20035 };
20036 if chars.next().is_some() {
20037 return false;
20038 }
20039
20040 let buffer = buffer.read(cx);
20041 let snapshot = buffer.snapshot();
20042 if !snapshot.settings_at(position, cx).show_completions_on_input {
20043 return false;
20044 }
20045 let classifier = snapshot.char_classifier_at(position).for_completion(true);
20046 if trigger_in_words && classifier.is_word(char) {
20047 return true;
20048 }
20049
20050 buffer.completion_triggers().contains(text)
20051 }
20052}
20053
20054impl SemanticsProvider for Entity<Project> {
20055 fn hover(
20056 &self,
20057 buffer: &Entity<Buffer>,
20058 position: text::Anchor,
20059 cx: &mut App,
20060 ) -> Option<Task<Vec<project::Hover>>> {
20061 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
20062 }
20063
20064 fn document_highlights(
20065 &self,
20066 buffer: &Entity<Buffer>,
20067 position: text::Anchor,
20068 cx: &mut App,
20069 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
20070 Some(self.update(cx, |project, cx| {
20071 project.document_highlights(buffer, position, cx)
20072 }))
20073 }
20074
20075 fn definitions(
20076 &self,
20077 buffer: &Entity<Buffer>,
20078 position: text::Anchor,
20079 kind: GotoDefinitionKind,
20080 cx: &mut App,
20081 ) -> Option<Task<Result<Vec<LocationLink>>>> {
20082 Some(self.update(cx, |project, cx| match kind {
20083 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
20084 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
20085 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
20086 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
20087 }))
20088 }
20089
20090 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
20091 // TODO: make this work for remote projects
20092 self.update(cx, |project, cx| {
20093 if project
20094 .active_debug_session(cx)
20095 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
20096 {
20097 return true;
20098 }
20099
20100 buffer.update(cx, |buffer, cx| {
20101 project.any_language_server_supports_inlay_hints(buffer, cx)
20102 })
20103 })
20104 }
20105
20106 fn inline_values(
20107 &self,
20108 buffer_handle: Entity<Buffer>,
20109
20110 range: Range<text::Anchor>,
20111 cx: &mut App,
20112 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20113 self.update(cx, |project, cx| {
20114 let (session, active_stack_frame) = project.active_debug_session(cx)?;
20115
20116 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
20117 })
20118 }
20119
20120 fn inlay_hints(
20121 &self,
20122 buffer_handle: Entity<Buffer>,
20123 range: Range<text::Anchor>,
20124 cx: &mut App,
20125 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
20126 Some(self.update(cx, |project, cx| {
20127 project.inlay_hints(buffer_handle, range, cx)
20128 }))
20129 }
20130
20131 fn resolve_inlay_hint(
20132 &self,
20133 hint: InlayHint,
20134 buffer_handle: Entity<Buffer>,
20135 server_id: LanguageServerId,
20136 cx: &mut App,
20137 ) -> Option<Task<anyhow::Result<InlayHint>>> {
20138 Some(self.update(cx, |project, cx| {
20139 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
20140 }))
20141 }
20142
20143 fn range_for_rename(
20144 &self,
20145 buffer: &Entity<Buffer>,
20146 position: text::Anchor,
20147 cx: &mut App,
20148 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
20149 Some(self.update(cx, |project, cx| {
20150 let buffer = buffer.clone();
20151 let task = project.prepare_rename(buffer.clone(), position, cx);
20152 cx.spawn(async move |_, cx| {
20153 Ok(match task.await? {
20154 PrepareRenameResponse::Success(range) => Some(range),
20155 PrepareRenameResponse::InvalidPosition => None,
20156 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
20157 // Fallback on using TreeSitter info to determine identifier range
20158 buffer.read_with(cx, |buffer, _| {
20159 let snapshot = buffer.snapshot();
20160 let (range, kind) = snapshot.surrounding_word(position);
20161 if kind != Some(CharKind::Word) {
20162 return None;
20163 }
20164 Some(
20165 snapshot.anchor_before(range.start)
20166 ..snapshot.anchor_after(range.end),
20167 )
20168 })?
20169 }
20170 })
20171 })
20172 }))
20173 }
20174
20175 fn perform_rename(
20176 &self,
20177 buffer: &Entity<Buffer>,
20178 position: text::Anchor,
20179 new_name: String,
20180 cx: &mut App,
20181 ) -> Option<Task<Result<ProjectTransaction>>> {
20182 Some(self.update(cx, |project, cx| {
20183 project.perform_rename(buffer.clone(), position, new_name, cx)
20184 }))
20185 }
20186}
20187
20188fn inlay_hint_settings(
20189 location: Anchor,
20190 snapshot: &MultiBufferSnapshot,
20191 cx: &mut Context<Editor>,
20192) -> InlayHintSettings {
20193 let file = snapshot.file_at(location);
20194 let language = snapshot.language_at(location).map(|l| l.name());
20195 language_settings(language, file, cx).inlay_hints
20196}
20197
20198fn consume_contiguous_rows(
20199 contiguous_row_selections: &mut Vec<Selection<Point>>,
20200 selection: &Selection<Point>,
20201 display_map: &DisplaySnapshot,
20202 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
20203) -> (MultiBufferRow, MultiBufferRow) {
20204 contiguous_row_selections.push(selection.clone());
20205 let start_row = MultiBufferRow(selection.start.row);
20206 let mut end_row = ending_row(selection, display_map);
20207
20208 while let Some(next_selection) = selections.peek() {
20209 if next_selection.start.row <= end_row.0 {
20210 end_row = ending_row(next_selection, display_map);
20211 contiguous_row_selections.push(selections.next().unwrap().clone());
20212 } else {
20213 break;
20214 }
20215 }
20216 (start_row, end_row)
20217}
20218
20219fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
20220 if next_selection.end.column > 0 || next_selection.is_empty() {
20221 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
20222 } else {
20223 MultiBufferRow(next_selection.end.row)
20224 }
20225}
20226
20227impl EditorSnapshot {
20228 pub fn remote_selections_in_range<'a>(
20229 &'a self,
20230 range: &'a Range<Anchor>,
20231 collaboration_hub: &dyn CollaborationHub,
20232 cx: &'a App,
20233 ) -> impl 'a + Iterator<Item = RemoteSelection> {
20234 let participant_names = collaboration_hub.user_names(cx);
20235 let participant_indices = collaboration_hub.user_participant_indices(cx);
20236 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
20237 let collaborators_by_replica_id = collaborators_by_peer_id
20238 .values()
20239 .map(|collaborator| (collaborator.replica_id, collaborator))
20240 .collect::<HashMap<_, _>>();
20241 self.buffer_snapshot
20242 .selections_in_range(range, false)
20243 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
20244 if replica_id == AGENT_REPLICA_ID {
20245 Some(RemoteSelection {
20246 replica_id,
20247 selection,
20248 cursor_shape,
20249 line_mode,
20250 collaborator_id: CollaboratorId::Agent,
20251 user_name: Some("Agent".into()),
20252 color: cx.theme().players().agent(),
20253 })
20254 } else {
20255 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
20256 let participant_index = participant_indices.get(&collaborator.user_id).copied();
20257 let user_name = participant_names.get(&collaborator.user_id).cloned();
20258 Some(RemoteSelection {
20259 replica_id,
20260 selection,
20261 cursor_shape,
20262 line_mode,
20263 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
20264 user_name,
20265 color: if let Some(index) = participant_index {
20266 cx.theme().players().color_for_participant(index.0)
20267 } else {
20268 cx.theme().players().absent()
20269 },
20270 })
20271 }
20272 })
20273 }
20274
20275 pub fn hunks_for_ranges(
20276 &self,
20277 ranges: impl IntoIterator<Item = Range<Point>>,
20278 ) -> Vec<MultiBufferDiffHunk> {
20279 let mut hunks = Vec::new();
20280 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
20281 HashMap::default();
20282 for query_range in ranges {
20283 let query_rows =
20284 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
20285 for hunk in self.buffer_snapshot.diff_hunks_in_range(
20286 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
20287 ) {
20288 // Include deleted hunks that are adjacent to the query range, because
20289 // otherwise they would be missed.
20290 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
20291 if hunk.status().is_deleted() {
20292 intersects_range |= hunk.row_range.start == query_rows.end;
20293 intersects_range |= hunk.row_range.end == query_rows.start;
20294 }
20295 if intersects_range {
20296 if !processed_buffer_rows
20297 .entry(hunk.buffer_id)
20298 .or_default()
20299 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
20300 {
20301 continue;
20302 }
20303 hunks.push(hunk);
20304 }
20305 }
20306 }
20307
20308 hunks
20309 }
20310
20311 fn display_diff_hunks_for_rows<'a>(
20312 &'a self,
20313 display_rows: Range<DisplayRow>,
20314 folded_buffers: &'a HashSet<BufferId>,
20315 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
20316 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
20317 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
20318
20319 self.buffer_snapshot
20320 .diff_hunks_in_range(buffer_start..buffer_end)
20321 .filter_map(|hunk| {
20322 if folded_buffers.contains(&hunk.buffer_id) {
20323 return None;
20324 }
20325
20326 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
20327 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
20328
20329 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
20330 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
20331
20332 let display_hunk = if hunk_display_start.column() != 0 {
20333 DisplayDiffHunk::Folded {
20334 display_row: hunk_display_start.row(),
20335 }
20336 } else {
20337 let mut end_row = hunk_display_end.row();
20338 if hunk_display_end.column() > 0 {
20339 end_row.0 += 1;
20340 }
20341 let is_created_file = hunk.is_created_file();
20342 DisplayDiffHunk::Unfolded {
20343 status: hunk.status(),
20344 diff_base_byte_range: hunk.diff_base_byte_range,
20345 display_row_range: hunk_display_start.row()..end_row,
20346 multi_buffer_range: Anchor::range_in_buffer(
20347 hunk.excerpt_id,
20348 hunk.buffer_id,
20349 hunk.buffer_range,
20350 ),
20351 is_created_file,
20352 }
20353 };
20354
20355 Some(display_hunk)
20356 })
20357 }
20358
20359 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
20360 self.display_snapshot.buffer_snapshot.language_at(position)
20361 }
20362
20363 pub fn is_focused(&self) -> bool {
20364 self.is_focused
20365 }
20366
20367 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
20368 self.placeholder_text.as_ref()
20369 }
20370
20371 pub fn scroll_position(&self) -> gpui::Point<f32> {
20372 self.scroll_anchor.scroll_position(&self.display_snapshot)
20373 }
20374
20375 fn gutter_dimensions(
20376 &self,
20377 font_id: FontId,
20378 font_size: Pixels,
20379 max_line_number_width: Pixels,
20380 cx: &App,
20381 ) -> Option<GutterDimensions> {
20382 if !self.show_gutter {
20383 return None;
20384 }
20385
20386 let em_width = cx.text_system().em_width(font_id, font_size).log_err()?;
20387 let em_advance = cx.text_system().em_advance(font_id, font_size).log_err()?;
20388
20389 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
20390 matches!(
20391 ProjectSettings::get_global(cx).git.git_gutter,
20392 Some(GitGutterSetting::TrackedFiles)
20393 )
20394 });
20395 let gutter_settings = EditorSettings::get_global(cx).gutter;
20396 let show_line_numbers = self
20397 .show_line_numbers
20398 .unwrap_or(gutter_settings.line_numbers);
20399 let line_gutter_width = if show_line_numbers {
20400 // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines.
20401 let min_width_for_number_on_gutter = em_advance * MIN_LINE_NUMBER_DIGITS as f32;
20402 max_line_number_width.max(min_width_for_number_on_gutter)
20403 } else {
20404 0.0.into()
20405 };
20406
20407 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
20408 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
20409
20410 let git_blame_entries_width =
20411 self.git_blame_gutter_max_author_length
20412 .map(|max_author_length| {
20413 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20414 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
20415
20416 /// The number of characters to dedicate to gaps and margins.
20417 const SPACING_WIDTH: usize = 4;
20418
20419 let max_char_count = max_author_length.min(renderer.max_author_length())
20420 + ::git::SHORT_SHA_LENGTH
20421 + MAX_RELATIVE_TIMESTAMP.len()
20422 + SPACING_WIDTH;
20423
20424 em_advance * max_char_count
20425 });
20426
20427 let is_singleton = self.buffer_snapshot.is_singleton();
20428
20429 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
20430 left_padding += if !is_singleton {
20431 em_width * 4.0
20432 } else if show_runnables || show_breakpoints {
20433 em_width * 3.0
20434 } else if show_git_gutter && show_line_numbers {
20435 em_width * 2.0
20436 } else if show_git_gutter || show_line_numbers {
20437 em_width
20438 } else {
20439 px(0.)
20440 };
20441
20442 let shows_folds = is_singleton && gutter_settings.folds;
20443
20444 let right_padding = if shows_folds && show_line_numbers {
20445 em_width * 4.0
20446 } else if shows_folds || (!is_singleton && show_line_numbers) {
20447 em_width * 3.0
20448 } else if show_line_numbers {
20449 em_width
20450 } else {
20451 px(0.)
20452 };
20453
20454 Some(GutterDimensions {
20455 left_padding,
20456 right_padding,
20457 width: line_gutter_width + left_padding + right_padding,
20458 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
20459 git_blame_entries_width,
20460 })
20461 }
20462
20463 pub fn render_crease_toggle(
20464 &self,
20465 buffer_row: MultiBufferRow,
20466 row_contains_cursor: bool,
20467 editor: Entity<Editor>,
20468 window: &mut Window,
20469 cx: &mut App,
20470 ) -> Option<AnyElement> {
20471 let folded = self.is_line_folded(buffer_row);
20472 let mut is_foldable = false;
20473
20474 if let Some(crease) = self
20475 .crease_snapshot
20476 .query_row(buffer_row, &self.buffer_snapshot)
20477 {
20478 is_foldable = true;
20479 match crease {
20480 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
20481 if let Some(render_toggle) = render_toggle {
20482 let toggle_callback =
20483 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
20484 if folded {
20485 editor.update(cx, |editor, cx| {
20486 editor.fold_at(buffer_row, window, cx)
20487 });
20488 } else {
20489 editor.update(cx, |editor, cx| {
20490 editor.unfold_at(buffer_row, window, cx)
20491 });
20492 }
20493 });
20494 return Some((render_toggle)(
20495 buffer_row,
20496 folded,
20497 toggle_callback,
20498 window,
20499 cx,
20500 ));
20501 }
20502 }
20503 }
20504 }
20505
20506 is_foldable |= self.starts_indent(buffer_row);
20507
20508 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
20509 Some(
20510 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
20511 .toggle_state(folded)
20512 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
20513 if folded {
20514 this.unfold_at(buffer_row, window, cx);
20515 } else {
20516 this.fold_at(buffer_row, window, cx);
20517 }
20518 }))
20519 .into_any_element(),
20520 )
20521 } else {
20522 None
20523 }
20524 }
20525
20526 pub fn render_crease_trailer(
20527 &self,
20528 buffer_row: MultiBufferRow,
20529 window: &mut Window,
20530 cx: &mut App,
20531 ) -> Option<AnyElement> {
20532 let folded = self.is_line_folded(buffer_row);
20533 if let Crease::Inline { render_trailer, .. } = self
20534 .crease_snapshot
20535 .query_row(buffer_row, &self.buffer_snapshot)?
20536 {
20537 let render_trailer = render_trailer.as_ref()?;
20538 Some(render_trailer(buffer_row, folded, window, cx))
20539 } else {
20540 None
20541 }
20542 }
20543}
20544
20545impl Deref for EditorSnapshot {
20546 type Target = DisplaySnapshot;
20547
20548 fn deref(&self) -> &Self::Target {
20549 &self.display_snapshot
20550 }
20551}
20552
20553#[derive(Clone, Debug, PartialEq, Eq)]
20554pub enum EditorEvent {
20555 InputIgnored {
20556 text: Arc<str>,
20557 },
20558 InputHandled {
20559 utf16_range_to_replace: Option<Range<isize>>,
20560 text: Arc<str>,
20561 },
20562 ExcerptsAdded {
20563 buffer: Entity<Buffer>,
20564 predecessor: ExcerptId,
20565 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
20566 },
20567 ExcerptsRemoved {
20568 ids: Vec<ExcerptId>,
20569 removed_buffer_ids: Vec<BufferId>,
20570 },
20571 BufferFoldToggled {
20572 ids: Vec<ExcerptId>,
20573 folded: bool,
20574 },
20575 ExcerptsEdited {
20576 ids: Vec<ExcerptId>,
20577 },
20578 ExcerptsExpanded {
20579 ids: Vec<ExcerptId>,
20580 },
20581 BufferEdited,
20582 Edited {
20583 transaction_id: clock::Lamport,
20584 },
20585 Reparsed(BufferId),
20586 Focused,
20587 FocusedIn,
20588 Blurred,
20589 DirtyChanged,
20590 Saved,
20591 TitleChanged,
20592 DiffBaseChanged,
20593 SelectionsChanged {
20594 local: bool,
20595 },
20596 ScrollPositionChanged {
20597 local: bool,
20598 autoscroll: bool,
20599 },
20600 Closed,
20601 TransactionUndone {
20602 transaction_id: clock::Lamport,
20603 },
20604 TransactionBegun {
20605 transaction_id: clock::Lamport,
20606 },
20607 Reloaded,
20608 CursorShapeChanged,
20609 PushedToNavHistory {
20610 anchor: Anchor,
20611 is_deactivate: bool,
20612 },
20613}
20614
20615impl EventEmitter<EditorEvent> for Editor {}
20616
20617impl Focusable for Editor {
20618 fn focus_handle(&self, _cx: &App) -> FocusHandle {
20619 self.focus_handle.clone()
20620 }
20621}
20622
20623impl Render for Editor {
20624 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
20625 let settings = ThemeSettings::get_global(cx);
20626
20627 let mut text_style = match self.mode {
20628 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
20629 color: cx.theme().colors().editor_foreground,
20630 font_family: settings.ui_font.family.clone(),
20631 font_features: settings.ui_font.features.clone(),
20632 font_fallbacks: settings.ui_font.fallbacks.clone(),
20633 font_size: rems(0.875).into(),
20634 font_weight: settings.ui_font.weight,
20635 line_height: relative(settings.buffer_line_height.value()),
20636 ..Default::default()
20637 },
20638 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
20639 color: cx.theme().colors().editor_foreground,
20640 font_family: settings.buffer_font.family.clone(),
20641 font_features: settings.buffer_font.features.clone(),
20642 font_fallbacks: settings.buffer_font.fallbacks.clone(),
20643 font_size: settings.buffer_font_size(cx).into(),
20644 font_weight: settings.buffer_font.weight,
20645 line_height: relative(settings.buffer_line_height.value()),
20646 ..Default::default()
20647 },
20648 };
20649 if let Some(text_style_refinement) = &self.text_style_refinement {
20650 text_style.refine(text_style_refinement)
20651 }
20652
20653 let background = match self.mode {
20654 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
20655 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
20656 EditorMode::Full { .. } => cx.theme().colors().editor_background,
20657 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
20658 };
20659
20660 EditorElement::new(
20661 &cx.entity(),
20662 EditorStyle {
20663 background,
20664 local_player: cx.theme().players().local(),
20665 text: text_style,
20666 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
20667 syntax: cx.theme().syntax().clone(),
20668 status: cx.theme().status().clone(),
20669 inlay_hints_style: make_inlay_hints_style(cx),
20670 inline_completion_styles: make_suggestion_styles(cx),
20671 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
20672 show_underlines: !self.mode.is_minimap(),
20673 },
20674 )
20675 }
20676}
20677
20678impl EntityInputHandler for Editor {
20679 fn text_for_range(
20680 &mut self,
20681 range_utf16: Range<usize>,
20682 adjusted_range: &mut Option<Range<usize>>,
20683 _: &mut Window,
20684 cx: &mut Context<Self>,
20685 ) -> Option<String> {
20686 let snapshot = self.buffer.read(cx).read(cx);
20687 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
20688 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
20689 if (start.0..end.0) != range_utf16 {
20690 adjusted_range.replace(start.0..end.0);
20691 }
20692 Some(snapshot.text_for_range(start..end).collect())
20693 }
20694
20695 fn selected_text_range(
20696 &mut self,
20697 ignore_disabled_input: bool,
20698 _: &mut Window,
20699 cx: &mut Context<Self>,
20700 ) -> Option<UTF16Selection> {
20701 // Prevent the IME menu from appearing when holding down an alphabetic key
20702 // while input is disabled.
20703 if !ignore_disabled_input && !self.input_enabled {
20704 return None;
20705 }
20706
20707 let selection = self.selections.newest::<OffsetUtf16>(cx);
20708 let range = selection.range();
20709
20710 Some(UTF16Selection {
20711 range: range.start.0..range.end.0,
20712 reversed: selection.reversed,
20713 })
20714 }
20715
20716 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
20717 let snapshot = self.buffer.read(cx).read(cx);
20718 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
20719 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
20720 }
20721
20722 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
20723 self.clear_highlights::<InputComposition>(cx);
20724 self.ime_transaction.take();
20725 }
20726
20727 fn replace_text_in_range(
20728 &mut self,
20729 range_utf16: Option<Range<usize>>,
20730 text: &str,
20731 window: &mut Window,
20732 cx: &mut Context<Self>,
20733 ) {
20734 if !self.input_enabled {
20735 cx.emit(EditorEvent::InputIgnored { text: text.into() });
20736 return;
20737 }
20738
20739 self.transact(window, cx, |this, window, cx| {
20740 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
20741 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20742 Some(this.selection_replacement_ranges(range_utf16, cx))
20743 } else {
20744 this.marked_text_ranges(cx)
20745 };
20746
20747 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
20748 let newest_selection_id = this.selections.newest_anchor().id;
20749 this.selections
20750 .all::<OffsetUtf16>(cx)
20751 .iter()
20752 .zip(ranges_to_replace.iter())
20753 .find_map(|(selection, range)| {
20754 if selection.id == newest_selection_id {
20755 Some(
20756 (range.start.0 as isize - selection.head().0 as isize)
20757 ..(range.end.0 as isize - selection.head().0 as isize),
20758 )
20759 } else {
20760 None
20761 }
20762 })
20763 });
20764
20765 cx.emit(EditorEvent::InputHandled {
20766 utf16_range_to_replace: range_to_replace,
20767 text: text.into(),
20768 });
20769
20770 if let Some(new_selected_ranges) = new_selected_ranges {
20771 this.change_selections(None, window, cx, |selections| {
20772 selections.select_ranges(new_selected_ranges)
20773 });
20774 this.backspace(&Default::default(), window, cx);
20775 }
20776
20777 this.handle_input(text, window, cx);
20778 });
20779
20780 if let Some(transaction) = self.ime_transaction {
20781 self.buffer.update(cx, |buffer, cx| {
20782 buffer.group_until_transaction(transaction, cx);
20783 });
20784 }
20785
20786 self.unmark_text(window, cx);
20787 }
20788
20789 fn replace_and_mark_text_in_range(
20790 &mut self,
20791 range_utf16: Option<Range<usize>>,
20792 text: &str,
20793 new_selected_range_utf16: Option<Range<usize>>,
20794 window: &mut Window,
20795 cx: &mut Context<Self>,
20796 ) {
20797 if !self.input_enabled {
20798 return;
20799 }
20800
20801 let transaction = self.transact(window, cx, |this, window, cx| {
20802 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
20803 let snapshot = this.buffer.read(cx).read(cx);
20804 if let Some(relative_range_utf16) = range_utf16.as_ref() {
20805 for marked_range in &mut marked_ranges {
20806 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
20807 marked_range.start.0 += relative_range_utf16.start;
20808 marked_range.start =
20809 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
20810 marked_range.end =
20811 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
20812 }
20813 }
20814 Some(marked_ranges)
20815 } else if let Some(range_utf16) = range_utf16 {
20816 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
20817 Some(this.selection_replacement_ranges(range_utf16, cx))
20818 } else {
20819 None
20820 };
20821
20822 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
20823 let newest_selection_id = this.selections.newest_anchor().id;
20824 this.selections
20825 .all::<OffsetUtf16>(cx)
20826 .iter()
20827 .zip(ranges_to_replace.iter())
20828 .find_map(|(selection, range)| {
20829 if selection.id == newest_selection_id {
20830 Some(
20831 (range.start.0 as isize - selection.head().0 as isize)
20832 ..(range.end.0 as isize - selection.head().0 as isize),
20833 )
20834 } else {
20835 None
20836 }
20837 })
20838 });
20839
20840 cx.emit(EditorEvent::InputHandled {
20841 utf16_range_to_replace: range_to_replace,
20842 text: text.into(),
20843 });
20844
20845 if let Some(ranges) = ranges_to_replace {
20846 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
20847 }
20848
20849 let marked_ranges = {
20850 let snapshot = this.buffer.read(cx).read(cx);
20851 this.selections
20852 .disjoint_anchors()
20853 .iter()
20854 .map(|selection| {
20855 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
20856 })
20857 .collect::<Vec<_>>()
20858 };
20859
20860 if text.is_empty() {
20861 this.unmark_text(window, cx);
20862 } else {
20863 this.highlight_text::<InputComposition>(
20864 marked_ranges.clone(),
20865 HighlightStyle {
20866 underline: Some(UnderlineStyle {
20867 thickness: px(1.),
20868 color: None,
20869 wavy: false,
20870 }),
20871 ..Default::default()
20872 },
20873 cx,
20874 );
20875 }
20876
20877 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
20878 let use_autoclose = this.use_autoclose;
20879 let use_auto_surround = this.use_auto_surround;
20880 this.set_use_autoclose(false);
20881 this.set_use_auto_surround(false);
20882 this.handle_input(text, window, cx);
20883 this.set_use_autoclose(use_autoclose);
20884 this.set_use_auto_surround(use_auto_surround);
20885
20886 if let Some(new_selected_range) = new_selected_range_utf16 {
20887 let snapshot = this.buffer.read(cx).read(cx);
20888 let new_selected_ranges = marked_ranges
20889 .into_iter()
20890 .map(|marked_range| {
20891 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
20892 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
20893 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
20894 snapshot.clip_offset_utf16(new_start, Bias::Left)
20895 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
20896 })
20897 .collect::<Vec<_>>();
20898
20899 drop(snapshot);
20900 this.change_selections(None, window, cx, |selections| {
20901 selections.select_ranges(new_selected_ranges)
20902 });
20903 }
20904 });
20905
20906 self.ime_transaction = self.ime_transaction.or(transaction);
20907 if let Some(transaction) = self.ime_transaction {
20908 self.buffer.update(cx, |buffer, cx| {
20909 buffer.group_until_transaction(transaction, cx);
20910 });
20911 }
20912
20913 if self.text_highlights::<InputComposition>(cx).is_none() {
20914 self.ime_transaction.take();
20915 }
20916 }
20917
20918 fn bounds_for_range(
20919 &mut self,
20920 range_utf16: Range<usize>,
20921 element_bounds: gpui::Bounds<Pixels>,
20922 window: &mut Window,
20923 cx: &mut Context<Self>,
20924 ) -> Option<gpui::Bounds<Pixels>> {
20925 let text_layout_details = self.text_layout_details(window);
20926 let gpui::Size {
20927 width: em_width,
20928 height: line_height,
20929 } = self.character_size(window);
20930
20931 let snapshot = self.snapshot(window, cx);
20932 let scroll_position = snapshot.scroll_position();
20933 let scroll_left = scroll_position.x * em_width;
20934
20935 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
20936 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
20937 + self.gutter_dimensions.width
20938 + self.gutter_dimensions.margin;
20939 let y = line_height * (start.row().as_f32() - scroll_position.y);
20940
20941 Some(Bounds {
20942 origin: element_bounds.origin + point(x, y),
20943 size: size(em_width, line_height),
20944 })
20945 }
20946
20947 fn character_index_for_point(
20948 &mut self,
20949 point: gpui::Point<Pixels>,
20950 _window: &mut Window,
20951 _cx: &mut Context<Self>,
20952 ) -> Option<usize> {
20953 let position_map = self.last_position_map.as_ref()?;
20954 if !position_map.text_hitbox.contains(&point) {
20955 return None;
20956 }
20957 let display_point = position_map.point_for_position(point).previous_valid;
20958 let anchor = position_map
20959 .snapshot
20960 .display_point_to_anchor(display_point, Bias::Left);
20961 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
20962 Some(utf16_offset.0)
20963 }
20964}
20965
20966trait SelectionExt {
20967 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
20968 fn spanned_rows(
20969 &self,
20970 include_end_if_at_line_start: bool,
20971 map: &DisplaySnapshot,
20972 ) -> Range<MultiBufferRow>;
20973}
20974
20975impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
20976 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
20977 let start = self
20978 .start
20979 .to_point(&map.buffer_snapshot)
20980 .to_display_point(map);
20981 let end = self
20982 .end
20983 .to_point(&map.buffer_snapshot)
20984 .to_display_point(map);
20985 if self.reversed {
20986 end..start
20987 } else {
20988 start..end
20989 }
20990 }
20991
20992 fn spanned_rows(
20993 &self,
20994 include_end_if_at_line_start: bool,
20995 map: &DisplaySnapshot,
20996 ) -> Range<MultiBufferRow> {
20997 let start = self.start.to_point(&map.buffer_snapshot);
20998 let mut end = self.end.to_point(&map.buffer_snapshot);
20999 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
21000 end.row -= 1;
21001 }
21002
21003 let buffer_start = map.prev_line_boundary(start).0;
21004 let buffer_end = map.next_line_boundary(end).0;
21005 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
21006 }
21007}
21008
21009impl<T: InvalidationRegion> InvalidationStack<T> {
21010 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
21011 where
21012 S: Clone + ToOffset,
21013 {
21014 while let Some(region) = self.last() {
21015 let all_selections_inside_invalidation_ranges =
21016 if selections.len() == region.ranges().len() {
21017 selections
21018 .iter()
21019 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
21020 .all(|(selection, invalidation_range)| {
21021 let head = selection.head().to_offset(buffer);
21022 invalidation_range.start <= head && invalidation_range.end >= head
21023 })
21024 } else {
21025 false
21026 };
21027
21028 if all_selections_inside_invalidation_ranges {
21029 break;
21030 } else {
21031 self.pop();
21032 }
21033 }
21034 }
21035}
21036
21037impl<T> Default for InvalidationStack<T> {
21038 fn default() -> Self {
21039 Self(Default::default())
21040 }
21041}
21042
21043impl<T> Deref for InvalidationStack<T> {
21044 type Target = Vec<T>;
21045
21046 fn deref(&self) -> &Self::Target {
21047 &self.0
21048 }
21049}
21050
21051impl<T> DerefMut for InvalidationStack<T> {
21052 fn deref_mut(&mut self) -> &mut Self::Target {
21053 &mut self.0
21054 }
21055}
21056
21057impl InvalidationRegion for SnippetState {
21058 fn ranges(&self) -> &[Range<Anchor>] {
21059 &self.ranges[self.active_index]
21060 }
21061}
21062
21063fn inline_completion_edit_text(
21064 current_snapshot: &BufferSnapshot,
21065 edits: &[(Range<Anchor>, String)],
21066 edit_preview: &EditPreview,
21067 include_deletions: bool,
21068 cx: &App,
21069) -> HighlightedText {
21070 let edits = edits
21071 .iter()
21072 .map(|(anchor, text)| {
21073 (
21074 anchor.start.text_anchor..anchor.end.text_anchor,
21075 text.clone(),
21076 )
21077 })
21078 .collect::<Vec<_>>();
21079
21080 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
21081}
21082
21083pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
21084 match severity {
21085 lsp::DiagnosticSeverity::ERROR => colors.error,
21086 lsp::DiagnosticSeverity::WARNING => colors.warning,
21087 lsp::DiagnosticSeverity::INFORMATION => colors.info,
21088 lsp::DiagnosticSeverity::HINT => colors.info,
21089 _ => colors.ignored,
21090 }
21091}
21092
21093pub fn styled_runs_for_code_label<'a>(
21094 label: &'a CodeLabel,
21095 syntax_theme: &'a theme::SyntaxTheme,
21096) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
21097 let fade_out = HighlightStyle {
21098 fade_out: Some(0.35),
21099 ..Default::default()
21100 };
21101
21102 let mut prev_end = label.filter_range.end;
21103 label
21104 .runs
21105 .iter()
21106 .enumerate()
21107 .flat_map(move |(ix, (range, highlight_id))| {
21108 let style = if let Some(style) = highlight_id.style(syntax_theme) {
21109 style
21110 } else {
21111 return Default::default();
21112 };
21113 let mut muted_style = style;
21114 muted_style.highlight(fade_out);
21115
21116 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
21117 if range.start >= label.filter_range.end {
21118 if range.start > prev_end {
21119 runs.push((prev_end..range.start, fade_out));
21120 }
21121 runs.push((range.clone(), muted_style));
21122 } else if range.end <= label.filter_range.end {
21123 runs.push((range.clone(), style));
21124 } else {
21125 runs.push((range.start..label.filter_range.end, style));
21126 runs.push((label.filter_range.end..range.end, muted_style));
21127 }
21128 prev_end = cmp::max(prev_end, range.end);
21129
21130 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
21131 runs.push((prev_end..label.text.len(), fade_out));
21132 }
21133
21134 runs
21135 })
21136}
21137
21138pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
21139 let mut prev_index = 0;
21140 let mut prev_codepoint: Option<char> = None;
21141 text.char_indices()
21142 .chain([(text.len(), '\0')])
21143 .filter_map(move |(index, codepoint)| {
21144 let prev_codepoint = prev_codepoint.replace(codepoint)?;
21145 let is_boundary = index == text.len()
21146 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
21147 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
21148 if is_boundary {
21149 let chunk = &text[prev_index..index];
21150 prev_index = index;
21151 Some(chunk)
21152 } else {
21153 None
21154 }
21155 })
21156}
21157
21158pub trait RangeToAnchorExt: Sized {
21159 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
21160
21161 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
21162 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
21163 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
21164 }
21165}
21166
21167impl<T: ToOffset> RangeToAnchorExt for Range<T> {
21168 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
21169 let start_offset = self.start.to_offset(snapshot);
21170 let end_offset = self.end.to_offset(snapshot);
21171 if start_offset == end_offset {
21172 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
21173 } else {
21174 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
21175 }
21176 }
21177}
21178
21179pub trait RowExt {
21180 fn as_f32(&self) -> f32;
21181
21182 fn next_row(&self) -> Self;
21183
21184 fn previous_row(&self) -> Self;
21185
21186 fn minus(&self, other: Self) -> u32;
21187}
21188
21189impl RowExt for DisplayRow {
21190 fn as_f32(&self) -> f32 {
21191 self.0 as f32
21192 }
21193
21194 fn next_row(&self) -> Self {
21195 Self(self.0 + 1)
21196 }
21197
21198 fn previous_row(&self) -> Self {
21199 Self(self.0.saturating_sub(1))
21200 }
21201
21202 fn minus(&self, other: Self) -> u32 {
21203 self.0 - other.0
21204 }
21205}
21206
21207impl RowExt for MultiBufferRow {
21208 fn as_f32(&self) -> f32 {
21209 self.0 as f32
21210 }
21211
21212 fn next_row(&self) -> Self {
21213 Self(self.0 + 1)
21214 }
21215
21216 fn previous_row(&self) -> Self {
21217 Self(self.0.saturating_sub(1))
21218 }
21219
21220 fn minus(&self, other: Self) -> u32 {
21221 self.0 - other.0
21222 }
21223}
21224
21225trait RowRangeExt {
21226 type Row;
21227
21228 fn len(&self) -> usize;
21229
21230 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
21231}
21232
21233impl RowRangeExt for Range<MultiBufferRow> {
21234 type Row = MultiBufferRow;
21235
21236 fn len(&self) -> usize {
21237 (self.end.0 - self.start.0) as usize
21238 }
21239
21240 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
21241 (self.start.0..self.end.0).map(MultiBufferRow)
21242 }
21243}
21244
21245impl RowRangeExt for Range<DisplayRow> {
21246 type Row = DisplayRow;
21247
21248 fn len(&self) -> usize {
21249 (self.end.0 - self.start.0) as usize
21250 }
21251
21252 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
21253 (self.start.0..self.end.0).map(DisplayRow)
21254 }
21255}
21256
21257/// If select range has more than one line, we
21258/// just point the cursor to range.start.
21259fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
21260 if range.start.row == range.end.row {
21261 range
21262 } else {
21263 range.start..range.start
21264 }
21265}
21266pub struct KillRing(ClipboardItem);
21267impl Global for KillRing {}
21268
21269const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
21270
21271enum BreakpointPromptEditAction {
21272 Log,
21273 Condition,
21274 HitCondition,
21275}
21276
21277struct BreakpointPromptEditor {
21278 pub(crate) prompt: Entity<Editor>,
21279 editor: WeakEntity<Editor>,
21280 breakpoint_anchor: Anchor,
21281 breakpoint: Breakpoint,
21282 edit_action: BreakpointPromptEditAction,
21283 block_ids: HashSet<CustomBlockId>,
21284 editor_margins: Arc<Mutex<EditorMargins>>,
21285 _subscriptions: Vec<Subscription>,
21286}
21287
21288impl BreakpointPromptEditor {
21289 const MAX_LINES: u8 = 4;
21290
21291 fn new(
21292 editor: WeakEntity<Editor>,
21293 breakpoint_anchor: Anchor,
21294 breakpoint: Breakpoint,
21295 edit_action: BreakpointPromptEditAction,
21296 window: &mut Window,
21297 cx: &mut Context<Self>,
21298 ) -> Self {
21299 let base_text = match edit_action {
21300 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
21301 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
21302 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
21303 }
21304 .map(|msg| msg.to_string())
21305 .unwrap_or_default();
21306
21307 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
21308 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21309
21310 let prompt = cx.new(|cx| {
21311 let mut prompt = Editor::new(
21312 EditorMode::AutoHeight {
21313 max_lines: Self::MAX_LINES as usize,
21314 },
21315 buffer,
21316 None,
21317 window,
21318 cx,
21319 );
21320 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
21321 prompt.set_show_cursor_when_unfocused(false, cx);
21322 prompt.set_placeholder_text(
21323 match edit_action {
21324 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
21325 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
21326 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
21327 },
21328 cx,
21329 );
21330
21331 prompt
21332 });
21333
21334 Self {
21335 prompt,
21336 editor,
21337 breakpoint_anchor,
21338 breakpoint,
21339 edit_action,
21340 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
21341 block_ids: Default::default(),
21342 _subscriptions: vec![],
21343 }
21344 }
21345
21346 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
21347 self.block_ids.extend(block_ids)
21348 }
21349
21350 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
21351 if let Some(editor) = self.editor.upgrade() {
21352 let message = self
21353 .prompt
21354 .read(cx)
21355 .buffer
21356 .read(cx)
21357 .as_singleton()
21358 .expect("A multi buffer in breakpoint prompt isn't possible")
21359 .read(cx)
21360 .as_rope()
21361 .to_string();
21362
21363 editor.update(cx, |editor, cx| {
21364 editor.edit_breakpoint_at_anchor(
21365 self.breakpoint_anchor,
21366 self.breakpoint.clone(),
21367 match self.edit_action {
21368 BreakpointPromptEditAction::Log => {
21369 BreakpointEditAction::EditLogMessage(message.into())
21370 }
21371 BreakpointPromptEditAction::Condition => {
21372 BreakpointEditAction::EditCondition(message.into())
21373 }
21374 BreakpointPromptEditAction::HitCondition => {
21375 BreakpointEditAction::EditHitCondition(message.into())
21376 }
21377 },
21378 cx,
21379 );
21380
21381 editor.remove_blocks(self.block_ids.clone(), None, cx);
21382 cx.focus_self(window);
21383 });
21384 }
21385 }
21386
21387 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
21388 self.editor
21389 .update(cx, |editor, cx| {
21390 editor.remove_blocks(self.block_ids.clone(), None, cx);
21391 window.focus(&editor.focus_handle);
21392 })
21393 .log_err();
21394 }
21395
21396 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
21397 let settings = ThemeSettings::get_global(cx);
21398 let text_style = TextStyle {
21399 color: if self.prompt.read(cx).read_only(cx) {
21400 cx.theme().colors().text_disabled
21401 } else {
21402 cx.theme().colors().text
21403 },
21404 font_family: settings.buffer_font.family.clone(),
21405 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21406 font_size: settings.buffer_font_size(cx).into(),
21407 font_weight: settings.buffer_font.weight,
21408 line_height: relative(settings.buffer_line_height.value()),
21409 ..Default::default()
21410 };
21411 EditorElement::new(
21412 &self.prompt,
21413 EditorStyle {
21414 background: cx.theme().colors().editor_background,
21415 local_player: cx.theme().players().local(),
21416 text: text_style,
21417 ..Default::default()
21418 },
21419 )
21420 }
21421}
21422
21423impl Render for BreakpointPromptEditor {
21424 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21425 let editor_margins = *self.editor_margins.lock();
21426 let gutter_dimensions = editor_margins.gutter;
21427 h_flex()
21428 .key_context("Editor")
21429 .bg(cx.theme().colors().editor_background)
21430 .border_y_1()
21431 .border_color(cx.theme().status().info_border)
21432 .size_full()
21433 .py(window.line_height() / 2.5)
21434 .on_action(cx.listener(Self::confirm))
21435 .on_action(cx.listener(Self::cancel))
21436 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
21437 .child(div().flex_1().child(self.render_prompt_editor(cx)))
21438 }
21439}
21440
21441impl Focusable for BreakpointPromptEditor {
21442 fn focus_handle(&self, cx: &App) -> FocusHandle {
21443 self.prompt.focus_handle(cx)
21444 }
21445}
21446
21447fn all_edits_insertions_or_deletions(
21448 edits: &Vec<(Range<Anchor>, String)>,
21449 snapshot: &MultiBufferSnapshot,
21450) -> bool {
21451 let mut all_insertions = true;
21452 let mut all_deletions = true;
21453
21454 for (range, new_text) in edits.iter() {
21455 let range_is_empty = range.to_offset(&snapshot).is_empty();
21456 let text_is_empty = new_text.is_empty();
21457
21458 if range_is_empty != text_is_empty {
21459 if range_is_empty {
21460 all_deletions = false;
21461 } else {
21462 all_insertions = false;
21463 }
21464 } else {
21465 return false;
21466 }
21467
21468 if !all_insertions && !all_deletions {
21469 return false;
21470 }
21471 }
21472 all_insertions || all_deletions
21473}
21474
21475struct MissingEditPredictionKeybindingTooltip;
21476
21477impl Render for MissingEditPredictionKeybindingTooltip {
21478 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21479 ui::tooltip_container(window, cx, |container, _, cx| {
21480 container
21481 .flex_shrink_0()
21482 .max_w_80()
21483 .min_h(rems_from_px(124.))
21484 .justify_between()
21485 .child(
21486 v_flex()
21487 .flex_1()
21488 .text_ui_sm(cx)
21489 .child(Label::new("Conflict with Accept Keybinding"))
21490 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
21491 )
21492 .child(
21493 h_flex()
21494 .pb_1()
21495 .gap_1()
21496 .items_end()
21497 .w_full()
21498 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
21499 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
21500 }))
21501 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
21502 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
21503 })),
21504 )
21505 })
21506 }
21507}
21508
21509#[derive(Debug, Clone, Copy, PartialEq)]
21510pub struct LineHighlight {
21511 pub background: Background,
21512 pub border: Option<gpui::Hsla>,
21513 pub include_gutter: bool,
21514 pub type_id: Option<TypeId>,
21515}
21516
21517fn render_diff_hunk_controls(
21518 row: u32,
21519 status: &DiffHunkStatus,
21520 hunk_range: Range<Anchor>,
21521 is_created_file: bool,
21522 line_height: Pixels,
21523 editor: &Entity<Editor>,
21524 _window: &mut Window,
21525 cx: &mut App,
21526) -> AnyElement {
21527 h_flex()
21528 .h(line_height)
21529 .mr_1()
21530 .gap_1()
21531 .px_0p5()
21532 .pb_1()
21533 .border_x_1()
21534 .border_b_1()
21535 .border_color(cx.theme().colors().border_variant)
21536 .rounded_b_lg()
21537 .bg(cx.theme().colors().editor_background)
21538 .gap_1()
21539 .stop_mouse_events_except_scroll()
21540 .shadow_md()
21541 .child(if status.has_secondary_hunk() {
21542 Button::new(("stage", row as u64), "Stage")
21543 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21544 .tooltip({
21545 let focus_handle = editor.focus_handle(cx);
21546 move |window, cx| {
21547 Tooltip::for_action_in(
21548 "Stage Hunk",
21549 &::git::ToggleStaged,
21550 &focus_handle,
21551 window,
21552 cx,
21553 )
21554 }
21555 })
21556 .on_click({
21557 let editor = editor.clone();
21558 move |_event, _window, cx| {
21559 editor.update(cx, |editor, cx| {
21560 editor.stage_or_unstage_diff_hunks(
21561 true,
21562 vec![hunk_range.start..hunk_range.start],
21563 cx,
21564 );
21565 });
21566 }
21567 })
21568 } else {
21569 Button::new(("unstage", row as u64), "Unstage")
21570 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
21571 .tooltip({
21572 let focus_handle = editor.focus_handle(cx);
21573 move |window, cx| {
21574 Tooltip::for_action_in(
21575 "Unstage Hunk",
21576 &::git::ToggleStaged,
21577 &focus_handle,
21578 window,
21579 cx,
21580 )
21581 }
21582 })
21583 .on_click({
21584 let editor = editor.clone();
21585 move |_event, _window, cx| {
21586 editor.update(cx, |editor, cx| {
21587 editor.stage_or_unstage_diff_hunks(
21588 false,
21589 vec![hunk_range.start..hunk_range.start],
21590 cx,
21591 );
21592 });
21593 }
21594 })
21595 })
21596 .child(
21597 Button::new(("restore", row as u64), "Restore")
21598 .tooltip({
21599 let focus_handle = editor.focus_handle(cx);
21600 move |window, cx| {
21601 Tooltip::for_action_in(
21602 "Restore Hunk",
21603 &::git::Restore,
21604 &focus_handle,
21605 window,
21606 cx,
21607 )
21608 }
21609 })
21610 .on_click({
21611 let editor = editor.clone();
21612 move |_event, window, cx| {
21613 editor.update(cx, |editor, cx| {
21614 let snapshot = editor.snapshot(window, cx);
21615 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
21616 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
21617 });
21618 }
21619 })
21620 .disabled(is_created_file),
21621 )
21622 .when(
21623 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
21624 |el| {
21625 el.child(
21626 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
21627 .shape(IconButtonShape::Square)
21628 .icon_size(IconSize::Small)
21629 // .disabled(!has_multiple_hunks)
21630 .tooltip({
21631 let focus_handle = editor.focus_handle(cx);
21632 move |window, cx| {
21633 Tooltip::for_action_in(
21634 "Next Hunk",
21635 &GoToHunk,
21636 &focus_handle,
21637 window,
21638 cx,
21639 )
21640 }
21641 })
21642 .on_click({
21643 let editor = editor.clone();
21644 move |_event, window, cx| {
21645 editor.update(cx, |editor, cx| {
21646 let snapshot = editor.snapshot(window, cx);
21647 let position =
21648 hunk_range.end.to_point(&snapshot.buffer_snapshot);
21649 editor.go_to_hunk_before_or_after_position(
21650 &snapshot,
21651 position,
21652 Direction::Next,
21653 window,
21654 cx,
21655 );
21656 editor.expand_selected_diff_hunks(cx);
21657 });
21658 }
21659 }),
21660 )
21661 .child(
21662 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
21663 .shape(IconButtonShape::Square)
21664 .icon_size(IconSize::Small)
21665 // .disabled(!has_multiple_hunks)
21666 .tooltip({
21667 let focus_handle = editor.focus_handle(cx);
21668 move |window, cx| {
21669 Tooltip::for_action_in(
21670 "Previous Hunk",
21671 &GoToPreviousHunk,
21672 &focus_handle,
21673 window,
21674 cx,
21675 )
21676 }
21677 })
21678 .on_click({
21679 let editor = editor.clone();
21680 move |_event, window, cx| {
21681 editor.update(cx, |editor, cx| {
21682 let snapshot = editor.snapshot(window, cx);
21683 let point =
21684 hunk_range.start.to_point(&snapshot.buffer_snapshot);
21685 editor.go_to_hunk_before_or_after_position(
21686 &snapshot,
21687 point,
21688 Direction::Prev,
21689 window,
21690 cx,
21691 );
21692 editor.expand_selected_diff_hunks(cx);
21693 });
21694 }
21695 }),
21696 )
21697 },
21698 )
21699 .into_any_element()
21700}