1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod editor_settings_controls;
22mod element;
23mod git;
24mod highlight_matching_bracket;
25mod hover_links;
26pub mod hover_popover;
27mod indent_guides;
28mod inlay_hint_cache;
29pub mod items;
30mod jsx_tag_auto_close;
31mod linked_editing_ranges;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod editor_tests;
46#[cfg(test)]
47mod inline_completion_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use actions::{AcceptEditPrediction, OpenExcerpts, OpenExcerptsSplit};
54use aho_corasick::AhoCorasick;
55use anyhow::{Context as _, Result, anyhow};
56use blink_manager::BlinkManager;
57use buffer_diff::DiffHunkStatus;
58use client::{Collaborator, ParticipantIndex};
59use clock::{AGENT_REPLICA_ID, ReplicaId};
60use collections::{BTreeMap, HashMap, HashSet, VecDeque};
61use convert_case::{Case, Casing};
62use dap::TelemetrySpawnLocation;
63use display_map::*;
64pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
65pub use editor_settings::{
66 CurrentLineHighlight, EditorSettings, HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes,
67 SearchSettings, ShowScrollbar,
68};
69use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
70pub use editor_settings_controls::*;
71use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
72pub use element::{
73 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
74};
75use feature_flags::{DebuggerFeatureFlag, FeatureFlagAppExt};
76use futures::{
77 FutureExt, StreamExt as _,
78 future::{self, Shared, join},
79 stream::FuturesUnordered,
80};
81use fuzzy::{StringMatch, StringMatchCandidate};
82
83use ::git::blame::BlameEntry;
84use ::git::{Restore, blame::ParsedCommitMessage};
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use git::blame::{GitBlame, GlobalBlameRenderer};
90use gpui::{
91 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
92 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
93 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
94 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
95 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
96 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
97 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
98 div, impl_actions, point, prelude::*, pulsating_between, px, relative, size,
99};
100use highlight_matching_bracket::refresh_matching_bracket_highlights;
101use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
102pub use hover_popover::hover_markdown_style;
103use hover_popover::{HoverState, hide_hover};
104use indent_guides::ActiveIndentGuidesState;
105use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
106pub use inline_completion::Direction;
107use inline_completion::{EditPredictionProvider, InlineCompletionProviderHandle};
108pub use items::MAX_TAB_TITLE_LEN;
109use itertools::Itertools;
110use language::{
111 AutoindentMode, BracketMatch, BracketPair, Buffer, Capability, CharKind, CodeLabel,
112 CursorShape, DiagnosticEntry, DiagnosticSourceKind, DiffOptions, DocumentationConfig,
113 EditPredictionsMode, EditPreview, HighlightedText, IndentKind, IndentSize, Language,
114 OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions,
115 WordsQuery,
116 language_settings::{
117 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
118 all_language_settings, language_settings,
119 },
120 point_from_lsp, text_diff_with_options,
121};
122use language::{BufferRow, CharClassifier, Runnable, RunnableRange, point_to_lsp};
123use linked_editing_ranges::refresh_linked_ranges;
124use markdown::Markdown;
125use mouse_context_menu::MouseContextMenu;
126use persistence::DB;
127use project::{
128 BreakpointWithPosition, CompletionResponse, LspPullDiagnostics, ProjectPath, PulledDiagnostics,
129 debugger::{
130 breakpoint_store::{
131 BreakpointEditAction, BreakpointSessionState, BreakpointState, BreakpointStore,
132 BreakpointStoreEvent,
133 },
134 session::{Session, SessionEvent},
135 },
136 project_settings::DiagnosticSeverity,
137};
138
139pub use git::blame::BlameRenderer;
140pub use proposed_changes_editor::{
141 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
142};
143use std::{cell::OnceCell, iter::Peekable, ops::Not};
144use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
145
146pub use lsp::CompletionContext;
147use lsp::{
148 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
149 LanguageServerId, LanguageServerName,
150};
151
152use language::BufferSnapshot;
153pub use lsp_ext::lsp_tasks;
154use movement::TextLayoutDetails;
155pub use multi_buffer::{
156 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
157 RowInfo, ToOffset, ToPoint,
158};
159use multi_buffer::{
160 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
161 MultiOrSingleBufferOffsetRange, ToOffsetUtf16,
162};
163use parking_lot::Mutex;
164use project::{
165 CodeAction, Completion, CompletionIntent, CompletionSource, DocumentHighlight, InlayHint,
166 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectTransaction,
167 TaskSourceKind,
168 debugger::breakpoint_store::Breakpoint,
169 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
170 project_settings::{GitGutterSetting, ProjectSettings},
171};
172use rand::prelude::*;
173use rpc::{ErrorExt, proto::*};
174use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager, ScrollbarAutoHide};
175use selections_collection::{
176 MutableSelectionsCollection, SelectionsCollection, resolve_selections,
177};
178use serde::{Deserialize, Serialize};
179use settings::{Settings, SettingsLocation, SettingsStore, update_settings_file};
180use smallvec::{SmallVec, smallvec};
181use snippet::Snippet;
182use std::sync::Arc;
183use std::{
184 any::TypeId,
185 borrow::Cow,
186 cell::RefCell,
187 cmp::{self, Ordering, Reverse},
188 mem,
189 num::NonZeroU32,
190 ops::{ControlFlow, Deref, DerefMut, Range, RangeInclusive},
191 path::{Path, PathBuf},
192 rc::Rc,
193 time::{Duration, Instant},
194};
195pub use sum_tree::Bias;
196use sum_tree::TreeMap;
197use text::{BufferId, FromAnchor, OffsetUtf16, Rope};
198use theme::{
199 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, ThemeColors, ThemeSettings,
200 observe_buffer_font_size_adjustment,
201};
202use ui::{
203 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
204 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*,
205};
206use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
207use workspace::{
208 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
209 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
210 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
211 item::{ItemHandle, PreviewTabsSettings},
212 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
213 searchable::SearchEvent,
214};
215
216use crate::{
217 code_context_menus::CompletionsMenuSource,
218 hover_links::{find_url, find_url_from_range},
219};
220use crate::{
221 editor_settings::MultiCursorModifier,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227pub const DEFAULT_MULTIBUFFER_CONTEXT: u32 = 2;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258struct InlineValueCache {
259 enabled: bool,
260 inlays: Vec<InlayId>,
261 refresh_task: Task<Option<()>>,
262}
263
264impl InlineValueCache {
265 fn new(enabled: bool) -> Self {
266 Self {
267 enabled,
268 inlays: Vec::new(),
269 refresh_task: Task::ready(None),
270 }
271 }
272}
273
274#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
275pub enum InlayId {
276 InlineCompletion(usize),
277 Hint(usize),
278 DebuggerValue(usize),
279}
280
281impl InlayId {
282 fn id(&self) -> usize {
283 match self {
284 Self::InlineCompletion(id) => *id,
285 Self::Hint(id) => *id,
286 Self::DebuggerValue(id) => *id,
287 }
288 }
289}
290
291pub enum ActiveDebugLine {}
292pub enum DebugStackFrameLine {}
293enum DocumentHighlightRead {}
294enum DocumentHighlightWrite {}
295enum InputComposition {}
296pub enum PendingInput {}
297enum SelectedTextHighlight {}
298
299pub enum ConflictsOuter {}
300pub enum ConflictsOurs {}
301pub enum ConflictsTheirs {}
302pub enum ConflictsOursMarker {}
303pub enum ConflictsTheirsMarker {}
304
305#[derive(Debug, Copy, Clone, PartialEq, Eq)]
306pub enum Navigated {
307 Yes,
308 No,
309}
310
311impl Navigated {
312 pub fn from_bool(yes: bool) -> Navigated {
313 if yes { Navigated::Yes } else { Navigated::No }
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq)]
318enum DisplayDiffHunk {
319 Folded {
320 display_row: DisplayRow,
321 },
322 Unfolded {
323 is_created_file: bool,
324 diff_base_byte_range: Range<usize>,
325 display_row_range: Range<DisplayRow>,
326 multi_buffer_range: Range<Anchor>,
327 status: DiffHunkStatus,
328 },
329}
330
331pub enum HideMouseCursorOrigin {
332 TypingAction,
333 MovementAction,
334}
335
336pub fn init_settings(cx: &mut App) {
337 EditorSettings::register(cx);
338}
339
340pub fn init(cx: &mut App) {
341 init_settings(cx);
342
343 cx.set_global(GlobalBlameRenderer(Arc::new(())));
344
345 workspace::register_project_item::<Editor>(cx);
346 workspace::FollowableViewRegistry::register::<Editor>(cx);
347 workspace::register_serializable_item::<Editor>(cx);
348
349 cx.observe_new(
350 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
351 workspace.register_action(Editor::new_file);
352 workspace.register_action(Editor::new_file_vertical);
353 workspace.register_action(Editor::new_file_horizontal);
354 workspace.register_action(Editor::cancel_language_server_work);
355 },
356 )
357 .detach();
358
359 cx.on_action(move |_: &workspace::NewFile, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373 cx.on_action(move |_: &workspace::NewWindow, cx| {
374 let app_state = workspace::AppState::global(cx);
375 if let Some(app_state) = app_state.upgrade() {
376 workspace::open_new(
377 Default::default(),
378 app_state,
379 cx,
380 |workspace, window, cx| {
381 cx.activate(true);
382 Editor::new_file(workspace, &Default::default(), window, cx)
383 },
384 )
385 .detach();
386 }
387 });
388}
389
390pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
391 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
392}
393
394pub trait DiagnosticRenderer {
395 fn render_group(
396 &self,
397 diagnostic_group: Vec<DiagnosticEntry<Point>>,
398 buffer_id: BufferId,
399 snapshot: EditorSnapshot,
400 editor: WeakEntity<Editor>,
401 cx: &mut App,
402 ) -> Vec<BlockProperties<Anchor>>;
403
404 fn render_hover(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntry<Point>>,
407 range: Range<Point>,
408 buffer_id: BufferId,
409 cx: &mut App,
410 ) -> Option<Entity<markdown::Markdown>>;
411
412 fn open_link(
413 &self,
414 editor: &mut Editor,
415 link: SharedString,
416 window: &mut Window,
417 cx: &mut Context<Editor>,
418 );
419}
420
421pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
422
423impl GlobalDiagnosticRenderer {
424 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
425 cx.try_global::<Self>().map(|g| g.0.clone())
426 }
427}
428
429impl gpui::Global for GlobalDiagnosticRenderer {}
430pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
431 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
432}
433
434pub struct SearchWithinRange;
435
436trait InvalidationRegion {
437 fn ranges(&self) -> &[Range<Anchor>];
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum SelectPhase {
442 Begin {
443 position: DisplayPoint,
444 add: bool,
445 click_count: usize,
446 },
447 BeginColumnar {
448 position: DisplayPoint,
449 reset: bool,
450 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, Vec<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
908enum SelectionDragState {
909 /// State when no drag related activity is detected.
910 None,
911 /// State when the mouse is down on a selection that is about to be dragged.
912 ReadyToDrag {
913 selection: Selection<Anchor>,
914 click_position: gpui::Point<Pixels>,
915 mouse_down_time: Instant,
916 },
917 /// State when the mouse is dragging the selection in the editor.
918 Dragging {
919 selection: Selection<Anchor>,
920 drop_cursor: Selection<Anchor>,
921 hide_drop_cursor: bool,
922 },
923}
924
925/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
926/// a breakpoint on them.
927#[derive(Clone, Copy, Debug, PartialEq, Eq)]
928struct PhantomBreakpointIndicator {
929 display_row: DisplayRow,
930 /// There's a small debounce between hovering over the line and showing the indicator.
931 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
932 is_active: bool,
933 collides_with_existing_breakpoint: bool,
934}
935
936/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
937///
938/// See the [module level documentation](self) for more information.
939pub struct Editor {
940 focus_handle: FocusHandle,
941 last_focused_descendant: Option<WeakFocusHandle>,
942 /// The text buffer being edited
943 buffer: Entity<MultiBuffer>,
944 /// Map of how text in the buffer should be displayed.
945 /// Handles soft wraps, folds, fake inlay text insertions, etc.
946 pub display_map: Entity<DisplayMap>,
947 pub selections: SelectionsCollection,
948 pub scroll_manager: ScrollManager,
949 /// When inline assist editors are linked, they all render cursors because
950 /// typing enters text into each of them, even the ones that aren't focused.
951 pub(crate) show_cursor_when_unfocused: bool,
952 columnar_selection_tail: Option<Anchor>,
953 columnar_display_point: Option<DisplayPoint>,
954 add_selections_state: Option<AddSelectionsState>,
955 select_next_state: Option<SelectNextState>,
956 select_prev_state: Option<SelectNextState>,
957 selection_history: SelectionHistory,
958 defer_selection_effects: bool,
959 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
960 autoclose_regions: Vec<AutocloseRegion>,
961 snippet_stack: InvalidationStack<SnippetState>,
962 select_syntax_node_history: SelectSyntaxNodeHistory,
963 ime_transaction: Option<TransactionId>,
964 pub diagnostics_max_severity: DiagnosticSeverity,
965 active_diagnostics: ActiveDiagnostic,
966 show_inline_diagnostics: bool,
967 inline_diagnostics_update: Task<()>,
968 inline_diagnostics_enabled: bool,
969 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
970 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
971 hard_wrap: Option<usize>,
972
973 // TODO: make this a access method
974 pub project: Option<Entity<Project>>,
975 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
976 completion_provider: Option<Rc<dyn CompletionProvider>>,
977 collaboration_hub: Option<Box<dyn CollaborationHub>>,
978 blink_manager: Entity<BlinkManager>,
979 show_cursor_names: bool,
980 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
981 pub show_local_selections: bool,
982 mode: EditorMode,
983 show_breadcrumbs: bool,
984 show_gutter: bool,
985 show_scrollbars: ScrollbarAxes,
986 minimap_visibility: MinimapVisibility,
987 offset_content: bool,
988 disable_expand_excerpt_buttons: bool,
989 show_line_numbers: Option<bool>,
990 use_relative_line_numbers: Option<bool>,
991 show_git_diff_gutter: Option<bool>,
992 show_code_actions: Option<bool>,
993 show_runnables: Option<bool>,
994 show_breakpoints: Option<bool>,
995 show_wrap_guides: Option<bool>,
996 show_indent_guides: Option<bool>,
997 placeholder_text: Option<Arc<str>>,
998 highlight_order: usize,
999 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1000 background_highlights: TreeMap<TypeId, BackgroundHighlight>,
1001 gutter_highlights: TreeMap<TypeId, GutterHighlight>,
1002 scrollbar_marker_state: ScrollbarMarkerState,
1003 active_indent_guides_state: ActiveIndentGuidesState,
1004 nav_history: Option<ItemNavHistory>,
1005 context_menu: RefCell<Option<CodeContextMenu>>,
1006 context_menu_options: Option<ContextMenuOptions>,
1007 mouse_context_menu: Option<MouseContextMenu>,
1008 completion_tasks: Vec<(CompletionId, Task<()>)>,
1009 inline_blame_popover: Option<InlineBlamePopover>,
1010 signature_help_state: SignatureHelpState,
1011 auto_signature_help: Option<bool>,
1012 find_all_references_task_sources: Vec<Anchor>,
1013 next_completion_id: CompletionId,
1014 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1015 code_actions_task: Option<Task<Result<()>>>,
1016 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1017 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1018 document_highlights_task: Option<Task<()>>,
1019 linked_editing_range_task: Option<Task<Option<()>>>,
1020 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1021 pending_rename: Option<RenameState>,
1022 searchable: bool,
1023 cursor_shape: CursorShape,
1024 current_line_highlight: Option<CurrentLineHighlight>,
1025 collapse_matches: bool,
1026 autoindent_mode: Option<AutoindentMode>,
1027 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1028 input_enabled: bool,
1029 use_modal_editing: bool,
1030 read_only: bool,
1031 leader_id: Option<CollaboratorId>,
1032 remote_id: Option<ViewId>,
1033 pub hover_state: HoverState,
1034 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1035 gutter_hovered: bool,
1036 hovered_link_state: Option<HoveredLinkState>,
1037 edit_prediction_provider: Option<RegisteredInlineCompletionProvider>,
1038 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1039 active_inline_completion: Option<InlineCompletionState>,
1040 /// Used to prevent flickering as the user types while the menu is open
1041 stale_inline_completion_in_menu: Option<InlineCompletionState>,
1042 edit_prediction_settings: EditPredictionSettings,
1043 inline_completions_hidden_for_vim_mode: bool,
1044 show_inline_completions_override: Option<bool>,
1045 menu_inline_completions_policy: MenuInlineCompletionsPolicy,
1046 edit_prediction_preview: EditPredictionPreview,
1047 edit_prediction_indent_conflict: bool,
1048 edit_prediction_requires_modifier_in_indent_conflict: bool,
1049 inlay_hint_cache: InlayHintCache,
1050 next_inlay_id: usize,
1051 _subscriptions: Vec<Subscription>,
1052 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1053 gutter_dimensions: GutterDimensions,
1054 style: Option<EditorStyle>,
1055 text_style_refinement: Option<TextStyleRefinement>,
1056 next_editor_action_id: EditorActionId,
1057 editor_actions: Rc<
1058 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1059 >,
1060 use_autoclose: bool,
1061 use_auto_surround: bool,
1062 auto_replace_emoji_shortcode: bool,
1063 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1064 show_git_blame_gutter: bool,
1065 show_git_blame_inline: bool,
1066 show_git_blame_inline_delay_task: Option<Task<()>>,
1067 git_blame_inline_enabled: bool,
1068 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1069 serialize_dirty_buffers: bool,
1070 show_selection_menu: Option<bool>,
1071 blame: Option<Entity<GitBlame>>,
1072 blame_subscription: Option<Subscription>,
1073 custom_context_menu: Option<
1074 Box<
1075 dyn 'static
1076 + Fn(
1077 &mut Self,
1078 DisplayPoint,
1079 &mut Window,
1080 &mut Context<Self>,
1081 ) -> Option<Entity<ui::ContextMenu>>,
1082 >,
1083 >,
1084 last_bounds: Option<Bounds<Pixels>>,
1085 last_position_map: Option<Rc<PositionMap>>,
1086 expect_bounds_change: Option<Bounds<Pixels>>,
1087 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1088 tasks_update_task: Option<Task<()>>,
1089 breakpoint_store: Option<Entity<BreakpointStore>>,
1090 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1091 pull_diagnostics_task: Task<()>,
1092 in_project_search: bool,
1093 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1094 breadcrumb_header: Option<String>,
1095 focused_block: Option<FocusedBlock>,
1096 next_scroll_position: NextScrollCursorCenterTopBottom,
1097 addons: HashMap<TypeId, Box<dyn Addon>>,
1098 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1099 load_diff_task: Option<Shared<Task<()>>>,
1100 /// Whether we are temporarily displaying a diff other than git's
1101 temporary_diff_override: bool,
1102 selection_mark_mode: bool,
1103 toggle_fold_multiple_buffers: Task<()>,
1104 _scroll_cursor_center_top_bottom_task: Task<()>,
1105 serialize_selections: Task<()>,
1106 serialize_folds: Task<()>,
1107 mouse_cursor_hidden: bool,
1108 minimap: Option<Entity<Self>>,
1109 hide_mouse_mode: HideMouseMode,
1110 pub change_list: ChangeList,
1111 inline_value_cache: InlineValueCache,
1112 selection_drag_state: SelectionDragState,
1113 drag_and_drop_selection_enabled: bool,
1114}
1115
1116#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1117enum NextScrollCursorCenterTopBottom {
1118 #[default]
1119 Center,
1120 Top,
1121 Bottom,
1122}
1123
1124impl NextScrollCursorCenterTopBottom {
1125 fn next(&self) -> Self {
1126 match self {
1127 Self::Center => Self::Top,
1128 Self::Top => Self::Bottom,
1129 Self::Bottom => Self::Center,
1130 }
1131 }
1132}
1133
1134#[derive(Clone)]
1135pub struct EditorSnapshot {
1136 pub mode: EditorMode,
1137 show_gutter: bool,
1138 show_line_numbers: Option<bool>,
1139 show_git_diff_gutter: Option<bool>,
1140 show_code_actions: Option<bool>,
1141 show_runnables: Option<bool>,
1142 show_breakpoints: Option<bool>,
1143 git_blame_gutter_max_author_length: Option<usize>,
1144 pub display_snapshot: DisplaySnapshot,
1145 pub placeholder_text: Option<Arc<str>>,
1146 is_focused: bool,
1147 scroll_anchor: ScrollAnchor,
1148 ongoing_scroll: OngoingScroll,
1149 current_line_highlight: CurrentLineHighlight,
1150 gutter_hovered: bool,
1151}
1152
1153#[derive(Default, Debug, Clone, Copy)]
1154pub struct GutterDimensions {
1155 pub left_padding: Pixels,
1156 pub right_padding: Pixels,
1157 pub width: Pixels,
1158 pub margin: Pixels,
1159 pub git_blame_entries_width: Option<Pixels>,
1160}
1161
1162impl GutterDimensions {
1163 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1164 Self {
1165 margin: Self::default_gutter_margin(font_id, font_size, cx),
1166 ..Default::default()
1167 }
1168 }
1169
1170 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1171 -cx.text_system().descent(font_id, font_size)
1172 }
1173 /// The full width of the space taken up by the gutter.
1174 pub fn full_width(&self) -> Pixels {
1175 self.margin + self.width
1176 }
1177
1178 /// The width of the space reserved for the fold indicators,
1179 /// use alongside 'justify_end' and `gutter_width` to
1180 /// right align content with the line numbers
1181 pub fn fold_area_width(&self) -> Pixels {
1182 self.margin + self.right_padding
1183 }
1184}
1185
1186#[derive(Debug)]
1187pub struct RemoteSelection {
1188 pub replica_id: ReplicaId,
1189 pub selection: Selection<Anchor>,
1190 pub cursor_shape: CursorShape,
1191 pub collaborator_id: CollaboratorId,
1192 pub line_mode: bool,
1193 pub user_name: Option<SharedString>,
1194 pub color: PlayerColor,
1195}
1196
1197#[derive(Clone, Debug)]
1198struct SelectionHistoryEntry {
1199 selections: Arc<[Selection<Anchor>]>,
1200 select_next_state: Option<SelectNextState>,
1201 select_prev_state: Option<SelectNextState>,
1202 add_selections_state: Option<AddSelectionsState>,
1203}
1204
1205#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1206enum SelectionHistoryMode {
1207 Normal,
1208 Undoing,
1209 Redoing,
1210 Skipping,
1211}
1212
1213#[derive(Clone, PartialEq, Eq, Hash)]
1214struct HoveredCursor {
1215 replica_id: u16,
1216 selection_id: usize,
1217}
1218
1219impl Default for SelectionHistoryMode {
1220 fn default() -> Self {
1221 Self::Normal
1222 }
1223}
1224
1225struct DeferredSelectionEffectsState {
1226 changed: bool,
1227 should_update_completions: bool,
1228 autoscroll: Option<Autoscroll>,
1229 old_cursor_position: Anchor,
1230 history_entry: SelectionHistoryEntry,
1231}
1232
1233#[derive(Default)]
1234struct SelectionHistory {
1235 #[allow(clippy::type_complexity)]
1236 selections_by_transaction:
1237 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1238 mode: SelectionHistoryMode,
1239 undo_stack: VecDeque<SelectionHistoryEntry>,
1240 redo_stack: VecDeque<SelectionHistoryEntry>,
1241}
1242
1243impl SelectionHistory {
1244 #[track_caller]
1245 fn insert_transaction(
1246 &mut self,
1247 transaction_id: TransactionId,
1248 selections: Arc<[Selection<Anchor>]>,
1249 ) {
1250 if selections.is_empty() {
1251 log::error!(
1252 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1253 std::panic::Location::caller()
1254 );
1255 return;
1256 }
1257 self.selections_by_transaction
1258 .insert(transaction_id, (selections, None));
1259 }
1260
1261 #[allow(clippy::type_complexity)]
1262 fn transaction(
1263 &self,
1264 transaction_id: TransactionId,
1265 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1266 self.selections_by_transaction.get(&transaction_id)
1267 }
1268
1269 #[allow(clippy::type_complexity)]
1270 fn transaction_mut(
1271 &mut self,
1272 transaction_id: TransactionId,
1273 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1274 self.selections_by_transaction.get_mut(&transaction_id)
1275 }
1276
1277 fn push(&mut self, entry: SelectionHistoryEntry) {
1278 if !entry.selections.is_empty() {
1279 match self.mode {
1280 SelectionHistoryMode::Normal => {
1281 self.push_undo(entry);
1282 self.redo_stack.clear();
1283 }
1284 SelectionHistoryMode::Undoing => self.push_redo(entry),
1285 SelectionHistoryMode::Redoing => self.push_undo(entry),
1286 SelectionHistoryMode::Skipping => {}
1287 }
1288 }
1289 }
1290
1291 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1292 if self
1293 .undo_stack
1294 .back()
1295 .map_or(true, |e| e.selections != entry.selections)
1296 {
1297 self.undo_stack.push_back(entry);
1298 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1299 self.undo_stack.pop_front();
1300 }
1301 }
1302 }
1303
1304 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1305 if self
1306 .redo_stack
1307 .back()
1308 .map_or(true, |e| e.selections != entry.selections)
1309 {
1310 self.redo_stack.push_back(entry);
1311 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1312 self.redo_stack.pop_front();
1313 }
1314 }
1315 }
1316}
1317
1318#[derive(Clone, Copy)]
1319pub struct RowHighlightOptions {
1320 pub autoscroll: bool,
1321 pub include_gutter: bool,
1322}
1323
1324impl Default for RowHighlightOptions {
1325 fn default() -> Self {
1326 Self {
1327 autoscroll: Default::default(),
1328 include_gutter: true,
1329 }
1330 }
1331}
1332
1333struct RowHighlight {
1334 index: usize,
1335 range: Range<Anchor>,
1336 color: Hsla,
1337 options: RowHighlightOptions,
1338 type_id: TypeId,
1339}
1340
1341#[derive(Clone, Debug)]
1342struct AddSelectionsState {
1343 groups: Vec<AddSelectionsGroup>,
1344}
1345
1346#[derive(Clone, Debug)]
1347struct AddSelectionsGroup {
1348 above: bool,
1349 stack: Vec<usize>,
1350}
1351
1352#[derive(Clone)]
1353struct SelectNextState {
1354 query: AhoCorasick,
1355 wordwise: bool,
1356 done: bool,
1357}
1358
1359impl std::fmt::Debug for SelectNextState {
1360 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1361 f.debug_struct(std::any::type_name::<Self>())
1362 .field("wordwise", &self.wordwise)
1363 .field("done", &self.done)
1364 .finish()
1365 }
1366}
1367
1368#[derive(Debug)]
1369struct AutocloseRegion {
1370 selection_id: usize,
1371 range: Range<Anchor>,
1372 pair: BracketPair,
1373}
1374
1375#[derive(Debug)]
1376struct SnippetState {
1377 ranges: Vec<Vec<Range<Anchor>>>,
1378 active_index: usize,
1379 choices: Vec<Option<Vec<String>>>,
1380}
1381
1382#[doc(hidden)]
1383pub struct RenameState {
1384 pub range: Range<Anchor>,
1385 pub old_name: Arc<str>,
1386 pub editor: Entity<Editor>,
1387 block_id: CustomBlockId,
1388}
1389
1390struct InvalidationStack<T>(Vec<T>);
1391
1392struct RegisteredInlineCompletionProvider {
1393 provider: Arc<dyn InlineCompletionProviderHandle>,
1394 _subscription: Subscription,
1395}
1396
1397#[derive(Debug, PartialEq, Eq)]
1398pub struct ActiveDiagnosticGroup {
1399 pub active_range: Range<Anchor>,
1400 pub active_message: String,
1401 pub group_id: usize,
1402 pub blocks: HashSet<CustomBlockId>,
1403}
1404
1405#[derive(Debug, PartialEq, Eq)]
1406
1407pub(crate) enum ActiveDiagnostic {
1408 None,
1409 All,
1410 Group(ActiveDiagnosticGroup),
1411}
1412
1413#[derive(Serialize, Deserialize, Clone, Debug)]
1414pub struct ClipboardSelection {
1415 /// The number of bytes in this selection.
1416 pub len: usize,
1417 /// Whether this was a full-line selection.
1418 pub is_entire_line: bool,
1419 /// The indentation of the first line when this content was originally copied.
1420 pub first_line_indent: u32,
1421}
1422
1423// selections, scroll behavior, was newest selection reversed
1424type SelectSyntaxNodeHistoryState = (
1425 Box<[Selection<usize>]>,
1426 SelectSyntaxNodeScrollBehavior,
1427 bool,
1428);
1429
1430#[derive(Default)]
1431struct SelectSyntaxNodeHistory {
1432 stack: Vec<SelectSyntaxNodeHistoryState>,
1433 // disable temporarily to allow changing selections without losing the stack
1434 pub disable_clearing: bool,
1435}
1436
1437impl SelectSyntaxNodeHistory {
1438 pub fn try_clear(&mut self) {
1439 if !self.disable_clearing {
1440 self.stack.clear();
1441 }
1442 }
1443
1444 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1445 self.stack.push(selection);
1446 }
1447
1448 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1449 self.stack.pop()
1450 }
1451}
1452
1453enum SelectSyntaxNodeScrollBehavior {
1454 CursorTop,
1455 FitSelection,
1456 CursorBottom,
1457}
1458
1459#[derive(Debug)]
1460pub(crate) struct NavigationData {
1461 cursor_anchor: Anchor,
1462 cursor_position: Point,
1463 scroll_anchor: ScrollAnchor,
1464 scroll_top_row: u32,
1465}
1466
1467#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1468pub enum GotoDefinitionKind {
1469 Symbol,
1470 Declaration,
1471 Type,
1472 Implementation,
1473}
1474
1475#[derive(Debug, Clone)]
1476enum InlayHintRefreshReason {
1477 ModifiersChanged(bool),
1478 Toggle(bool),
1479 SettingsChange(InlayHintSettings),
1480 NewLinesShown,
1481 BufferEdited(HashSet<Arc<Language>>),
1482 RefreshRequested,
1483 ExcerptsRemoved(Vec<ExcerptId>),
1484}
1485
1486impl InlayHintRefreshReason {
1487 fn description(&self) -> &'static str {
1488 match self {
1489 Self::ModifiersChanged(_) => "modifiers changed",
1490 Self::Toggle(_) => "toggle",
1491 Self::SettingsChange(_) => "settings change",
1492 Self::NewLinesShown => "new lines shown",
1493 Self::BufferEdited(_) => "buffer edited",
1494 Self::RefreshRequested => "refresh requested",
1495 Self::ExcerptsRemoved(_) => "excerpts removed",
1496 }
1497 }
1498}
1499
1500pub enum FormatTarget {
1501 Buffers,
1502 Ranges(Vec<Range<MultiBufferPoint>>),
1503}
1504
1505pub(crate) struct FocusedBlock {
1506 id: BlockId,
1507 focus_handle: WeakFocusHandle,
1508}
1509
1510#[derive(Clone)]
1511enum JumpData {
1512 MultiBufferRow {
1513 row: MultiBufferRow,
1514 line_offset_from_top: u32,
1515 },
1516 MultiBufferPoint {
1517 excerpt_id: ExcerptId,
1518 position: Point,
1519 anchor: text::Anchor,
1520 line_offset_from_top: u32,
1521 },
1522}
1523
1524pub enum MultibufferSelectionMode {
1525 First,
1526 All,
1527}
1528
1529#[derive(Clone, Copy, Debug, Default)]
1530pub struct RewrapOptions {
1531 pub override_language_settings: bool,
1532 pub preserve_existing_whitespace: bool,
1533}
1534
1535impl Editor {
1536 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1537 let buffer = cx.new(|cx| Buffer::local("", cx));
1538 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1539 Self::new(
1540 EditorMode::SingleLine { auto_width: false },
1541 buffer,
1542 None,
1543 window,
1544 cx,
1545 )
1546 }
1547
1548 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1549 let buffer = cx.new(|cx| Buffer::local("", cx));
1550 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1551 Self::new(EditorMode::full(), buffer, None, window, cx)
1552 }
1553
1554 pub fn auto_width(window: &mut Window, cx: &mut Context<Self>) -> Self {
1555 let buffer = cx.new(|cx| Buffer::local("", cx));
1556 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1557 Self::new(
1558 EditorMode::SingleLine { auto_width: true },
1559 buffer,
1560 None,
1561 window,
1562 cx,
1563 )
1564 }
1565
1566 pub fn auto_height(max_lines: usize, window: &mut Window, cx: &mut Context<Self>) -> Self {
1567 let buffer = cx.new(|cx| Buffer::local("", cx));
1568 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1569 Self::new(
1570 EditorMode::AutoHeight { max_lines },
1571 buffer,
1572 None,
1573 window,
1574 cx,
1575 )
1576 }
1577
1578 pub fn for_buffer(
1579 buffer: Entity<Buffer>,
1580 project: Option<Entity<Project>>,
1581 window: &mut Window,
1582 cx: &mut Context<Self>,
1583 ) -> Self {
1584 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1585 Self::new(EditorMode::full(), buffer, project, window, cx)
1586 }
1587
1588 pub fn for_multibuffer(
1589 buffer: Entity<MultiBuffer>,
1590 project: Option<Entity<Project>>,
1591 window: &mut Window,
1592 cx: &mut Context<Self>,
1593 ) -> Self {
1594 Self::new(EditorMode::full(), buffer, project, window, cx)
1595 }
1596
1597 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1598 let mut clone = Self::new(
1599 self.mode.clone(),
1600 self.buffer.clone(),
1601 self.project.clone(),
1602 window,
1603 cx,
1604 );
1605 self.display_map.update(cx, |display_map, cx| {
1606 let snapshot = display_map.snapshot(cx);
1607 clone.display_map.update(cx, |display_map, cx| {
1608 display_map.set_state(&snapshot, cx);
1609 });
1610 });
1611 clone.folds_did_change(cx);
1612 clone.selections.clone_state(&self.selections);
1613 clone.scroll_manager.clone_state(&self.scroll_manager);
1614 clone.searchable = self.searchable;
1615 clone.read_only = self.read_only;
1616 clone
1617 }
1618
1619 pub fn new(
1620 mode: EditorMode,
1621 buffer: Entity<MultiBuffer>,
1622 project: Option<Entity<Project>>,
1623 window: &mut Window,
1624 cx: &mut Context<Self>,
1625 ) -> Self {
1626 Editor::new_internal(mode, buffer, project, None, window, cx)
1627 }
1628
1629 fn new_internal(
1630 mode: EditorMode,
1631 buffer: Entity<MultiBuffer>,
1632 project: Option<Entity<Project>>,
1633 display_map: Option<Entity<DisplayMap>>,
1634 window: &mut Window,
1635 cx: &mut Context<Self>,
1636 ) -> Self {
1637 debug_assert!(
1638 display_map.is_none() || mode.is_minimap(),
1639 "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
1640 );
1641
1642 let full_mode = mode.is_full();
1643 let diagnostics_max_severity = if full_mode {
1644 EditorSettings::get_global(cx)
1645 .diagnostics_max_severity
1646 .unwrap_or(DiagnosticSeverity::Hint)
1647 } else {
1648 DiagnosticSeverity::Off
1649 };
1650 let style = window.text_style();
1651 let font_size = style.font_size.to_pixels(window.rem_size());
1652 let editor = cx.entity().downgrade();
1653 let fold_placeholder = FoldPlaceholder {
1654 constrain_width: true,
1655 render: Arc::new(move |fold_id, fold_range, cx| {
1656 let editor = editor.clone();
1657 div()
1658 .id(fold_id)
1659 .bg(cx.theme().colors().ghost_element_background)
1660 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1661 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1662 .rounded_xs()
1663 .size_full()
1664 .cursor_pointer()
1665 .child("⋯")
1666 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1667 .on_click(move |_, _window, cx| {
1668 editor
1669 .update(cx, |editor, cx| {
1670 editor.unfold_ranges(
1671 &[fold_range.start..fold_range.end],
1672 true,
1673 false,
1674 cx,
1675 );
1676 cx.stop_propagation();
1677 })
1678 .ok();
1679 })
1680 .into_any()
1681 }),
1682 merge_adjacent: true,
1683 ..FoldPlaceholder::default()
1684 };
1685 let display_map = display_map.unwrap_or_else(|| {
1686 cx.new(|cx| {
1687 DisplayMap::new(
1688 buffer.clone(),
1689 style.font(),
1690 font_size,
1691 None,
1692 FILE_HEADER_HEIGHT,
1693 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1694 fold_placeholder,
1695 diagnostics_max_severity,
1696 cx,
1697 )
1698 })
1699 });
1700
1701 let selections = SelectionsCollection::new(display_map.clone(), buffer.clone());
1702
1703 let blink_manager = cx.new(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
1704
1705 let soft_wrap_mode_override = matches!(mode, EditorMode::SingleLine { .. })
1706 .then(|| language_settings::SoftWrap::None);
1707
1708 let mut project_subscriptions = Vec::new();
1709 if mode.is_full() {
1710 if let Some(project) = project.as_ref() {
1711 project_subscriptions.push(cx.subscribe_in(
1712 project,
1713 window,
1714 |editor, _, event, window, cx| match event {
1715 project::Event::RefreshCodeLens => {
1716 // we always query lens with actions, without storing them, always refreshing them
1717 }
1718 project::Event::RefreshInlayHints => {
1719 editor
1720 .refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1721 }
1722 project::Event::LanguageServerAdded(..)
1723 | project::Event::LanguageServerRemoved(..) => {
1724 if editor.tasks_update_task.is_none() {
1725 editor.tasks_update_task =
1726 Some(editor.refresh_runnables(window, cx));
1727 }
1728 editor.pull_diagnostics(None, window, cx);
1729 }
1730 project::Event::SnippetEdit(id, snippet_edits) => {
1731 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1732 let focus_handle = editor.focus_handle(cx);
1733 if focus_handle.is_focused(window) {
1734 let snapshot = buffer.read(cx).snapshot();
1735 for (range, snippet) in snippet_edits {
1736 let editor_range =
1737 language::range_from_lsp(*range).to_offset(&snapshot);
1738 editor
1739 .insert_snippet(
1740 &[editor_range],
1741 snippet.clone(),
1742 window,
1743 cx,
1744 )
1745 .ok();
1746 }
1747 }
1748 }
1749 }
1750 _ => {}
1751 },
1752 ));
1753 if let Some(task_inventory) = project
1754 .read(cx)
1755 .task_store()
1756 .read(cx)
1757 .task_inventory()
1758 .cloned()
1759 {
1760 project_subscriptions.push(cx.observe_in(
1761 &task_inventory,
1762 window,
1763 |editor, _, window, cx| {
1764 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1765 },
1766 ));
1767 };
1768
1769 project_subscriptions.push(cx.subscribe_in(
1770 &project.read(cx).breakpoint_store(),
1771 window,
1772 |editor, _, event, window, cx| match event {
1773 BreakpointStoreEvent::ClearDebugLines => {
1774 editor.clear_row_highlights::<ActiveDebugLine>();
1775 editor.refresh_inline_values(cx);
1776 }
1777 BreakpointStoreEvent::SetDebugLine => {
1778 if editor.go_to_active_debug_line(window, cx) {
1779 cx.stop_propagation();
1780 }
1781
1782 editor.refresh_inline_values(cx);
1783 }
1784 _ => {}
1785 },
1786 ));
1787 }
1788 }
1789
1790 let buffer_snapshot = buffer.read(cx).snapshot(cx);
1791
1792 let inlay_hint_settings =
1793 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1794 let focus_handle = cx.focus_handle();
1795 cx.on_focus(&focus_handle, window, Self::handle_focus)
1796 .detach();
1797 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
1798 .detach();
1799 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
1800 .detach();
1801 cx.on_blur(&focus_handle, window, Self::handle_blur)
1802 .detach();
1803 cx.observe_pending_input(window, Self::observe_pending_input)
1804 .detach();
1805
1806 let show_indent_guides = if matches!(mode, EditorMode::SingleLine { .. }) {
1807 Some(false)
1808 } else {
1809 None
1810 };
1811
1812 let breakpoint_store = match (&mode, project.as_ref()) {
1813 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
1814 _ => None,
1815 };
1816
1817 let mut code_action_providers = Vec::new();
1818 let mut load_uncommitted_diff = None;
1819 if let Some(project) = project.clone() {
1820 load_uncommitted_diff = Some(
1821 update_uncommitted_diff_for_buffer(
1822 cx.entity(),
1823 &project,
1824 buffer.read(cx).all_buffers(),
1825 buffer.clone(),
1826 cx,
1827 )
1828 .shared(),
1829 );
1830 code_action_providers.push(Rc::new(project) as Rc<_>);
1831 }
1832
1833 let mut editor = Self {
1834 focus_handle,
1835 show_cursor_when_unfocused: false,
1836 last_focused_descendant: None,
1837 buffer: buffer.clone(),
1838 display_map: display_map.clone(),
1839 selections,
1840 scroll_manager: ScrollManager::new(cx),
1841 columnar_selection_tail: None,
1842 columnar_display_point: None,
1843 add_selections_state: None,
1844 select_next_state: None,
1845 select_prev_state: None,
1846 selection_history: SelectionHistory::default(),
1847 defer_selection_effects: false,
1848 deferred_selection_effects_state: None,
1849 autoclose_regions: Vec::new(),
1850 snippet_stack: InvalidationStack::default(),
1851 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
1852 ime_transaction: None,
1853 active_diagnostics: ActiveDiagnostic::None,
1854 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
1855 inline_diagnostics_update: Task::ready(()),
1856 inline_diagnostics: Vec::new(),
1857 soft_wrap_mode_override,
1858 diagnostics_max_severity,
1859 hard_wrap: None,
1860 completion_provider: project.clone().map(|project| Rc::new(project) as _),
1861 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
1862 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
1863 project,
1864 blink_manager: blink_manager.clone(),
1865 show_local_selections: true,
1866 show_scrollbars: ScrollbarAxes {
1867 horizontal: full_mode,
1868 vertical: full_mode,
1869 },
1870 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
1871 offset_content: !matches!(mode, EditorMode::SingleLine { .. }),
1872 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
1873 show_gutter: mode.is_full(),
1874 show_line_numbers: None,
1875 use_relative_line_numbers: None,
1876 disable_expand_excerpt_buttons: false,
1877 show_git_diff_gutter: None,
1878 show_code_actions: None,
1879 show_runnables: None,
1880 show_breakpoints: None,
1881 show_wrap_guides: None,
1882 show_indent_guides,
1883 placeholder_text: None,
1884 highlight_order: 0,
1885 highlighted_rows: HashMap::default(),
1886 background_highlights: TreeMap::default(),
1887 gutter_highlights: TreeMap::default(),
1888 scrollbar_marker_state: ScrollbarMarkerState::default(),
1889 active_indent_guides_state: ActiveIndentGuidesState::default(),
1890 nav_history: None,
1891 context_menu: RefCell::new(None),
1892 context_menu_options: None,
1893 mouse_context_menu: None,
1894 completion_tasks: Vec::new(),
1895 inline_blame_popover: None,
1896 signature_help_state: SignatureHelpState::default(),
1897 auto_signature_help: None,
1898 find_all_references_task_sources: Vec::new(),
1899 next_completion_id: 0,
1900 next_inlay_id: 0,
1901 code_action_providers,
1902 available_code_actions: None,
1903 code_actions_task: None,
1904 quick_selection_highlight_task: None,
1905 debounced_selection_highlight_task: None,
1906 document_highlights_task: None,
1907 linked_editing_range_task: None,
1908 pending_rename: None,
1909 searchable: true,
1910 cursor_shape: EditorSettings::get_global(cx)
1911 .cursor_shape
1912 .unwrap_or_default(),
1913 current_line_highlight: None,
1914 autoindent_mode: Some(AutoindentMode::EachLine),
1915 collapse_matches: false,
1916 workspace: None,
1917 input_enabled: true,
1918 use_modal_editing: mode.is_full(),
1919 read_only: mode.is_minimap(),
1920 use_autoclose: true,
1921 use_auto_surround: true,
1922 auto_replace_emoji_shortcode: false,
1923 jsx_tag_auto_close_enabled_in_any_buffer: false,
1924 leader_id: None,
1925 remote_id: None,
1926 hover_state: HoverState::default(),
1927 pending_mouse_down: None,
1928 hovered_link_state: None,
1929 edit_prediction_provider: None,
1930 active_inline_completion: None,
1931 stale_inline_completion_in_menu: None,
1932 edit_prediction_preview: EditPredictionPreview::Inactive {
1933 released_too_fast: false,
1934 },
1935 inline_diagnostics_enabled: mode.is_full(),
1936 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
1937 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
1938
1939 gutter_hovered: false,
1940 pixel_position_of_newest_cursor: None,
1941 last_bounds: None,
1942 last_position_map: None,
1943 expect_bounds_change: None,
1944 gutter_dimensions: GutterDimensions::default(),
1945 style: None,
1946 show_cursor_names: false,
1947 hovered_cursors: HashMap::default(),
1948 next_editor_action_id: EditorActionId::default(),
1949 editor_actions: Rc::default(),
1950 inline_completions_hidden_for_vim_mode: false,
1951 show_inline_completions_override: None,
1952 menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
1953 edit_prediction_settings: EditPredictionSettings::Disabled,
1954 edit_prediction_indent_conflict: false,
1955 edit_prediction_requires_modifier_in_indent_conflict: true,
1956 custom_context_menu: None,
1957 show_git_blame_gutter: false,
1958 show_git_blame_inline: false,
1959 show_selection_menu: None,
1960 show_git_blame_inline_delay_task: None,
1961 git_blame_inline_enabled: ProjectSettings::get_global(cx).git.inline_blame_enabled(),
1962 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
1963 serialize_dirty_buffers: !mode.is_minimap()
1964 && ProjectSettings::get_global(cx)
1965 .session
1966 .restore_unsaved_buffers,
1967 blame: None,
1968 blame_subscription: None,
1969 tasks: BTreeMap::default(),
1970
1971 breakpoint_store,
1972 gutter_breakpoint_indicator: (None, None),
1973 _subscriptions: vec![
1974 cx.observe(&buffer, Self::on_buffer_changed),
1975 cx.subscribe_in(&buffer, window, Self::on_buffer_event),
1976 cx.observe_in(&display_map, window, Self::on_display_map_changed),
1977 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
1978 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
1979 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
1980 cx.observe_window_activation(window, |editor, window, cx| {
1981 let active = window.is_window_active();
1982 editor.blink_manager.update(cx, |blink_manager, cx| {
1983 if active {
1984 blink_manager.enable(cx);
1985 } else {
1986 blink_manager.disable(cx);
1987 }
1988 });
1989 if active {
1990 editor.show_mouse_cursor();
1991 }
1992 }),
1993 ],
1994 tasks_update_task: None,
1995 pull_diagnostics_task: Task::ready(()),
1996 linked_edit_ranges: Default::default(),
1997 in_project_search: false,
1998 previous_search_ranges: None,
1999 breadcrumb_header: None,
2000 focused_block: None,
2001 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2002 addons: HashMap::default(),
2003 registered_buffers: HashMap::default(),
2004 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2005 selection_mark_mode: false,
2006 toggle_fold_multiple_buffers: Task::ready(()),
2007 serialize_selections: Task::ready(()),
2008 serialize_folds: Task::ready(()),
2009 text_style_refinement: None,
2010 load_diff_task: load_uncommitted_diff,
2011 temporary_diff_override: false,
2012 mouse_cursor_hidden: false,
2013 minimap: None,
2014 hide_mouse_mode: EditorSettings::get_global(cx)
2015 .hide_mouse
2016 .unwrap_or_default(),
2017 change_list: ChangeList::new(),
2018 mode,
2019 selection_drag_state: SelectionDragState::None,
2020 drag_and_drop_selection_enabled: EditorSettings::get_global(cx).drag_and_drop_selection,
2021 };
2022 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2023 editor
2024 ._subscriptions
2025 .push(cx.observe(breakpoints, |_, _, cx| {
2026 cx.notify();
2027 }));
2028 }
2029 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2030 editor._subscriptions.extend(project_subscriptions);
2031
2032 editor._subscriptions.push(cx.subscribe_in(
2033 &cx.entity(),
2034 window,
2035 |editor, _, e: &EditorEvent, window, cx| match e {
2036 EditorEvent::ScrollPositionChanged { local, .. } => {
2037 if *local {
2038 let new_anchor = editor.scroll_manager.anchor();
2039 let snapshot = editor.snapshot(window, cx);
2040 editor.update_restoration_data(cx, move |data| {
2041 data.scroll_position = (
2042 new_anchor.top_row(&snapshot.buffer_snapshot),
2043 new_anchor.offset,
2044 );
2045 });
2046 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2047 editor.inline_blame_popover.take();
2048 }
2049 }
2050 EditorEvent::Edited { .. } => {
2051 if !vim_enabled(cx) {
2052 let (map, selections) = editor.selections.all_adjusted_display(cx);
2053 let pop_state = editor
2054 .change_list
2055 .last()
2056 .map(|previous| {
2057 previous.len() == selections.len()
2058 && previous.iter().enumerate().all(|(ix, p)| {
2059 p.to_display_point(&map).row()
2060 == selections[ix].head().row()
2061 })
2062 })
2063 .unwrap_or(false);
2064 let new_positions = selections
2065 .into_iter()
2066 .map(|s| map.display_point_to_anchor(s.head(), Bias::Left))
2067 .collect();
2068 editor
2069 .change_list
2070 .push_to_change_list(pop_state, new_positions);
2071 }
2072 }
2073 _ => (),
2074 },
2075 ));
2076
2077 if let Some(dap_store) = editor
2078 .project
2079 .as_ref()
2080 .map(|project| project.read(cx).dap_store())
2081 {
2082 let weak_editor = cx.weak_entity();
2083
2084 editor
2085 ._subscriptions
2086 .push(
2087 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2088 let session_entity = cx.entity();
2089 weak_editor
2090 .update(cx, |editor, cx| {
2091 editor._subscriptions.push(
2092 cx.subscribe(&session_entity, Self::on_debug_session_event),
2093 );
2094 })
2095 .ok();
2096 }),
2097 );
2098
2099 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2100 editor
2101 ._subscriptions
2102 .push(cx.subscribe(&session, Self::on_debug_session_event));
2103 }
2104 }
2105
2106 // skip adding the initial selection to selection history
2107 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2108 editor.end_selection(window, cx);
2109 editor.selection_history.mode = SelectionHistoryMode::Normal;
2110
2111 editor.scroll_manager.show_scrollbars(window, cx);
2112 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &buffer, cx);
2113
2114 if full_mode {
2115 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2116 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2117
2118 if editor.git_blame_inline_enabled {
2119 editor.start_git_blame_inline(false, window, cx);
2120 }
2121
2122 editor.go_to_active_debug_line(window, cx);
2123
2124 if let Some(buffer) = buffer.read(cx).as_singleton() {
2125 if let Some(project) = editor.project.as_ref() {
2126 let handle = project.update(cx, |project, cx| {
2127 project.register_buffer_with_language_servers(&buffer, cx)
2128 });
2129 editor
2130 .registered_buffers
2131 .insert(buffer.read(cx).remote_id(), handle);
2132 }
2133 }
2134
2135 editor.minimap =
2136 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2137 editor.pull_diagnostics(None, window, cx);
2138 }
2139
2140 editor.report_editor_event("Editor Opened", None, cx);
2141 editor
2142 }
2143
2144 pub fn deploy_mouse_context_menu(
2145 &mut self,
2146 position: gpui::Point<Pixels>,
2147 context_menu: Entity<ContextMenu>,
2148 window: &mut Window,
2149 cx: &mut Context<Self>,
2150 ) {
2151 self.mouse_context_menu = Some(MouseContextMenu::new(
2152 self,
2153 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2154 context_menu,
2155 window,
2156 cx,
2157 ));
2158 }
2159
2160 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2161 self.mouse_context_menu
2162 .as_ref()
2163 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2164 }
2165
2166 pub fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
2167 self.key_context_internal(self.has_active_inline_completion(), window, cx)
2168 }
2169
2170 fn key_context_internal(
2171 &self,
2172 has_active_edit_prediction: bool,
2173 window: &Window,
2174 cx: &App,
2175 ) -> KeyContext {
2176 let mut key_context = KeyContext::new_with_defaults();
2177 key_context.add("Editor");
2178 let mode = match self.mode {
2179 EditorMode::SingleLine { .. } => "single_line",
2180 EditorMode::AutoHeight { .. } => "auto_height",
2181 EditorMode::Minimap { .. } => "minimap",
2182 EditorMode::Full { .. } => "full",
2183 };
2184
2185 if EditorSettings::jupyter_enabled(cx) {
2186 key_context.add("jupyter");
2187 }
2188
2189 key_context.set("mode", mode);
2190 if self.pending_rename.is_some() {
2191 key_context.add("renaming");
2192 }
2193
2194 match self.context_menu.borrow().as_ref() {
2195 Some(CodeContextMenu::Completions(_)) => {
2196 key_context.add("menu");
2197 key_context.add("showing_completions");
2198 }
2199 Some(CodeContextMenu::CodeActions(_)) => {
2200 key_context.add("menu");
2201 key_context.add("showing_code_actions")
2202 }
2203 None => {}
2204 }
2205
2206 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2207 if !self.focus_handle(cx).contains_focused(window, cx)
2208 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2209 {
2210 for addon in self.addons.values() {
2211 addon.extend_key_context(&mut key_context, cx)
2212 }
2213 }
2214
2215 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2216 if let Some(extension) = singleton_buffer
2217 .read(cx)
2218 .file()
2219 .and_then(|file| file.path().extension()?.to_str())
2220 {
2221 key_context.set("extension", extension.to_string());
2222 }
2223 } else {
2224 key_context.add("multibuffer");
2225 }
2226
2227 if has_active_edit_prediction {
2228 if self.edit_prediction_in_conflict() {
2229 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2230 } else {
2231 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2232 key_context.add("copilot_suggestion");
2233 }
2234 }
2235
2236 if self.selection_mark_mode {
2237 key_context.add("selection_mode");
2238 }
2239
2240 key_context
2241 }
2242
2243 fn show_mouse_cursor(&mut self) {
2244 self.mouse_cursor_hidden = false;
2245 }
2246
2247 pub fn hide_mouse_cursor(&mut self, origin: &HideMouseCursorOrigin) {
2248 self.mouse_cursor_hidden = match origin {
2249 HideMouseCursorOrigin::TypingAction => {
2250 matches!(
2251 self.hide_mouse_mode,
2252 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2253 )
2254 }
2255 HideMouseCursorOrigin::MovementAction => {
2256 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2257 }
2258 };
2259 }
2260
2261 pub fn edit_prediction_in_conflict(&self) -> bool {
2262 if !self.show_edit_predictions_in_menu() {
2263 return false;
2264 }
2265
2266 let showing_completions = self
2267 .context_menu
2268 .borrow()
2269 .as_ref()
2270 .map_or(false, |context| {
2271 matches!(context, CodeContextMenu::Completions(_))
2272 });
2273
2274 showing_completions
2275 || self.edit_prediction_requires_modifier()
2276 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2277 // bindings to insert tab characters.
2278 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2279 }
2280
2281 pub fn accept_edit_prediction_keybind(
2282 &self,
2283 accept_partial: bool,
2284 window: &Window,
2285 cx: &App,
2286 ) -> AcceptEditPredictionBinding {
2287 let key_context = self.key_context_internal(true, window, cx);
2288 let in_conflict = self.edit_prediction_in_conflict();
2289
2290 let bindings = if accept_partial {
2291 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2292 } else {
2293 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2294 };
2295
2296 // TODO: if the binding contains multiple keystrokes, display all of them, not
2297 // just the first one.
2298 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2299 !in_conflict
2300 || binding
2301 .keystrokes()
2302 .first()
2303 .map_or(false, |keystroke| keystroke.modifiers.modified())
2304 }))
2305 }
2306
2307 pub fn new_file(
2308 workspace: &mut Workspace,
2309 _: &workspace::NewFile,
2310 window: &mut Window,
2311 cx: &mut Context<Workspace>,
2312 ) {
2313 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2314 "Failed to create buffer",
2315 window,
2316 cx,
2317 |e, _, _| match e.error_code() {
2318 ErrorCode::RemoteUpgradeRequired => Some(format!(
2319 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2320 e.error_tag("required").unwrap_or("the latest version")
2321 )),
2322 _ => None,
2323 },
2324 );
2325 }
2326
2327 pub fn new_in_workspace(
2328 workspace: &mut Workspace,
2329 window: &mut Window,
2330 cx: &mut Context<Workspace>,
2331 ) -> Task<Result<Entity<Editor>>> {
2332 let project = workspace.project().clone();
2333 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2334
2335 cx.spawn_in(window, async move |workspace, cx| {
2336 let buffer = create.await?;
2337 workspace.update_in(cx, |workspace, window, cx| {
2338 let editor =
2339 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2340 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2341 editor
2342 })
2343 })
2344 }
2345
2346 fn new_file_vertical(
2347 workspace: &mut Workspace,
2348 _: &workspace::NewFileSplitVertical,
2349 window: &mut Window,
2350 cx: &mut Context<Workspace>,
2351 ) {
2352 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2353 }
2354
2355 fn new_file_horizontal(
2356 workspace: &mut Workspace,
2357 _: &workspace::NewFileSplitHorizontal,
2358 window: &mut Window,
2359 cx: &mut Context<Workspace>,
2360 ) {
2361 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2362 }
2363
2364 fn new_file_in_direction(
2365 workspace: &mut Workspace,
2366 direction: SplitDirection,
2367 window: &mut Window,
2368 cx: &mut Context<Workspace>,
2369 ) {
2370 let project = workspace.project().clone();
2371 let create = project.update(cx, |project, cx| project.create_buffer(cx));
2372
2373 cx.spawn_in(window, async move |workspace, cx| {
2374 let buffer = create.await?;
2375 workspace.update_in(cx, move |workspace, window, cx| {
2376 workspace.split_item(
2377 direction,
2378 Box::new(
2379 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2380 ),
2381 window,
2382 cx,
2383 )
2384 })?;
2385 anyhow::Ok(())
2386 })
2387 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2388 match e.error_code() {
2389 ErrorCode::RemoteUpgradeRequired => Some(format!(
2390 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2391 e.error_tag("required").unwrap_or("the latest version")
2392 )),
2393 _ => None,
2394 }
2395 });
2396 }
2397
2398 pub fn leader_id(&self) -> Option<CollaboratorId> {
2399 self.leader_id
2400 }
2401
2402 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2403 &self.buffer
2404 }
2405
2406 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2407 self.workspace.as_ref()?.0.upgrade()
2408 }
2409
2410 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2411 self.buffer().read(cx).title(cx)
2412 }
2413
2414 pub fn snapshot(&self, window: &mut Window, cx: &mut App) -> EditorSnapshot {
2415 let git_blame_gutter_max_author_length = self
2416 .render_git_blame_gutter(cx)
2417 .then(|| {
2418 if let Some(blame) = self.blame.as_ref() {
2419 let max_author_length =
2420 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2421 Some(max_author_length)
2422 } else {
2423 None
2424 }
2425 })
2426 .flatten();
2427
2428 EditorSnapshot {
2429 mode: self.mode.clone(),
2430 show_gutter: self.show_gutter,
2431 show_line_numbers: self.show_line_numbers,
2432 show_git_diff_gutter: self.show_git_diff_gutter,
2433 show_code_actions: self.show_code_actions,
2434 show_runnables: self.show_runnables,
2435 show_breakpoints: self.show_breakpoints,
2436 git_blame_gutter_max_author_length,
2437 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2438 scroll_anchor: self.scroll_manager.anchor(),
2439 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2440 placeholder_text: self.placeholder_text.clone(),
2441 is_focused: self.focus_handle.is_focused(window),
2442 current_line_highlight: self
2443 .current_line_highlight
2444 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2445 gutter_hovered: self.gutter_hovered,
2446 }
2447 }
2448
2449 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2450 self.buffer.read(cx).language_at(point, cx)
2451 }
2452
2453 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2454 self.buffer.read(cx).read(cx).file_at(point).cloned()
2455 }
2456
2457 pub fn active_excerpt(
2458 &self,
2459 cx: &App,
2460 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2461 self.buffer
2462 .read(cx)
2463 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2464 }
2465
2466 pub fn mode(&self) -> &EditorMode {
2467 &self.mode
2468 }
2469
2470 pub fn set_mode(&mut self, mode: EditorMode) {
2471 self.mode = mode;
2472 }
2473
2474 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2475 self.collaboration_hub.as_deref()
2476 }
2477
2478 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2479 self.collaboration_hub = Some(hub);
2480 }
2481
2482 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2483 self.in_project_search = in_project_search;
2484 }
2485
2486 pub fn set_custom_context_menu(
2487 &mut self,
2488 f: impl 'static
2489 + Fn(
2490 &mut Self,
2491 DisplayPoint,
2492 &mut Window,
2493 &mut Context<Self>,
2494 ) -> Option<Entity<ui::ContextMenu>>,
2495 ) {
2496 self.custom_context_menu = Some(Box::new(f))
2497 }
2498
2499 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2500 self.completion_provider = provider;
2501 }
2502
2503 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2504 self.semantics_provider.clone()
2505 }
2506
2507 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2508 self.semantics_provider = provider;
2509 }
2510
2511 pub fn set_edit_prediction_provider<T>(
2512 &mut self,
2513 provider: Option<Entity<T>>,
2514 window: &mut Window,
2515 cx: &mut Context<Self>,
2516 ) where
2517 T: EditPredictionProvider,
2518 {
2519 self.edit_prediction_provider =
2520 provider.map(|provider| RegisteredInlineCompletionProvider {
2521 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2522 if this.focus_handle.is_focused(window) {
2523 this.update_visible_inline_completion(window, cx);
2524 }
2525 }),
2526 provider: Arc::new(provider),
2527 });
2528 self.update_edit_prediction_settings(cx);
2529 self.refresh_inline_completion(false, false, window, cx);
2530 }
2531
2532 pub fn placeholder_text(&self) -> Option<&str> {
2533 self.placeholder_text.as_deref()
2534 }
2535
2536 pub fn set_placeholder_text(
2537 &mut self,
2538 placeholder_text: impl Into<Arc<str>>,
2539 cx: &mut Context<Self>,
2540 ) {
2541 let placeholder_text = Some(placeholder_text.into());
2542 if self.placeholder_text != placeholder_text {
2543 self.placeholder_text = placeholder_text;
2544 cx.notify();
2545 }
2546 }
2547
2548 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2549 self.cursor_shape = cursor_shape;
2550
2551 // Disrupt blink for immediate user feedback that the cursor shape has changed
2552 self.blink_manager.update(cx, BlinkManager::show_cursor);
2553
2554 cx.notify();
2555 }
2556
2557 pub fn set_current_line_highlight(
2558 &mut self,
2559 current_line_highlight: Option<CurrentLineHighlight>,
2560 ) {
2561 self.current_line_highlight = current_line_highlight;
2562 }
2563
2564 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2565 self.collapse_matches = collapse_matches;
2566 }
2567
2568 fn register_buffers_with_language_servers(&mut self, cx: &mut Context<Self>) {
2569 let buffers = self.buffer.read(cx).all_buffers();
2570 let Some(project) = self.project.as_ref() else {
2571 return;
2572 };
2573 project.update(cx, |project, cx| {
2574 for buffer in buffers {
2575 self.registered_buffers
2576 .entry(buffer.read(cx).remote_id())
2577 .or_insert_with(|| project.register_buffer_with_language_servers(&buffer, cx));
2578 }
2579 })
2580 }
2581
2582 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2583 if self.collapse_matches {
2584 return range.start..range.start;
2585 }
2586 range.clone()
2587 }
2588
2589 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2590 if self.display_map.read(cx).clip_at_line_ends != clip {
2591 self.display_map
2592 .update(cx, |map, _| map.clip_at_line_ends = clip);
2593 }
2594 }
2595
2596 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2597 self.input_enabled = input_enabled;
2598 }
2599
2600 pub fn set_inline_completions_hidden_for_vim_mode(
2601 &mut self,
2602 hidden: bool,
2603 window: &mut Window,
2604 cx: &mut Context<Self>,
2605 ) {
2606 if hidden != self.inline_completions_hidden_for_vim_mode {
2607 self.inline_completions_hidden_for_vim_mode = hidden;
2608 if hidden {
2609 self.update_visible_inline_completion(window, cx);
2610 } else {
2611 self.refresh_inline_completion(true, false, window, cx);
2612 }
2613 }
2614 }
2615
2616 pub fn set_menu_inline_completions_policy(&mut self, value: MenuInlineCompletionsPolicy) {
2617 self.menu_inline_completions_policy = value;
2618 }
2619
2620 pub fn set_autoindent(&mut self, autoindent: bool) {
2621 if autoindent {
2622 self.autoindent_mode = Some(AutoindentMode::EachLine);
2623 } else {
2624 self.autoindent_mode = None;
2625 }
2626 }
2627
2628 pub fn read_only(&self, cx: &App) -> bool {
2629 self.read_only || self.buffer.read(cx).read_only()
2630 }
2631
2632 pub fn set_read_only(&mut self, read_only: bool) {
2633 self.read_only = read_only;
2634 }
2635
2636 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2637 self.use_autoclose = autoclose;
2638 }
2639
2640 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2641 self.use_auto_surround = auto_surround;
2642 }
2643
2644 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2645 self.auto_replace_emoji_shortcode = auto_replace;
2646 }
2647
2648 pub fn toggle_edit_predictions(
2649 &mut self,
2650 _: &ToggleEditPrediction,
2651 window: &mut Window,
2652 cx: &mut Context<Self>,
2653 ) {
2654 if self.show_inline_completions_override.is_some() {
2655 self.set_show_edit_predictions(None, window, cx);
2656 } else {
2657 let show_edit_predictions = !self.edit_predictions_enabled();
2658 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2659 }
2660 }
2661
2662 pub fn set_show_edit_predictions(
2663 &mut self,
2664 show_edit_predictions: Option<bool>,
2665 window: &mut Window,
2666 cx: &mut Context<Self>,
2667 ) {
2668 self.show_inline_completions_override = show_edit_predictions;
2669 self.update_edit_prediction_settings(cx);
2670
2671 if let Some(false) = show_edit_predictions {
2672 self.discard_inline_completion(false, cx);
2673 } else {
2674 self.refresh_inline_completion(false, true, window, cx);
2675 }
2676 }
2677
2678 fn inline_completions_disabled_in_scope(
2679 &self,
2680 buffer: &Entity<Buffer>,
2681 buffer_position: language::Anchor,
2682 cx: &App,
2683 ) -> bool {
2684 let snapshot = buffer.read(cx).snapshot();
2685 let settings = snapshot.settings_at(buffer_position, cx);
2686
2687 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2688 return false;
2689 };
2690
2691 scope.override_name().map_or(false, |scope_name| {
2692 settings
2693 .edit_predictions_disabled_in
2694 .iter()
2695 .any(|s| s == scope_name)
2696 })
2697 }
2698
2699 pub fn set_use_modal_editing(&mut self, to: bool) {
2700 self.use_modal_editing = to;
2701 }
2702
2703 pub fn use_modal_editing(&self) -> bool {
2704 self.use_modal_editing
2705 }
2706
2707 fn selections_did_change(
2708 &mut self,
2709 local: bool,
2710 old_cursor_position: &Anchor,
2711 should_update_completions: bool,
2712 window: &mut Window,
2713 cx: &mut Context<Self>,
2714 ) {
2715 window.invalidate_character_coordinates();
2716
2717 // Copy selections to primary selection buffer
2718 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
2719 if local {
2720 let selections = self.selections.all::<usize>(cx);
2721 let buffer_handle = self.buffer.read(cx).read(cx);
2722
2723 let mut text = String::new();
2724 for (index, selection) in selections.iter().enumerate() {
2725 let text_for_selection = buffer_handle
2726 .text_for_range(selection.start..selection.end)
2727 .collect::<String>();
2728
2729 text.push_str(&text_for_selection);
2730 if index != selections.len() - 1 {
2731 text.push('\n');
2732 }
2733 }
2734
2735 if !text.is_empty() {
2736 cx.write_to_primary(ClipboardItem::new_string(text));
2737 }
2738 }
2739
2740 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
2741 self.buffer.update(cx, |buffer, cx| {
2742 buffer.set_active_selections(
2743 &self.selections.disjoint_anchors(),
2744 self.selections.line_mode,
2745 self.cursor_shape,
2746 cx,
2747 )
2748 });
2749 }
2750 let display_map = self
2751 .display_map
2752 .update(cx, |display_map, cx| display_map.snapshot(cx));
2753 let buffer = &display_map.buffer_snapshot;
2754 if self.selections.count() == 1 {
2755 self.add_selections_state = None;
2756 }
2757 self.select_next_state = None;
2758 self.select_prev_state = None;
2759 self.select_syntax_node_history.try_clear();
2760 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors(), buffer);
2761 self.snippet_stack
2762 .invalidate(&self.selections.disjoint_anchors(), buffer);
2763 self.take_rename(false, window, cx);
2764
2765 let newest_selection = self.selections.newest_anchor();
2766 let new_cursor_position = newest_selection.head();
2767 let selection_start = newest_selection.start;
2768
2769 self.push_to_nav_history(
2770 *old_cursor_position,
2771 Some(new_cursor_position.to_point(buffer)),
2772 false,
2773 cx,
2774 );
2775
2776 if local {
2777 if let Some(buffer_id) = new_cursor_position.buffer_id {
2778 if !self.registered_buffers.contains_key(&buffer_id) {
2779 if let Some(project) = self.project.as_ref() {
2780 project.update(cx, |project, cx| {
2781 let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) else {
2782 return;
2783 };
2784 self.registered_buffers.insert(
2785 buffer_id,
2786 project.register_buffer_with_language_servers(&buffer, cx),
2787 );
2788 })
2789 }
2790 }
2791 }
2792
2793 let mut context_menu = self.context_menu.borrow_mut();
2794 let completion_menu = match context_menu.as_ref() {
2795 Some(CodeContextMenu::Completions(menu)) => Some(menu),
2796 Some(CodeContextMenu::CodeActions(_)) => {
2797 *context_menu = None;
2798 None
2799 }
2800 None => None,
2801 };
2802 let completion_position = completion_menu.map(|menu| menu.initial_position);
2803 drop(context_menu);
2804
2805 if should_update_completions {
2806 if let Some(completion_position) = completion_position {
2807 let start_offset = selection_start.to_offset(buffer);
2808 let position_matches = start_offset == completion_position.to_offset(buffer);
2809 let continue_showing = if position_matches {
2810 if self.snippet_stack.is_empty() {
2811 buffer.char_kind_before(start_offset, true) == Some(CharKind::Word)
2812 } else {
2813 // Snippet choices can be shown even when the cursor is in whitespace.
2814 // Dismissing the menu when actions like backspace
2815 true
2816 }
2817 } else {
2818 false
2819 };
2820
2821 if continue_showing {
2822 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
2823 } else {
2824 self.hide_context_menu(window, cx);
2825 }
2826 }
2827 }
2828
2829 hide_hover(self, cx);
2830
2831 if old_cursor_position.to_display_point(&display_map).row()
2832 != new_cursor_position.to_display_point(&display_map).row()
2833 {
2834 self.available_code_actions.take();
2835 }
2836 self.refresh_code_actions(window, cx);
2837 self.refresh_document_highlights(cx);
2838 self.refresh_selected_text_highlights(false, window, cx);
2839 refresh_matching_bracket_highlights(self, window, cx);
2840 self.update_visible_inline_completion(window, cx);
2841 self.edit_prediction_requires_modifier_in_indent_conflict = true;
2842 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
2843 self.inline_blame_popover.take();
2844 if self.git_blame_inline_enabled {
2845 self.start_inline_blame_timer(window, cx);
2846 }
2847 }
2848
2849 self.blink_manager.update(cx, BlinkManager::pause_blinking);
2850 cx.emit(EditorEvent::SelectionsChanged { local });
2851
2852 let selections = &self.selections.disjoint;
2853 if selections.len() == 1 {
2854 cx.emit(SearchEvent::ActiveMatchChanged)
2855 }
2856 if local {
2857 if let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
2858 let inmemory_selections = selections
2859 .iter()
2860 .map(|s| {
2861 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
2862 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
2863 })
2864 .collect();
2865 self.update_restoration_data(cx, |data| {
2866 data.selections = inmemory_selections;
2867 });
2868
2869 if WorkspaceSettings::get(None, cx).restore_on_startup
2870 != RestoreOnStartupBehavior::None
2871 {
2872 if let Some(workspace_id) =
2873 self.workspace.as_ref().and_then(|workspace| workspace.1)
2874 {
2875 let snapshot = self.buffer().read(cx).snapshot(cx);
2876 let selections = selections.clone();
2877 let background_executor = cx.background_executor().clone();
2878 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2879 self.serialize_selections = cx.background_spawn(async move {
2880 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2881 let db_selections = selections
2882 .iter()
2883 .map(|selection| {
2884 (
2885 selection.start.to_offset(&snapshot),
2886 selection.end.to_offset(&snapshot),
2887 )
2888 })
2889 .collect();
2890
2891 DB.save_editor_selections(editor_id, workspace_id, db_selections)
2892 .await
2893 .with_context(|| format!("persisting editor selections for editor {editor_id}, workspace {workspace_id:?}"))
2894 .log_err();
2895 });
2896 }
2897 }
2898 }
2899 }
2900
2901 cx.notify();
2902 }
2903
2904 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
2905 use text::ToOffset as _;
2906 use text::ToPoint as _;
2907
2908 if self.mode.is_minimap()
2909 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
2910 {
2911 return;
2912 }
2913
2914 let Some(singleton) = self.buffer().read(cx).as_singleton() else {
2915 return;
2916 };
2917
2918 let snapshot = singleton.read(cx).snapshot();
2919 let inmemory_folds = self.display_map.update(cx, |display_map, cx| {
2920 let display_snapshot = display_map.snapshot(cx);
2921
2922 display_snapshot
2923 .folds_in_range(0..display_snapshot.buffer_snapshot.len())
2924 .map(|fold| {
2925 fold.range.start.text_anchor.to_point(&snapshot)
2926 ..fold.range.end.text_anchor.to_point(&snapshot)
2927 })
2928 .collect()
2929 });
2930 self.update_restoration_data(cx, |data| {
2931 data.folds = inmemory_folds;
2932 });
2933
2934 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
2935 return;
2936 };
2937 let background_executor = cx.background_executor().clone();
2938 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
2939 let db_folds = self.display_map.update(cx, |display_map, cx| {
2940 display_map
2941 .snapshot(cx)
2942 .folds_in_range(0..snapshot.len())
2943 .map(|fold| {
2944 (
2945 fold.range.start.text_anchor.to_offset(&snapshot),
2946 fold.range.end.text_anchor.to_offset(&snapshot),
2947 )
2948 })
2949 .collect()
2950 });
2951 self.serialize_folds = cx.background_spawn(async move {
2952 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
2953 DB.save_editor_folds(editor_id, workspace_id, db_folds)
2954 .await
2955 .with_context(|| {
2956 format!(
2957 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
2958 )
2959 })
2960 .log_err();
2961 });
2962 }
2963
2964 pub fn sync_selections(
2965 &mut self,
2966 other: Entity<Editor>,
2967 cx: &mut Context<Self>,
2968 ) -> gpui::Subscription {
2969 let other_selections = other.read(cx).selections.disjoint.to_vec();
2970 self.selections.change_with(cx, |selections| {
2971 selections.select_anchors(other_selections);
2972 });
2973
2974 let other_subscription =
2975 cx.subscribe(&other, |this, other, other_evt, cx| match other_evt {
2976 EditorEvent::SelectionsChanged { local: true } => {
2977 let other_selections = other.read(cx).selections.disjoint.to_vec();
2978 if other_selections.is_empty() {
2979 return;
2980 }
2981 this.selections.change_with(cx, |selections| {
2982 selections.select_anchors(other_selections);
2983 });
2984 }
2985 _ => {}
2986 });
2987
2988 let this_subscription =
2989 cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| match this_evt {
2990 EditorEvent::SelectionsChanged { local: true } => {
2991 let these_selections = this.selections.disjoint.to_vec();
2992 if these_selections.is_empty() {
2993 return;
2994 }
2995 other.update(cx, |other_editor, cx| {
2996 other_editor.selections.change_with(cx, |selections| {
2997 selections.select_anchors(these_selections);
2998 })
2999 });
3000 }
3001 _ => {}
3002 });
3003
3004 Subscription::join(other_subscription, this_subscription)
3005 }
3006
3007 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3008 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3009 /// effects of selection change occur at the end of the transaction.
3010 pub fn change_selections<R>(
3011 &mut self,
3012 autoscroll: Option<Autoscroll>,
3013 window: &mut Window,
3014 cx: &mut Context<Self>,
3015 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3016 ) -> R {
3017 self.change_selections_inner(true, autoscroll, window, cx, change)
3018 }
3019
3020 pub(crate) fn change_selections_without_updating_completions<R>(
3021 &mut self,
3022 autoscroll: Option<Autoscroll>,
3023 window: &mut Window,
3024 cx: &mut Context<Self>,
3025 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3026 ) -> R {
3027 self.change_selections_inner(false, autoscroll, window, cx, change)
3028 }
3029
3030 fn change_selections_inner<R>(
3031 &mut self,
3032 should_update_completions: bool,
3033 autoscroll: Option<Autoscroll>,
3034 window: &mut Window,
3035 cx: &mut Context<Self>,
3036 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3037 ) -> R {
3038 if let Some(state) = &mut self.deferred_selection_effects_state {
3039 state.autoscroll = autoscroll.or(state.autoscroll);
3040 state.should_update_completions = should_update_completions;
3041 let (changed, result) = self.selections.change_with(cx, change);
3042 state.changed |= changed;
3043 return result;
3044 }
3045 let mut state = DeferredSelectionEffectsState {
3046 changed: false,
3047 should_update_completions,
3048 autoscroll,
3049 old_cursor_position: self.selections.newest_anchor().head(),
3050 history_entry: SelectionHistoryEntry {
3051 selections: self.selections.disjoint_anchors(),
3052 select_next_state: self.select_next_state.clone(),
3053 select_prev_state: self.select_prev_state.clone(),
3054 add_selections_state: self.add_selections_state.clone(),
3055 },
3056 };
3057 let (changed, result) = self.selections.change_with(cx, change);
3058 state.changed = state.changed || changed;
3059 if self.defer_selection_effects {
3060 self.deferred_selection_effects_state = Some(state);
3061 } else {
3062 self.apply_selection_effects(state, window, cx);
3063 }
3064 result
3065 }
3066
3067 /// Defers the effects of selection change, so that the effects of multiple calls to
3068 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3069 /// to selection history and the state of popovers based on selection position aren't
3070 /// erroneously updated.
3071 pub fn with_selection_effects_deferred<R>(
3072 &mut self,
3073 window: &mut Window,
3074 cx: &mut Context<Self>,
3075 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3076 ) -> R {
3077 let already_deferred = self.defer_selection_effects;
3078 self.defer_selection_effects = true;
3079 let result = update(self, window, cx);
3080 if !already_deferred {
3081 self.defer_selection_effects = false;
3082 if let Some(state) = self.deferred_selection_effects_state.take() {
3083 self.apply_selection_effects(state, window, cx);
3084 }
3085 }
3086 result
3087 }
3088
3089 fn apply_selection_effects(
3090 &mut self,
3091 state: DeferredSelectionEffectsState,
3092 window: &mut Window,
3093 cx: &mut Context<Self>,
3094 ) {
3095 if state.changed {
3096 self.selection_history.push(state.history_entry);
3097
3098 if let Some(autoscroll) = state.autoscroll {
3099 self.request_autoscroll(autoscroll, cx);
3100 }
3101
3102 let old_cursor_position = &state.old_cursor_position;
3103
3104 self.selections_did_change(
3105 true,
3106 &old_cursor_position,
3107 state.should_update_completions,
3108 window,
3109 cx,
3110 );
3111
3112 if self.should_open_signature_help_automatically(&old_cursor_position, cx) {
3113 self.show_signature_help(&ShowSignatureHelp, window, cx);
3114 }
3115 }
3116 }
3117
3118 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3119 where
3120 I: IntoIterator<Item = (Range<S>, T)>,
3121 S: ToOffset,
3122 T: Into<Arc<str>>,
3123 {
3124 if self.read_only(cx) {
3125 return;
3126 }
3127
3128 self.buffer
3129 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3130 }
3131
3132 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3133 where
3134 I: IntoIterator<Item = (Range<S>, T)>,
3135 S: ToOffset,
3136 T: Into<Arc<str>>,
3137 {
3138 if self.read_only(cx) {
3139 return;
3140 }
3141
3142 self.buffer.update(cx, |buffer, cx| {
3143 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3144 });
3145 }
3146
3147 pub fn edit_with_block_indent<I, S, T>(
3148 &mut self,
3149 edits: I,
3150 original_indent_columns: Vec<Option<u32>>,
3151 cx: &mut Context<Self>,
3152 ) where
3153 I: IntoIterator<Item = (Range<S>, T)>,
3154 S: ToOffset,
3155 T: Into<Arc<str>>,
3156 {
3157 if self.read_only(cx) {
3158 return;
3159 }
3160
3161 self.buffer.update(cx, |buffer, cx| {
3162 buffer.edit(
3163 edits,
3164 Some(AutoindentMode::Block {
3165 original_indent_columns,
3166 }),
3167 cx,
3168 )
3169 });
3170 }
3171
3172 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3173 self.hide_context_menu(window, cx);
3174
3175 match phase {
3176 SelectPhase::Begin {
3177 position,
3178 add,
3179 click_count,
3180 } => self.begin_selection(position, add, click_count, window, cx),
3181 SelectPhase::BeginColumnar {
3182 position,
3183 goal_column,
3184 reset,
3185 } => self.begin_columnar_selection(position, goal_column, reset, window, cx),
3186 SelectPhase::Extend {
3187 position,
3188 click_count,
3189 } => self.extend_selection(position, click_count, window, cx),
3190 SelectPhase::Update {
3191 position,
3192 goal_column,
3193 scroll_delta,
3194 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3195 SelectPhase::End => self.end_selection(window, cx),
3196 }
3197 }
3198
3199 fn extend_selection(
3200 &mut self,
3201 position: DisplayPoint,
3202 click_count: usize,
3203 window: &mut Window,
3204 cx: &mut Context<Self>,
3205 ) {
3206 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3207 let tail = self.selections.newest::<usize>(cx).tail();
3208 self.begin_selection(position, false, click_count, window, cx);
3209
3210 let position = position.to_offset(&display_map, Bias::Left);
3211 let tail_anchor = display_map.buffer_snapshot.anchor_before(tail);
3212
3213 let mut pending_selection = self
3214 .selections
3215 .pending_anchor()
3216 .expect("extend_selection not called with pending selection");
3217 if position >= tail {
3218 pending_selection.start = tail_anchor;
3219 } else {
3220 pending_selection.end = tail_anchor;
3221 pending_selection.reversed = true;
3222 }
3223
3224 let mut pending_mode = self.selections.pending_mode().unwrap();
3225 match &mut pending_mode {
3226 SelectMode::Word(range) | SelectMode::Line(range) => *range = tail_anchor..tail_anchor,
3227 _ => {}
3228 }
3229
3230 let auto_scroll = EditorSettings::get_global(cx).autoscroll_on_clicks;
3231
3232 self.change_selections(auto_scroll.then(Autoscroll::fit), window, cx, |s| {
3233 s.set_pending(pending_selection, pending_mode)
3234 });
3235 }
3236
3237 fn begin_selection(
3238 &mut self,
3239 position: DisplayPoint,
3240 add: bool,
3241 click_count: usize,
3242 window: &mut Window,
3243 cx: &mut Context<Self>,
3244 ) {
3245 if !self.focus_handle.is_focused(window) {
3246 self.last_focused_descendant = None;
3247 window.focus(&self.focus_handle);
3248 }
3249
3250 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3251 let buffer = &display_map.buffer_snapshot;
3252 let position = display_map.clip_point(position, Bias::Left);
3253
3254 let start;
3255 let end;
3256 let mode;
3257 let mut auto_scroll;
3258 match click_count {
3259 1 => {
3260 start = buffer.anchor_before(position.to_point(&display_map));
3261 end = start;
3262 mode = SelectMode::Character;
3263 auto_scroll = true;
3264 }
3265 2 => {
3266 let range = movement::surrounding_word(&display_map, position);
3267 start = buffer.anchor_before(range.start.to_point(&display_map));
3268 end = buffer.anchor_before(range.end.to_point(&display_map));
3269 mode = SelectMode::Word(start..end);
3270 auto_scroll = true;
3271 }
3272 3 => {
3273 let position = display_map
3274 .clip_point(position, Bias::Left)
3275 .to_point(&display_map);
3276 let line_start = display_map.prev_line_boundary(position).0;
3277 let next_line_start = buffer.clip_point(
3278 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3279 Bias::Left,
3280 );
3281 start = buffer.anchor_before(line_start);
3282 end = buffer.anchor_before(next_line_start);
3283 mode = SelectMode::Line(start..end);
3284 auto_scroll = true;
3285 }
3286 _ => {
3287 start = buffer.anchor_before(0);
3288 end = buffer.anchor_before(buffer.len());
3289 mode = SelectMode::All;
3290 auto_scroll = false;
3291 }
3292 }
3293 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3294
3295 let point_to_delete: Option<usize> = {
3296 let selected_points: Vec<Selection<Point>> =
3297 self.selections.disjoint_in_range(start..end, cx);
3298
3299 if !add || click_count > 1 {
3300 None
3301 } else if !selected_points.is_empty() {
3302 Some(selected_points[0].id)
3303 } else {
3304 let clicked_point_already_selected =
3305 self.selections.disjoint.iter().find(|selection| {
3306 selection.start.to_point(buffer) == start.to_point(buffer)
3307 || selection.end.to_point(buffer) == end.to_point(buffer)
3308 });
3309
3310 clicked_point_already_selected.map(|selection| selection.id)
3311 }
3312 };
3313
3314 let selections_count = self.selections.count();
3315
3316 self.change_selections(auto_scroll.then(Autoscroll::newest), window, cx, |s| {
3317 if let Some(point_to_delete) = point_to_delete {
3318 s.delete(point_to_delete);
3319
3320 if selections_count == 1 {
3321 s.set_pending_anchor_range(start..end, mode);
3322 }
3323 } else {
3324 if !add {
3325 s.clear_disjoint();
3326 }
3327
3328 s.set_pending_anchor_range(start..end, mode);
3329 }
3330 });
3331 }
3332
3333 fn begin_columnar_selection(
3334 &mut self,
3335 position: DisplayPoint,
3336 goal_column: u32,
3337 reset: bool,
3338 window: &mut Window,
3339 cx: &mut Context<Self>,
3340 ) {
3341 if !self.focus_handle.is_focused(window) {
3342 self.last_focused_descendant = None;
3343 window.focus(&self.focus_handle);
3344 }
3345
3346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3347
3348 if reset {
3349 let pointer_position = display_map
3350 .buffer_snapshot
3351 .anchor_before(position.to_point(&display_map));
3352
3353 self.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
3354 s.clear_disjoint();
3355 s.set_pending_anchor_range(
3356 pointer_position..pointer_position,
3357 SelectMode::Character,
3358 );
3359 });
3360 if position.column() != goal_column {
3361 self.columnar_display_point = Some(DisplayPoint::new(position.row(), goal_column));
3362 } else {
3363 self.columnar_display_point = None;
3364 }
3365 }
3366
3367 let tail = self.selections.newest::<Point>(cx).tail();
3368 self.columnar_selection_tail = Some(display_map.buffer_snapshot.anchor_before(tail));
3369
3370 if !reset {
3371 self.columnar_display_point = None;
3372 self.select_columns(
3373 tail.to_display_point(&display_map),
3374 position,
3375 goal_column,
3376 &display_map,
3377 window,
3378 cx,
3379 );
3380 }
3381 }
3382
3383 fn update_selection(
3384 &mut self,
3385 position: DisplayPoint,
3386 goal_column: u32,
3387 scroll_delta: gpui::Point<f32>,
3388 window: &mut Window,
3389 cx: &mut Context<Self>,
3390 ) {
3391 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3392
3393 if let Some(tail) = self.columnar_selection_tail.as_ref() {
3394 let tail = self
3395 .columnar_display_point
3396 .unwrap_or_else(|| tail.to_display_point(&display_map));
3397 self.select_columns(tail, position, goal_column, &display_map, window, cx);
3398 } else if let Some(mut pending) = self.selections.pending_anchor() {
3399 let buffer = self.buffer.read(cx).snapshot(cx);
3400 let head;
3401 let tail;
3402 let mode = self.selections.pending_mode().unwrap();
3403 match &mode {
3404 SelectMode::Character => {
3405 head = position.to_point(&display_map);
3406 tail = pending.tail().to_point(&buffer);
3407 }
3408 SelectMode::Word(original_range) => {
3409 let original_display_range = original_range.start.to_display_point(&display_map)
3410 ..original_range.end.to_display_point(&display_map);
3411 let original_buffer_range = original_display_range.start.to_point(&display_map)
3412 ..original_display_range.end.to_point(&display_map);
3413 if movement::is_inside_word(&display_map, position)
3414 || original_display_range.contains(&position)
3415 {
3416 let word_range = movement::surrounding_word(&display_map, position);
3417 if word_range.start < original_display_range.start {
3418 head = word_range.start.to_point(&display_map);
3419 } else {
3420 head = word_range.end.to_point(&display_map);
3421 }
3422 } else {
3423 head = position.to_point(&display_map);
3424 }
3425
3426 if head <= original_buffer_range.start {
3427 tail = original_buffer_range.end;
3428 } else {
3429 tail = original_buffer_range.start;
3430 }
3431 }
3432 SelectMode::Line(original_range) => {
3433 let original_range = original_range.to_point(&display_map.buffer_snapshot);
3434
3435 let position = display_map
3436 .clip_point(position, Bias::Left)
3437 .to_point(&display_map);
3438 let line_start = display_map.prev_line_boundary(position).0;
3439 let next_line_start = buffer.clip_point(
3440 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3441 Bias::Left,
3442 );
3443
3444 if line_start < original_range.start {
3445 head = line_start
3446 } else {
3447 head = next_line_start
3448 }
3449
3450 if head <= original_range.start {
3451 tail = original_range.end;
3452 } else {
3453 tail = original_range.start;
3454 }
3455 }
3456 SelectMode::All => {
3457 return;
3458 }
3459 };
3460
3461 if head < tail {
3462 pending.start = buffer.anchor_before(head);
3463 pending.end = buffer.anchor_before(tail);
3464 pending.reversed = true;
3465 } else {
3466 pending.start = buffer.anchor_before(tail);
3467 pending.end = buffer.anchor_before(head);
3468 pending.reversed = false;
3469 }
3470
3471 self.change_selections(None, window, cx, |s| {
3472 s.set_pending(pending, mode);
3473 });
3474 } else {
3475 log::error!("update_selection dispatched with no pending selection");
3476 return;
3477 }
3478
3479 self.apply_scroll_delta(scroll_delta, window, cx);
3480 cx.notify();
3481 }
3482
3483 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3484 self.columnar_selection_tail.take();
3485 if self.selections.pending_anchor().is_some() {
3486 let selections = self.selections.all::<usize>(cx);
3487 self.change_selections(None, window, cx, |s| {
3488 s.select(selections);
3489 s.clear_pending();
3490 });
3491 }
3492 }
3493
3494 fn select_columns(
3495 &mut self,
3496 tail: DisplayPoint,
3497 head: DisplayPoint,
3498 goal_column: u32,
3499 display_map: &DisplaySnapshot,
3500 window: &mut Window,
3501 cx: &mut Context<Self>,
3502 ) {
3503 let start_row = cmp::min(tail.row(), head.row());
3504 let end_row = cmp::max(tail.row(), head.row());
3505 let start_column = cmp::min(tail.column(), goal_column);
3506 let end_column = cmp::max(tail.column(), goal_column);
3507 let reversed = start_column < tail.column();
3508
3509 let selection_ranges = (start_row.0..=end_row.0)
3510 .map(DisplayRow)
3511 .filter_map(|row| {
3512 if !display_map.is_block_line(row) {
3513 let start = display_map
3514 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3515 .to_point(display_map);
3516 let end = display_map
3517 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3518 .to_point(display_map);
3519 if reversed {
3520 Some(end..start)
3521 } else {
3522 Some(start..end)
3523 }
3524 } else {
3525 None
3526 }
3527 })
3528 .collect::<Vec<_>>();
3529
3530 let mut non_empty_ranges = selection_ranges
3531 .iter()
3532 .filter(|selection_range| selection_range.start != selection_range.end)
3533 .peekable();
3534
3535 let ranges = if non_empty_ranges.peek().is_some() {
3536 non_empty_ranges.cloned().collect()
3537 } else {
3538 selection_ranges
3539 };
3540
3541 self.change_selections(None, window, cx, |s| {
3542 s.select_ranges(ranges);
3543 });
3544 cx.notify();
3545 }
3546
3547 pub fn has_non_empty_selection(&self, cx: &mut App) -> bool {
3548 self.selections
3549 .all_adjusted(cx)
3550 .iter()
3551 .any(|selection| !selection.is_empty())
3552 }
3553
3554 pub fn has_pending_nonempty_selection(&self) -> bool {
3555 let pending_nonempty_selection = match self.selections.pending_anchor() {
3556 Some(Selection { start, end, .. }) => start != end,
3557 None => false,
3558 };
3559
3560 pending_nonempty_selection
3561 || (self.columnar_selection_tail.is_some() && self.selections.disjoint.len() > 1)
3562 }
3563
3564 pub fn has_pending_selection(&self) -> bool {
3565 self.selections.pending_anchor().is_some() || self.columnar_selection_tail.is_some()
3566 }
3567
3568 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3569 self.selection_mark_mode = false;
3570 self.selection_drag_state = SelectionDragState::None;
3571
3572 if self.clear_expanded_diff_hunks(cx) {
3573 cx.notify();
3574 return;
3575 }
3576 if self.dismiss_menus_and_popups(true, window, cx) {
3577 return;
3578 }
3579
3580 if self.mode.is_full()
3581 && self.change_selections(Some(Autoscroll::fit()), window, cx, |s| s.try_cancel())
3582 {
3583 return;
3584 }
3585
3586 cx.propagate();
3587 }
3588
3589 pub fn dismiss_menus_and_popups(
3590 &mut self,
3591 is_user_requested: bool,
3592 window: &mut Window,
3593 cx: &mut Context<Self>,
3594 ) -> bool {
3595 if self.take_rename(false, window, cx).is_some() {
3596 return true;
3597 }
3598
3599 if hide_hover(self, cx) {
3600 return true;
3601 }
3602
3603 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3604 return true;
3605 }
3606
3607 if self.hide_context_menu(window, cx).is_some() {
3608 return true;
3609 }
3610
3611 if self.mouse_context_menu.take().is_some() {
3612 return true;
3613 }
3614
3615 if is_user_requested && self.discard_inline_completion(true, cx) {
3616 return true;
3617 }
3618
3619 if self.snippet_stack.pop().is_some() {
3620 return true;
3621 }
3622
3623 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3624 self.dismiss_diagnostics(cx);
3625 return true;
3626 }
3627
3628 false
3629 }
3630
3631 fn linked_editing_ranges_for(
3632 &self,
3633 selection: Range<text::Anchor>,
3634 cx: &App,
3635 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3636 if self.linked_edit_ranges.is_empty() {
3637 return None;
3638 }
3639 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3640 selection.end.buffer_id.and_then(|end_buffer_id| {
3641 if selection.start.buffer_id != Some(end_buffer_id) {
3642 return None;
3643 }
3644 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3645 let snapshot = buffer.read(cx).snapshot();
3646 self.linked_edit_ranges
3647 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3648 .map(|ranges| (ranges, snapshot, buffer))
3649 })?;
3650 use text::ToOffset as TO;
3651 // find offset from the start of current range to current cursor position
3652 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3653
3654 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3655 let start_difference = start_offset - start_byte_offset;
3656 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
3657 let end_difference = end_offset - start_byte_offset;
3658 // Current range has associated linked ranges.
3659 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3660 for range in linked_ranges.iter() {
3661 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
3662 let end_offset = start_offset + end_difference;
3663 let start_offset = start_offset + start_difference;
3664 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
3665 continue;
3666 }
3667 if self.selections.disjoint_anchor_ranges().any(|s| {
3668 if s.start.buffer_id != selection.start.buffer_id
3669 || s.end.buffer_id != selection.end.buffer_id
3670 {
3671 return false;
3672 }
3673 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
3674 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
3675 }) {
3676 continue;
3677 }
3678 let start = buffer_snapshot.anchor_after(start_offset);
3679 let end = buffer_snapshot.anchor_after(end_offset);
3680 linked_edits
3681 .entry(buffer.clone())
3682 .or_default()
3683 .push(start..end);
3684 }
3685 Some(linked_edits)
3686 }
3687
3688 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
3689 let text: Arc<str> = text.into();
3690
3691 if self.read_only(cx) {
3692 return;
3693 }
3694
3695 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
3696
3697 let selections = self.selections.all_adjusted(cx);
3698 let mut bracket_inserted = false;
3699 let mut edits = Vec::new();
3700 let mut linked_edits = HashMap::<_, Vec<_>>::default();
3701 let mut new_selections = Vec::with_capacity(selections.len());
3702 let mut new_autoclose_regions = Vec::new();
3703 let snapshot = self.buffer.read(cx).read(cx);
3704 let mut clear_linked_edit_ranges = false;
3705
3706 for (selection, autoclose_region) in
3707 self.selections_with_autoclose_regions(selections, &snapshot)
3708 {
3709 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
3710 // Determine if the inserted text matches the opening or closing
3711 // bracket of any of this language's bracket pairs.
3712 let mut bracket_pair = None;
3713 let mut is_bracket_pair_start = false;
3714 let mut is_bracket_pair_end = false;
3715 if !text.is_empty() {
3716 let mut bracket_pair_matching_end = None;
3717 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
3718 // and they are removing the character that triggered IME popup.
3719 for (pair, enabled) in scope.brackets() {
3720 if !pair.close && !pair.surround {
3721 continue;
3722 }
3723
3724 if enabled && pair.start.ends_with(text.as_ref()) {
3725 let prefix_len = pair.start.len() - text.len();
3726 let preceding_text_matches_prefix = prefix_len == 0
3727 || (selection.start.column >= (prefix_len as u32)
3728 && snapshot.contains_str_at(
3729 Point::new(
3730 selection.start.row,
3731 selection.start.column - (prefix_len as u32),
3732 ),
3733 &pair.start[..prefix_len],
3734 ));
3735 if preceding_text_matches_prefix {
3736 bracket_pair = Some(pair.clone());
3737 is_bracket_pair_start = true;
3738 break;
3739 }
3740 }
3741 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
3742 {
3743 // take first bracket pair matching end, but don't break in case a later bracket
3744 // pair matches start
3745 bracket_pair_matching_end = Some(pair.clone());
3746 }
3747 }
3748 if bracket_pair.is_none() && bracket_pair_matching_end.is_some() {
3749 bracket_pair = Some(bracket_pair_matching_end.unwrap());
3750 is_bracket_pair_end = true;
3751 }
3752 }
3753
3754 if let Some(bracket_pair) = bracket_pair {
3755 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
3756 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
3757 let auto_surround =
3758 self.use_auto_surround && snapshot_settings.use_auto_surround;
3759 if selection.is_empty() {
3760 if is_bracket_pair_start {
3761 // If the inserted text is a suffix of an opening bracket and the
3762 // selection is preceded by the rest of the opening bracket, then
3763 // insert the closing bracket.
3764 let following_text_allows_autoclose = snapshot
3765 .chars_at(selection.start)
3766 .next()
3767 .map_or(true, |c| scope.should_autoclose_before(c));
3768
3769 let preceding_text_allows_autoclose = selection.start.column == 0
3770 || snapshot.reversed_chars_at(selection.start).next().map_or(
3771 true,
3772 |c| {
3773 bracket_pair.start != bracket_pair.end
3774 || !snapshot
3775 .char_classifier_at(selection.start)
3776 .is_word(c)
3777 },
3778 );
3779
3780 let is_closing_quote = if bracket_pair.end == bracket_pair.start
3781 && bracket_pair.start.len() == 1
3782 {
3783 let target = bracket_pair.start.chars().next().unwrap();
3784 let current_line_count = snapshot
3785 .reversed_chars_at(selection.start)
3786 .take_while(|&c| c != '\n')
3787 .filter(|&c| c == target)
3788 .count();
3789 current_line_count % 2 == 1
3790 } else {
3791 false
3792 };
3793
3794 if autoclose
3795 && bracket_pair.close
3796 && following_text_allows_autoclose
3797 && preceding_text_allows_autoclose
3798 && !is_closing_quote
3799 {
3800 let anchor = snapshot.anchor_before(selection.end);
3801 new_selections.push((selection.map(|_| anchor), text.len()));
3802 new_autoclose_regions.push((
3803 anchor,
3804 text.len(),
3805 selection.id,
3806 bracket_pair.clone(),
3807 ));
3808 edits.push((
3809 selection.range(),
3810 format!("{}{}", text, bracket_pair.end).into(),
3811 ));
3812 bracket_inserted = true;
3813 continue;
3814 }
3815 }
3816
3817 if let Some(region) = autoclose_region {
3818 // If the selection is followed by an auto-inserted closing bracket,
3819 // then don't insert that closing bracket again; just move the selection
3820 // past the closing bracket.
3821 let should_skip = selection.end == region.range.end.to_point(&snapshot)
3822 && text.as_ref() == region.pair.end.as_str();
3823 if should_skip {
3824 let anchor = snapshot.anchor_after(selection.end);
3825 new_selections
3826 .push((selection.map(|_| anchor), region.pair.end.len()));
3827 continue;
3828 }
3829 }
3830
3831 let always_treat_brackets_as_autoclosed = snapshot
3832 .language_settings_at(selection.start, cx)
3833 .always_treat_brackets_as_autoclosed;
3834 if always_treat_brackets_as_autoclosed
3835 && is_bracket_pair_end
3836 && snapshot.contains_str_at(selection.end, text.as_ref())
3837 {
3838 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
3839 // and the inserted text is a closing bracket and the selection is followed
3840 // by the closing bracket then move the selection past the closing bracket.
3841 let anchor = snapshot.anchor_after(selection.end);
3842 new_selections.push((selection.map(|_| anchor), text.len()));
3843 continue;
3844 }
3845 }
3846 // If an opening bracket is 1 character long and is typed while
3847 // text is selected, then surround that text with the bracket pair.
3848 else if auto_surround
3849 && bracket_pair.surround
3850 && is_bracket_pair_start
3851 && bracket_pair.start.chars().count() == 1
3852 {
3853 edits.push((selection.start..selection.start, text.clone()));
3854 edits.push((
3855 selection.end..selection.end,
3856 bracket_pair.end.as_str().into(),
3857 ));
3858 bracket_inserted = true;
3859 new_selections.push((
3860 Selection {
3861 id: selection.id,
3862 start: snapshot.anchor_after(selection.start),
3863 end: snapshot.anchor_before(selection.end),
3864 reversed: selection.reversed,
3865 goal: selection.goal,
3866 },
3867 0,
3868 ));
3869 continue;
3870 }
3871 }
3872 }
3873
3874 if self.auto_replace_emoji_shortcode
3875 && selection.is_empty()
3876 && text.as_ref().ends_with(':')
3877 {
3878 if let Some(possible_emoji_short_code) =
3879 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
3880 {
3881 if !possible_emoji_short_code.is_empty() {
3882 if let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code) {
3883 let emoji_shortcode_start = Point::new(
3884 selection.start.row,
3885 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
3886 );
3887
3888 // Remove shortcode from buffer
3889 edits.push((
3890 emoji_shortcode_start..selection.start,
3891 "".to_string().into(),
3892 ));
3893 new_selections.push((
3894 Selection {
3895 id: selection.id,
3896 start: snapshot.anchor_after(emoji_shortcode_start),
3897 end: snapshot.anchor_before(selection.start),
3898 reversed: selection.reversed,
3899 goal: selection.goal,
3900 },
3901 0,
3902 ));
3903
3904 // Insert emoji
3905 let selection_start_anchor = snapshot.anchor_after(selection.start);
3906 new_selections.push((selection.map(|_| selection_start_anchor), 0));
3907 edits.push((selection.start..selection.end, emoji.to_string().into()));
3908
3909 continue;
3910 }
3911 }
3912 }
3913 }
3914
3915 // If not handling any auto-close operation, then just replace the selected
3916 // text with the given input and move the selection to the end of the
3917 // newly inserted text.
3918 let anchor = snapshot.anchor_after(selection.end);
3919 if !self.linked_edit_ranges.is_empty() {
3920 let start_anchor = snapshot.anchor_before(selection.start);
3921
3922 let is_word_char = text.chars().next().map_or(true, |char| {
3923 let classifier = snapshot
3924 .char_classifier_at(start_anchor.to_offset(&snapshot))
3925 .ignore_punctuation(true);
3926 classifier.is_word(char)
3927 });
3928
3929 if is_word_char {
3930 if let Some(ranges) = self
3931 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
3932 {
3933 for (buffer, edits) in ranges {
3934 linked_edits
3935 .entry(buffer.clone())
3936 .or_default()
3937 .extend(edits.into_iter().map(|range| (range, text.clone())));
3938 }
3939 }
3940 } else {
3941 clear_linked_edit_ranges = true;
3942 }
3943 }
3944
3945 new_selections.push((selection.map(|_| anchor), 0));
3946 edits.push((selection.start..selection.end, text.clone()));
3947 }
3948
3949 drop(snapshot);
3950
3951 self.transact(window, cx, |this, window, cx| {
3952 if clear_linked_edit_ranges {
3953 this.linked_edit_ranges.clear();
3954 }
3955 let initial_buffer_versions =
3956 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
3957
3958 this.buffer.update(cx, |buffer, cx| {
3959 buffer.edit(edits, this.autoindent_mode.clone(), cx);
3960 });
3961 for (buffer, edits) in linked_edits {
3962 buffer.update(cx, |buffer, cx| {
3963 let snapshot = buffer.snapshot();
3964 let edits = edits
3965 .into_iter()
3966 .map(|(range, text)| {
3967 use text::ToPoint as TP;
3968 let end_point = TP::to_point(&range.end, &snapshot);
3969 let start_point = TP::to_point(&range.start, &snapshot);
3970 (start_point..end_point, text)
3971 })
3972 .sorted_by_key(|(range, _)| range.start);
3973 buffer.edit(edits, None, cx);
3974 })
3975 }
3976 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
3977 let new_selection_deltas = new_selections.iter().map(|e| e.1);
3978 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
3979 let new_selections = resolve_selections::<usize, _>(new_anchor_selections, &map)
3980 .zip(new_selection_deltas)
3981 .map(|(selection, delta)| Selection {
3982 id: selection.id,
3983 start: selection.start + delta,
3984 end: selection.end + delta,
3985 reversed: selection.reversed,
3986 goal: SelectionGoal::None,
3987 })
3988 .collect::<Vec<_>>();
3989
3990 let mut i = 0;
3991 for (position, delta, selection_id, pair) in new_autoclose_regions {
3992 let position = position.to_offset(&map.buffer_snapshot) + delta;
3993 let start = map.buffer_snapshot.anchor_before(position);
3994 let end = map.buffer_snapshot.anchor_after(position);
3995 while let Some(existing_state) = this.autoclose_regions.get(i) {
3996 match existing_state.range.start.cmp(&start, &map.buffer_snapshot) {
3997 Ordering::Less => i += 1,
3998 Ordering::Greater => break,
3999 Ordering::Equal => {
4000 match end.cmp(&existing_state.range.end, &map.buffer_snapshot) {
4001 Ordering::Less => i += 1,
4002 Ordering::Equal => break,
4003 Ordering::Greater => break,
4004 }
4005 }
4006 }
4007 }
4008 this.autoclose_regions.insert(
4009 i,
4010 AutocloseRegion {
4011 selection_id,
4012 range: start..end,
4013 pair,
4014 },
4015 );
4016 }
4017
4018 let had_active_inline_completion = this.has_active_inline_completion();
4019 this.change_selections_without_updating_completions(
4020 Some(Autoscroll::fit()),
4021 window,
4022 cx,
4023 |s| s.select(new_selections),
4024 );
4025
4026 if !bracket_inserted {
4027 if let Some(on_type_format_task) =
4028 this.trigger_on_type_formatting(text.to_string(), window, cx)
4029 {
4030 on_type_format_task.detach_and_log_err(cx);
4031 }
4032 }
4033
4034 let editor_settings = EditorSettings::get_global(cx);
4035 if bracket_inserted
4036 && (editor_settings.auto_signature_help
4037 || editor_settings.show_signature_help_after_edits)
4038 {
4039 this.show_signature_help(&ShowSignatureHelp, window, cx);
4040 }
4041
4042 let trigger_in_words =
4043 this.show_edit_predictions_in_menu() || !had_active_inline_completion;
4044 if this.hard_wrap.is_some() {
4045 let latest: Range<Point> = this.selections.newest(cx).range();
4046 if latest.is_empty()
4047 && this
4048 .buffer()
4049 .read(cx)
4050 .snapshot(cx)
4051 .line_len(MultiBufferRow(latest.start.row))
4052 == latest.start.column
4053 {
4054 this.rewrap_impl(
4055 RewrapOptions {
4056 override_language_settings: true,
4057 preserve_existing_whitespace: true,
4058 },
4059 cx,
4060 )
4061 }
4062 }
4063 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4064 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
4065 this.refresh_inline_completion(true, false, window, cx);
4066 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4067 });
4068 }
4069
4070 fn find_possible_emoji_shortcode_at_position(
4071 snapshot: &MultiBufferSnapshot,
4072 position: Point,
4073 ) -> Option<String> {
4074 let mut chars = Vec::new();
4075 let mut found_colon = false;
4076 for char in snapshot.reversed_chars_at(position).take(100) {
4077 // Found a possible emoji shortcode in the middle of the buffer
4078 if found_colon {
4079 if char.is_whitespace() {
4080 chars.reverse();
4081 return Some(chars.iter().collect());
4082 }
4083 // If the previous character is not a whitespace, we are in the middle of a word
4084 // and we only want to complete the shortcode if the word is made up of other emojis
4085 let mut containing_word = String::new();
4086 for ch in snapshot
4087 .reversed_chars_at(position)
4088 .skip(chars.len() + 1)
4089 .take(100)
4090 {
4091 if ch.is_whitespace() {
4092 break;
4093 }
4094 containing_word.push(ch);
4095 }
4096 let containing_word = containing_word.chars().rev().collect::<String>();
4097 if util::word_consists_of_emojis(containing_word.as_str()) {
4098 chars.reverse();
4099 return Some(chars.iter().collect());
4100 }
4101 }
4102
4103 if char.is_whitespace() || !char.is_ascii() {
4104 return None;
4105 }
4106 if char == ':' {
4107 found_colon = true;
4108 } else {
4109 chars.push(char);
4110 }
4111 }
4112 // Found a possible emoji shortcode at the beginning of the buffer
4113 chars.reverse();
4114 Some(chars.iter().collect())
4115 }
4116
4117 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4118 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4119 self.transact(window, cx, |this, window, cx| {
4120 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4121 let selections = this.selections.all::<usize>(cx);
4122 let multi_buffer = this.buffer.read(cx);
4123 let buffer = multi_buffer.snapshot(cx);
4124 selections
4125 .iter()
4126 .map(|selection| {
4127 let start_point = selection.start.to_point(&buffer);
4128 let mut existing_indent =
4129 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4130 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4131 let start = selection.start;
4132 let end = selection.end;
4133 let selection_is_empty = start == end;
4134 let language_scope = buffer.language_scope_at(start);
4135 let (
4136 comment_delimiter,
4137 doc_delimiter,
4138 insert_extra_newline,
4139 indent_on_newline,
4140 indent_on_extra_newline,
4141 ) = if let Some(language) = &language_scope {
4142 let mut insert_extra_newline =
4143 insert_extra_newline_brackets(&buffer, start..end, language)
4144 || insert_extra_newline_tree_sitter(&buffer, start..end);
4145
4146 // Comment extension on newline is allowed only for cursor selections
4147 let comment_delimiter = maybe!({
4148 if !selection_is_empty {
4149 return None;
4150 }
4151
4152 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4153 return None;
4154 }
4155
4156 let delimiters = language.line_comment_prefixes();
4157 let max_len_of_delimiter =
4158 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4159 let (snapshot, range) =
4160 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4161
4162 let num_of_whitespaces = snapshot
4163 .chars_for_range(range.clone())
4164 .take_while(|c| c.is_whitespace())
4165 .count();
4166 let comment_candidate = snapshot
4167 .chars_for_range(range)
4168 .skip(num_of_whitespaces)
4169 .take(max_len_of_delimiter)
4170 .collect::<String>();
4171 let (delimiter, trimmed_len) = delimiters
4172 .iter()
4173 .filter_map(|delimiter| {
4174 let prefix = delimiter.trim_end();
4175 if comment_candidate.starts_with(prefix) {
4176 Some((delimiter, prefix.len()))
4177 } else {
4178 None
4179 }
4180 })
4181 .max_by_key(|(_, len)| *len)?;
4182
4183 let cursor_is_placed_after_comment_marker =
4184 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4185 if cursor_is_placed_after_comment_marker {
4186 Some(delimiter.clone())
4187 } else {
4188 None
4189 }
4190 });
4191
4192 let mut indent_on_newline = IndentSize::spaces(0);
4193 let mut indent_on_extra_newline = IndentSize::spaces(0);
4194
4195 let doc_delimiter = maybe!({
4196 if !selection_is_empty {
4197 return None;
4198 }
4199
4200 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4201 return None;
4202 }
4203
4204 let DocumentationConfig {
4205 start: start_tag,
4206 end: end_tag,
4207 prefix: delimiter,
4208 tab_size: len,
4209 } = language.documentation()?;
4210
4211 let is_within_block_comment = buffer
4212 .language_scope_at(start_point)
4213 .is_some_and(|scope| scope.override_name() == Some("comment"));
4214 if !is_within_block_comment {
4215 return None;
4216 }
4217
4218 let (snapshot, range) =
4219 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4220
4221 let num_of_whitespaces = snapshot
4222 .chars_for_range(range.clone())
4223 .take_while(|c| c.is_whitespace())
4224 .count();
4225
4226 // 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.
4227 let column = start_point.column;
4228 let cursor_is_after_start_tag = {
4229 let start_tag_len = start_tag.len();
4230 let start_tag_line = snapshot
4231 .chars_for_range(range.clone())
4232 .skip(num_of_whitespaces)
4233 .take(start_tag_len)
4234 .collect::<String>();
4235 if start_tag_line.starts_with(start_tag.as_ref()) {
4236 num_of_whitespaces + start_tag_len <= column as usize
4237 } else {
4238 false
4239 }
4240 };
4241
4242 let cursor_is_after_delimiter = {
4243 let delimiter_trim = delimiter.trim_end();
4244 let delimiter_line = snapshot
4245 .chars_for_range(range.clone())
4246 .skip(num_of_whitespaces)
4247 .take(delimiter_trim.len())
4248 .collect::<String>();
4249 if delimiter_line.starts_with(delimiter_trim) {
4250 num_of_whitespaces + delimiter_trim.len() <= column as usize
4251 } else {
4252 false
4253 }
4254 };
4255
4256 let cursor_is_before_end_tag_if_exists = {
4257 let mut char_position = 0u32;
4258 let mut end_tag_offset = None;
4259
4260 'outer: for chunk in snapshot.text_for_range(range.clone()) {
4261 if let Some(byte_pos) = chunk.find(&**end_tag) {
4262 let chars_before_match =
4263 chunk[..byte_pos].chars().count() as u32;
4264 end_tag_offset =
4265 Some(char_position + chars_before_match);
4266 break 'outer;
4267 }
4268 char_position += chunk.chars().count() as u32;
4269 }
4270
4271 if let Some(end_tag_offset) = end_tag_offset {
4272 let cursor_is_before_end_tag = column <= end_tag_offset;
4273 if cursor_is_after_start_tag {
4274 if cursor_is_before_end_tag {
4275 insert_extra_newline = true;
4276 }
4277 let cursor_is_at_start_of_end_tag =
4278 column == end_tag_offset;
4279 if cursor_is_at_start_of_end_tag {
4280 indent_on_extra_newline.len = (*len).into();
4281 }
4282 }
4283 cursor_is_before_end_tag
4284 } else {
4285 true
4286 }
4287 };
4288
4289 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4290 && cursor_is_before_end_tag_if_exists
4291 {
4292 if cursor_is_after_start_tag {
4293 indent_on_newline.len = (*len).into();
4294 }
4295 Some(delimiter.clone())
4296 } else {
4297 None
4298 }
4299 });
4300
4301 (
4302 comment_delimiter,
4303 doc_delimiter,
4304 insert_extra_newline,
4305 indent_on_newline,
4306 indent_on_extra_newline,
4307 )
4308 } else {
4309 (
4310 None,
4311 None,
4312 false,
4313 IndentSize::default(),
4314 IndentSize::default(),
4315 )
4316 };
4317
4318 let prevent_auto_indent = doc_delimiter.is_some();
4319 let delimiter = comment_delimiter.or(doc_delimiter);
4320
4321 let capacity_for_delimiter =
4322 delimiter.as_deref().map(str::len).unwrap_or_default();
4323 let mut new_text = String::with_capacity(
4324 1 + capacity_for_delimiter
4325 + existing_indent.len as usize
4326 + indent_on_newline.len as usize
4327 + indent_on_extra_newline.len as usize,
4328 );
4329 new_text.push('\n');
4330 new_text.extend(existing_indent.chars());
4331 new_text.extend(indent_on_newline.chars());
4332
4333 if let Some(delimiter) = &delimiter {
4334 new_text.push_str(delimiter);
4335 }
4336
4337 if insert_extra_newline {
4338 new_text.push('\n');
4339 new_text.extend(existing_indent.chars());
4340 new_text.extend(indent_on_extra_newline.chars());
4341 }
4342
4343 let anchor = buffer.anchor_after(end);
4344 let new_selection = selection.map(|_| anchor);
4345 (
4346 ((start..end, new_text), prevent_auto_indent),
4347 (insert_extra_newline, new_selection),
4348 )
4349 })
4350 .unzip()
4351 };
4352
4353 let mut auto_indent_edits = Vec::new();
4354 let mut edits = Vec::new();
4355 for (edit, prevent_auto_indent) in edits_with_flags {
4356 if prevent_auto_indent {
4357 edits.push(edit);
4358 } else {
4359 auto_indent_edits.push(edit);
4360 }
4361 }
4362 if !edits.is_empty() {
4363 this.edit(edits, cx);
4364 }
4365 if !auto_indent_edits.is_empty() {
4366 this.edit_with_autoindent(auto_indent_edits, cx);
4367 }
4368
4369 let buffer = this.buffer.read(cx).snapshot(cx);
4370 let new_selections = selection_info
4371 .into_iter()
4372 .map(|(extra_newline_inserted, new_selection)| {
4373 let mut cursor = new_selection.end.to_point(&buffer);
4374 if extra_newline_inserted {
4375 cursor.row -= 1;
4376 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4377 }
4378 new_selection.map(|_| cursor)
4379 })
4380 .collect();
4381
4382 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4383 s.select(new_selections)
4384 });
4385 this.refresh_inline_completion(true, false, window, cx);
4386 });
4387 }
4388
4389 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4390 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4391
4392 let buffer = self.buffer.read(cx);
4393 let snapshot = buffer.snapshot(cx);
4394
4395 let mut edits = Vec::new();
4396 let mut rows = Vec::new();
4397
4398 for (rows_inserted, selection) in self.selections.all_adjusted(cx).into_iter().enumerate() {
4399 let cursor = selection.head();
4400 let row = cursor.row;
4401
4402 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4403
4404 let newline = "\n".to_string();
4405 edits.push((start_of_line..start_of_line, newline));
4406
4407 rows.push(row + rows_inserted as u32);
4408 }
4409
4410 self.transact(window, cx, |editor, window, cx| {
4411 editor.edit(edits, cx);
4412
4413 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4414 let mut index = 0;
4415 s.move_cursors_with(|map, _, _| {
4416 let row = rows[index];
4417 index += 1;
4418
4419 let point = Point::new(row, 0);
4420 let boundary = map.next_line_boundary(point).1;
4421 let clipped = map.clip_point(boundary, Bias::Left);
4422
4423 (clipped, SelectionGoal::None)
4424 });
4425 });
4426
4427 let mut indent_edits = Vec::new();
4428 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4429 for row in rows {
4430 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4431 for (row, indent) in indents {
4432 if indent.len == 0 {
4433 continue;
4434 }
4435
4436 let text = match indent.kind {
4437 IndentKind::Space => " ".repeat(indent.len as usize),
4438 IndentKind::Tab => "\t".repeat(indent.len as usize),
4439 };
4440 let point = Point::new(row.0, 0);
4441 indent_edits.push((point..point, text));
4442 }
4443 }
4444 editor.edit(indent_edits, cx);
4445 });
4446 }
4447
4448 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4449 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
4450
4451 let buffer = self.buffer.read(cx);
4452 let snapshot = buffer.snapshot(cx);
4453
4454 let mut edits = Vec::new();
4455 let mut rows = Vec::new();
4456 let mut rows_inserted = 0;
4457
4458 for selection in self.selections.all_adjusted(cx) {
4459 let cursor = selection.head();
4460 let row = cursor.row;
4461
4462 let point = Point::new(row + 1, 0);
4463 let start_of_line = snapshot.clip_point(point, Bias::Left);
4464
4465 let newline = "\n".to_string();
4466 edits.push((start_of_line..start_of_line, newline));
4467
4468 rows_inserted += 1;
4469 rows.push(row + rows_inserted);
4470 }
4471
4472 self.transact(window, cx, |editor, window, cx| {
4473 editor.edit(edits, cx);
4474
4475 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4476 let mut index = 0;
4477 s.move_cursors_with(|map, _, _| {
4478 let row = rows[index];
4479 index += 1;
4480
4481 let point = Point::new(row, 0);
4482 let boundary = map.next_line_boundary(point).1;
4483 let clipped = map.clip_point(boundary, Bias::Left);
4484
4485 (clipped, SelectionGoal::None)
4486 });
4487 });
4488
4489 let mut indent_edits = Vec::new();
4490 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4491 for row in rows {
4492 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4493 for (row, indent) in indents {
4494 if indent.len == 0 {
4495 continue;
4496 }
4497
4498 let text = match indent.kind {
4499 IndentKind::Space => " ".repeat(indent.len as usize),
4500 IndentKind::Tab => "\t".repeat(indent.len as usize),
4501 };
4502 let point = Point::new(row.0, 0);
4503 indent_edits.push((point..point, text));
4504 }
4505 }
4506 editor.edit(indent_edits, cx);
4507 });
4508 }
4509
4510 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4511 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4512 original_indent_columns: Vec::new(),
4513 });
4514 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4515 }
4516
4517 fn insert_with_autoindent_mode(
4518 &mut self,
4519 text: &str,
4520 autoindent_mode: Option<AutoindentMode>,
4521 window: &mut Window,
4522 cx: &mut Context<Self>,
4523 ) {
4524 if self.read_only(cx) {
4525 return;
4526 }
4527
4528 let text: Arc<str> = text.into();
4529 self.transact(window, cx, |this, window, cx| {
4530 let old_selections = this.selections.all_adjusted(cx);
4531 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4532 let anchors = {
4533 let snapshot = buffer.read(cx);
4534 old_selections
4535 .iter()
4536 .map(|s| {
4537 let anchor = snapshot.anchor_after(s.head());
4538 s.map(|_| anchor)
4539 })
4540 .collect::<Vec<_>>()
4541 };
4542 buffer.edit(
4543 old_selections
4544 .iter()
4545 .map(|s| (s.start..s.end, text.clone())),
4546 autoindent_mode,
4547 cx,
4548 );
4549 anchors
4550 });
4551
4552 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
4553 s.select_anchors(selection_anchors);
4554 });
4555
4556 cx.notify();
4557 });
4558 }
4559
4560 fn trigger_completion_on_input(
4561 &mut self,
4562 text: &str,
4563 trigger_in_words: bool,
4564 window: &mut Window,
4565 cx: &mut Context<Self>,
4566 ) {
4567 let completions_source = self
4568 .context_menu
4569 .borrow()
4570 .as_ref()
4571 .and_then(|menu| match menu {
4572 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4573 CodeContextMenu::CodeActions(_) => None,
4574 });
4575
4576 match completions_source {
4577 Some(CompletionsMenuSource::Words) => {
4578 self.show_word_completions(&ShowWordCompletions, window, cx)
4579 }
4580 Some(CompletionsMenuSource::Normal)
4581 | Some(CompletionsMenuSource::SnippetChoices)
4582 | None
4583 if self.is_completion_trigger(
4584 text,
4585 trigger_in_words,
4586 completions_source.is_some(),
4587 cx,
4588 ) =>
4589 {
4590 self.show_completions(
4591 &ShowCompletions {
4592 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4593 },
4594 window,
4595 cx,
4596 )
4597 }
4598 _ => {
4599 self.hide_context_menu(window, cx);
4600 }
4601 }
4602 }
4603
4604 fn is_completion_trigger(
4605 &self,
4606 text: &str,
4607 trigger_in_words: bool,
4608 menu_is_open: bool,
4609 cx: &mut Context<Self>,
4610 ) -> bool {
4611 let position = self.selections.newest_anchor().head();
4612 let multibuffer = self.buffer.read(cx);
4613 let Some(buffer) = position
4614 .buffer_id
4615 .and_then(|buffer_id| multibuffer.buffer(buffer_id).clone())
4616 else {
4617 return false;
4618 };
4619
4620 if let Some(completion_provider) = &self.completion_provider {
4621 completion_provider.is_completion_trigger(
4622 &buffer,
4623 position.text_anchor,
4624 text,
4625 trigger_in_words,
4626 menu_is_open,
4627 cx,
4628 )
4629 } else {
4630 false
4631 }
4632 }
4633
4634 /// If any empty selections is touching the start of its innermost containing autoclose
4635 /// region, expand it to select the brackets.
4636 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4637 let selections = self.selections.all::<usize>(cx);
4638 let buffer = self.buffer.read(cx).read(cx);
4639 let new_selections = self
4640 .selections_with_autoclose_regions(selections, &buffer)
4641 .map(|(mut selection, region)| {
4642 if !selection.is_empty() {
4643 return selection;
4644 }
4645
4646 if let Some(region) = region {
4647 let mut range = region.range.to_offset(&buffer);
4648 if selection.start == range.start && range.start >= region.pair.start.len() {
4649 range.start -= region.pair.start.len();
4650 if buffer.contains_str_at(range.start, ®ion.pair.start)
4651 && buffer.contains_str_at(range.end, ®ion.pair.end)
4652 {
4653 range.end += region.pair.end.len();
4654 selection.start = range.start;
4655 selection.end = range.end;
4656
4657 return selection;
4658 }
4659 }
4660 }
4661
4662 let always_treat_brackets_as_autoclosed = buffer
4663 .language_settings_at(selection.start, cx)
4664 .always_treat_brackets_as_autoclosed;
4665
4666 if !always_treat_brackets_as_autoclosed {
4667 return selection;
4668 }
4669
4670 if let Some(scope) = buffer.language_scope_at(selection.start) {
4671 for (pair, enabled) in scope.brackets() {
4672 if !enabled || !pair.close {
4673 continue;
4674 }
4675
4676 if buffer.contains_str_at(selection.start, &pair.end) {
4677 let pair_start_len = pair.start.len();
4678 if buffer.contains_str_at(
4679 selection.start.saturating_sub(pair_start_len),
4680 &pair.start,
4681 ) {
4682 selection.start -= pair_start_len;
4683 selection.end += pair.end.len();
4684
4685 return selection;
4686 }
4687 }
4688 }
4689 }
4690
4691 selection
4692 })
4693 .collect();
4694
4695 drop(buffer);
4696 self.change_selections(None, window, cx, |selections| {
4697 selections.select(new_selections)
4698 });
4699 }
4700
4701 /// Iterate the given selections, and for each one, find the smallest surrounding
4702 /// autoclose region. This uses the ordering of the selections and the autoclose
4703 /// regions to avoid repeated comparisons.
4704 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
4705 &'a self,
4706 selections: impl IntoIterator<Item = Selection<D>>,
4707 buffer: &'a MultiBufferSnapshot,
4708 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
4709 let mut i = 0;
4710 let mut regions = self.autoclose_regions.as_slice();
4711 selections.into_iter().map(move |selection| {
4712 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
4713
4714 let mut enclosing = None;
4715 while let Some(pair_state) = regions.get(i) {
4716 if pair_state.range.end.to_offset(buffer) < range.start {
4717 regions = ®ions[i + 1..];
4718 i = 0;
4719 } else if pair_state.range.start.to_offset(buffer) > range.end {
4720 break;
4721 } else {
4722 if pair_state.selection_id == selection.id {
4723 enclosing = Some(pair_state);
4724 }
4725 i += 1;
4726 }
4727 }
4728
4729 (selection, enclosing)
4730 })
4731 }
4732
4733 /// Remove any autoclose regions that no longer contain their selection.
4734 fn invalidate_autoclose_regions(
4735 &mut self,
4736 mut selections: &[Selection<Anchor>],
4737 buffer: &MultiBufferSnapshot,
4738 ) {
4739 self.autoclose_regions.retain(|state| {
4740 let mut i = 0;
4741 while let Some(selection) = selections.get(i) {
4742 if selection.end.cmp(&state.range.start, buffer).is_lt() {
4743 selections = &selections[1..];
4744 continue;
4745 }
4746 if selection.start.cmp(&state.range.end, buffer).is_gt() {
4747 break;
4748 }
4749 if selection.id == state.selection_id {
4750 return true;
4751 } else {
4752 i += 1;
4753 }
4754 }
4755 false
4756 });
4757 }
4758
4759 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
4760 let offset = position.to_offset(buffer);
4761 let (word_range, kind) = buffer.surrounding_word(offset, true);
4762 if offset > word_range.start && kind == Some(CharKind::Word) {
4763 Some(
4764 buffer
4765 .text_for_range(word_range.start..offset)
4766 .collect::<String>(),
4767 )
4768 } else {
4769 None
4770 }
4771 }
4772
4773 pub fn toggle_inline_values(
4774 &mut self,
4775 _: &ToggleInlineValues,
4776 _: &mut Window,
4777 cx: &mut Context<Self>,
4778 ) {
4779 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
4780
4781 self.refresh_inline_values(cx);
4782 }
4783
4784 pub fn toggle_inlay_hints(
4785 &mut self,
4786 _: &ToggleInlayHints,
4787 _: &mut Window,
4788 cx: &mut Context<Self>,
4789 ) {
4790 self.refresh_inlay_hints(
4791 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
4792 cx,
4793 );
4794 }
4795
4796 pub fn inlay_hints_enabled(&self) -> bool {
4797 self.inlay_hint_cache.enabled
4798 }
4799
4800 pub fn inline_values_enabled(&self) -> bool {
4801 self.inline_value_cache.enabled
4802 }
4803
4804 #[cfg(any(test, feature = "test-support"))]
4805 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
4806 self.display_map
4807 .read(cx)
4808 .current_inlays()
4809 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
4810 .cloned()
4811 .collect()
4812 }
4813
4814 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
4815 if self.semantics_provider.is_none() || !self.mode.is_full() {
4816 return;
4817 }
4818
4819 let reason_description = reason.description();
4820 let ignore_debounce = matches!(
4821 reason,
4822 InlayHintRefreshReason::SettingsChange(_)
4823 | InlayHintRefreshReason::Toggle(_)
4824 | InlayHintRefreshReason::ExcerptsRemoved(_)
4825 | InlayHintRefreshReason::ModifiersChanged(_)
4826 );
4827 let (invalidate_cache, required_languages) = match reason {
4828 InlayHintRefreshReason::ModifiersChanged(enabled) => {
4829 match self.inlay_hint_cache.modifiers_override(enabled) {
4830 Some(enabled) => {
4831 if enabled {
4832 (InvalidationStrategy::RefreshRequested, None)
4833 } else {
4834 self.splice_inlays(
4835 &self
4836 .visible_inlay_hints(cx)
4837 .iter()
4838 .map(|inlay| inlay.id)
4839 .collect::<Vec<InlayId>>(),
4840 Vec::new(),
4841 cx,
4842 );
4843 return;
4844 }
4845 }
4846 None => return,
4847 }
4848 }
4849 InlayHintRefreshReason::Toggle(enabled) => {
4850 if self.inlay_hint_cache.toggle(enabled) {
4851 if enabled {
4852 (InvalidationStrategy::RefreshRequested, None)
4853 } else {
4854 self.splice_inlays(
4855 &self
4856 .visible_inlay_hints(cx)
4857 .iter()
4858 .map(|inlay| inlay.id)
4859 .collect::<Vec<InlayId>>(),
4860 Vec::new(),
4861 cx,
4862 );
4863 return;
4864 }
4865 } else {
4866 return;
4867 }
4868 }
4869 InlayHintRefreshReason::SettingsChange(new_settings) => {
4870 match self.inlay_hint_cache.update_settings(
4871 &self.buffer,
4872 new_settings,
4873 self.visible_inlay_hints(cx),
4874 cx,
4875 ) {
4876 ControlFlow::Break(Some(InlaySplice {
4877 to_remove,
4878 to_insert,
4879 })) => {
4880 self.splice_inlays(&to_remove, to_insert, cx);
4881 return;
4882 }
4883 ControlFlow::Break(None) => return,
4884 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
4885 }
4886 }
4887 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
4888 if let Some(InlaySplice {
4889 to_remove,
4890 to_insert,
4891 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
4892 {
4893 self.splice_inlays(&to_remove, to_insert, cx);
4894 }
4895 self.display_map.update(cx, |display_map, _| {
4896 display_map.remove_inlays_for_excerpts(&excerpts_removed)
4897 });
4898 return;
4899 }
4900 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
4901 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
4902 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
4903 }
4904 InlayHintRefreshReason::RefreshRequested => {
4905 (InvalidationStrategy::RefreshRequested, None)
4906 }
4907 };
4908
4909 if let Some(InlaySplice {
4910 to_remove,
4911 to_insert,
4912 }) = self.inlay_hint_cache.spawn_hint_refresh(
4913 reason_description,
4914 self.excerpts_for_inlay_hints_query(required_languages.as_ref(), cx),
4915 invalidate_cache,
4916 ignore_debounce,
4917 cx,
4918 ) {
4919 self.splice_inlays(&to_remove, to_insert, cx);
4920 }
4921 }
4922
4923 fn visible_inlay_hints(&self, cx: &Context<Editor>) -> Vec<Inlay> {
4924 self.display_map
4925 .read(cx)
4926 .current_inlays()
4927 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
4928 .cloned()
4929 .collect()
4930 }
4931
4932 pub fn excerpts_for_inlay_hints_query(
4933 &self,
4934 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
4935 cx: &mut Context<Editor>,
4936 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
4937 let Some(project) = self.project.as_ref() else {
4938 return HashMap::default();
4939 };
4940 let project = project.read(cx);
4941 let multi_buffer = self.buffer().read(cx);
4942 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
4943 let multi_buffer_visible_start = self
4944 .scroll_manager
4945 .anchor()
4946 .anchor
4947 .to_point(&multi_buffer_snapshot);
4948 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
4949 multi_buffer_visible_start
4950 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
4951 Bias::Left,
4952 );
4953 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
4954 multi_buffer_snapshot
4955 .range_to_buffer_ranges(multi_buffer_visible_range)
4956 .into_iter()
4957 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
4958 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
4959 let buffer_file = project::File::from_dyn(buffer.file())?;
4960 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
4961 let worktree_entry = buffer_worktree
4962 .read(cx)
4963 .entry_for_id(buffer_file.project_entry_id(cx)?)?;
4964 if worktree_entry.is_ignored {
4965 return None;
4966 }
4967
4968 let language = buffer.language()?;
4969 if let Some(restrict_to_languages) = restrict_to_languages {
4970 if !restrict_to_languages.contains(language) {
4971 return None;
4972 }
4973 }
4974 Some((
4975 excerpt_id,
4976 (
4977 multi_buffer.buffer(buffer.remote_id()).unwrap(),
4978 buffer.version().clone(),
4979 excerpt_visible_range,
4980 ),
4981 ))
4982 })
4983 .collect()
4984 }
4985
4986 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
4987 TextLayoutDetails {
4988 text_system: window.text_system().clone(),
4989 editor_style: self.style.clone().unwrap(),
4990 rem_size: window.rem_size(),
4991 scroll_anchor: self.scroll_manager.anchor(),
4992 visible_rows: self.visible_line_count(),
4993 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
4994 }
4995 }
4996
4997 pub fn splice_inlays(
4998 &self,
4999 to_remove: &[InlayId],
5000 to_insert: Vec<Inlay>,
5001 cx: &mut Context<Self>,
5002 ) {
5003 self.display_map.update(cx, |display_map, cx| {
5004 display_map.splice_inlays(to_remove, to_insert, cx)
5005 });
5006 cx.notify();
5007 }
5008
5009 fn trigger_on_type_formatting(
5010 &self,
5011 input: String,
5012 window: &mut Window,
5013 cx: &mut Context<Self>,
5014 ) -> Option<Task<Result<()>>> {
5015 if input.len() != 1 {
5016 return None;
5017 }
5018
5019 let project = self.project.as_ref()?;
5020 let position = self.selections.newest_anchor().head();
5021 let (buffer, buffer_position) = self
5022 .buffer
5023 .read(cx)
5024 .text_anchor_for_position(position, cx)?;
5025
5026 let settings = language_settings::language_settings(
5027 buffer
5028 .read(cx)
5029 .language_at(buffer_position)
5030 .map(|l| l.name()),
5031 buffer.read(cx).file(),
5032 cx,
5033 );
5034 if !settings.use_on_type_format {
5035 return None;
5036 }
5037
5038 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5039 // hence we do LSP request & edit on host side only — add formats to host's history.
5040 let push_to_lsp_host_history = true;
5041 // If this is not the host, append its history with new edits.
5042 let push_to_client_history = project.read(cx).is_via_collab();
5043
5044 let on_type_formatting = project.update(cx, |project, cx| {
5045 project.on_type_format(
5046 buffer.clone(),
5047 buffer_position,
5048 input,
5049 push_to_lsp_host_history,
5050 cx,
5051 )
5052 });
5053 Some(cx.spawn_in(window, async move |editor, cx| {
5054 if let Some(transaction) = on_type_formatting.await? {
5055 if push_to_client_history {
5056 buffer
5057 .update(cx, |buffer, _| {
5058 buffer.push_transaction(transaction, Instant::now());
5059 buffer.finalize_last_transaction();
5060 })
5061 .ok();
5062 }
5063 editor.update(cx, |editor, cx| {
5064 editor.refresh_document_highlights(cx);
5065 })?;
5066 }
5067 Ok(())
5068 }))
5069 }
5070
5071 pub fn show_word_completions(
5072 &mut self,
5073 _: &ShowWordCompletions,
5074 window: &mut Window,
5075 cx: &mut Context<Self>,
5076 ) {
5077 self.open_or_update_completions_menu(Some(CompletionsMenuSource::Words), None, window, cx);
5078 }
5079
5080 pub fn show_completions(
5081 &mut self,
5082 options: &ShowCompletions,
5083 window: &mut Window,
5084 cx: &mut Context<Self>,
5085 ) {
5086 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5087 }
5088
5089 fn open_or_update_completions_menu(
5090 &mut self,
5091 requested_source: Option<CompletionsMenuSource>,
5092 trigger: Option<&str>,
5093 window: &mut Window,
5094 cx: &mut Context<Self>,
5095 ) {
5096 if self.pending_rename.is_some() {
5097 return;
5098 }
5099
5100 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5101
5102 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5103 // inserted and selected. To handle that case, the start of the selection is used so that
5104 // the menu starts with all choices.
5105 let position = self
5106 .selections
5107 .newest_anchor()
5108 .start
5109 .bias_right(&multibuffer_snapshot);
5110 if position.diff_base_anchor.is_some() {
5111 return;
5112 }
5113 let (buffer, buffer_position) =
5114 if let Some(output) = self.buffer.read(cx).text_anchor_for_position(position, cx) {
5115 output
5116 } else {
5117 return;
5118 };
5119 let buffer_snapshot = buffer.read(cx).snapshot();
5120
5121 let query: Option<Arc<String>> =
5122 Self::completion_query(&multibuffer_snapshot, position).map(|query| query.into());
5123
5124 drop(multibuffer_snapshot);
5125
5126 let provider = match requested_source {
5127 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5128 Some(CompletionsMenuSource::Words) => None,
5129 Some(CompletionsMenuSource::SnippetChoices) => {
5130 log::error!("bug: SnippetChoices requested_source is not handled");
5131 None
5132 }
5133 };
5134
5135 let sort_completions = provider
5136 .as_ref()
5137 .map_or(false, |provider| provider.sort_completions());
5138
5139 let filter_completions = provider
5140 .as_ref()
5141 .map_or(true, |provider| provider.filter_completions());
5142
5143 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5144 if filter_completions {
5145 menu.filter(query.clone(), provider.clone(), window, cx);
5146 }
5147 // When `is_incomplete` is false, no need to re-query completions when the current query
5148 // is a suffix of the initial query.
5149 if !menu.is_incomplete {
5150 // If the new query is a suffix of the old query (typing more characters) and
5151 // the previous result was complete, the existing completions can be filtered.
5152 //
5153 // Note that this is always true for snippet completions.
5154 let query_matches = match (&menu.initial_query, &query) {
5155 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5156 (None, _) => true,
5157 _ => false,
5158 };
5159 if query_matches {
5160 let position_matches = if menu.initial_position == position {
5161 true
5162 } else {
5163 let snapshot = self.buffer.read(cx).read(cx);
5164 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5165 };
5166 if position_matches {
5167 return;
5168 }
5169 }
5170 }
5171 };
5172
5173 let trigger_kind = match trigger {
5174 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5175 CompletionTriggerKind::TRIGGER_CHARACTER
5176 }
5177 _ => CompletionTriggerKind::INVOKED,
5178 };
5179 let completion_context = CompletionContext {
5180 trigger_character: trigger.and_then(|trigger| {
5181 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5182 Some(String::from(trigger))
5183 } else {
5184 None
5185 }
5186 }),
5187 trigger_kind,
5188 };
5189
5190 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5191 buffer_snapshot.surrounding_word(buffer_position)
5192 {
5193 let word_to_exclude = buffer_snapshot
5194 .text_for_range(word_range.clone())
5195 .collect::<String>();
5196 (
5197 buffer_snapshot.anchor_before(word_range.start)
5198 ..buffer_snapshot.anchor_after(buffer_position),
5199 Some(word_to_exclude),
5200 )
5201 } else {
5202 (buffer_position..buffer_position, None)
5203 };
5204
5205 let language = buffer_snapshot
5206 .language_at(buffer_position)
5207 .map(|language| language.name());
5208
5209 let completion_settings =
5210 language_settings(language.clone(), buffer_snapshot.file(), cx).completions;
5211
5212 let show_completion_documentation = buffer_snapshot
5213 .settings_at(buffer_position, cx)
5214 .show_completion_documentation;
5215
5216 // The document can be large, so stay in reasonable bounds when searching for words,
5217 // otherwise completion pop-up might be slow to appear.
5218 const WORD_LOOKUP_ROWS: u32 = 5_000;
5219 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5220 let min_word_search = buffer_snapshot.clip_point(
5221 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5222 Bias::Left,
5223 );
5224 let max_word_search = buffer_snapshot.clip_point(
5225 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5226 Bias::Right,
5227 );
5228 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5229 ..buffer_snapshot.point_to_offset(max_word_search);
5230
5231 let skip_digits = query
5232 .as_ref()
5233 .map_or(true, |query| !query.chars().any(|c| c.is_digit(10)));
5234
5235 let (mut words, provider_responses) = match &provider {
5236 Some(provider) => {
5237 let provider_responses = provider.completions(
5238 position.excerpt_id,
5239 &buffer,
5240 buffer_position,
5241 completion_context,
5242 window,
5243 cx,
5244 );
5245
5246 let words = match completion_settings.words {
5247 WordsCompletionMode::Disabled => Task::ready(BTreeMap::default()),
5248 WordsCompletionMode::Enabled | WordsCompletionMode::Fallback => cx
5249 .background_spawn(async move {
5250 buffer_snapshot.words_in_range(WordsQuery {
5251 fuzzy_contents: None,
5252 range: word_search_range,
5253 skip_digits,
5254 })
5255 }),
5256 };
5257
5258 (words, provider_responses)
5259 }
5260 None => (
5261 cx.background_spawn(async move {
5262 buffer_snapshot.words_in_range(WordsQuery {
5263 fuzzy_contents: None,
5264 range: word_search_range,
5265 skip_digits,
5266 })
5267 }),
5268 Task::ready(Ok(Vec::new())),
5269 ),
5270 };
5271
5272 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5273
5274 let id = post_inc(&mut self.next_completion_id);
5275 let task = cx.spawn_in(window, async move |editor, cx| {
5276 let Ok(()) = editor.update(cx, |this, _| {
5277 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5278 }) else {
5279 return;
5280 };
5281
5282 // TODO: Ideally completions from different sources would be selectively re-queried, so
5283 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5284 let mut completions = Vec::new();
5285 let mut is_incomplete = false;
5286 if let Some(provider_responses) = provider_responses.await.log_err() {
5287 if !provider_responses.is_empty() {
5288 for response in provider_responses {
5289 completions.extend(response.completions);
5290 is_incomplete = is_incomplete || response.is_incomplete;
5291 }
5292 if completion_settings.words == WordsCompletionMode::Fallback {
5293 words = Task::ready(BTreeMap::default());
5294 }
5295 }
5296 }
5297
5298 let mut words = words.await;
5299 if let Some(word_to_exclude) = &word_to_exclude {
5300 words.remove(word_to_exclude);
5301 }
5302 for lsp_completion in &completions {
5303 words.remove(&lsp_completion.new_text);
5304 }
5305 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5306 replace_range: word_replace_range.clone(),
5307 new_text: word.clone(),
5308 label: CodeLabel::plain(word, None),
5309 icon_path: None,
5310 documentation: None,
5311 source: CompletionSource::BufferWord {
5312 word_range,
5313 resolved: false,
5314 },
5315 insert_text_mode: Some(InsertTextMode::AS_IS),
5316 confirm: None,
5317 }));
5318
5319 let menu = if completions.is_empty() {
5320 None
5321 } else {
5322 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5323 let languages = editor
5324 .workspace
5325 .as_ref()
5326 .and_then(|(workspace, _)| workspace.upgrade())
5327 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5328 let menu = CompletionsMenu::new(
5329 id,
5330 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5331 sort_completions,
5332 show_completion_documentation,
5333 position,
5334 query.clone(),
5335 is_incomplete,
5336 buffer.clone(),
5337 completions.into(),
5338 snippet_sort_order,
5339 languages,
5340 language,
5341 cx,
5342 );
5343
5344 let query = if filter_completions { query } else { None };
5345 let matches_task = if let Some(query) = query {
5346 menu.do_async_filtering(query, cx)
5347 } else {
5348 Task::ready(menu.unfiltered_matches())
5349 };
5350 (menu, matches_task)
5351 }) else {
5352 return;
5353 };
5354
5355 let matches = matches_task.await;
5356
5357 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5358 // Newer menu already set, so exit.
5359 match editor.context_menu.borrow().as_ref() {
5360 Some(CodeContextMenu::Completions(prev_menu)) => {
5361 if prev_menu.id > id {
5362 return;
5363 }
5364 }
5365 _ => {}
5366 };
5367
5368 // Only valid to take prev_menu because it the new menu is immediately set
5369 // below, or the menu is hidden.
5370 match editor.context_menu.borrow_mut().take() {
5371 Some(CodeContextMenu::Completions(prev_menu)) => {
5372 let position_matches =
5373 if prev_menu.initial_position == menu.initial_position {
5374 true
5375 } else {
5376 let snapshot = editor.buffer.read(cx).read(cx);
5377 prev_menu.initial_position.to_offset(&snapshot)
5378 == menu.initial_position.to_offset(&snapshot)
5379 };
5380 if position_matches {
5381 // Preserve markdown cache before `set_filter_results` because it will
5382 // try to populate the documentation cache.
5383 menu.preserve_markdown_cache(prev_menu);
5384 }
5385 }
5386 _ => {}
5387 };
5388
5389 menu.set_filter_results(matches, provider, window, cx);
5390 }) else {
5391 return;
5392 };
5393
5394 menu.visible().then_some(menu)
5395 };
5396
5397 editor
5398 .update_in(cx, |editor, window, cx| {
5399 if editor.focus_handle.is_focused(window) {
5400 if let Some(menu) = menu {
5401 *editor.context_menu.borrow_mut() =
5402 Some(CodeContextMenu::Completions(menu));
5403
5404 crate::hover_popover::hide_hover(editor, cx);
5405 if editor.show_edit_predictions_in_menu() {
5406 editor.update_visible_inline_completion(window, cx);
5407 } else {
5408 editor.discard_inline_completion(false, cx);
5409 }
5410
5411 cx.notify();
5412 return;
5413 }
5414 }
5415
5416 if editor.completion_tasks.len() <= 1 {
5417 // If there are no more completion tasks and the last menu was empty, we should hide it.
5418 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5419 // If it was already hidden and we don't show inline completions in the menu, we should
5420 // also show the inline-completion when available.
5421 if was_hidden && editor.show_edit_predictions_in_menu() {
5422 editor.update_visible_inline_completion(window, cx);
5423 }
5424 }
5425 })
5426 .ok();
5427 });
5428
5429 self.completion_tasks.push((id, task));
5430 }
5431
5432 #[cfg(feature = "test-support")]
5433 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5434 let menu = self.context_menu.borrow();
5435 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5436 let completions = menu.completions.borrow();
5437 Some(completions.to_vec())
5438 } else {
5439 None
5440 }
5441 }
5442
5443 pub fn with_completions_menu_matching_id<R>(
5444 &self,
5445 id: CompletionId,
5446 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5447 ) -> R {
5448 let mut context_menu = self.context_menu.borrow_mut();
5449 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5450 return f(None);
5451 };
5452 if completions_menu.id != id {
5453 return f(None);
5454 }
5455 f(Some(completions_menu))
5456 }
5457
5458 pub fn confirm_completion(
5459 &mut self,
5460 action: &ConfirmCompletion,
5461 window: &mut Window,
5462 cx: &mut Context<Self>,
5463 ) -> Option<Task<Result<()>>> {
5464 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5465 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5466 }
5467
5468 pub fn confirm_completion_insert(
5469 &mut self,
5470 _: &ConfirmCompletionInsert,
5471 window: &mut Window,
5472 cx: &mut Context<Self>,
5473 ) -> Option<Task<Result<()>>> {
5474 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5475 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5476 }
5477
5478 pub fn confirm_completion_replace(
5479 &mut self,
5480 _: &ConfirmCompletionReplace,
5481 window: &mut Window,
5482 cx: &mut Context<Self>,
5483 ) -> Option<Task<Result<()>>> {
5484 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5485 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5486 }
5487
5488 pub fn compose_completion(
5489 &mut self,
5490 action: &ComposeCompletion,
5491 window: &mut Window,
5492 cx: &mut Context<Self>,
5493 ) -> Option<Task<Result<()>>> {
5494 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5495 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5496 }
5497
5498 fn do_completion(
5499 &mut self,
5500 item_ix: Option<usize>,
5501 intent: CompletionIntent,
5502 window: &mut Window,
5503 cx: &mut Context<Editor>,
5504 ) -> Option<Task<Result<()>>> {
5505 use language::ToOffset as _;
5506
5507 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5508 else {
5509 return None;
5510 };
5511
5512 let candidate_id = {
5513 let entries = completions_menu.entries.borrow();
5514 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5515 if self.show_edit_predictions_in_menu() {
5516 self.discard_inline_completion(true, cx);
5517 }
5518 mat.candidate_id
5519 };
5520
5521 let completion = completions_menu
5522 .completions
5523 .borrow()
5524 .get(candidate_id)?
5525 .clone();
5526 cx.stop_propagation();
5527
5528 let buffer_handle = completions_menu.buffer.clone();
5529
5530 let CompletionEdit {
5531 new_text,
5532 snippet,
5533 replace_range,
5534 } = process_completion_for_edit(
5535 &completion,
5536 intent,
5537 &buffer_handle,
5538 &completions_menu.initial_position.text_anchor,
5539 cx,
5540 );
5541
5542 let buffer = buffer_handle.read(cx);
5543 let snapshot = self.buffer.read(cx).snapshot(cx);
5544 let newest_anchor = self.selections.newest_anchor();
5545 let replace_range_multibuffer = {
5546 let excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5547 let multibuffer_anchor = snapshot
5548 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.start))
5549 .unwrap()
5550 ..snapshot
5551 .anchor_in_excerpt(excerpt.id(), buffer.anchor_before(replace_range.end))
5552 .unwrap();
5553 multibuffer_anchor.start.to_offset(&snapshot)
5554 ..multibuffer_anchor.end.to_offset(&snapshot)
5555 };
5556 if newest_anchor.head().buffer_id != Some(buffer.remote_id()) {
5557 return None;
5558 }
5559
5560 let old_text = buffer
5561 .text_for_range(replace_range.clone())
5562 .collect::<String>();
5563 let lookbehind = newest_anchor
5564 .start
5565 .text_anchor
5566 .to_offset(buffer)
5567 .saturating_sub(replace_range.start);
5568 let lookahead = replace_range
5569 .end
5570 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5571 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5572 let suffix = &old_text[lookbehind.min(old_text.len())..];
5573
5574 let selections = self.selections.all::<usize>(cx);
5575 let mut ranges = Vec::new();
5576 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5577
5578 for selection in &selections {
5579 let range = if selection.id == newest_anchor.id {
5580 replace_range_multibuffer.clone()
5581 } else {
5582 let mut range = selection.range();
5583
5584 // if prefix is present, don't duplicate it
5585 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5586 range.start = range.start.saturating_sub(lookbehind);
5587
5588 // if suffix is also present, mimic the newest cursor and replace it
5589 if selection.id != newest_anchor.id
5590 && snapshot.contains_str_at(range.end, suffix)
5591 {
5592 range.end += lookahead;
5593 }
5594 }
5595 range
5596 };
5597
5598 ranges.push(range.clone());
5599
5600 if !self.linked_edit_ranges.is_empty() {
5601 let start_anchor = snapshot.anchor_before(range.start);
5602 let end_anchor = snapshot.anchor_after(range.end);
5603 if let Some(ranges) = self
5604 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5605 {
5606 for (buffer, edits) in ranges {
5607 linked_edits
5608 .entry(buffer.clone())
5609 .or_default()
5610 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5611 }
5612 }
5613 }
5614 }
5615
5616 let common_prefix_len = old_text
5617 .chars()
5618 .zip(new_text.chars())
5619 .take_while(|(a, b)| a == b)
5620 .map(|(a, _)| a.len_utf8())
5621 .sum::<usize>();
5622
5623 cx.emit(EditorEvent::InputHandled {
5624 utf16_range_to_replace: None,
5625 text: new_text[common_prefix_len..].into(),
5626 });
5627
5628 self.transact(window, cx, |this, window, cx| {
5629 if let Some(mut snippet) = snippet {
5630 snippet.text = new_text.to_string();
5631 this.insert_snippet(&ranges, snippet, window, cx).log_err();
5632 } else {
5633 this.buffer.update(cx, |buffer, cx| {
5634 let auto_indent = match completion.insert_text_mode {
5635 Some(InsertTextMode::AS_IS) => None,
5636 _ => this.autoindent_mode.clone(),
5637 };
5638 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5639 buffer.edit(edits, auto_indent, cx);
5640 });
5641 }
5642 for (buffer, edits) in linked_edits {
5643 buffer.update(cx, |buffer, cx| {
5644 let snapshot = buffer.snapshot();
5645 let edits = edits
5646 .into_iter()
5647 .map(|(range, text)| {
5648 use text::ToPoint as TP;
5649 let end_point = TP::to_point(&range.end, &snapshot);
5650 let start_point = TP::to_point(&range.start, &snapshot);
5651 (start_point..end_point, text)
5652 })
5653 .sorted_by_key(|(range, _)| range.start);
5654 buffer.edit(edits, None, cx);
5655 })
5656 }
5657
5658 this.refresh_inline_completion(true, false, window, cx);
5659 });
5660
5661 let show_new_completions_on_confirm = completion
5662 .confirm
5663 .as_ref()
5664 .map_or(false, |confirm| confirm(intent, window, cx));
5665 if show_new_completions_on_confirm {
5666 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5667 }
5668
5669 let provider = self.completion_provider.as_ref()?;
5670 drop(completion);
5671 let apply_edits = provider.apply_additional_edits_for_completion(
5672 buffer_handle,
5673 completions_menu.completions.clone(),
5674 candidate_id,
5675 true,
5676 cx,
5677 );
5678
5679 let editor_settings = EditorSettings::get_global(cx);
5680 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5681 // After the code completion is finished, users often want to know what signatures are needed.
5682 // so we should automatically call signature_help
5683 self.show_signature_help(&ShowSignatureHelp, window, cx);
5684 }
5685
5686 Some(cx.foreground_executor().spawn(async move {
5687 apply_edits.await?;
5688 Ok(())
5689 }))
5690 }
5691
5692 pub fn toggle_code_actions(
5693 &mut self,
5694 action: &ToggleCodeActions,
5695 window: &mut Window,
5696 cx: &mut Context<Self>,
5697 ) {
5698 let quick_launch = action.quick_launch;
5699 let mut context_menu = self.context_menu.borrow_mut();
5700 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5701 if code_actions.deployed_from == action.deployed_from {
5702 // Toggle if we're selecting the same one
5703 *context_menu = None;
5704 cx.notify();
5705 return;
5706 } else {
5707 // Otherwise, clear it and start a new one
5708 *context_menu = None;
5709 cx.notify();
5710 }
5711 }
5712 drop(context_menu);
5713 let snapshot = self.snapshot(window, cx);
5714 let deployed_from = action.deployed_from.clone();
5715 let action = action.clone();
5716 self.completion_tasks.clear();
5717 self.discard_inline_completion(false, cx);
5718
5719 let multibuffer_point = match &action.deployed_from {
5720 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5721 DisplayPoint::new(*row, 0).to_point(&snapshot)
5722 }
5723 _ => self.selections.newest::<Point>(cx).head(),
5724 };
5725 let Some((buffer, buffer_row)) = snapshot
5726 .buffer_snapshot
5727 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5728 .and_then(|(buffer_snapshot, range)| {
5729 self.buffer()
5730 .read(cx)
5731 .buffer(buffer_snapshot.remote_id())
5732 .map(|buffer| (buffer, range.start.row))
5733 })
5734 else {
5735 return;
5736 };
5737 let buffer_id = buffer.read(cx).remote_id();
5738 let tasks = self
5739 .tasks
5740 .get(&(buffer_id, buffer_row))
5741 .map(|t| Arc::new(t.to_owned()));
5742
5743 if !self.focus_handle.is_focused(window) {
5744 return;
5745 }
5746 let project = self.project.clone();
5747
5748 let code_actions_task = match deployed_from {
5749 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5750 _ => self.code_actions(buffer_row, window, cx),
5751 };
5752
5753 let runnable_task = match deployed_from {
5754 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
5755 _ => {
5756 let mut task_context_task = Task::ready(None);
5757 if let Some(tasks) = &tasks {
5758 if let Some(project) = project {
5759 task_context_task =
5760 Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
5761 }
5762 }
5763
5764 cx.spawn_in(window, {
5765 let buffer = buffer.clone();
5766 async move |editor, cx| {
5767 let task_context = task_context_task.await;
5768
5769 let resolved_tasks =
5770 tasks
5771 .zip(task_context.clone())
5772 .map(|(tasks, task_context)| ResolvedTasks {
5773 templates: tasks.resolve(&task_context).collect(),
5774 position: snapshot.buffer_snapshot.anchor_before(Point::new(
5775 multibuffer_point.row,
5776 tasks.column,
5777 )),
5778 });
5779 let debug_scenarios = editor.update(cx, |editor, cx| {
5780 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
5781 })?;
5782 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
5783 }
5784 })
5785 }
5786 };
5787
5788 cx.spawn_in(window, async move |editor, cx| {
5789 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
5790 let code_actions = code_actions_task.await;
5791 let spawn_straight_away = quick_launch
5792 && resolved_tasks
5793 .as_ref()
5794 .map_or(false, |tasks| tasks.templates.len() == 1)
5795 && code_actions
5796 .as_ref()
5797 .map_or(true, |actions| actions.is_empty())
5798 && debug_scenarios.is_empty();
5799
5800 editor.update_in(cx, |editor, window, cx| {
5801 crate::hover_popover::hide_hover(editor, cx);
5802 *editor.context_menu.borrow_mut() =
5803 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
5804 buffer,
5805 actions: CodeActionContents::new(
5806 resolved_tasks,
5807 code_actions,
5808 debug_scenarios,
5809 task_context.unwrap_or_default(),
5810 ),
5811 selected_item: Default::default(),
5812 scroll_handle: UniformListScrollHandle::default(),
5813 deployed_from,
5814 }));
5815 if spawn_straight_away {
5816 if let Some(task) = editor.confirm_code_action(
5817 &ConfirmCodeAction { item_ix: Some(0) },
5818 window,
5819 cx,
5820 ) {
5821 cx.notify();
5822 return task;
5823 }
5824 }
5825
5826 Task::ready(Ok(()))
5827 })
5828 })
5829 .detach_and_log_err(cx);
5830 }
5831
5832 fn debug_scenarios(
5833 &mut self,
5834 resolved_tasks: &Option<ResolvedTasks>,
5835 buffer: &Entity<Buffer>,
5836 cx: &mut App,
5837 ) -> Vec<task::DebugScenario> {
5838 if cx.has_flag::<DebuggerFeatureFlag>() {
5839 maybe!({
5840 let project = self.project.as_ref()?;
5841 let dap_store = project.read(cx).dap_store();
5842 let mut scenarios = vec![];
5843 let resolved_tasks = resolved_tasks.as_ref()?;
5844 let buffer = buffer.read(cx);
5845 let language = buffer.language()?;
5846 let file = buffer.file();
5847 let debug_adapter = language_settings(language.name().into(), file, cx)
5848 .debuggers
5849 .first()
5850 .map(SharedString::from)
5851 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
5852
5853 dap_store.update(cx, |dap_store, cx| {
5854 for (_, task) in &resolved_tasks.templates {
5855 if let Some(scenario) = dap_store.debug_scenario_for_build_task(
5856 task.original_task().clone(),
5857 debug_adapter.clone().into(),
5858 task.display_label().to_owned().into(),
5859 cx,
5860 ) {
5861 scenarios.push(scenario);
5862 }
5863 }
5864 });
5865 Some(scenarios)
5866 })
5867 .unwrap_or_default()
5868 } else {
5869 vec![]
5870 }
5871 }
5872
5873 fn code_actions(
5874 &mut self,
5875 buffer_row: u32,
5876 window: &mut Window,
5877 cx: &mut Context<Self>,
5878 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
5879 let mut task = self.code_actions_task.take();
5880 cx.spawn_in(window, async move |editor, cx| {
5881 while let Some(prev_task) = task {
5882 prev_task.await.log_err();
5883 task = editor
5884 .update(cx, |this, _| this.code_actions_task.take())
5885 .ok()?;
5886 }
5887
5888 editor
5889 .update(cx, |editor, cx| {
5890 editor
5891 .available_code_actions
5892 .clone()
5893 .and_then(|(location, code_actions)| {
5894 let snapshot = location.buffer.read(cx).snapshot();
5895 let point_range = location.range.to_point(&snapshot);
5896 let point_range = point_range.start.row..=point_range.end.row;
5897 if point_range.contains(&buffer_row) {
5898 Some(code_actions)
5899 } else {
5900 None
5901 }
5902 })
5903 })
5904 .ok()
5905 .flatten()
5906 })
5907 }
5908
5909 pub fn confirm_code_action(
5910 &mut self,
5911 action: &ConfirmCodeAction,
5912 window: &mut Window,
5913 cx: &mut Context<Self>,
5914 ) -> Option<Task<Result<()>>> {
5915 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
5916
5917 let actions_menu =
5918 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
5919 menu
5920 } else {
5921 return None;
5922 };
5923
5924 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
5925 let action = actions_menu.actions.get(action_ix)?;
5926 let title = action.label();
5927 let buffer = actions_menu.buffer;
5928 let workspace = self.workspace()?;
5929
5930 match action {
5931 CodeActionsItem::Task(task_source_kind, resolved_task) => {
5932 workspace.update(cx, |workspace, cx| {
5933 workspace.schedule_resolved_task(
5934 task_source_kind,
5935 resolved_task,
5936 false,
5937 window,
5938 cx,
5939 );
5940
5941 Some(Task::ready(Ok(())))
5942 })
5943 }
5944 CodeActionsItem::CodeAction {
5945 excerpt_id,
5946 action,
5947 provider,
5948 } => {
5949 let apply_code_action =
5950 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
5951 let workspace = workspace.downgrade();
5952 Some(cx.spawn_in(window, async move |editor, cx| {
5953 let project_transaction = apply_code_action.await?;
5954 Self::open_project_transaction(
5955 &editor,
5956 workspace,
5957 project_transaction,
5958 title,
5959 cx,
5960 )
5961 .await
5962 }))
5963 }
5964 CodeActionsItem::DebugScenario(scenario) => {
5965 let context = actions_menu.actions.context.clone();
5966
5967 workspace.update(cx, |workspace, cx| {
5968 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
5969 workspace.start_debug_session(scenario, context, Some(buffer), window, cx);
5970 });
5971 Some(Task::ready(Ok(())))
5972 }
5973 }
5974 }
5975
5976 pub async fn open_project_transaction(
5977 this: &WeakEntity<Editor>,
5978 workspace: WeakEntity<Workspace>,
5979 transaction: ProjectTransaction,
5980 title: String,
5981 cx: &mut AsyncWindowContext,
5982 ) -> Result<()> {
5983 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
5984 cx.update(|_, cx| {
5985 entries.sort_unstable_by_key(|(buffer, _)| {
5986 buffer.read(cx).file().map(|f| f.path().clone())
5987 });
5988 })?;
5989
5990 // If the project transaction's edits are all contained within this editor, then
5991 // avoid opening a new editor to display them.
5992
5993 if let Some((buffer, transaction)) = entries.first() {
5994 if entries.len() == 1 {
5995 let excerpt = this.update(cx, |editor, cx| {
5996 editor
5997 .buffer()
5998 .read(cx)
5999 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6000 })?;
6001 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt {
6002 if excerpted_buffer == *buffer {
6003 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6004 let excerpt_range = excerpt_range.to_offset(buffer);
6005 buffer
6006 .edited_ranges_for_transaction::<usize>(transaction)
6007 .all(|range| {
6008 excerpt_range.start <= range.start
6009 && excerpt_range.end >= range.end
6010 })
6011 })?;
6012
6013 if all_edits_within_excerpt {
6014 return Ok(());
6015 }
6016 }
6017 }
6018 }
6019 } else {
6020 return Ok(());
6021 }
6022
6023 let mut ranges_to_highlight = Vec::new();
6024 let excerpt_buffer = cx.new(|cx| {
6025 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6026 for (buffer_handle, transaction) in &entries {
6027 let edited_ranges = buffer_handle
6028 .read(cx)
6029 .edited_ranges_for_transaction::<Point>(transaction)
6030 .collect::<Vec<_>>();
6031 let (ranges, _) = multibuffer.set_excerpts_for_path(
6032 PathKey::for_buffer(buffer_handle, cx),
6033 buffer_handle.clone(),
6034 edited_ranges,
6035 DEFAULT_MULTIBUFFER_CONTEXT,
6036 cx,
6037 );
6038
6039 ranges_to_highlight.extend(ranges);
6040 }
6041 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6042 multibuffer
6043 })?;
6044
6045 workspace.update_in(cx, |workspace, window, cx| {
6046 let project = workspace.project().clone();
6047 let editor =
6048 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6049 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6050 editor.update(cx, |editor, cx| {
6051 editor.highlight_background::<Self>(
6052 &ranges_to_highlight,
6053 |theme| theme.editor_highlighted_line_background,
6054 cx,
6055 );
6056 });
6057 })?;
6058
6059 Ok(())
6060 }
6061
6062 pub fn clear_code_action_providers(&mut self) {
6063 self.code_action_providers.clear();
6064 self.available_code_actions.take();
6065 }
6066
6067 pub fn add_code_action_provider(
6068 &mut self,
6069 provider: Rc<dyn CodeActionProvider>,
6070 window: &mut Window,
6071 cx: &mut Context<Self>,
6072 ) {
6073 if self
6074 .code_action_providers
6075 .iter()
6076 .any(|existing_provider| existing_provider.id() == provider.id())
6077 {
6078 return;
6079 }
6080
6081 self.code_action_providers.push(provider);
6082 self.refresh_code_actions(window, cx);
6083 }
6084
6085 pub fn remove_code_action_provider(
6086 &mut self,
6087 id: Arc<str>,
6088 window: &mut Window,
6089 cx: &mut Context<Self>,
6090 ) {
6091 self.code_action_providers
6092 .retain(|provider| provider.id() != id);
6093 self.refresh_code_actions(window, cx);
6094 }
6095
6096 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6097 !self.code_action_providers.is_empty()
6098 && EditorSettings::get_global(cx).toolbar.code_actions
6099 }
6100
6101 pub fn has_available_code_actions(&self) -> bool {
6102 self.available_code_actions
6103 .as_ref()
6104 .is_some_and(|(_, actions)| !actions.is_empty())
6105 }
6106
6107 fn render_inline_code_actions(
6108 &self,
6109 icon_size: ui::IconSize,
6110 display_row: DisplayRow,
6111 is_active: bool,
6112 cx: &mut Context<Self>,
6113 ) -> AnyElement {
6114 let show_tooltip = !self.context_menu_visible();
6115 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6116 .icon_size(icon_size)
6117 .shape(ui::IconButtonShape::Square)
6118 .style(ButtonStyle::Transparent)
6119 .icon_color(ui::Color::Hidden)
6120 .toggle_state(is_active)
6121 .when(show_tooltip, |this| {
6122 this.tooltip({
6123 let focus_handle = self.focus_handle.clone();
6124 move |window, cx| {
6125 Tooltip::for_action_in(
6126 "Toggle Code Actions",
6127 &ToggleCodeActions {
6128 deployed_from: None,
6129 quick_launch: false,
6130 },
6131 &focus_handle,
6132 window,
6133 cx,
6134 )
6135 }
6136 })
6137 })
6138 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6139 window.focus(&editor.focus_handle(cx));
6140 editor.toggle_code_actions(
6141 &crate::actions::ToggleCodeActions {
6142 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6143 display_row,
6144 )),
6145 quick_launch: false,
6146 },
6147 window,
6148 cx,
6149 );
6150 }))
6151 .into_any_element()
6152 }
6153
6154 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6155 &self.context_menu
6156 }
6157
6158 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Option<()> {
6159 let newest_selection = self.selections.newest_anchor().clone();
6160 let newest_selection_adjusted = self.selections.newest_adjusted(cx).clone();
6161 let buffer = self.buffer.read(cx);
6162 if newest_selection.head().diff_base_anchor.is_some() {
6163 return None;
6164 }
6165 let (start_buffer, start) =
6166 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6167 let (end_buffer, end) =
6168 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6169 if start_buffer != end_buffer {
6170 return None;
6171 }
6172
6173 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6174 cx.background_executor()
6175 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6176 .await;
6177
6178 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6179 let providers = this.code_action_providers.clone();
6180 let tasks = this
6181 .code_action_providers
6182 .iter()
6183 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6184 .collect::<Vec<_>>();
6185 (providers, tasks)
6186 })?;
6187
6188 let mut actions = Vec::new();
6189 for (provider, provider_actions) in
6190 providers.into_iter().zip(future::join_all(tasks).await)
6191 {
6192 if let Some(provider_actions) = provider_actions.log_err() {
6193 actions.extend(provider_actions.into_iter().map(|action| {
6194 AvailableCodeAction {
6195 excerpt_id: newest_selection.start.excerpt_id,
6196 action,
6197 provider: provider.clone(),
6198 }
6199 }));
6200 }
6201 }
6202
6203 this.update(cx, |this, cx| {
6204 this.available_code_actions = if actions.is_empty() {
6205 None
6206 } else {
6207 Some((
6208 Location {
6209 buffer: start_buffer,
6210 range: start..end,
6211 },
6212 actions.into(),
6213 ))
6214 };
6215 cx.notify();
6216 })
6217 }));
6218 None
6219 }
6220
6221 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6222 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6223 self.show_git_blame_inline = false;
6224
6225 self.show_git_blame_inline_delay_task =
6226 Some(cx.spawn_in(window, async move |this, cx| {
6227 cx.background_executor().timer(delay).await;
6228
6229 this.update(cx, |this, cx| {
6230 this.show_git_blame_inline = true;
6231 cx.notify();
6232 })
6233 .log_err();
6234 }));
6235 }
6236 }
6237
6238 fn show_blame_popover(
6239 &mut self,
6240 blame_entry: &BlameEntry,
6241 position: gpui::Point<Pixels>,
6242 cx: &mut Context<Self>,
6243 ) {
6244 if let Some(state) = &mut self.inline_blame_popover {
6245 state.hide_task.take();
6246 cx.notify();
6247 } else {
6248 let delay = EditorSettings::get_global(cx).hover_popover_delay;
6249 let show_task = cx.spawn(async move |editor, cx| {
6250 cx.background_executor()
6251 .timer(std::time::Duration::from_millis(delay))
6252 .await;
6253 editor
6254 .update(cx, |editor, cx| {
6255 if let Some(state) = &mut editor.inline_blame_popover {
6256 state.show_task = None;
6257 cx.notify();
6258 }
6259 })
6260 .ok();
6261 });
6262 let Some(blame) = self.blame.as_ref() else {
6263 return;
6264 };
6265 let blame = blame.read(cx);
6266 let details = blame.details_for_entry(&blame_entry);
6267 let markdown = cx.new(|cx| {
6268 Markdown::new(
6269 details
6270 .as_ref()
6271 .map(|message| message.message.clone())
6272 .unwrap_or_default(),
6273 None,
6274 None,
6275 cx,
6276 )
6277 });
6278 self.inline_blame_popover = Some(InlineBlamePopover {
6279 position,
6280 show_task: Some(show_task),
6281 hide_task: None,
6282 popover_bounds: None,
6283 popover_state: InlineBlamePopoverState {
6284 scroll_handle: ScrollHandle::new(),
6285 commit_message: details,
6286 markdown,
6287 },
6288 });
6289 }
6290 }
6291
6292 fn hide_blame_popover(&mut self, cx: &mut Context<Self>) {
6293 if let Some(state) = &mut self.inline_blame_popover {
6294 if state.show_task.is_some() {
6295 self.inline_blame_popover.take();
6296 cx.notify();
6297 } else {
6298 let hide_task = cx.spawn(async move |editor, cx| {
6299 cx.background_executor()
6300 .timer(std::time::Duration::from_millis(100))
6301 .await;
6302 editor
6303 .update(cx, |editor, cx| {
6304 editor.inline_blame_popover.take();
6305 cx.notify();
6306 })
6307 .ok();
6308 });
6309 state.hide_task = Some(hide_task);
6310 }
6311 }
6312 }
6313
6314 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6315 if self.pending_rename.is_some() {
6316 return None;
6317 }
6318
6319 let provider = self.semantics_provider.clone()?;
6320 let buffer = self.buffer.read(cx);
6321 let newest_selection = self.selections.newest_anchor().clone();
6322 let cursor_position = newest_selection.head();
6323 let (cursor_buffer, cursor_buffer_position) =
6324 buffer.text_anchor_for_position(cursor_position, cx)?;
6325 let (tail_buffer, tail_buffer_position) =
6326 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6327 if cursor_buffer != tail_buffer {
6328 return None;
6329 }
6330
6331 let snapshot = cursor_buffer.read(cx).snapshot();
6332 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position);
6333 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position);
6334 if start_word_range != end_word_range {
6335 self.document_highlights_task.take();
6336 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6337 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6338 return None;
6339 }
6340
6341 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
6342 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6343 cx.background_executor()
6344 .timer(Duration::from_millis(debounce))
6345 .await;
6346
6347 let highlights = if let Some(highlights) = cx
6348 .update(|cx| {
6349 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6350 })
6351 .ok()
6352 .flatten()
6353 {
6354 highlights.await.log_err()
6355 } else {
6356 None
6357 };
6358
6359 if let Some(highlights) = highlights {
6360 this.update(cx, |this, cx| {
6361 if this.pending_rename.is_some() {
6362 return;
6363 }
6364
6365 let buffer_id = cursor_position.buffer_id;
6366 let buffer = this.buffer.read(cx);
6367 if !buffer
6368 .text_anchor_for_position(cursor_position, cx)
6369 .map_or(false, |(buffer, _)| buffer == cursor_buffer)
6370 {
6371 return;
6372 }
6373
6374 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6375 let mut write_ranges = Vec::new();
6376 let mut read_ranges = Vec::new();
6377 for highlight in highlights {
6378 for (excerpt_id, excerpt_range) in
6379 buffer.excerpts_for_buffer(cursor_buffer.read(cx).remote_id(), cx)
6380 {
6381 let start = highlight
6382 .range
6383 .start
6384 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6385 let end = highlight
6386 .range
6387 .end
6388 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6389 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6390 continue;
6391 }
6392
6393 let range = Anchor {
6394 buffer_id,
6395 excerpt_id,
6396 text_anchor: start,
6397 diff_base_anchor: None,
6398 }..Anchor {
6399 buffer_id,
6400 excerpt_id,
6401 text_anchor: end,
6402 diff_base_anchor: None,
6403 };
6404 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6405 write_ranges.push(range);
6406 } else {
6407 read_ranges.push(range);
6408 }
6409 }
6410 }
6411
6412 this.highlight_background::<DocumentHighlightRead>(
6413 &read_ranges,
6414 |theme| theme.editor_document_highlight_read_background,
6415 cx,
6416 );
6417 this.highlight_background::<DocumentHighlightWrite>(
6418 &write_ranges,
6419 |theme| theme.editor_document_highlight_write_background,
6420 cx,
6421 );
6422 cx.notify();
6423 })
6424 .log_err();
6425 }
6426 }));
6427 None
6428 }
6429
6430 fn prepare_highlight_query_from_selection(
6431 &mut self,
6432 cx: &mut Context<Editor>,
6433 ) -> Option<(String, Range<Anchor>)> {
6434 if matches!(self.mode, EditorMode::SingleLine { .. }) {
6435 return None;
6436 }
6437 if !EditorSettings::get_global(cx).selection_highlight {
6438 return None;
6439 }
6440 if self.selections.count() != 1 || self.selections.line_mode {
6441 return None;
6442 }
6443 let selection = self.selections.newest::<Point>(cx);
6444 if selection.is_empty() || selection.start.row != selection.end.row {
6445 return None;
6446 }
6447 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6448 let selection_anchor_range = selection.range().to_anchors(&multi_buffer_snapshot);
6449 let query = multi_buffer_snapshot
6450 .text_for_range(selection_anchor_range.clone())
6451 .collect::<String>();
6452 if query.trim().is_empty() {
6453 return None;
6454 }
6455 Some((query, selection_anchor_range))
6456 }
6457
6458 fn update_selection_occurrence_highlights(
6459 &mut self,
6460 query_text: String,
6461 query_range: Range<Anchor>,
6462 multi_buffer_range_to_query: Range<Point>,
6463 use_debounce: bool,
6464 window: &mut Window,
6465 cx: &mut Context<Editor>,
6466 ) -> Task<()> {
6467 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6468 cx.spawn_in(window, async move |editor, cx| {
6469 if use_debounce {
6470 cx.background_executor()
6471 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6472 .await;
6473 }
6474 let match_task = cx.background_spawn(async move {
6475 let buffer_ranges = multi_buffer_snapshot
6476 .range_to_buffer_ranges(multi_buffer_range_to_query)
6477 .into_iter()
6478 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6479 let mut match_ranges = Vec::new();
6480 let Ok(regex) = project::search::SearchQuery::text(
6481 query_text.clone(),
6482 false,
6483 false,
6484 false,
6485 Default::default(),
6486 Default::default(),
6487 false,
6488 None,
6489 ) else {
6490 return Vec::default();
6491 };
6492 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6493 match_ranges.extend(
6494 regex
6495 .search(&buffer_snapshot, Some(search_range.clone()))
6496 .await
6497 .into_iter()
6498 .filter_map(|match_range| {
6499 let match_start = buffer_snapshot
6500 .anchor_after(search_range.start + match_range.start);
6501 let match_end = buffer_snapshot
6502 .anchor_before(search_range.start + match_range.end);
6503 let match_anchor_range = Anchor::range_in_buffer(
6504 excerpt_id,
6505 buffer_snapshot.remote_id(),
6506 match_start..match_end,
6507 );
6508 (match_anchor_range != query_range).then_some(match_anchor_range)
6509 }),
6510 );
6511 }
6512 match_ranges
6513 });
6514 let match_ranges = match_task.await;
6515 editor
6516 .update_in(cx, |editor, _, cx| {
6517 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6518 if !match_ranges.is_empty() {
6519 editor.highlight_background::<SelectedTextHighlight>(
6520 &match_ranges,
6521 |theme| theme.editor_document_highlight_bracket_background,
6522 cx,
6523 )
6524 }
6525 })
6526 .log_err();
6527 })
6528 }
6529
6530 fn refresh_selected_text_highlights(
6531 &mut self,
6532 on_buffer_edit: bool,
6533 window: &mut Window,
6534 cx: &mut Context<Editor>,
6535 ) {
6536 let Some((query_text, query_range)) = self.prepare_highlight_query_from_selection(cx)
6537 else {
6538 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6539 self.quick_selection_highlight_task.take();
6540 self.debounced_selection_highlight_task.take();
6541 return;
6542 };
6543 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6544 if on_buffer_edit
6545 || self
6546 .quick_selection_highlight_task
6547 .as_ref()
6548 .map_or(true, |(prev_anchor_range, _)| {
6549 prev_anchor_range != &query_range
6550 })
6551 {
6552 let multi_buffer_visible_start = self
6553 .scroll_manager
6554 .anchor()
6555 .anchor
6556 .to_point(&multi_buffer_snapshot);
6557 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6558 multi_buffer_visible_start
6559 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6560 Bias::Left,
6561 );
6562 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6563 self.quick_selection_highlight_task = Some((
6564 query_range.clone(),
6565 self.update_selection_occurrence_highlights(
6566 query_text.clone(),
6567 query_range.clone(),
6568 multi_buffer_visible_range,
6569 false,
6570 window,
6571 cx,
6572 ),
6573 ));
6574 }
6575 if on_buffer_edit
6576 || self
6577 .debounced_selection_highlight_task
6578 .as_ref()
6579 .map_or(true, |(prev_anchor_range, _)| {
6580 prev_anchor_range != &query_range
6581 })
6582 {
6583 let multi_buffer_start = multi_buffer_snapshot
6584 .anchor_before(0)
6585 .to_point(&multi_buffer_snapshot);
6586 let multi_buffer_end = multi_buffer_snapshot
6587 .anchor_after(multi_buffer_snapshot.len())
6588 .to_point(&multi_buffer_snapshot);
6589 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6590 self.debounced_selection_highlight_task = Some((
6591 query_range.clone(),
6592 self.update_selection_occurrence_highlights(
6593 query_text,
6594 query_range,
6595 multi_buffer_full_range,
6596 true,
6597 window,
6598 cx,
6599 ),
6600 ));
6601 }
6602 }
6603
6604 pub fn refresh_inline_completion(
6605 &mut self,
6606 debounce: bool,
6607 user_requested: bool,
6608 window: &mut Window,
6609 cx: &mut Context<Self>,
6610 ) -> Option<()> {
6611 let provider = self.edit_prediction_provider()?;
6612 let cursor = self.selections.newest_anchor().head();
6613 let (buffer, cursor_buffer_position) =
6614 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6615
6616 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6617 self.discard_inline_completion(false, cx);
6618 return None;
6619 }
6620
6621 if !user_requested
6622 && (!self.should_show_edit_predictions()
6623 || !self.is_focused(window)
6624 || buffer.read(cx).is_empty())
6625 {
6626 self.discard_inline_completion(false, cx);
6627 return None;
6628 }
6629
6630 self.update_visible_inline_completion(window, cx);
6631 provider.refresh(
6632 self.project.clone(),
6633 buffer,
6634 cursor_buffer_position,
6635 debounce,
6636 cx,
6637 );
6638 Some(())
6639 }
6640
6641 fn show_edit_predictions_in_menu(&self) -> bool {
6642 match self.edit_prediction_settings {
6643 EditPredictionSettings::Disabled => false,
6644 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
6645 }
6646 }
6647
6648 pub fn edit_predictions_enabled(&self) -> bool {
6649 match self.edit_prediction_settings {
6650 EditPredictionSettings::Disabled => false,
6651 EditPredictionSettings::Enabled { .. } => true,
6652 }
6653 }
6654
6655 fn edit_prediction_requires_modifier(&self) -> bool {
6656 match self.edit_prediction_settings {
6657 EditPredictionSettings::Disabled => false,
6658 EditPredictionSettings::Enabled {
6659 preview_requires_modifier,
6660 ..
6661 } => preview_requires_modifier,
6662 }
6663 }
6664
6665 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
6666 if self.edit_prediction_provider.is_none() {
6667 self.edit_prediction_settings = EditPredictionSettings::Disabled;
6668 } else {
6669 let selection = self.selections.newest_anchor();
6670 let cursor = selection.head();
6671
6672 if let Some((buffer, cursor_buffer_position)) =
6673 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6674 {
6675 self.edit_prediction_settings =
6676 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
6677 }
6678 }
6679 }
6680
6681 fn edit_prediction_settings_at_position(
6682 &self,
6683 buffer: &Entity<Buffer>,
6684 buffer_position: language::Anchor,
6685 cx: &App,
6686 ) -> EditPredictionSettings {
6687 if !self.mode.is_full()
6688 || !self.show_inline_completions_override.unwrap_or(true)
6689 || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx)
6690 {
6691 return EditPredictionSettings::Disabled;
6692 }
6693
6694 let buffer = buffer.read(cx);
6695
6696 let file = buffer.file();
6697
6698 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
6699 return EditPredictionSettings::Disabled;
6700 };
6701
6702 let by_provider = matches!(
6703 self.menu_inline_completions_policy,
6704 MenuInlineCompletionsPolicy::ByProvider
6705 );
6706
6707 let show_in_menu = by_provider
6708 && self
6709 .edit_prediction_provider
6710 .as_ref()
6711 .map_or(false, |provider| {
6712 provider.provider.show_completions_in_menu()
6713 });
6714
6715 let preview_requires_modifier =
6716 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
6717
6718 EditPredictionSettings::Enabled {
6719 show_in_menu,
6720 preview_requires_modifier,
6721 }
6722 }
6723
6724 fn should_show_edit_predictions(&self) -> bool {
6725 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
6726 }
6727
6728 pub fn edit_prediction_preview_is_active(&self) -> bool {
6729 matches!(
6730 self.edit_prediction_preview,
6731 EditPredictionPreview::Active { .. }
6732 )
6733 }
6734
6735 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
6736 let cursor = self.selections.newest_anchor().head();
6737 if let Some((buffer, cursor_position)) =
6738 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
6739 {
6740 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
6741 } else {
6742 false
6743 }
6744 }
6745
6746 pub fn supports_minimap(&self, cx: &App) -> bool {
6747 !self.minimap_visibility.disabled() && self.is_singleton(cx)
6748 }
6749
6750 fn edit_predictions_enabled_in_buffer(
6751 &self,
6752 buffer: &Entity<Buffer>,
6753 buffer_position: language::Anchor,
6754 cx: &App,
6755 ) -> bool {
6756 maybe!({
6757 if self.read_only(cx) {
6758 return Some(false);
6759 }
6760 let provider = self.edit_prediction_provider()?;
6761 if !provider.is_enabled(&buffer, buffer_position, cx) {
6762 return Some(false);
6763 }
6764 let buffer = buffer.read(cx);
6765 let Some(file) = buffer.file() else {
6766 return Some(true);
6767 };
6768 let settings = all_language_settings(Some(file), cx);
6769 Some(settings.edit_predictions_enabled_for_file(file, cx))
6770 })
6771 .unwrap_or(false)
6772 }
6773
6774 fn cycle_inline_completion(
6775 &mut self,
6776 direction: Direction,
6777 window: &mut Window,
6778 cx: &mut Context<Self>,
6779 ) -> Option<()> {
6780 let provider = self.edit_prediction_provider()?;
6781 let cursor = self.selections.newest_anchor().head();
6782 let (buffer, cursor_buffer_position) =
6783 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6784 if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
6785 return None;
6786 }
6787
6788 provider.cycle(buffer, cursor_buffer_position, direction, cx);
6789 self.update_visible_inline_completion(window, cx);
6790
6791 Some(())
6792 }
6793
6794 pub fn show_inline_completion(
6795 &mut self,
6796 _: &ShowEditPrediction,
6797 window: &mut Window,
6798 cx: &mut Context<Self>,
6799 ) {
6800 if !self.has_active_inline_completion() {
6801 self.refresh_inline_completion(false, true, window, cx);
6802 return;
6803 }
6804
6805 self.update_visible_inline_completion(window, cx);
6806 }
6807
6808 pub fn display_cursor_names(
6809 &mut self,
6810 _: &DisplayCursorNames,
6811 window: &mut Window,
6812 cx: &mut Context<Self>,
6813 ) {
6814 self.show_cursor_names(window, cx);
6815 }
6816
6817 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6818 self.show_cursor_names = true;
6819 cx.notify();
6820 cx.spawn_in(window, async move |this, cx| {
6821 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
6822 this.update(cx, |this, cx| {
6823 this.show_cursor_names = false;
6824 cx.notify()
6825 })
6826 .ok()
6827 })
6828 .detach();
6829 }
6830
6831 pub fn next_edit_prediction(
6832 &mut self,
6833 _: &NextEditPrediction,
6834 window: &mut Window,
6835 cx: &mut Context<Self>,
6836 ) {
6837 if self.has_active_inline_completion() {
6838 self.cycle_inline_completion(Direction::Next, window, cx);
6839 } else {
6840 let is_copilot_disabled = self
6841 .refresh_inline_completion(false, true, window, cx)
6842 .is_none();
6843 if is_copilot_disabled {
6844 cx.propagate();
6845 }
6846 }
6847 }
6848
6849 pub fn previous_edit_prediction(
6850 &mut self,
6851 _: &PreviousEditPrediction,
6852 window: &mut Window,
6853 cx: &mut Context<Self>,
6854 ) {
6855 if self.has_active_inline_completion() {
6856 self.cycle_inline_completion(Direction::Prev, window, cx);
6857 } else {
6858 let is_copilot_disabled = self
6859 .refresh_inline_completion(false, true, window, cx)
6860 .is_none();
6861 if is_copilot_disabled {
6862 cx.propagate();
6863 }
6864 }
6865 }
6866
6867 pub fn accept_edit_prediction(
6868 &mut self,
6869 _: &AcceptEditPrediction,
6870 window: &mut Window,
6871 cx: &mut Context<Self>,
6872 ) {
6873 if self.show_edit_predictions_in_menu() {
6874 self.hide_context_menu(window, cx);
6875 }
6876
6877 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6878 return;
6879 };
6880
6881 self.report_inline_completion_event(
6882 active_inline_completion.completion_id.clone(),
6883 true,
6884 cx,
6885 );
6886
6887 match &active_inline_completion.completion {
6888 InlineCompletion::Move { target, .. } => {
6889 let target = *target;
6890
6891 if let Some(position_map) = &self.last_position_map {
6892 if position_map
6893 .visible_row_range
6894 .contains(&target.to_display_point(&position_map.snapshot).row())
6895 || !self.edit_prediction_requires_modifier()
6896 {
6897 self.unfold_ranges(&[target..target], true, false, cx);
6898 // Note that this is also done in vim's handler of the Tab action.
6899 self.change_selections(
6900 Some(Autoscroll::newest()),
6901 window,
6902 cx,
6903 |selections| {
6904 selections.select_anchor_ranges([target..target]);
6905 },
6906 );
6907 self.clear_row_highlights::<EditPredictionPreview>();
6908
6909 self.edit_prediction_preview
6910 .set_previous_scroll_position(None);
6911 } else {
6912 self.edit_prediction_preview
6913 .set_previous_scroll_position(Some(
6914 position_map.snapshot.scroll_anchor,
6915 ));
6916
6917 self.highlight_rows::<EditPredictionPreview>(
6918 target..target,
6919 cx.theme().colors().editor_highlighted_line_background,
6920 RowHighlightOptions {
6921 autoscroll: true,
6922 ..Default::default()
6923 },
6924 cx,
6925 );
6926 self.request_autoscroll(Autoscroll::fit(), cx);
6927 }
6928 }
6929 }
6930 InlineCompletion::Edit { edits, .. } => {
6931 if let Some(provider) = self.edit_prediction_provider() {
6932 provider.accept(cx);
6933 }
6934
6935 // Store the transaction ID and selections before applying the edit
6936 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
6937
6938 let snapshot = self.buffer.read(cx).snapshot(cx);
6939 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
6940
6941 self.buffer.update(cx, |buffer, cx| {
6942 buffer.edit(edits.iter().cloned(), None, cx)
6943 });
6944
6945 self.change_selections(None, window, cx, |s| {
6946 s.select_anchor_ranges([last_edit_end..last_edit_end]);
6947 });
6948
6949 let selections = self.selections.disjoint_anchors();
6950 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
6951 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
6952 if has_new_transaction {
6953 self.selection_history
6954 .insert_transaction(transaction_id_now, selections);
6955 }
6956 }
6957
6958 self.update_visible_inline_completion(window, cx);
6959 if self.active_inline_completion.is_none() {
6960 self.refresh_inline_completion(true, true, window, cx);
6961 }
6962
6963 cx.notify();
6964 }
6965 }
6966
6967 self.edit_prediction_requires_modifier_in_indent_conflict = false;
6968 }
6969
6970 pub fn accept_partial_inline_completion(
6971 &mut self,
6972 _: &AcceptPartialEditPrediction,
6973 window: &mut Window,
6974 cx: &mut Context<Self>,
6975 ) {
6976 let Some(active_inline_completion) = self.active_inline_completion.as_ref() else {
6977 return;
6978 };
6979 if self.selections.count() != 1 {
6980 return;
6981 }
6982
6983 self.report_inline_completion_event(
6984 active_inline_completion.completion_id.clone(),
6985 true,
6986 cx,
6987 );
6988
6989 match &active_inline_completion.completion {
6990 InlineCompletion::Move { target, .. } => {
6991 let target = *target;
6992 self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
6993 selections.select_anchor_ranges([target..target]);
6994 });
6995 }
6996 InlineCompletion::Edit { edits, .. } => {
6997 // Find an insertion that starts at the cursor position.
6998 let snapshot = self.buffer.read(cx).snapshot(cx);
6999 let cursor_offset = self.selections.newest::<usize>(cx).head();
7000 let insertion = edits.iter().find_map(|(range, text)| {
7001 let range = range.to_offset(&snapshot);
7002 if range.is_empty() && range.start == cursor_offset {
7003 Some(text)
7004 } else {
7005 None
7006 }
7007 });
7008
7009 if let Some(text) = insertion {
7010 let mut partial_completion = text
7011 .chars()
7012 .by_ref()
7013 .take_while(|c| c.is_alphabetic())
7014 .collect::<String>();
7015 if partial_completion.is_empty() {
7016 partial_completion = text
7017 .chars()
7018 .by_ref()
7019 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7020 .collect::<String>();
7021 }
7022
7023 cx.emit(EditorEvent::InputHandled {
7024 utf16_range_to_replace: None,
7025 text: partial_completion.clone().into(),
7026 });
7027
7028 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7029
7030 self.refresh_inline_completion(true, true, window, cx);
7031 cx.notify();
7032 } else {
7033 self.accept_edit_prediction(&Default::default(), window, cx);
7034 }
7035 }
7036 }
7037 }
7038
7039 fn discard_inline_completion(
7040 &mut self,
7041 should_report_inline_completion_event: bool,
7042 cx: &mut Context<Self>,
7043 ) -> bool {
7044 if should_report_inline_completion_event {
7045 let completion_id = self
7046 .active_inline_completion
7047 .as_ref()
7048 .and_then(|active_completion| active_completion.completion_id.clone());
7049
7050 self.report_inline_completion_event(completion_id, false, cx);
7051 }
7052
7053 if let Some(provider) = self.edit_prediction_provider() {
7054 provider.discard(cx);
7055 }
7056
7057 self.take_active_inline_completion(cx)
7058 }
7059
7060 fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7061 let Some(provider) = self.edit_prediction_provider() else {
7062 return;
7063 };
7064
7065 let Some((_, buffer, _)) = self
7066 .buffer
7067 .read(cx)
7068 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7069 else {
7070 return;
7071 };
7072
7073 let extension = buffer
7074 .read(cx)
7075 .file()
7076 .and_then(|file| Some(file.path().extension()?.to_string_lossy().to_string()));
7077
7078 let event_type = match accepted {
7079 true => "Edit Prediction Accepted",
7080 false => "Edit Prediction Discarded",
7081 };
7082 telemetry::event!(
7083 event_type,
7084 provider = provider.name(),
7085 prediction_id = id,
7086 suggestion_accepted = accepted,
7087 file_extension = extension,
7088 );
7089 }
7090
7091 pub fn has_active_inline_completion(&self) -> bool {
7092 self.active_inline_completion.is_some()
7093 }
7094
7095 fn take_active_inline_completion(&mut self, cx: &mut Context<Self>) -> bool {
7096 let Some(active_inline_completion) = self.active_inline_completion.take() else {
7097 return false;
7098 };
7099
7100 self.splice_inlays(&active_inline_completion.inlay_ids, Default::default(), cx);
7101 self.clear_highlights::<InlineCompletionHighlight>(cx);
7102 self.stale_inline_completion_in_menu = Some(active_inline_completion);
7103 true
7104 }
7105
7106 /// Returns true when we're displaying the edit prediction popover below the cursor
7107 /// like we are not previewing and the LSP autocomplete menu is visible
7108 /// or we are in `when_holding_modifier` mode.
7109 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7110 if self.edit_prediction_preview_is_active()
7111 || !self.show_edit_predictions_in_menu()
7112 || !self.edit_predictions_enabled()
7113 {
7114 return false;
7115 }
7116
7117 if self.has_visible_completions_menu() {
7118 return true;
7119 }
7120
7121 has_completion && self.edit_prediction_requires_modifier()
7122 }
7123
7124 fn handle_modifiers_changed(
7125 &mut self,
7126 modifiers: Modifiers,
7127 position_map: &PositionMap,
7128 window: &mut Window,
7129 cx: &mut Context<Self>,
7130 ) {
7131 if self.show_edit_predictions_in_menu() {
7132 self.update_edit_prediction_preview(&modifiers, window, cx);
7133 }
7134
7135 self.update_selection_mode(&modifiers, position_map, window, cx);
7136
7137 let mouse_position = window.mouse_position();
7138 if !position_map.text_hitbox.is_hovered(window) {
7139 return;
7140 }
7141
7142 self.update_hovered_link(
7143 position_map.point_for_position(mouse_position),
7144 &position_map.snapshot,
7145 modifiers,
7146 window,
7147 cx,
7148 )
7149 }
7150
7151 fn multi_cursor_modifier(
7152 cursor_event: bool,
7153 modifiers: &Modifiers,
7154 cx: &mut Context<Self>,
7155 ) -> bool {
7156 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7157 if cursor_event {
7158 match multi_cursor_setting {
7159 MultiCursorModifier::Alt => modifiers.alt,
7160 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7161 }
7162 } else {
7163 match multi_cursor_setting {
7164 MultiCursorModifier::Alt => modifiers.secondary(),
7165 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7166 }
7167 }
7168 }
7169
7170 fn columnar_selection_modifiers(multi_cursor_modifier: bool, modifiers: &Modifiers) -> bool {
7171 modifiers.shift && multi_cursor_modifier && modifiers.number_of_modifiers() == 2
7172 }
7173
7174 fn update_selection_mode(
7175 &mut self,
7176 modifiers: &Modifiers,
7177 position_map: &PositionMap,
7178 window: &mut Window,
7179 cx: &mut Context<Self>,
7180 ) {
7181 let multi_cursor_modifier = Self::multi_cursor_modifier(true, modifiers, cx);
7182 if !Self::columnar_selection_modifiers(multi_cursor_modifier, modifiers)
7183 || self.selections.pending.is_none()
7184 {
7185 return;
7186 }
7187
7188 let mouse_position = window.mouse_position();
7189 let point_for_position = position_map.point_for_position(mouse_position);
7190 let position = point_for_position.previous_valid;
7191
7192 self.select(
7193 SelectPhase::BeginColumnar {
7194 position,
7195 reset: false,
7196 goal_column: point_for_position.exact_unclipped.column(),
7197 },
7198 window,
7199 cx,
7200 );
7201 }
7202
7203 fn update_edit_prediction_preview(
7204 &mut self,
7205 modifiers: &Modifiers,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 let mut modifiers_held = false;
7210 if let Some(accept_keystroke) = self
7211 .accept_edit_prediction_keybind(false, window, cx)
7212 .keystroke()
7213 {
7214 modifiers_held = modifiers_held
7215 || (&accept_keystroke.modifiers == modifiers
7216 && accept_keystroke.modifiers.modified());
7217 };
7218 if let Some(accept_partial_keystroke) = self
7219 .accept_edit_prediction_keybind(true, window, cx)
7220 .keystroke()
7221 {
7222 modifiers_held = modifiers_held
7223 || (&accept_partial_keystroke.modifiers == modifiers
7224 && accept_partial_keystroke.modifiers.modified());
7225 }
7226
7227 if modifiers_held {
7228 if matches!(
7229 self.edit_prediction_preview,
7230 EditPredictionPreview::Inactive { .. }
7231 ) {
7232 self.edit_prediction_preview = EditPredictionPreview::Active {
7233 previous_scroll_position: None,
7234 since: Instant::now(),
7235 };
7236
7237 self.update_visible_inline_completion(window, cx);
7238 cx.notify();
7239 }
7240 } else if let EditPredictionPreview::Active {
7241 previous_scroll_position,
7242 since,
7243 } = self.edit_prediction_preview
7244 {
7245 if let (Some(previous_scroll_position), Some(position_map)) =
7246 (previous_scroll_position, self.last_position_map.as_ref())
7247 {
7248 self.set_scroll_position(
7249 previous_scroll_position
7250 .scroll_position(&position_map.snapshot.display_snapshot),
7251 window,
7252 cx,
7253 );
7254 }
7255
7256 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7257 released_too_fast: since.elapsed() < Duration::from_millis(200),
7258 };
7259 self.clear_row_highlights::<EditPredictionPreview>();
7260 self.update_visible_inline_completion(window, cx);
7261 cx.notify();
7262 }
7263 }
7264
7265 fn update_visible_inline_completion(
7266 &mut self,
7267 _window: &mut Window,
7268 cx: &mut Context<Self>,
7269 ) -> Option<()> {
7270 let selection = self.selections.newest_anchor();
7271 let cursor = selection.head();
7272 let multibuffer = self.buffer.read(cx).snapshot(cx);
7273 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7274 let excerpt_id = cursor.excerpt_id;
7275
7276 let show_in_menu = self.show_edit_predictions_in_menu();
7277 let completions_menu_has_precedence = !show_in_menu
7278 && (self.context_menu.borrow().is_some()
7279 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
7280
7281 if completions_menu_has_precedence
7282 || !offset_selection.is_empty()
7283 || self
7284 .active_inline_completion
7285 .as_ref()
7286 .map_or(false, |completion| {
7287 let invalidation_range = completion.invalidation_range.to_offset(&multibuffer);
7288 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7289 !invalidation_range.contains(&offset_selection.head())
7290 })
7291 {
7292 self.discard_inline_completion(false, cx);
7293 return None;
7294 }
7295
7296 self.take_active_inline_completion(cx);
7297 let Some(provider) = self.edit_prediction_provider() else {
7298 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7299 return None;
7300 };
7301
7302 let (buffer, cursor_buffer_position) =
7303 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7304
7305 self.edit_prediction_settings =
7306 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7307
7308 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7309
7310 if self.edit_prediction_indent_conflict {
7311 let cursor_point = cursor.to_point(&multibuffer);
7312
7313 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7314
7315 if let Some((_, indent)) = indents.iter().next() {
7316 if indent.len == cursor_point.column {
7317 self.edit_prediction_indent_conflict = false;
7318 }
7319 }
7320 }
7321
7322 let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7323 let edits = inline_completion
7324 .edits
7325 .into_iter()
7326 .flat_map(|(range, new_text)| {
7327 let start = multibuffer.anchor_in_excerpt(excerpt_id, range.start)?;
7328 let end = multibuffer.anchor_in_excerpt(excerpt_id, range.end)?;
7329 Some((start..end, new_text))
7330 })
7331 .collect::<Vec<_>>();
7332 if edits.is_empty() {
7333 return None;
7334 }
7335
7336 let first_edit_start = edits.first().unwrap().0.start;
7337 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7338 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7339
7340 let last_edit_end = edits.last().unwrap().0.end;
7341 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7342 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7343
7344 let cursor_row = cursor.to_point(&multibuffer).row;
7345
7346 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7347
7348 let mut inlay_ids = Vec::new();
7349 let invalidation_row_range;
7350 let move_invalidation_row_range = if cursor_row < edit_start_row {
7351 Some(cursor_row..edit_end_row)
7352 } else if cursor_row > edit_end_row {
7353 Some(edit_start_row..cursor_row)
7354 } else {
7355 None
7356 };
7357 let is_move =
7358 move_invalidation_row_range.is_some() || self.inline_completions_hidden_for_vim_mode;
7359 let completion = if is_move {
7360 invalidation_row_range =
7361 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7362 let target = first_edit_start;
7363 InlineCompletion::Move { target, snapshot }
7364 } else {
7365 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7366 && !self.inline_completions_hidden_for_vim_mode;
7367
7368 if show_completions_in_buffer {
7369 if edits
7370 .iter()
7371 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7372 {
7373 let mut inlays = Vec::new();
7374 for (range, new_text) in &edits {
7375 let inlay = Inlay::inline_completion(
7376 post_inc(&mut self.next_inlay_id),
7377 range.start,
7378 new_text.as_str(),
7379 );
7380 inlay_ids.push(inlay.id);
7381 inlays.push(inlay);
7382 }
7383
7384 self.splice_inlays(&[], inlays, cx);
7385 } else {
7386 let background_color = cx.theme().status().deleted_background;
7387 self.highlight_text::<InlineCompletionHighlight>(
7388 edits.iter().map(|(range, _)| range.clone()).collect(),
7389 HighlightStyle {
7390 background_color: Some(background_color),
7391 ..Default::default()
7392 },
7393 cx,
7394 );
7395 }
7396 }
7397
7398 invalidation_row_range = edit_start_row..edit_end_row;
7399
7400 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7401 if provider.show_tab_accept_marker() {
7402 EditDisplayMode::TabAccept
7403 } else {
7404 EditDisplayMode::Inline
7405 }
7406 } else {
7407 EditDisplayMode::DiffPopover
7408 };
7409
7410 InlineCompletion::Edit {
7411 edits,
7412 edit_preview: inline_completion.edit_preview,
7413 display_mode,
7414 snapshot,
7415 }
7416 };
7417
7418 let invalidation_range = multibuffer
7419 .anchor_before(Point::new(invalidation_row_range.start, 0))
7420 ..multibuffer.anchor_after(Point::new(
7421 invalidation_row_range.end,
7422 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7423 ));
7424
7425 self.stale_inline_completion_in_menu = None;
7426 self.active_inline_completion = Some(InlineCompletionState {
7427 inlay_ids,
7428 completion,
7429 completion_id: inline_completion.id,
7430 invalidation_range,
7431 });
7432
7433 cx.notify();
7434
7435 Some(())
7436 }
7437
7438 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn InlineCompletionProviderHandle>> {
7439 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7440 }
7441
7442 fn clear_tasks(&mut self) {
7443 self.tasks.clear()
7444 }
7445
7446 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7447 if self.tasks.insert(key, value).is_some() {
7448 // This case should hopefully be rare, but just in case...
7449 log::error!(
7450 "multiple different run targets found on a single line, only the last target will be rendered"
7451 )
7452 }
7453 }
7454
7455 /// Get all display points of breakpoints that will be rendered within editor
7456 ///
7457 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7458 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7459 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7460 fn active_breakpoints(
7461 &self,
7462 range: Range<DisplayRow>,
7463 window: &mut Window,
7464 cx: &mut Context<Self>,
7465 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7466 let mut breakpoint_display_points = HashMap::default();
7467
7468 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7469 return breakpoint_display_points;
7470 };
7471
7472 let snapshot = self.snapshot(window, cx);
7473
7474 let multi_buffer_snapshot = &snapshot.display_snapshot.buffer_snapshot;
7475 let Some(project) = self.project.as_ref() else {
7476 return breakpoint_display_points;
7477 };
7478
7479 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7480 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7481
7482 for (buffer_snapshot, range, excerpt_id) in
7483 multi_buffer_snapshot.range_to_buffer_ranges(range)
7484 {
7485 let Some(buffer) = project
7486 .read(cx)
7487 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7488 else {
7489 continue;
7490 };
7491 let breakpoints = breakpoint_store.read(cx).breakpoints(
7492 &buffer,
7493 Some(
7494 buffer_snapshot.anchor_before(range.start)
7495 ..buffer_snapshot.anchor_after(range.end),
7496 ),
7497 buffer_snapshot,
7498 cx,
7499 );
7500 for (breakpoint, state) in breakpoints {
7501 let multi_buffer_anchor =
7502 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7503 let position = multi_buffer_anchor
7504 .to_point(&multi_buffer_snapshot)
7505 .to_display_point(&snapshot);
7506
7507 breakpoint_display_points.insert(
7508 position.row(),
7509 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7510 );
7511 }
7512 }
7513
7514 breakpoint_display_points
7515 }
7516
7517 fn breakpoint_context_menu(
7518 &self,
7519 anchor: Anchor,
7520 window: &mut Window,
7521 cx: &mut Context<Self>,
7522 ) -> Entity<ui::ContextMenu> {
7523 let weak_editor = cx.weak_entity();
7524 let focus_handle = self.focus_handle(cx);
7525
7526 let row = self
7527 .buffer
7528 .read(cx)
7529 .snapshot(cx)
7530 .summary_for_anchor::<Point>(&anchor)
7531 .row;
7532
7533 let breakpoint = self
7534 .breakpoint_at_row(row, window, cx)
7535 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
7536
7537 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
7538 "Edit Log Breakpoint"
7539 } else {
7540 "Set Log Breakpoint"
7541 };
7542
7543 let condition_breakpoint_msg = if breakpoint
7544 .as_ref()
7545 .is_some_and(|bp| bp.1.condition.is_some())
7546 {
7547 "Edit Condition Breakpoint"
7548 } else {
7549 "Set Condition Breakpoint"
7550 };
7551
7552 let hit_condition_breakpoint_msg = if breakpoint
7553 .as_ref()
7554 .is_some_and(|bp| bp.1.hit_condition.is_some())
7555 {
7556 "Edit Hit Condition Breakpoint"
7557 } else {
7558 "Set Hit Condition Breakpoint"
7559 };
7560
7561 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
7562 "Unset Breakpoint"
7563 } else {
7564 "Set Breakpoint"
7565 };
7566
7567 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
7568
7569 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
7570 BreakpointState::Enabled => Some("Disable"),
7571 BreakpointState::Disabled => Some("Enable"),
7572 });
7573
7574 let (anchor, breakpoint) =
7575 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
7576
7577 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
7578 menu.on_blur_subscription(Subscription::new(|| {}))
7579 .context(focus_handle)
7580 .when(run_to_cursor, |this| {
7581 let weak_editor = weak_editor.clone();
7582 this.entry("Run to cursor", None, move |window, cx| {
7583 weak_editor
7584 .update(cx, |editor, cx| {
7585 editor.change_selections(None, window, cx, |s| {
7586 s.select_ranges([Point::new(row, 0)..Point::new(row, 0)])
7587 });
7588 })
7589 .ok();
7590
7591 window.dispatch_action(Box::new(RunToCursor), cx);
7592 })
7593 .separator()
7594 })
7595 .when_some(toggle_state_msg, |this, msg| {
7596 this.entry(msg, None, {
7597 let weak_editor = weak_editor.clone();
7598 let breakpoint = breakpoint.clone();
7599 move |_window, cx| {
7600 weak_editor
7601 .update(cx, |this, cx| {
7602 this.edit_breakpoint_at_anchor(
7603 anchor,
7604 breakpoint.as_ref().clone(),
7605 BreakpointEditAction::InvertState,
7606 cx,
7607 );
7608 })
7609 .log_err();
7610 }
7611 })
7612 })
7613 .entry(set_breakpoint_msg, None, {
7614 let weak_editor = weak_editor.clone();
7615 let breakpoint = breakpoint.clone();
7616 move |_window, cx| {
7617 weak_editor
7618 .update(cx, |this, cx| {
7619 this.edit_breakpoint_at_anchor(
7620 anchor,
7621 breakpoint.as_ref().clone(),
7622 BreakpointEditAction::Toggle,
7623 cx,
7624 );
7625 })
7626 .log_err();
7627 }
7628 })
7629 .entry(log_breakpoint_msg, None, {
7630 let breakpoint = breakpoint.clone();
7631 let weak_editor = weak_editor.clone();
7632 move |window, cx| {
7633 weak_editor
7634 .update(cx, |this, cx| {
7635 this.add_edit_breakpoint_block(
7636 anchor,
7637 breakpoint.as_ref(),
7638 BreakpointPromptEditAction::Log,
7639 window,
7640 cx,
7641 );
7642 })
7643 .log_err();
7644 }
7645 })
7646 .entry(condition_breakpoint_msg, None, {
7647 let breakpoint = breakpoint.clone();
7648 let weak_editor = weak_editor.clone();
7649 move |window, cx| {
7650 weak_editor
7651 .update(cx, |this, cx| {
7652 this.add_edit_breakpoint_block(
7653 anchor,
7654 breakpoint.as_ref(),
7655 BreakpointPromptEditAction::Condition,
7656 window,
7657 cx,
7658 );
7659 })
7660 .log_err();
7661 }
7662 })
7663 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
7664 weak_editor
7665 .update(cx, |this, cx| {
7666 this.add_edit_breakpoint_block(
7667 anchor,
7668 breakpoint.as_ref(),
7669 BreakpointPromptEditAction::HitCondition,
7670 window,
7671 cx,
7672 );
7673 })
7674 .log_err();
7675 })
7676 })
7677 }
7678
7679 fn render_breakpoint(
7680 &self,
7681 position: Anchor,
7682 row: DisplayRow,
7683 breakpoint: &Breakpoint,
7684 state: Option<BreakpointSessionState>,
7685 cx: &mut Context<Self>,
7686 ) -> IconButton {
7687 let is_rejected = state.is_some_and(|s| !s.verified);
7688 // Is it a breakpoint that shows up when hovering over gutter?
7689 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
7690 (false, false),
7691 |PhantomBreakpointIndicator {
7692 is_active,
7693 display_row,
7694 collides_with_existing_breakpoint,
7695 }| {
7696 (
7697 is_active && display_row == row,
7698 collides_with_existing_breakpoint,
7699 )
7700 },
7701 );
7702
7703 let (color, icon) = {
7704 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
7705 (false, false) => ui::IconName::DebugBreakpoint,
7706 (true, false) => ui::IconName::DebugLogBreakpoint,
7707 (false, true) => ui::IconName::DebugDisabledBreakpoint,
7708 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
7709 };
7710
7711 let color = if is_phantom {
7712 Color::Hint
7713 } else if is_rejected {
7714 Color::Disabled
7715 } else {
7716 Color::Debugger
7717 };
7718
7719 (color, icon)
7720 };
7721
7722 let breakpoint = Arc::from(breakpoint.clone());
7723
7724 let alt_as_text = gpui::Keystroke {
7725 modifiers: Modifiers::secondary_key(),
7726 ..Default::default()
7727 };
7728 let primary_action_text = if breakpoint.is_disabled() {
7729 "Enable breakpoint"
7730 } else if is_phantom && !collides_with_existing {
7731 "Set breakpoint"
7732 } else {
7733 "Unset breakpoint"
7734 };
7735 let focus_handle = self.focus_handle.clone();
7736
7737 let meta = if is_rejected {
7738 SharedString::from("No executable code is associated with this line.")
7739 } else if collides_with_existing && !breakpoint.is_disabled() {
7740 SharedString::from(format!(
7741 "{alt_as_text}-click to disable,\nright-click for more options."
7742 ))
7743 } else {
7744 SharedString::from("Right-click for more options.")
7745 };
7746 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
7747 .icon_size(IconSize::XSmall)
7748 .size(ui::ButtonSize::None)
7749 .when(is_rejected, |this| {
7750 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
7751 })
7752 .icon_color(color)
7753 .style(ButtonStyle::Transparent)
7754 .on_click(cx.listener({
7755 let breakpoint = breakpoint.clone();
7756
7757 move |editor, event: &ClickEvent, window, cx| {
7758 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
7759 BreakpointEditAction::InvertState
7760 } else {
7761 BreakpointEditAction::Toggle
7762 };
7763
7764 window.focus(&editor.focus_handle(cx));
7765 editor.edit_breakpoint_at_anchor(
7766 position,
7767 breakpoint.as_ref().clone(),
7768 edit_action,
7769 cx,
7770 );
7771 }
7772 }))
7773 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7774 editor.set_breakpoint_context_menu(
7775 row,
7776 Some(position),
7777 event.down.position,
7778 window,
7779 cx,
7780 );
7781 }))
7782 .tooltip(move |window, cx| {
7783 Tooltip::with_meta_in(
7784 primary_action_text,
7785 Some(&ToggleBreakpoint),
7786 meta.clone(),
7787 &focus_handle,
7788 window,
7789 cx,
7790 )
7791 })
7792 }
7793
7794 fn build_tasks_context(
7795 project: &Entity<Project>,
7796 buffer: &Entity<Buffer>,
7797 buffer_row: u32,
7798 tasks: &Arc<RunnableTasks>,
7799 cx: &mut Context<Self>,
7800 ) -> Task<Option<task::TaskContext>> {
7801 let position = Point::new(buffer_row, tasks.column);
7802 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
7803 let location = Location {
7804 buffer: buffer.clone(),
7805 range: range_start..range_start,
7806 };
7807 // Fill in the environmental variables from the tree-sitter captures
7808 let mut captured_task_variables = TaskVariables::default();
7809 for (capture_name, value) in tasks.extra_variables.clone() {
7810 captured_task_variables.insert(
7811 task::VariableName::Custom(capture_name.into()),
7812 value.clone(),
7813 );
7814 }
7815 project.update(cx, |project, cx| {
7816 project.task_store().update(cx, |task_store, cx| {
7817 task_store.task_context_for_location(captured_task_variables, location, cx)
7818 })
7819 })
7820 }
7821
7822 pub fn spawn_nearest_task(
7823 &mut self,
7824 action: &SpawnNearestTask,
7825 window: &mut Window,
7826 cx: &mut Context<Self>,
7827 ) {
7828 let Some((workspace, _)) = self.workspace.clone() else {
7829 return;
7830 };
7831 let Some(project) = self.project.clone() else {
7832 return;
7833 };
7834
7835 // Try to find a closest, enclosing node using tree-sitter that has a
7836 // task
7837 let Some((buffer, buffer_row, tasks)) = self
7838 .find_enclosing_node_task(cx)
7839 // Or find the task that's closest in row-distance.
7840 .or_else(|| self.find_closest_task(cx))
7841 else {
7842 return;
7843 };
7844
7845 let reveal_strategy = action.reveal;
7846 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
7847 cx.spawn_in(window, async move |_, cx| {
7848 let context = task_context.await?;
7849 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
7850
7851 let resolved = &mut resolved_task.resolved;
7852 resolved.reveal = reveal_strategy;
7853
7854 workspace
7855 .update_in(cx, |workspace, window, cx| {
7856 workspace.schedule_resolved_task(
7857 task_source_kind,
7858 resolved_task,
7859 false,
7860 window,
7861 cx,
7862 );
7863 })
7864 .ok()
7865 })
7866 .detach();
7867 }
7868
7869 fn find_closest_task(
7870 &mut self,
7871 cx: &mut Context<Self>,
7872 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7873 let cursor_row = self.selections.newest_adjusted(cx).head().row;
7874
7875 let ((buffer_id, row), tasks) = self
7876 .tasks
7877 .iter()
7878 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
7879
7880 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
7881 let tasks = Arc::new(tasks.to_owned());
7882 Some((buffer, *row, tasks))
7883 }
7884
7885 fn find_enclosing_node_task(
7886 &mut self,
7887 cx: &mut Context<Self>,
7888 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
7889 let snapshot = self.buffer.read(cx).snapshot(cx);
7890 let offset = self.selections.newest::<usize>(cx).head();
7891 let excerpt = snapshot.excerpt_containing(offset..offset)?;
7892 let buffer_id = excerpt.buffer().remote_id();
7893
7894 let layer = excerpt.buffer().syntax_layer_at(offset)?;
7895 let mut cursor = layer.node().walk();
7896
7897 while cursor.goto_first_child_for_byte(offset).is_some() {
7898 if cursor.node().end_byte() == offset {
7899 cursor.goto_next_sibling();
7900 }
7901 }
7902
7903 // Ascend to the smallest ancestor that contains the range and has a task.
7904 loop {
7905 let node = cursor.node();
7906 let node_range = node.byte_range();
7907 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
7908
7909 // Check if this node contains our offset
7910 if node_range.start <= offset && node_range.end >= offset {
7911 // If it contains offset, check for task
7912 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
7913 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
7914 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
7915 }
7916 }
7917
7918 if !cursor.goto_parent() {
7919 break;
7920 }
7921 }
7922 None
7923 }
7924
7925 fn render_run_indicator(
7926 &self,
7927 _style: &EditorStyle,
7928 is_active: bool,
7929 row: DisplayRow,
7930 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
7931 cx: &mut Context<Self>,
7932 ) -> IconButton {
7933 let color = Color::Muted;
7934 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
7935
7936 IconButton::new(("run_indicator", row.0 as usize), ui::IconName::Play)
7937 .shape(ui::IconButtonShape::Square)
7938 .icon_size(IconSize::XSmall)
7939 .icon_color(color)
7940 .toggle_state(is_active)
7941 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
7942 let quick_launch = e.down.button == MouseButton::Left;
7943 window.focus(&editor.focus_handle(cx));
7944 editor.toggle_code_actions(
7945 &ToggleCodeActions {
7946 deployed_from: Some(CodeActionSource::RunMenu(row)),
7947 quick_launch,
7948 },
7949 window,
7950 cx,
7951 );
7952 }))
7953 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
7954 editor.set_breakpoint_context_menu(row, position, event.down.position, window, cx);
7955 }))
7956 }
7957
7958 pub fn context_menu_visible(&self) -> bool {
7959 !self.edit_prediction_preview_is_active()
7960 && self
7961 .context_menu
7962 .borrow()
7963 .as_ref()
7964 .map_or(false, |menu| menu.visible())
7965 }
7966
7967 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
7968 self.context_menu
7969 .borrow()
7970 .as_ref()
7971 .map(|menu| menu.origin())
7972 }
7973
7974 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
7975 self.context_menu_options = Some(options);
7976 }
7977
7978 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
7979 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
7980
7981 fn render_edit_prediction_popover(
7982 &mut self,
7983 text_bounds: &Bounds<Pixels>,
7984 content_origin: gpui::Point<Pixels>,
7985 right_margin: Pixels,
7986 editor_snapshot: &EditorSnapshot,
7987 visible_row_range: Range<DisplayRow>,
7988 scroll_top: f32,
7989 scroll_bottom: f32,
7990 line_layouts: &[LineWithInvisibles],
7991 line_height: Pixels,
7992 scroll_pixel_position: gpui::Point<Pixels>,
7993 newest_selection_head: Option<DisplayPoint>,
7994 editor_width: Pixels,
7995 style: &EditorStyle,
7996 window: &mut Window,
7997 cx: &mut App,
7998 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
7999 if self.mode().is_minimap() {
8000 return None;
8001 }
8002 let active_inline_completion = self.active_inline_completion.as_ref()?;
8003
8004 if self.edit_prediction_visible_in_cursor_popover(true) {
8005 return None;
8006 }
8007
8008 match &active_inline_completion.completion {
8009 InlineCompletion::Move { target, .. } => {
8010 let target_display_point = target.to_display_point(editor_snapshot);
8011
8012 if self.edit_prediction_requires_modifier() {
8013 if !self.edit_prediction_preview_is_active() {
8014 return None;
8015 }
8016
8017 self.render_edit_prediction_modifier_jump_popover(
8018 text_bounds,
8019 content_origin,
8020 visible_row_range,
8021 line_layouts,
8022 line_height,
8023 scroll_pixel_position,
8024 newest_selection_head,
8025 target_display_point,
8026 window,
8027 cx,
8028 )
8029 } else {
8030 self.render_edit_prediction_eager_jump_popover(
8031 text_bounds,
8032 content_origin,
8033 editor_snapshot,
8034 visible_row_range,
8035 scroll_top,
8036 scroll_bottom,
8037 line_height,
8038 scroll_pixel_position,
8039 target_display_point,
8040 editor_width,
8041 window,
8042 cx,
8043 )
8044 }
8045 }
8046 InlineCompletion::Edit {
8047 display_mode: EditDisplayMode::Inline,
8048 ..
8049 } => None,
8050 InlineCompletion::Edit {
8051 display_mode: EditDisplayMode::TabAccept,
8052 edits,
8053 ..
8054 } => {
8055 let range = &edits.first()?.0;
8056 let target_display_point = range.end.to_display_point(editor_snapshot);
8057
8058 self.render_edit_prediction_end_of_line_popover(
8059 "Accept",
8060 editor_snapshot,
8061 visible_row_range,
8062 target_display_point,
8063 line_height,
8064 scroll_pixel_position,
8065 content_origin,
8066 editor_width,
8067 window,
8068 cx,
8069 )
8070 }
8071 InlineCompletion::Edit {
8072 edits,
8073 edit_preview,
8074 display_mode: EditDisplayMode::DiffPopover,
8075 snapshot,
8076 } => self.render_edit_prediction_diff_popover(
8077 text_bounds,
8078 content_origin,
8079 right_margin,
8080 editor_snapshot,
8081 visible_row_range,
8082 line_layouts,
8083 line_height,
8084 scroll_pixel_position,
8085 newest_selection_head,
8086 editor_width,
8087 style,
8088 edits,
8089 edit_preview,
8090 snapshot,
8091 window,
8092 cx,
8093 ),
8094 }
8095 }
8096
8097 fn render_edit_prediction_modifier_jump_popover(
8098 &mut self,
8099 text_bounds: &Bounds<Pixels>,
8100 content_origin: gpui::Point<Pixels>,
8101 visible_row_range: Range<DisplayRow>,
8102 line_layouts: &[LineWithInvisibles],
8103 line_height: Pixels,
8104 scroll_pixel_position: gpui::Point<Pixels>,
8105 newest_selection_head: Option<DisplayPoint>,
8106 target_display_point: DisplayPoint,
8107 window: &mut Window,
8108 cx: &mut App,
8109 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8110 let scrolled_content_origin =
8111 content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
8112
8113 const SCROLL_PADDING_Y: Pixels = px(12.);
8114
8115 if target_display_point.row() < visible_row_range.start {
8116 return self.render_edit_prediction_scroll_popover(
8117 |_| SCROLL_PADDING_Y,
8118 IconName::ArrowUp,
8119 visible_row_range,
8120 line_layouts,
8121 newest_selection_head,
8122 scrolled_content_origin,
8123 window,
8124 cx,
8125 );
8126 } else if target_display_point.row() >= visible_row_range.end {
8127 return self.render_edit_prediction_scroll_popover(
8128 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8129 IconName::ArrowDown,
8130 visible_row_range,
8131 line_layouts,
8132 newest_selection_head,
8133 scrolled_content_origin,
8134 window,
8135 cx,
8136 );
8137 }
8138
8139 const POLE_WIDTH: Pixels = px(2.);
8140
8141 let line_layout =
8142 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8143 let target_column = target_display_point.column() as usize;
8144
8145 let target_x = line_layout.x_for_index(target_column);
8146 let target_y =
8147 (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
8148
8149 let flag_on_right = target_x < text_bounds.size.width / 2.;
8150
8151 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8152 border_color.l += 0.001;
8153
8154 let mut element = v_flex()
8155 .items_end()
8156 .when(flag_on_right, |el| el.items_start())
8157 .child(if flag_on_right {
8158 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8159 .rounded_bl(px(0.))
8160 .rounded_tl(px(0.))
8161 .border_l_2()
8162 .border_color(border_color)
8163 } else {
8164 self.render_edit_prediction_line_popover("Jump", None, window, cx)?
8165 .rounded_br(px(0.))
8166 .rounded_tr(px(0.))
8167 .border_r_2()
8168 .border_color(border_color)
8169 })
8170 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8171 .into_any();
8172
8173 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8174
8175 let mut origin = scrolled_content_origin + point(target_x, target_y)
8176 - point(
8177 if flag_on_right {
8178 POLE_WIDTH
8179 } else {
8180 size.width - POLE_WIDTH
8181 },
8182 size.height - line_height,
8183 );
8184
8185 origin.x = origin.x.max(content_origin.x);
8186
8187 element.prepaint_at(origin, window, cx);
8188
8189 Some((element, origin))
8190 }
8191
8192 fn render_edit_prediction_scroll_popover(
8193 &mut self,
8194 to_y: impl Fn(Size<Pixels>) -> Pixels,
8195 scroll_icon: IconName,
8196 visible_row_range: Range<DisplayRow>,
8197 line_layouts: &[LineWithInvisibles],
8198 newest_selection_head: Option<DisplayPoint>,
8199 scrolled_content_origin: gpui::Point<Pixels>,
8200 window: &mut Window,
8201 cx: &mut App,
8202 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8203 let mut element = self
8204 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)?
8205 .into_any();
8206
8207 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8208
8209 let cursor = newest_selection_head?;
8210 let cursor_row_layout =
8211 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8212 let cursor_column = cursor.column() as usize;
8213
8214 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8215
8216 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8217
8218 element.prepaint_at(origin, window, cx);
8219 Some((element, origin))
8220 }
8221
8222 fn render_edit_prediction_eager_jump_popover(
8223 &mut self,
8224 text_bounds: &Bounds<Pixels>,
8225 content_origin: gpui::Point<Pixels>,
8226 editor_snapshot: &EditorSnapshot,
8227 visible_row_range: Range<DisplayRow>,
8228 scroll_top: f32,
8229 scroll_bottom: f32,
8230 line_height: Pixels,
8231 scroll_pixel_position: gpui::Point<Pixels>,
8232 target_display_point: DisplayPoint,
8233 editor_width: Pixels,
8234 window: &mut Window,
8235 cx: &mut App,
8236 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8237 if target_display_point.row().as_f32() < scroll_top {
8238 let mut element = self
8239 .render_edit_prediction_line_popover(
8240 "Jump to Edit",
8241 Some(IconName::ArrowUp),
8242 window,
8243 cx,
8244 )?
8245 .into_any();
8246
8247 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8248 let offset = point(
8249 (text_bounds.size.width - size.width) / 2.,
8250 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8251 );
8252
8253 let origin = text_bounds.origin + offset;
8254 element.prepaint_at(origin, window, cx);
8255 Some((element, origin))
8256 } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
8257 let mut element = self
8258 .render_edit_prediction_line_popover(
8259 "Jump to Edit",
8260 Some(IconName::ArrowDown),
8261 window,
8262 cx,
8263 )?
8264 .into_any();
8265
8266 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8267 let offset = point(
8268 (text_bounds.size.width - size.width) / 2.,
8269 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8270 );
8271
8272 let origin = text_bounds.origin + offset;
8273 element.prepaint_at(origin, window, cx);
8274 Some((element, origin))
8275 } else {
8276 self.render_edit_prediction_end_of_line_popover(
8277 "Jump to Edit",
8278 editor_snapshot,
8279 visible_row_range,
8280 target_display_point,
8281 line_height,
8282 scroll_pixel_position,
8283 content_origin,
8284 editor_width,
8285 window,
8286 cx,
8287 )
8288 }
8289 }
8290
8291 fn render_edit_prediction_end_of_line_popover(
8292 self: &mut Editor,
8293 label: &'static str,
8294 editor_snapshot: &EditorSnapshot,
8295 visible_row_range: Range<DisplayRow>,
8296 target_display_point: DisplayPoint,
8297 line_height: Pixels,
8298 scroll_pixel_position: gpui::Point<Pixels>,
8299 content_origin: gpui::Point<Pixels>,
8300 editor_width: Pixels,
8301 window: &mut Window,
8302 cx: &mut App,
8303 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8304 let target_line_end = DisplayPoint::new(
8305 target_display_point.row(),
8306 editor_snapshot.line_len(target_display_point.row()),
8307 );
8308
8309 let mut element = self
8310 .render_edit_prediction_line_popover(label, None, window, cx)?
8311 .into_any();
8312
8313 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8314
8315 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8316
8317 let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
8318 let mut origin = start_point
8319 + line_origin
8320 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8321 origin.x = origin.x.max(content_origin.x);
8322
8323 let max_x = content_origin.x + editor_width - size.width;
8324
8325 if origin.x > max_x {
8326 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8327
8328 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8329 origin.y += offset;
8330 IconName::ArrowUp
8331 } else {
8332 origin.y -= offset;
8333 IconName::ArrowDown
8334 };
8335
8336 element = self
8337 .render_edit_prediction_line_popover(label, Some(icon), window, cx)?
8338 .into_any();
8339
8340 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8341
8342 origin.x = content_origin.x + editor_width - size.width - px(2.);
8343 }
8344
8345 element.prepaint_at(origin, window, cx);
8346 Some((element, origin))
8347 }
8348
8349 fn render_edit_prediction_diff_popover(
8350 self: &Editor,
8351 text_bounds: &Bounds<Pixels>,
8352 content_origin: gpui::Point<Pixels>,
8353 right_margin: Pixels,
8354 editor_snapshot: &EditorSnapshot,
8355 visible_row_range: Range<DisplayRow>,
8356 line_layouts: &[LineWithInvisibles],
8357 line_height: Pixels,
8358 scroll_pixel_position: gpui::Point<Pixels>,
8359 newest_selection_head: Option<DisplayPoint>,
8360 editor_width: Pixels,
8361 style: &EditorStyle,
8362 edits: &Vec<(Range<Anchor>, String)>,
8363 edit_preview: &Option<language::EditPreview>,
8364 snapshot: &language::BufferSnapshot,
8365 window: &mut Window,
8366 cx: &mut App,
8367 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8368 let edit_start = edits
8369 .first()
8370 .unwrap()
8371 .0
8372 .start
8373 .to_display_point(editor_snapshot);
8374 let edit_end = edits
8375 .last()
8376 .unwrap()
8377 .0
8378 .end
8379 .to_display_point(editor_snapshot);
8380
8381 let is_visible = visible_row_range.contains(&edit_start.row())
8382 || visible_row_range.contains(&edit_end.row());
8383 if !is_visible {
8384 return None;
8385 }
8386
8387 let highlighted_edits =
8388 crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
8389
8390 let styled_text = highlighted_edits.to_styled_text(&style.text);
8391 let line_count = highlighted_edits.text.lines().count();
8392
8393 const BORDER_WIDTH: Pixels = px(1.);
8394
8395 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8396 let has_keybind = keybind.is_some();
8397
8398 let mut element = h_flex()
8399 .items_start()
8400 .child(
8401 h_flex()
8402 .bg(cx.theme().colors().editor_background)
8403 .border(BORDER_WIDTH)
8404 .shadow_sm()
8405 .border_color(cx.theme().colors().border)
8406 .rounded_l_lg()
8407 .when(line_count > 1, |el| el.rounded_br_lg())
8408 .pr_1()
8409 .child(styled_text),
8410 )
8411 .child(
8412 h_flex()
8413 .h(line_height + BORDER_WIDTH * 2.)
8414 .px_1p5()
8415 .gap_1()
8416 // Workaround: For some reason, there's a gap if we don't do this
8417 .ml(-BORDER_WIDTH)
8418 .shadow(vec![gpui::BoxShadow {
8419 color: gpui::black().opacity(0.05),
8420 offset: point(px(1.), px(1.)),
8421 blur_radius: px(2.),
8422 spread_radius: px(0.),
8423 }])
8424 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8425 .border(BORDER_WIDTH)
8426 .border_color(cx.theme().colors().border)
8427 .rounded_r_lg()
8428 .id("edit_prediction_diff_popover_keybind")
8429 .when(!has_keybind, |el| {
8430 let status_colors = cx.theme().status();
8431
8432 el.bg(status_colors.error_background)
8433 .border_color(status_colors.error.opacity(0.6))
8434 .child(Icon::new(IconName::Info).color(Color::Error))
8435 .cursor_default()
8436 .hoverable_tooltip(move |_window, cx| {
8437 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8438 })
8439 })
8440 .children(keybind),
8441 )
8442 .into_any();
8443
8444 let longest_row =
8445 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8446 let longest_line_width = if visible_row_range.contains(&longest_row) {
8447 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8448 } else {
8449 layout_line(
8450 longest_row,
8451 editor_snapshot,
8452 style,
8453 editor_width,
8454 |_| false,
8455 window,
8456 cx,
8457 )
8458 .width
8459 };
8460
8461 let viewport_bounds =
8462 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8463 right: -right_margin,
8464 ..Default::default()
8465 });
8466
8467 let x_after_longest =
8468 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
8469 - scroll_pixel_position.x;
8470
8471 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8472
8473 // Fully visible if it can be displayed within the window (allow overlapping other
8474 // panes). However, this is only allowed if the popover starts within text_bounds.
8475 let can_position_to_the_right = x_after_longest < text_bounds.right()
8476 && x_after_longest + element_bounds.width < viewport_bounds.right();
8477
8478 let mut origin = if can_position_to_the_right {
8479 point(
8480 x_after_longest,
8481 text_bounds.origin.y + edit_start.row().as_f32() * line_height
8482 - scroll_pixel_position.y,
8483 )
8484 } else {
8485 let cursor_row = newest_selection_head.map(|head| head.row());
8486 let above_edit = edit_start
8487 .row()
8488 .0
8489 .checked_sub(line_count as u32)
8490 .map(DisplayRow);
8491 let below_edit = Some(edit_end.row() + 1);
8492 let above_cursor =
8493 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
8494 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
8495
8496 // Place the edit popover adjacent to the edit if there is a location
8497 // available that is onscreen and does not obscure the cursor. Otherwise,
8498 // place it adjacent to the cursor.
8499 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
8500 .into_iter()
8501 .flatten()
8502 .find(|&start_row| {
8503 let end_row = start_row + line_count as u32;
8504 visible_row_range.contains(&start_row)
8505 && visible_row_range.contains(&end_row)
8506 && cursor_row.map_or(true, |cursor_row| {
8507 !((start_row..end_row).contains(&cursor_row))
8508 })
8509 })?;
8510
8511 content_origin
8512 + point(
8513 -scroll_pixel_position.x,
8514 row_target.as_f32() * line_height - scroll_pixel_position.y,
8515 )
8516 };
8517
8518 origin.x -= BORDER_WIDTH;
8519
8520 window.defer_draw(element, origin, 1);
8521
8522 // Do not return an element, since it will already be drawn due to defer_draw.
8523 None
8524 }
8525
8526 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
8527 px(30.)
8528 }
8529
8530 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
8531 if self.read_only(cx) {
8532 cx.theme().players().read_only()
8533 } else {
8534 self.style.as_ref().unwrap().local_player
8535 }
8536 }
8537
8538 fn render_edit_prediction_accept_keybind(
8539 &self,
8540 window: &mut Window,
8541 cx: &App,
8542 ) -> Option<AnyElement> {
8543 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
8544 let accept_keystroke = accept_binding.keystroke()?;
8545
8546 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8547
8548 let modifiers_color = if accept_keystroke.modifiers == window.modifiers() {
8549 Color::Accent
8550 } else {
8551 Color::Muted
8552 };
8553
8554 h_flex()
8555 .px_0p5()
8556 .when(is_platform_style_mac, |parent| parent.gap_0p5())
8557 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8558 .text_size(TextSize::XSmall.rems(cx))
8559 .child(h_flex().children(ui::render_modifiers(
8560 &accept_keystroke.modifiers,
8561 PlatformStyle::platform(),
8562 Some(modifiers_color),
8563 Some(IconSize::XSmall.rems().into()),
8564 true,
8565 )))
8566 .when(is_platform_style_mac, |parent| {
8567 parent.child(accept_keystroke.key.clone())
8568 })
8569 .when(!is_platform_style_mac, |parent| {
8570 parent.child(
8571 Key::new(
8572 util::capitalize(&accept_keystroke.key),
8573 Some(Color::Default),
8574 )
8575 .size(Some(IconSize::XSmall.rems().into())),
8576 )
8577 })
8578 .into_any()
8579 .into()
8580 }
8581
8582 fn render_edit_prediction_line_popover(
8583 &self,
8584 label: impl Into<SharedString>,
8585 icon: Option<IconName>,
8586 window: &mut Window,
8587 cx: &App,
8588 ) -> Option<Stateful<Div>> {
8589 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
8590
8591 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8592 let has_keybind = keybind.is_some();
8593
8594 let result = h_flex()
8595 .id("ep-line-popover")
8596 .py_0p5()
8597 .pl_1()
8598 .pr(padding_right)
8599 .gap_1()
8600 .rounded_md()
8601 .border_1()
8602 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8603 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
8604 .shadow_sm()
8605 .when(!has_keybind, |el| {
8606 let status_colors = cx.theme().status();
8607
8608 el.bg(status_colors.error_background)
8609 .border_color(status_colors.error.opacity(0.6))
8610 .pl_2()
8611 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
8612 .cursor_default()
8613 .hoverable_tooltip(move |_window, cx| {
8614 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8615 })
8616 })
8617 .children(keybind)
8618 .child(
8619 Label::new(label)
8620 .size(LabelSize::Small)
8621 .when(!has_keybind, |el| {
8622 el.color(cx.theme().status().error.into()).strikethrough()
8623 }),
8624 )
8625 .when(!has_keybind, |el| {
8626 el.child(
8627 h_flex().ml_1().child(
8628 Icon::new(IconName::Info)
8629 .size(IconSize::Small)
8630 .color(cx.theme().status().error.into()),
8631 ),
8632 )
8633 })
8634 .when_some(icon, |element, icon| {
8635 element.child(
8636 div()
8637 .mt(px(1.5))
8638 .child(Icon::new(icon).size(IconSize::Small)),
8639 )
8640 });
8641
8642 Some(result)
8643 }
8644
8645 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
8646 let accent_color = cx.theme().colors().text_accent;
8647 let editor_bg_color = cx.theme().colors().editor_background;
8648 editor_bg_color.blend(accent_color.opacity(0.1))
8649 }
8650
8651 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
8652 let accent_color = cx.theme().colors().text_accent;
8653 let editor_bg_color = cx.theme().colors().editor_background;
8654 editor_bg_color.blend(accent_color.opacity(0.6))
8655 }
8656
8657 fn render_edit_prediction_cursor_popover(
8658 &self,
8659 min_width: Pixels,
8660 max_width: Pixels,
8661 cursor_point: Point,
8662 style: &EditorStyle,
8663 accept_keystroke: Option<&gpui::Keystroke>,
8664 _window: &Window,
8665 cx: &mut Context<Editor>,
8666 ) -> Option<AnyElement> {
8667 let provider = self.edit_prediction_provider.as_ref()?;
8668
8669 if provider.provider.needs_terms_acceptance(cx) {
8670 return Some(
8671 h_flex()
8672 .min_w(min_width)
8673 .flex_1()
8674 .px_2()
8675 .py_1()
8676 .gap_3()
8677 .elevation_2(cx)
8678 .hover(|style| style.bg(cx.theme().colors().element_hover))
8679 .id("accept-terms")
8680 .cursor_pointer()
8681 .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
8682 .on_click(cx.listener(|this, _event, window, cx| {
8683 cx.stop_propagation();
8684 this.report_editor_event("Edit Prediction Provider ToS Clicked", None, cx);
8685 window.dispatch_action(
8686 zed_actions::OpenZedPredictOnboarding.boxed_clone(),
8687 cx,
8688 );
8689 }))
8690 .child(
8691 h_flex()
8692 .flex_1()
8693 .gap_2()
8694 .child(Icon::new(IconName::ZedPredict))
8695 .child(Label::new("Accept Terms of Service"))
8696 .child(div().w_full())
8697 .child(
8698 Icon::new(IconName::ArrowUpRight)
8699 .color(Color::Muted)
8700 .size(IconSize::Small),
8701 )
8702 .into_any_element(),
8703 )
8704 .into_any(),
8705 );
8706 }
8707
8708 let is_refreshing = provider.provider.is_refreshing(cx);
8709
8710 fn pending_completion_container() -> Div {
8711 h_flex()
8712 .h_full()
8713 .flex_1()
8714 .gap_2()
8715 .child(Icon::new(IconName::ZedPredict))
8716 }
8717
8718 let completion = match &self.active_inline_completion {
8719 Some(prediction) => {
8720 if !self.has_visible_completions_menu() {
8721 const RADIUS: Pixels = px(6.);
8722 const BORDER_WIDTH: Pixels = px(1.);
8723
8724 return Some(
8725 h_flex()
8726 .elevation_2(cx)
8727 .border(BORDER_WIDTH)
8728 .border_color(cx.theme().colors().border)
8729 .when(accept_keystroke.is_none(), |el| {
8730 el.border_color(cx.theme().status().error)
8731 })
8732 .rounded(RADIUS)
8733 .rounded_tl(px(0.))
8734 .overflow_hidden()
8735 .child(div().px_1p5().child(match &prediction.completion {
8736 InlineCompletion::Move { target, snapshot } => {
8737 use text::ToPoint as _;
8738 if target.text_anchor.to_point(&snapshot).row > cursor_point.row
8739 {
8740 Icon::new(IconName::ZedPredictDown)
8741 } else {
8742 Icon::new(IconName::ZedPredictUp)
8743 }
8744 }
8745 InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
8746 }))
8747 .child(
8748 h_flex()
8749 .gap_1()
8750 .py_1()
8751 .px_2()
8752 .rounded_r(RADIUS - BORDER_WIDTH)
8753 .border_l_1()
8754 .border_color(cx.theme().colors().border)
8755 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8756 .when(self.edit_prediction_preview.released_too_fast(), |el| {
8757 el.child(
8758 Label::new("Hold")
8759 .size(LabelSize::Small)
8760 .when(accept_keystroke.is_none(), |el| {
8761 el.strikethrough()
8762 })
8763 .line_height_style(LineHeightStyle::UiLabel),
8764 )
8765 })
8766 .id("edit_prediction_cursor_popover_keybind")
8767 .when(accept_keystroke.is_none(), |el| {
8768 let status_colors = cx.theme().status();
8769
8770 el.bg(status_colors.error_background)
8771 .border_color(status_colors.error.opacity(0.6))
8772 .child(Icon::new(IconName::Info).color(Color::Error))
8773 .cursor_default()
8774 .hoverable_tooltip(move |_window, cx| {
8775 cx.new(|_| MissingEditPredictionKeybindingTooltip)
8776 .into()
8777 })
8778 })
8779 .when_some(
8780 accept_keystroke.as_ref(),
8781 |el, accept_keystroke| {
8782 el.child(h_flex().children(ui::render_modifiers(
8783 &accept_keystroke.modifiers,
8784 PlatformStyle::platform(),
8785 Some(Color::Default),
8786 Some(IconSize::XSmall.rems().into()),
8787 false,
8788 )))
8789 },
8790 ),
8791 )
8792 .into_any(),
8793 );
8794 }
8795
8796 self.render_edit_prediction_cursor_popover_preview(
8797 prediction,
8798 cursor_point,
8799 style,
8800 cx,
8801 )?
8802 }
8803
8804 None if is_refreshing => match &self.stale_inline_completion_in_menu {
8805 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
8806 stale_completion,
8807 cursor_point,
8808 style,
8809 cx,
8810 )?,
8811
8812 None => {
8813 pending_completion_container().child(Label::new("...").size(LabelSize::Small))
8814 }
8815 },
8816
8817 None => pending_completion_container().child(Label::new("No Prediction")),
8818 };
8819
8820 let completion = if is_refreshing {
8821 completion
8822 .with_animation(
8823 "loading-completion",
8824 Animation::new(Duration::from_secs(2))
8825 .repeat()
8826 .with_easing(pulsating_between(0.4, 0.8)),
8827 |label, delta| label.opacity(delta),
8828 )
8829 .into_any_element()
8830 } else {
8831 completion.into_any_element()
8832 };
8833
8834 let has_completion = self.active_inline_completion.is_some();
8835
8836 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
8837 Some(
8838 h_flex()
8839 .min_w(min_width)
8840 .max_w(max_width)
8841 .flex_1()
8842 .elevation_2(cx)
8843 .border_color(cx.theme().colors().border)
8844 .child(
8845 div()
8846 .flex_1()
8847 .py_1()
8848 .px_2()
8849 .overflow_hidden()
8850 .child(completion),
8851 )
8852 .when_some(accept_keystroke, |el, accept_keystroke| {
8853 if !accept_keystroke.modifiers.modified() {
8854 return el;
8855 }
8856
8857 el.child(
8858 h_flex()
8859 .h_full()
8860 .border_l_1()
8861 .rounded_r_lg()
8862 .border_color(cx.theme().colors().border)
8863 .bg(Self::edit_prediction_line_popover_bg_color(cx))
8864 .gap_1()
8865 .py_1()
8866 .px_2()
8867 .child(
8868 h_flex()
8869 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8870 .when(is_platform_style_mac, |parent| parent.gap_1())
8871 .child(h_flex().children(ui::render_modifiers(
8872 &accept_keystroke.modifiers,
8873 PlatformStyle::platform(),
8874 Some(if !has_completion {
8875 Color::Muted
8876 } else {
8877 Color::Default
8878 }),
8879 None,
8880 false,
8881 ))),
8882 )
8883 .child(Label::new("Preview").into_any_element())
8884 .opacity(if has_completion { 1.0 } else { 0.4 }),
8885 )
8886 })
8887 .into_any(),
8888 )
8889 }
8890
8891 fn render_edit_prediction_cursor_popover_preview(
8892 &self,
8893 completion: &InlineCompletionState,
8894 cursor_point: Point,
8895 style: &EditorStyle,
8896 cx: &mut Context<Editor>,
8897 ) -> Option<Div> {
8898 use text::ToPoint as _;
8899
8900 fn render_relative_row_jump(
8901 prefix: impl Into<String>,
8902 current_row: u32,
8903 target_row: u32,
8904 ) -> Div {
8905 let (row_diff, arrow) = if target_row < current_row {
8906 (current_row - target_row, IconName::ArrowUp)
8907 } else {
8908 (target_row - current_row, IconName::ArrowDown)
8909 };
8910
8911 h_flex()
8912 .child(
8913 Label::new(format!("{}{}", prefix.into(), row_diff))
8914 .color(Color::Muted)
8915 .size(LabelSize::Small),
8916 )
8917 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
8918 }
8919
8920 match &completion.completion {
8921 InlineCompletion::Move {
8922 target, snapshot, ..
8923 } => Some(
8924 h_flex()
8925 .px_2()
8926 .gap_2()
8927 .flex_1()
8928 .child(
8929 if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
8930 Icon::new(IconName::ZedPredictDown)
8931 } else {
8932 Icon::new(IconName::ZedPredictUp)
8933 },
8934 )
8935 .child(Label::new("Jump to Edit")),
8936 ),
8937
8938 InlineCompletion::Edit {
8939 edits,
8940 edit_preview,
8941 snapshot,
8942 display_mode: _,
8943 } => {
8944 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(&snapshot).row;
8945
8946 let (highlighted_edits, has_more_lines) = crate::inline_completion_edit_text(
8947 &snapshot,
8948 &edits,
8949 edit_preview.as_ref()?,
8950 true,
8951 cx,
8952 )
8953 .first_line_preview();
8954
8955 let styled_text = gpui::StyledText::new(highlighted_edits.text)
8956 .with_default_highlights(&style.text, highlighted_edits.highlights);
8957
8958 let preview = h_flex()
8959 .gap_1()
8960 .min_w_16()
8961 .child(styled_text)
8962 .when(has_more_lines, |parent| parent.child("…"));
8963
8964 let left = if first_edit_row != cursor_point.row {
8965 render_relative_row_jump("", cursor_point.row, first_edit_row)
8966 .into_any_element()
8967 } else {
8968 Icon::new(IconName::ZedPredict).into_any_element()
8969 };
8970
8971 Some(
8972 h_flex()
8973 .h_full()
8974 .flex_1()
8975 .gap_2()
8976 .pr_1()
8977 .overflow_x_hidden()
8978 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
8979 .child(left)
8980 .child(preview),
8981 )
8982 }
8983 }
8984 }
8985
8986 pub fn render_context_menu(
8987 &self,
8988 style: &EditorStyle,
8989 max_height_in_lines: u32,
8990 window: &mut Window,
8991 cx: &mut Context<Editor>,
8992 ) -> Option<AnyElement> {
8993 let menu = self.context_menu.borrow();
8994 let menu = menu.as_ref()?;
8995 if !menu.visible() {
8996 return None;
8997 };
8998 Some(menu.render(style, max_height_in_lines, window, cx))
8999 }
9000
9001 fn render_context_menu_aside(
9002 &mut self,
9003 max_size: Size<Pixels>,
9004 window: &mut Window,
9005 cx: &mut Context<Editor>,
9006 ) -> Option<AnyElement> {
9007 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9008 if menu.visible() {
9009 menu.render_aside(max_size, window, cx)
9010 } else {
9011 None
9012 }
9013 })
9014 }
9015
9016 fn hide_context_menu(
9017 &mut self,
9018 window: &mut Window,
9019 cx: &mut Context<Self>,
9020 ) -> Option<CodeContextMenu> {
9021 cx.notify();
9022 self.completion_tasks.clear();
9023 let context_menu = self.context_menu.borrow_mut().take();
9024 self.stale_inline_completion_in_menu.take();
9025 self.update_visible_inline_completion(window, cx);
9026 if let Some(CodeContextMenu::Completions(_)) = &context_menu {
9027 if let Some(completion_provider) = &self.completion_provider {
9028 completion_provider.selection_changed(None, window, cx);
9029 }
9030 }
9031 context_menu
9032 }
9033
9034 fn show_snippet_choices(
9035 &mut self,
9036 choices: &Vec<String>,
9037 selection: Range<Anchor>,
9038 cx: &mut Context<Self>,
9039 ) {
9040 let buffer_id = match (&selection.start.buffer_id, &selection.end.buffer_id) {
9041 (Some(a), Some(b)) if a == b => a,
9042 _ => {
9043 log::error!("expected anchor range to have matching buffer IDs");
9044 return;
9045 }
9046 };
9047 let multi_buffer = self.buffer().read(cx);
9048 let Some(buffer) = multi_buffer.buffer(*buffer_id) else {
9049 return;
9050 };
9051
9052 let id = post_inc(&mut self.next_completion_id);
9053 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9054 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9055 CompletionsMenu::new_snippet_choices(
9056 id,
9057 true,
9058 choices,
9059 selection,
9060 buffer,
9061 snippet_sort_order,
9062 ),
9063 ));
9064 }
9065
9066 pub fn insert_snippet(
9067 &mut self,
9068 insertion_ranges: &[Range<usize>],
9069 snippet: Snippet,
9070 window: &mut Window,
9071 cx: &mut Context<Self>,
9072 ) -> Result<()> {
9073 struct Tabstop<T> {
9074 is_end_tabstop: bool,
9075 ranges: Vec<Range<T>>,
9076 choices: Option<Vec<String>>,
9077 }
9078
9079 let tabstops = self.buffer.update(cx, |buffer, cx| {
9080 let snippet_text: Arc<str> = snippet.text.clone().into();
9081 let edits = insertion_ranges
9082 .iter()
9083 .cloned()
9084 .map(|range| (range, snippet_text.clone()));
9085 let autoindent_mode = AutoindentMode::Block {
9086 original_indent_columns: Vec::new(),
9087 };
9088 buffer.edit(edits, Some(autoindent_mode), cx);
9089
9090 let snapshot = &*buffer.read(cx);
9091 let snippet = &snippet;
9092 snippet
9093 .tabstops
9094 .iter()
9095 .map(|tabstop| {
9096 let is_end_tabstop = tabstop.ranges.first().map_or(false, |tabstop| {
9097 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9098 });
9099 let mut tabstop_ranges = tabstop
9100 .ranges
9101 .iter()
9102 .flat_map(|tabstop_range| {
9103 let mut delta = 0_isize;
9104 insertion_ranges.iter().map(move |insertion_range| {
9105 let insertion_start = insertion_range.start as isize + delta;
9106 delta +=
9107 snippet.text.len() as isize - insertion_range.len() as isize;
9108
9109 let start = ((insertion_start + tabstop_range.start) as usize)
9110 .min(snapshot.len());
9111 let end = ((insertion_start + tabstop_range.end) as usize)
9112 .min(snapshot.len());
9113 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9114 })
9115 })
9116 .collect::<Vec<_>>();
9117 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9118
9119 Tabstop {
9120 is_end_tabstop,
9121 ranges: tabstop_ranges,
9122 choices: tabstop.choices.clone(),
9123 }
9124 })
9125 .collect::<Vec<_>>()
9126 });
9127 if let Some(tabstop) = tabstops.first() {
9128 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9129 // Reverse order so that the first range is the newest created selection.
9130 // Completions will use it and autoscroll will prioritize it.
9131 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9132 });
9133
9134 if let Some(choices) = &tabstop.choices {
9135 if let Some(selection) = tabstop.ranges.first() {
9136 self.show_snippet_choices(choices, selection.clone(), cx)
9137 }
9138 }
9139
9140 // If we're already at the last tabstop and it's at the end of the snippet,
9141 // we're done, we don't need to keep the state around.
9142 if !tabstop.is_end_tabstop {
9143 let choices = tabstops
9144 .iter()
9145 .map(|tabstop| tabstop.choices.clone())
9146 .collect();
9147
9148 let ranges = tabstops
9149 .into_iter()
9150 .map(|tabstop| tabstop.ranges)
9151 .collect::<Vec<_>>();
9152
9153 self.snippet_stack.push(SnippetState {
9154 active_index: 0,
9155 ranges,
9156 choices,
9157 });
9158 }
9159
9160 // Check whether the just-entered snippet ends with an auto-closable bracket.
9161 if self.autoclose_regions.is_empty() {
9162 let snapshot = self.buffer.read(cx).snapshot(cx);
9163 for selection in &mut self.selections.all::<Point>(cx) {
9164 let selection_head = selection.head();
9165 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9166 continue;
9167 };
9168
9169 let mut bracket_pair = None;
9170 let next_chars = snapshot.chars_at(selection_head).collect::<String>();
9171 let prev_chars = snapshot
9172 .reversed_chars_at(selection_head)
9173 .collect::<String>();
9174 for (pair, enabled) in scope.brackets() {
9175 if enabled
9176 && pair.close
9177 && prev_chars.starts_with(pair.start.as_str())
9178 && next_chars.starts_with(pair.end.as_str())
9179 {
9180 bracket_pair = Some(pair.clone());
9181 break;
9182 }
9183 }
9184 if let Some(pair) = bracket_pair {
9185 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9186 let autoclose_enabled =
9187 self.use_autoclose && snapshot_settings.use_autoclose;
9188 if autoclose_enabled {
9189 let start = snapshot.anchor_after(selection_head);
9190 let end = snapshot.anchor_after(selection_head);
9191 self.autoclose_regions.push(AutocloseRegion {
9192 selection_id: selection.id,
9193 range: start..end,
9194 pair,
9195 });
9196 }
9197 }
9198 }
9199 }
9200 }
9201 Ok(())
9202 }
9203
9204 pub fn move_to_next_snippet_tabstop(
9205 &mut self,
9206 window: &mut Window,
9207 cx: &mut Context<Self>,
9208 ) -> bool {
9209 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9210 }
9211
9212 pub fn move_to_prev_snippet_tabstop(
9213 &mut self,
9214 window: &mut Window,
9215 cx: &mut Context<Self>,
9216 ) -> bool {
9217 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9218 }
9219
9220 pub fn move_to_snippet_tabstop(
9221 &mut self,
9222 bias: Bias,
9223 window: &mut Window,
9224 cx: &mut Context<Self>,
9225 ) -> bool {
9226 if let Some(mut snippet) = self.snippet_stack.pop() {
9227 match bias {
9228 Bias::Left => {
9229 if snippet.active_index > 0 {
9230 snippet.active_index -= 1;
9231 } else {
9232 self.snippet_stack.push(snippet);
9233 return false;
9234 }
9235 }
9236 Bias::Right => {
9237 if snippet.active_index + 1 < snippet.ranges.len() {
9238 snippet.active_index += 1;
9239 } else {
9240 self.snippet_stack.push(snippet);
9241 return false;
9242 }
9243 }
9244 }
9245 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9246 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9247 // Reverse order so that the first range is the newest created selection.
9248 // Completions will use it and autoscroll will prioritize it.
9249 s.select_ranges(current_ranges.iter().rev().cloned())
9250 });
9251
9252 if let Some(choices) = &snippet.choices[snippet.active_index] {
9253 if let Some(selection) = current_ranges.first() {
9254 self.show_snippet_choices(&choices, selection.clone(), cx);
9255 }
9256 }
9257
9258 // If snippet state is not at the last tabstop, push it back on the stack
9259 if snippet.active_index + 1 < snippet.ranges.len() {
9260 self.snippet_stack.push(snippet);
9261 }
9262 return true;
9263 }
9264 }
9265
9266 false
9267 }
9268
9269 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9270 self.transact(window, cx, |this, window, cx| {
9271 this.select_all(&SelectAll, window, cx);
9272 this.insert("", window, cx);
9273 });
9274 }
9275
9276 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9277 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9278 self.transact(window, cx, |this, window, cx| {
9279 this.select_autoclose_pair(window, cx);
9280 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9281 if !this.linked_edit_ranges.is_empty() {
9282 let selections = this.selections.all::<MultiBufferPoint>(cx);
9283 let snapshot = this.buffer.read(cx).snapshot(cx);
9284
9285 for selection in selections.iter() {
9286 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9287 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9288 if selection_start.buffer_id != selection_end.buffer_id {
9289 continue;
9290 }
9291 if let Some(ranges) =
9292 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9293 {
9294 for (buffer, entries) in ranges {
9295 linked_ranges.entry(buffer).or_default().extend(entries);
9296 }
9297 }
9298 }
9299 }
9300
9301 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
9302 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9303 for selection in &mut selections {
9304 if selection.is_empty() {
9305 let old_head = selection.head();
9306 let mut new_head =
9307 movement::left(&display_map, old_head.to_display_point(&display_map))
9308 .to_point(&display_map);
9309 if let Some((buffer, line_buffer_range)) = display_map
9310 .buffer_snapshot
9311 .buffer_line_for_row(MultiBufferRow(old_head.row))
9312 {
9313 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9314 let indent_len = match indent_size.kind {
9315 IndentKind::Space => {
9316 buffer.settings_at(line_buffer_range.start, cx).tab_size
9317 }
9318 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9319 };
9320 if old_head.column <= indent_size.len && old_head.column > 0 {
9321 let indent_len = indent_len.get();
9322 new_head = cmp::min(
9323 new_head,
9324 MultiBufferPoint::new(
9325 old_head.row,
9326 ((old_head.column - 1) / indent_len) * indent_len,
9327 ),
9328 );
9329 }
9330 }
9331
9332 selection.set_head(new_head, SelectionGoal::None);
9333 }
9334 }
9335
9336 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9337 s.select(selections)
9338 });
9339 this.insert("", window, cx);
9340 let empty_str: Arc<str> = Arc::from("");
9341 for (buffer, edits) in linked_ranges {
9342 let snapshot = buffer.read(cx).snapshot();
9343 use text::ToPoint as TP;
9344
9345 let edits = edits
9346 .into_iter()
9347 .map(|range| {
9348 let end_point = TP::to_point(&range.end, &snapshot);
9349 let mut start_point = TP::to_point(&range.start, &snapshot);
9350
9351 if end_point == start_point {
9352 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9353 .saturating_sub(1);
9354 start_point =
9355 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9356 };
9357
9358 (start_point..end_point, empty_str.clone())
9359 })
9360 .sorted_by_key(|(range, _)| range.start)
9361 .collect::<Vec<_>>();
9362 buffer.update(cx, |this, cx| {
9363 this.edit(edits, None, cx);
9364 })
9365 }
9366 this.refresh_inline_completion(true, false, window, cx);
9367 linked_editing_ranges::refresh_linked_ranges(this, window, cx);
9368 });
9369 }
9370
9371 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9372 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9373 self.transact(window, cx, |this, window, cx| {
9374 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9375 s.move_with(|map, selection| {
9376 if selection.is_empty() {
9377 let cursor = movement::right(map, selection.head());
9378 selection.end = cursor;
9379 selection.reversed = true;
9380 selection.goal = SelectionGoal::None;
9381 }
9382 })
9383 });
9384 this.insert("", window, cx);
9385 this.refresh_inline_completion(true, false, window, cx);
9386 });
9387 }
9388
9389 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9390 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9391 if self.move_to_prev_snippet_tabstop(window, cx) {
9392 return;
9393 }
9394 self.outdent(&Outdent, window, cx);
9395 }
9396
9397 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9398 if self.move_to_next_snippet_tabstop(window, cx) {
9399 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9400 return;
9401 }
9402 if self.read_only(cx) {
9403 return;
9404 }
9405 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9406 let mut selections = self.selections.all_adjusted(cx);
9407 let buffer = self.buffer.read(cx);
9408 let snapshot = buffer.snapshot(cx);
9409 let rows_iter = selections.iter().map(|s| s.head().row);
9410 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9411
9412 let has_some_cursor_in_whitespace = selections
9413 .iter()
9414 .filter(|selection| selection.is_empty())
9415 .any(|selection| {
9416 let cursor = selection.head();
9417 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9418 cursor.column < current_indent.len
9419 });
9420
9421 let mut edits = Vec::new();
9422 let mut prev_edited_row = 0;
9423 let mut row_delta = 0;
9424 for selection in &mut selections {
9425 if selection.start.row != prev_edited_row {
9426 row_delta = 0;
9427 }
9428 prev_edited_row = selection.end.row;
9429
9430 // If the selection is non-empty, then increase the indentation of the selected lines.
9431 if !selection.is_empty() {
9432 row_delta =
9433 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9434 continue;
9435 }
9436
9437 let cursor = selection.head();
9438 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9439 if let Some(suggested_indent) =
9440 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9441 {
9442 // Don't do anything if already at suggested indent
9443 // and there is any other cursor which is not
9444 if has_some_cursor_in_whitespace
9445 && cursor.column == current_indent.len
9446 && current_indent.len == suggested_indent.len
9447 {
9448 continue;
9449 }
9450
9451 // Adjust line and move cursor to suggested indent
9452 // if cursor is not at suggested indent
9453 if cursor.column < suggested_indent.len
9454 && cursor.column <= current_indent.len
9455 && current_indent.len <= suggested_indent.len
9456 {
9457 selection.start = Point::new(cursor.row, suggested_indent.len);
9458 selection.end = selection.start;
9459 if row_delta == 0 {
9460 edits.extend(Buffer::edit_for_indent_size_adjustment(
9461 cursor.row,
9462 current_indent,
9463 suggested_indent,
9464 ));
9465 row_delta = suggested_indent.len - current_indent.len;
9466 }
9467 continue;
9468 }
9469
9470 // If current indent is more than suggested indent
9471 // only move cursor to current indent and skip indent
9472 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
9473 selection.start = Point::new(cursor.row, current_indent.len);
9474 selection.end = selection.start;
9475 continue;
9476 }
9477 }
9478
9479 // Otherwise, insert a hard or soft tab.
9480 let settings = buffer.language_settings_at(cursor, cx);
9481 let tab_size = if settings.hard_tabs {
9482 IndentSize::tab()
9483 } else {
9484 let tab_size = settings.tab_size.get();
9485 let indent_remainder = snapshot
9486 .text_for_range(Point::new(cursor.row, 0)..cursor)
9487 .flat_map(str::chars)
9488 .fold(row_delta % tab_size, |counter: u32, c| {
9489 if c == '\t' {
9490 0
9491 } else {
9492 (counter + 1) % tab_size
9493 }
9494 });
9495
9496 let chars_to_next_tab_stop = tab_size - indent_remainder;
9497 IndentSize::spaces(chars_to_next_tab_stop)
9498 };
9499 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
9500 selection.end = selection.start;
9501 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
9502 row_delta += tab_size.len;
9503 }
9504
9505 self.transact(window, cx, |this, window, cx| {
9506 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9507 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9508 s.select(selections)
9509 });
9510 this.refresh_inline_completion(true, false, window, cx);
9511 });
9512 }
9513
9514 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
9515 if self.read_only(cx) {
9516 return;
9517 }
9518 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9519 let mut selections = self.selections.all::<Point>(cx);
9520 let mut prev_edited_row = 0;
9521 let mut row_delta = 0;
9522 let mut edits = Vec::new();
9523 let buffer = self.buffer.read(cx);
9524 let snapshot = buffer.snapshot(cx);
9525 for selection in &mut selections {
9526 if selection.start.row != prev_edited_row {
9527 row_delta = 0;
9528 }
9529 prev_edited_row = selection.end.row;
9530
9531 row_delta =
9532 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9533 }
9534
9535 self.transact(window, cx, |this, window, cx| {
9536 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
9537 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9538 s.select(selections)
9539 });
9540 });
9541 }
9542
9543 fn indent_selection(
9544 buffer: &MultiBuffer,
9545 snapshot: &MultiBufferSnapshot,
9546 selection: &mut Selection<Point>,
9547 edits: &mut Vec<(Range<Point>, String)>,
9548 delta_for_start_row: u32,
9549 cx: &App,
9550 ) -> u32 {
9551 let settings = buffer.language_settings_at(selection.start, cx);
9552 let tab_size = settings.tab_size.get();
9553 let indent_kind = if settings.hard_tabs {
9554 IndentKind::Tab
9555 } else {
9556 IndentKind::Space
9557 };
9558 let mut start_row = selection.start.row;
9559 let mut end_row = selection.end.row + 1;
9560
9561 // If a selection ends at the beginning of a line, don't indent
9562 // that last line.
9563 if selection.end.column == 0 && selection.end.row > selection.start.row {
9564 end_row -= 1;
9565 }
9566
9567 // Avoid re-indenting a row that has already been indented by a
9568 // previous selection, but still update this selection's column
9569 // to reflect that indentation.
9570 if delta_for_start_row > 0 {
9571 start_row += 1;
9572 selection.start.column += delta_for_start_row;
9573 if selection.end.row == selection.start.row {
9574 selection.end.column += delta_for_start_row;
9575 }
9576 }
9577
9578 let mut delta_for_end_row = 0;
9579 let has_multiple_rows = start_row + 1 != end_row;
9580 for row in start_row..end_row {
9581 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
9582 let indent_delta = match (current_indent.kind, indent_kind) {
9583 (IndentKind::Space, IndentKind::Space) => {
9584 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
9585 IndentSize::spaces(columns_to_next_tab_stop)
9586 }
9587 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
9588 (_, IndentKind::Tab) => IndentSize::tab(),
9589 };
9590
9591 let start = if has_multiple_rows || current_indent.len < selection.start.column {
9592 0
9593 } else {
9594 selection.start.column
9595 };
9596 let row_start = Point::new(row, start);
9597 edits.push((
9598 row_start..row_start,
9599 indent_delta.chars().collect::<String>(),
9600 ));
9601
9602 // Update this selection's endpoints to reflect the indentation.
9603 if row == selection.start.row {
9604 selection.start.column += indent_delta.len;
9605 }
9606 if row == selection.end.row {
9607 selection.end.column += indent_delta.len;
9608 delta_for_end_row = indent_delta.len;
9609 }
9610 }
9611
9612 if selection.start.row == selection.end.row {
9613 delta_for_start_row + delta_for_end_row
9614 } else {
9615 delta_for_end_row
9616 }
9617 }
9618
9619 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
9620 if self.read_only(cx) {
9621 return;
9622 }
9623 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9625 let selections = self.selections.all::<Point>(cx);
9626 let mut deletion_ranges = Vec::new();
9627 let mut last_outdent = None;
9628 {
9629 let buffer = self.buffer.read(cx);
9630 let snapshot = buffer.snapshot(cx);
9631 for selection in &selections {
9632 let settings = buffer.language_settings_at(selection.start, cx);
9633 let tab_size = settings.tab_size.get();
9634 let mut rows = selection.spanned_rows(false, &display_map);
9635
9636 // Avoid re-outdenting a row that has already been outdented by a
9637 // previous selection.
9638 if let Some(last_row) = last_outdent {
9639 if last_row == rows.start {
9640 rows.start = rows.start.next_row();
9641 }
9642 }
9643 let has_multiple_rows = rows.len() > 1;
9644 for row in rows.iter_rows() {
9645 let indent_size = snapshot.indent_size_for_line(row);
9646 if indent_size.len > 0 {
9647 let deletion_len = match indent_size.kind {
9648 IndentKind::Space => {
9649 let columns_to_prev_tab_stop = indent_size.len % tab_size;
9650 if columns_to_prev_tab_stop == 0 {
9651 tab_size
9652 } else {
9653 columns_to_prev_tab_stop
9654 }
9655 }
9656 IndentKind::Tab => 1,
9657 };
9658 let start = if has_multiple_rows
9659 || deletion_len > selection.start.column
9660 || indent_size.len < selection.start.column
9661 {
9662 0
9663 } else {
9664 selection.start.column - deletion_len
9665 };
9666 deletion_ranges.push(
9667 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
9668 );
9669 last_outdent = Some(row);
9670 }
9671 }
9672 }
9673 }
9674
9675 self.transact(window, cx, |this, window, cx| {
9676 this.buffer.update(cx, |buffer, cx| {
9677 let empty_str: Arc<str> = Arc::default();
9678 buffer.edit(
9679 deletion_ranges
9680 .into_iter()
9681 .map(|range| (range, empty_str.clone())),
9682 None,
9683 cx,
9684 );
9685 });
9686 let selections = this.selections.all::<usize>(cx);
9687 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9688 s.select(selections)
9689 });
9690 });
9691 }
9692
9693 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
9694 if self.read_only(cx) {
9695 return;
9696 }
9697 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9698 let selections = self
9699 .selections
9700 .all::<usize>(cx)
9701 .into_iter()
9702 .map(|s| s.range());
9703
9704 self.transact(window, cx, |this, window, cx| {
9705 this.buffer.update(cx, |buffer, cx| {
9706 buffer.autoindent_ranges(selections, cx);
9707 });
9708 let selections = this.selections.all::<usize>(cx);
9709 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9710 s.select(selections)
9711 });
9712 });
9713 }
9714
9715 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
9716 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9717 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
9718 let selections = self.selections.all::<Point>(cx);
9719
9720 let mut new_cursors = Vec::new();
9721 let mut edit_ranges = Vec::new();
9722 let mut selections = selections.iter().peekable();
9723 while let Some(selection) = selections.next() {
9724 let mut rows = selection.spanned_rows(false, &display_map);
9725 let goal_display_column = selection.head().to_display_point(&display_map).column();
9726
9727 // Accumulate contiguous regions of rows that we want to delete.
9728 while let Some(next_selection) = selections.peek() {
9729 let next_rows = next_selection.spanned_rows(false, &display_map);
9730 if next_rows.start <= rows.end {
9731 rows.end = next_rows.end;
9732 selections.next().unwrap();
9733 } else {
9734 break;
9735 }
9736 }
9737
9738 let buffer = &display_map.buffer_snapshot;
9739 let mut edit_start = Point::new(rows.start.0, 0).to_offset(buffer);
9740 let edit_end;
9741 let cursor_buffer_row;
9742 if buffer.max_point().row >= rows.end.0 {
9743 // If there's a line after the range, delete the \n from the end of the row range
9744 // and position the cursor on the next line.
9745 edit_end = Point::new(rows.end.0, 0).to_offset(buffer);
9746 cursor_buffer_row = rows.end;
9747 } else {
9748 // If there isn't a line after the range, delete the \n from the line before the
9749 // start of the row range and position the cursor there.
9750 edit_start = edit_start.saturating_sub(1);
9751 edit_end = buffer.len();
9752 cursor_buffer_row = rows.start.previous_row();
9753 }
9754
9755 let mut cursor = Point::new(cursor_buffer_row.0, 0).to_display_point(&display_map);
9756 *cursor.column_mut() =
9757 cmp::min(goal_display_column, display_map.line_len(cursor.row()));
9758
9759 new_cursors.push((
9760 selection.id,
9761 buffer.anchor_after(cursor.to_point(&display_map)),
9762 ));
9763 edit_ranges.push(edit_start..edit_end);
9764 }
9765
9766 self.transact(window, cx, |this, window, cx| {
9767 let buffer = this.buffer.update(cx, |buffer, cx| {
9768 let empty_str: Arc<str> = Arc::default();
9769 buffer.edit(
9770 edit_ranges
9771 .into_iter()
9772 .map(|range| (range, empty_str.clone())),
9773 None,
9774 cx,
9775 );
9776 buffer.snapshot(cx)
9777 });
9778 let new_selections = new_cursors
9779 .into_iter()
9780 .map(|(id, cursor)| {
9781 let cursor = cursor.to_point(&buffer);
9782 Selection {
9783 id,
9784 start: cursor,
9785 end: cursor,
9786 reversed: false,
9787 goal: SelectionGoal::None,
9788 }
9789 })
9790 .collect();
9791
9792 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9793 s.select(new_selections);
9794 });
9795 });
9796 }
9797
9798 pub fn join_lines_impl(
9799 &mut self,
9800 insert_whitespace: bool,
9801 window: &mut Window,
9802 cx: &mut Context<Self>,
9803 ) {
9804 if self.read_only(cx) {
9805 return;
9806 }
9807 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
9808 for selection in self.selections.all::<Point>(cx) {
9809 let start = MultiBufferRow(selection.start.row);
9810 // Treat single line selections as if they include the next line. Otherwise this action
9811 // would do nothing for single line selections individual cursors.
9812 let end = if selection.start.row == selection.end.row {
9813 MultiBufferRow(selection.start.row + 1)
9814 } else {
9815 MultiBufferRow(selection.end.row)
9816 };
9817
9818 if let Some(last_row_range) = row_ranges.last_mut() {
9819 if start <= last_row_range.end {
9820 last_row_range.end = end;
9821 continue;
9822 }
9823 }
9824 row_ranges.push(start..end);
9825 }
9826
9827 let snapshot = self.buffer.read(cx).snapshot(cx);
9828 let mut cursor_positions = Vec::new();
9829 for row_range in &row_ranges {
9830 let anchor = snapshot.anchor_before(Point::new(
9831 row_range.end.previous_row().0,
9832 snapshot.line_len(row_range.end.previous_row()),
9833 ));
9834 cursor_positions.push(anchor..anchor);
9835 }
9836
9837 self.transact(window, cx, |this, window, cx| {
9838 for row_range in row_ranges.into_iter().rev() {
9839 for row in row_range.iter_rows().rev() {
9840 let end_of_line = Point::new(row.0, snapshot.line_len(row));
9841 let next_line_row = row.next_row();
9842 let indent = snapshot.indent_size_for_line(next_line_row);
9843 let start_of_next_line = Point::new(next_line_row.0, indent.len);
9844
9845 let replace =
9846 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
9847 " "
9848 } else {
9849 ""
9850 };
9851
9852 this.buffer.update(cx, |buffer, cx| {
9853 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
9854 });
9855 }
9856 }
9857
9858 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
9859 s.select_anchor_ranges(cursor_positions)
9860 });
9861 });
9862 }
9863
9864 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
9865 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9866 self.join_lines_impl(true, window, cx);
9867 }
9868
9869 pub fn sort_lines_case_sensitive(
9870 &mut self,
9871 _: &SortLinesCaseSensitive,
9872 window: &mut Window,
9873 cx: &mut Context<Self>,
9874 ) {
9875 self.manipulate_lines(window, cx, |lines| lines.sort())
9876 }
9877
9878 pub fn sort_lines_case_insensitive(
9879 &mut self,
9880 _: &SortLinesCaseInsensitive,
9881 window: &mut Window,
9882 cx: &mut Context<Self>,
9883 ) {
9884 self.manipulate_lines(window, cx, |lines| {
9885 lines.sort_by_key(|line| line.to_lowercase())
9886 })
9887 }
9888
9889 pub fn unique_lines_case_insensitive(
9890 &mut self,
9891 _: &UniqueLinesCaseInsensitive,
9892 window: &mut Window,
9893 cx: &mut Context<Self>,
9894 ) {
9895 self.manipulate_lines(window, cx, |lines| {
9896 let mut seen = HashSet::default();
9897 lines.retain(|line| seen.insert(line.to_lowercase()));
9898 })
9899 }
9900
9901 pub fn unique_lines_case_sensitive(
9902 &mut self,
9903 _: &UniqueLinesCaseSensitive,
9904 window: &mut Window,
9905 cx: &mut Context<Self>,
9906 ) {
9907 self.manipulate_lines(window, cx, |lines| {
9908 let mut seen = HashSet::default();
9909 lines.retain(|line| seen.insert(*line));
9910 })
9911 }
9912
9913 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
9914 let Some(project) = self.project.clone() else {
9915 return;
9916 };
9917 self.reload(project, window, cx)
9918 .detach_and_notify_err(window, cx);
9919 }
9920
9921 pub fn restore_file(
9922 &mut self,
9923 _: &::git::RestoreFile,
9924 window: &mut Window,
9925 cx: &mut Context<Self>,
9926 ) {
9927 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9928 let mut buffer_ids = HashSet::default();
9929 let snapshot = self.buffer().read(cx).snapshot(cx);
9930 for selection in self.selections.all::<usize>(cx) {
9931 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
9932 }
9933
9934 let buffer = self.buffer().read(cx);
9935 let ranges = buffer_ids
9936 .into_iter()
9937 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
9938 .collect::<Vec<_>>();
9939
9940 self.restore_hunks_in_ranges(ranges, window, cx);
9941 }
9942
9943 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
9944 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
9945 let selections = self
9946 .selections
9947 .all(cx)
9948 .into_iter()
9949 .map(|s| s.range())
9950 .collect();
9951 self.restore_hunks_in_ranges(selections, window, cx);
9952 }
9953
9954 pub fn restore_hunks_in_ranges(
9955 &mut self,
9956 ranges: Vec<Range<Point>>,
9957 window: &mut Window,
9958 cx: &mut Context<Editor>,
9959 ) {
9960 let mut revert_changes = HashMap::default();
9961 let chunk_by = self
9962 .snapshot(window, cx)
9963 .hunks_for_ranges(ranges)
9964 .into_iter()
9965 .chunk_by(|hunk| hunk.buffer_id);
9966 for (buffer_id, hunks) in &chunk_by {
9967 let hunks = hunks.collect::<Vec<_>>();
9968 for hunk in &hunks {
9969 self.prepare_restore_change(&mut revert_changes, hunk, cx);
9970 }
9971 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
9972 }
9973 drop(chunk_by);
9974 if !revert_changes.is_empty() {
9975 self.transact(window, cx, |editor, window, cx| {
9976 editor.restore(revert_changes, window, cx);
9977 });
9978 }
9979 }
9980
9981 pub fn open_active_item_in_terminal(
9982 &mut self,
9983 _: &OpenInTerminal,
9984 window: &mut Window,
9985 cx: &mut Context<Self>,
9986 ) {
9987 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
9988 let project_path = buffer.read(cx).project_path(cx)?;
9989 let project = self.project.as_ref()?.read(cx);
9990 let entry = project.entry_for_path(&project_path, cx)?;
9991 let parent = match &entry.canonical_path {
9992 Some(canonical_path) => canonical_path.to_path_buf(),
9993 None => project.absolute_path(&project_path, cx)?,
9994 }
9995 .parent()?
9996 .to_path_buf();
9997 Some(parent)
9998 }) {
9999 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10000 }
10001 }
10002
10003 fn set_breakpoint_context_menu(
10004 &mut self,
10005 display_row: DisplayRow,
10006 position: Option<Anchor>,
10007 clicked_point: gpui::Point<Pixels>,
10008 window: &mut Window,
10009 cx: &mut Context<Self>,
10010 ) {
10011 if !cx.has_flag::<DebuggerFeatureFlag>() {
10012 return;
10013 }
10014 let source = self
10015 .buffer
10016 .read(cx)
10017 .snapshot(cx)
10018 .anchor_before(Point::new(display_row.0, 0u32));
10019
10020 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10021
10022 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10023 self,
10024 source,
10025 clicked_point,
10026 context_menu,
10027 window,
10028 cx,
10029 );
10030 }
10031
10032 fn add_edit_breakpoint_block(
10033 &mut self,
10034 anchor: Anchor,
10035 breakpoint: &Breakpoint,
10036 edit_action: BreakpointPromptEditAction,
10037 window: &mut Window,
10038 cx: &mut Context<Self>,
10039 ) {
10040 let weak_editor = cx.weak_entity();
10041 let bp_prompt = cx.new(|cx| {
10042 BreakpointPromptEditor::new(
10043 weak_editor,
10044 anchor,
10045 breakpoint.clone(),
10046 edit_action,
10047 window,
10048 cx,
10049 )
10050 });
10051
10052 let height = bp_prompt.update(cx, |this, cx| {
10053 this.prompt
10054 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10055 });
10056 let cloned_prompt = bp_prompt.clone();
10057 let blocks = vec![BlockProperties {
10058 style: BlockStyle::Sticky,
10059 placement: BlockPlacement::Above(anchor),
10060 height: Some(height),
10061 render: Arc::new(move |cx| {
10062 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10063 cloned_prompt.clone().into_any_element()
10064 }),
10065 priority: 0,
10066 render_in_minimap: true,
10067 }];
10068
10069 let focus_handle = bp_prompt.focus_handle(cx);
10070 window.focus(&focus_handle);
10071
10072 let block_ids = self.insert_blocks(blocks, None, cx);
10073 bp_prompt.update(cx, |prompt, _| {
10074 prompt.add_block_ids(block_ids);
10075 });
10076 }
10077
10078 pub(crate) fn breakpoint_at_row(
10079 &self,
10080 row: u32,
10081 window: &mut Window,
10082 cx: &mut Context<Self>,
10083 ) -> Option<(Anchor, Breakpoint)> {
10084 let snapshot = self.snapshot(window, cx);
10085 let breakpoint_position = snapshot.buffer_snapshot.anchor_before(Point::new(row, 0));
10086
10087 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10088 }
10089
10090 pub(crate) fn breakpoint_at_anchor(
10091 &self,
10092 breakpoint_position: Anchor,
10093 snapshot: &EditorSnapshot,
10094 cx: &mut Context<Self>,
10095 ) -> Option<(Anchor, Breakpoint)> {
10096 let project = self.project.clone()?;
10097
10098 let buffer_id = breakpoint_position.buffer_id.or_else(|| {
10099 snapshot
10100 .buffer_snapshot
10101 .buffer_id_for_excerpt(breakpoint_position.excerpt_id)
10102 })?;
10103
10104 let enclosing_excerpt = breakpoint_position.excerpt_id;
10105 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
10106 let buffer_snapshot = buffer.read(cx).snapshot();
10107
10108 let row = buffer_snapshot
10109 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10110 .row;
10111
10112 let line_len = snapshot.buffer_snapshot.line_len(MultiBufferRow(row));
10113 let anchor_end = snapshot
10114 .buffer_snapshot
10115 .anchor_after(Point::new(row, line_len));
10116
10117 let bp = self
10118 .breakpoint_store
10119 .as_ref()?
10120 .read_with(cx, |breakpoint_store, cx| {
10121 breakpoint_store
10122 .breakpoints(
10123 &buffer,
10124 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10125 &buffer_snapshot,
10126 cx,
10127 )
10128 .next()
10129 .and_then(|(bp, _)| {
10130 let breakpoint_row = buffer_snapshot
10131 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10132 .row;
10133
10134 if breakpoint_row == row {
10135 snapshot
10136 .buffer_snapshot
10137 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10138 .map(|position| (position, bp.bp.clone()))
10139 } else {
10140 None
10141 }
10142 })
10143 });
10144 bp
10145 }
10146
10147 pub fn edit_log_breakpoint(
10148 &mut self,
10149 _: &EditLogBreakpoint,
10150 window: &mut Window,
10151 cx: &mut Context<Self>,
10152 ) {
10153 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10154 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10155 message: None,
10156 state: BreakpointState::Enabled,
10157 condition: None,
10158 hit_condition: None,
10159 });
10160
10161 self.add_edit_breakpoint_block(
10162 anchor,
10163 &breakpoint,
10164 BreakpointPromptEditAction::Log,
10165 window,
10166 cx,
10167 );
10168 }
10169 }
10170
10171 fn breakpoints_at_cursors(
10172 &self,
10173 window: &mut Window,
10174 cx: &mut Context<Self>,
10175 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10176 let snapshot = self.snapshot(window, cx);
10177 let cursors = self
10178 .selections
10179 .disjoint_anchors()
10180 .into_iter()
10181 .map(|selection| {
10182 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot);
10183
10184 let breakpoint_position = self
10185 .breakpoint_at_row(cursor_position.row, window, cx)
10186 .map(|bp| bp.0)
10187 .unwrap_or_else(|| {
10188 snapshot
10189 .display_snapshot
10190 .buffer_snapshot
10191 .anchor_after(Point::new(cursor_position.row, 0))
10192 });
10193
10194 let breakpoint = self
10195 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10196 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10197
10198 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10199 })
10200 // 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.
10201 .collect::<HashMap<Anchor, _>>();
10202
10203 cursors.into_iter().collect()
10204 }
10205
10206 pub fn enable_breakpoint(
10207 &mut self,
10208 _: &crate::actions::EnableBreakpoint,
10209 window: &mut Window,
10210 cx: &mut Context<Self>,
10211 ) {
10212 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10213 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10214 continue;
10215 };
10216 self.edit_breakpoint_at_anchor(
10217 anchor,
10218 breakpoint,
10219 BreakpointEditAction::InvertState,
10220 cx,
10221 );
10222 }
10223 }
10224
10225 pub fn disable_breakpoint(
10226 &mut self,
10227 _: &crate::actions::DisableBreakpoint,
10228 window: &mut Window,
10229 cx: &mut Context<Self>,
10230 ) {
10231 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10232 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10233 continue;
10234 };
10235 self.edit_breakpoint_at_anchor(
10236 anchor,
10237 breakpoint,
10238 BreakpointEditAction::InvertState,
10239 cx,
10240 );
10241 }
10242 }
10243
10244 pub fn toggle_breakpoint(
10245 &mut self,
10246 _: &crate::actions::ToggleBreakpoint,
10247 window: &mut Window,
10248 cx: &mut Context<Self>,
10249 ) {
10250 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10251 if let Some(breakpoint) = breakpoint {
10252 self.edit_breakpoint_at_anchor(
10253 anchor,
10254 breakpoint,
10255 BreakpointEditAction::Toggle,
10256 cx,
10257 );
10258 } else {
10259 self.edit_breakpoint_at_anchor(
10260 anchor,
10261 Breakpoint::new_standard(),
10262 BreakpointEditAction::Toggle,
10263 cx,
10264 );
10265 }
10266 }
10267 }
10268
10269 pub fn edit_breakpoint_at_anchor(
10270 &mut self,
10271 breakpoint_position: Anchor,
10272 breakpoint: Breakpoint,
10273 edit_action: BreakpointEditAction,
10274 cx: &mut Context<Self>,
10275 ) {
10276 let Some(breakpoint_store) = &self.breakpoint_store else {
10277 return;
10278 };
10279
10280 let Some(buffer_id) = breakpoint_position.buffer_id.or_else(|| {
10281 if breakpoint_position == Anchor::min() {
10282 self.buffer()
10283 .read(cx)
10284 .excerpt_buffer_ids()
10285 .into_iter()
10286 .next()
10287 } else {
10288 None
10289 }
10290 }) else {
10291 return;
10292 };
10293
10294 let Some(buffer) = self.buffer().read(cx).buffer(buffer_id) else {
10295 return;
10296 };
10297
10298 breakpoint_store.update(cx, |breakpoint_store, cx| {
10299 breakpoint_store.toggle_breakpoint(
10300 buffer,
10301 BreakpointWithPosition {
10302 position: breakpoint_position.text_anchor,
10303 bp: breakpoint,
10304 },
10305 edit_action,
10306 cx,
10307 );
10308 });
10309
10310 cx.notify();
10311 }
10312
10313 #[cfg(any(test, feature = "test-support"))]
10314 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10315 self.breakpoint_store.clone()
10316 }
10317
10318 pub fn prepare_restore_change(
10319 &self,
10320 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10321 hunk: &MultiBufferDiffHunk,
10322 cx: &mut App,
10323 ) -> Option<()> {
10324 if hunk.is_created_file() {
10325 return None;
10326 }
10327 let buffer = self.buffer.read(cx);
10328 let diff = buffer.diff_for(hunk.buffer_id)?;
10329 let buffer = buffer.buffer(hunk.buffer_id)?;
10330 let buffer = buffer.read(cx);
10331 let original_text = diff
10332 .read(cx)
10333 .base_text()
10334 .as_rope()
10335 .slice(hunk.diff_base_byte_range.clone());
10336 let buffer_snapshot = buffer.snapshot();
10337 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10338 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10339 probe
10340 .0
10341 .start
10342 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
10343 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
10344 }) {
10345 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
10346 Some(())
10347 } else {
10348 None
10349 }
10350 }
10351
10352 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
10353 self.manipulate_lines(window, cx, |lines| lines.reverse())
10354 }
10355
10356 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
10357 self.manipulate_lines(window, cx, |lines| lines.shuffle(&mut thread_rng()))
10358 }
10359
10360 fn manipulate_lines<Fn>(
10361 &mut self,
10362 window: &mut Window,
10363 cx: &mut Context<Self>,
10364 mut callback: Fn,
10365 ) where
10366 Fn: FnMut(&mut Vec<&str>),
10367 {
10368 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10369
10370 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10371 let buffer = self.buffer.read(cx).snapshot(cx);
10372
10373 let mut edits = Vec::new();
10374
10375 let selections = self.selections.all::<Point>(cx);
10376 let mut selections = selections.iter().peekable();
10377 let mut contiguous_row_selections = Vec::new();
10378 let mut new_selections = Vec::new();
10379 let mut added_lines = 0;
10380 let mut removed_lines = 0;
10381
10382 while let Some(selection) = selections.next() {
10383 let (start_row, end_row) = consume_contiguous_rows(
10384 &mut contiguous_row_selections,
10385 selection,
10386 &display_map,
10387 &mut selections,
10388 );
10389
10390 let start_point = Point::new(start_row.0, 0);
10391 let end_point = Point::new(
10392 end_row.previous_row().0,
10393 buffer.line_len(end_row.previous_row()),
10394 );
10395 let text = buffer
10396 .text_for_range(start_point..end_point)
10397 .collect::<String>();
10398
10399 let mut lines = text.split('\n').collect_vec();
10400
10401 let lines_before = lines.len();
10402 callback(&mut lines);
10403 let lines_after = lines.len();
10404
10405 edits.push((start_point..end_point, lines.join("\n")));
10406
10407 // Selections must change based on added and removed line count
10408 let start_row =
10409 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
10410 let end_row = MultiBufferRow(start_row.0 + lines_after.saturating_sub(1) as u32);
10411 new_selections.push(Selection {
10412 id: selection.id,
10413 start: start_row,
10414 end: end_row,
10415 goal: SelectionGoal::None,
10416 reversed: selection.reversed,
10417 });
10418
10419 if lines_after > lines_before {
10420 added_lines += lines_after - lines_before;
10421 } else if lines_before > lines_after {
10422 removed_lines += lines_before - lines_after;
10423 }
10424 }
10425
10426 self.transact(window, cx, |this, window, cx| {
10427 let buffer = this.buffer.update(cx, |buffer, cx| {
10428 buffer.edit(edits, None, cx);
10429 buffer.snapshot(cx)
10430 });
10431
10432 // Recalculate offsets on newly edited buffer
10433 let new_selections = new_selections
10434 .iter()
10435 .map(|s| {
10436 let start_point = Point::new(s.start.0, 0);
10437 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
10438 Selection {
10439 id: s.id,
10440 start: buffer.point_to_offset(start_point),
10441 end: buffer.point_to_offset(end_point),
10442 goal: s.goal,
10443 reversed: s.reversed,
10444 }
10445 })
10446 .collect();
10447
10448 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10449 s.select(new_selections);
10450 });
10451
10452 this.request_autoscroll(Autoscroll::fit(), cx);
10453 });
10454 }
10455
10456 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
10457 self.manipulate_text(window, cx, |text| {
10458 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
10459 if has_upper_case_characters {
10460 text.to_lowercase()
10461 } else {
10462 text.to_uppercase()
10463 }
10464 })
10465 }
10466
10467 pub fn convert_to_upper_case(
10468 &mut self,
10469 _: &ConvertToUpperCase,
10470 window: &mut Window,
10471 cx: &mut Context<Self>,
10472 ) {
10473 self.manipulate_text(window, cx, |text| text.to_uppercase())
10474 }
10475
10476 pub fn convert_to_lower_case(
10477 &mut self,
10478 _: &ConvertToLowerCase,
10479 window: &mut Window,
10480 cx: &mut Context<Self>,
10481 ) {
10482 self.manipulate_text(window, cx, |text| text.to_lowercase())
10483 }
10484
10485 pub fn convert_to_title_case(
10486 &mut self,
10487 _: &ConvertToTitleCase,
10488 window: &mut Window,
10489 cx: &mut Context<Self>,
10490 ) {
10491 self.manipulate_text(window, cx, |text| {
10492 text.split('\n')
10493 .map(|line| line.to_case(Case::Title))
10494 .join("\n")
10495 })
10496 }
10497
10498 pub fn convert_to_snake_case(
10499 &mut self,
10500 _: &ConvertToSnakeCase,
10501 window: &mut Window,
10502 cx: &mut Context<Self>,
10503 ) {
10504 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
10505 }
10506
10507 pub fn convert_to_kebab_case(
10508 &mut self,
10509 _: &ConvertToKebabCase,
10510 window: &mut Window,
10511 cx: &mut Context<Self>,
10512 ) {
10513 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
10514 }
10515
10516 pub fn convert_to_upper_camel_case(
10517 &mut self,
10518 _: &ConvertToUpperCamelCase,
10519 window: &mut Window,
10520 cx: &mut Context<Self>,
10521 ) {
10522 self.manipulate_text(window, cx, |text| {
10523 text.split('\n')
10524 .map(|line| line.to_case(Case::UpperCamel))
10525 .join("\n")
10526 })
10527 }
10528
10529 pub fn convert_to_lower_camel_case(
10530 &mut self,
10531 _: &ConvertToLowerCamelCase,
10532 window: &mut Window,
10533 cx: &mut Context<Self>,
10534 ) {
10535 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
10536 }
10537
10538 pub fn convert_to_opposite_case(
10539 &mut self,
10540 _: &ConvertToOppositeCase,
10541 window: &mut Window,
10542 cx: &mut Context<Self>,
10543 ) {
10544 self.manipulate_text(window, cx, |text| {
10545 text.chars()
10546 .fold(String::with_capacity(text.len()), |mut t, c| {
10547 if c.is_uppercase() {
10548 t.extend(c.to_lowercase());
10549 } else {
10550 t.extend(c.to_uppercase());
10551 }
10552 t
10553 })
10554 })
10555 }
10556
10557 pub fn convert_to_rot13(
10558 &mut self,
10559 _: &ConvertToRot13,
10560 window: &mut Window,
10561 cx: &mut Context<Self>,
10562 ) {
10563 self.manipulate_text(window, cx, |text| {
10564 text.chars()
10565 .map(|c| match c {
10566 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
10567 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
10568 _ => c,
10569 })
10570 .collect()
10571 })
10572 }
10573
10574 pub fn convert_to_rot47(
10575 &mut self,
10576 _: &ConvertToRot47,
10577 window: &mut Window,
10578 cx: &mut Context<Self>,
10579 ) {
10580 self.manipulate_text(window, cx, |text| {
10581 text.chars()
10582 .map(|c| {
10583 let code_point = c as u32;
10584 if code_point >= 33 && code_point <= 126 {
10585 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
10586 }
10587 c
10588 })
10589 .collect()
10590 })
10591 }
10592
10593 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
10594 where
10595 Fn: FnMut(&str) -> String,
10596 {
10597 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10598 let buffer = self.buffer.read(cx).snapshot(cx);
10599
10600 let mut new_selections = Vec::new();
10601 let mut edits = Vec::new();
10602 let mut selection_adjustment = 0i32;
10603
10604 for selection in self.selections.all::<usize>(cx) {
10605 let selection_is_empty = selection.is_empty();
10606
10607 let (start, end) = if selection_is_empty {
10608 let word_range = movement::surrounding_word(
10609 &display_map,
10610 selection.start.to_display_point(&display_map),
10611 );
10612 let start = word_range.start.to_offset(&display_map, Bias::Left);
10613 let end = word_range.end.to_offset(&display_map, Bias::Left);
10614 (start, end)
10615 } else {
10616 (selection.start, selection.end)
10617 };
10618
10619 let text = buffer.text_for_range(start..end).collect::<String>();
10620 let old_length = text.len() as i32;
10621 let text = callback(&text);
10622
10623 new_selections.push(Selection {
10624 start: (start as i32 - selection_adjustment) as usize,
10625 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
10626 goal: SelectionGoal::None,
10627 ..selection
10628 });
10629
10630 selection_adjustment += old_length - text.len() as i32;
10631
10632 edits.push((start..end, text));
10633 }
10634
10635 self.transact(window, cx, |this, window, cx| {
10636 this.buffer.update(cx, |buffer, cx| {
10637 buffer.edit(edits, None, cx);
10638 });
10639
10640 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10641 s.select(new_selections);
10642 });
10643
10644 this.request_autoscroll(Autoscroll::fit(), cx);
10645 });
10646 }
10647
10648 pub fn move_selection_on_drop(
10649 &mut self,
10650 selection: &Selection<Anchor>,
10651 target: DisplayPoint,
10652 is_cut: bool,
10653 window: &mut Window,
10654 cx: &mut Context<Self>,
10655 ) {
10656 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10657 let buffer = &display_map.buffer_snapshot;
10658 let mut edits = Vec::new();
10659 let insert_point = display_map
10660 .clip_point(target, Bias::Left)
10661 .to_point(&display_map);
10662 let text = buffer
10663 .text_for_range(selection.start..selection.end)
10664 .collect::<String>();
10665 if is_cut {
10666 edits.push(((selection.start..selection.end), String::new()));
10667 }
10668 let insert_anchor = buffer.anchor_before(insert_point);
10669 edits.push(((insert_anchor..insert_anchor), text));
10670 let last_edit_start = insert_anchor.bias_left(buffer);
10671 let last_edit_end = insert_anchor.bias_right(buffer);
10672 self.transact(window, cx, |this, window, cx| {
10673 this.buffer.update(cx, |buffer, cx| {
10674 buffer.edit(edits, None, cx);
10675 });
10676 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10677 s.select_anchor_ranges([last_edit_start..last_edit_end]);
10678 });
10679 });
10680 }
10681
10682 pub fn clear_selection_drag_state(&mut self) {
10683 self.selection_drag_state = SelectionDragState::None;
10684 }
10685
10686 pub fn duplicate(
10687 &mut self,
10688 upwards: bool,
10689 whole_lines: bool,
10690 window: &mut Window,
10691 cx: &mut Context<Self>,
10692 ) {
10693 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10694
10695 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10696 let buffer = &display_map.buffer_snapshot;
10697 let selections = self.selections.all::<Point>(cx);
10698
10699 let mut edits = Vec::new();
10700 let mut selections_iter = selections.iter().peekable();
10701 while let Some(selection) = selections_iter.next() {
10702 let mut rows = selection.spanned_rows(false, &display_map);
10703 // duplicate line-wise
10704 if whole_lines || selection.start == selection.end {
10705 // Avoid duplicating the same lines twice.
10706 while let Some(next_selection) = selections_iter.peek() {
10707 let next_rows = next_selection.spanned_rows(false, &display_map);
10708 if next_rows.start < rows.end {
10709 rows.end = next_rows.end;
10710 selections_iter.next().unwrap();
10711 } else {
10712 break;
10713 }
10714 }
10715
10716 // Copy the text from the selected row region and splice it either at the start
10717 // or end of the region.
10718 let start = Point::new(rows.start.0, 0);
10719 let end = Point::new(
10720 rows.end.previous_row().0,
10721 buffer.line_len(rows.end.previous_row()),
10722 );
10723 let text = buffer
10724 .text_for_range(start..end)
10725 .chain(Some("\n"))
10726 .collect::<String>();
10727 let insert_location = if upwards {
10728 Point::new(rows.end.0, 0)
10729 } else {
10730 start
10731 };
10732 edits.push((insert_location..insert_location, text));
10733 } else {
10734 // duplicate character-wise
10735 let start = selection.start;
10736 let end = selection.end;
10737 let text = buffer.text_for_range(start..end).collect::<String>();
10738 edits.push((selection.end..selection.end, text));
10739 }
10740 }
10741
10742 self.transact(window, cx, |this, _, cx| {
10743 this.buffer.update(cx, |buffer, cx| {
10744 buffer.edit(edits, None, cx);
10745 });
10746
10747 this.request_autoscroll(Autoscroll::fit(), cx);
10748 });
10749 }
10750
10751 pub fn duplicate_line_up(
10752 &mut self,
10753 _: &DuplicateLineUp,
10754 window: &mut Window,
10755 cx: &mut Context<Self>,
10756 ) {
10757 self.duplicate(true, true, window, cx);
10758 }
10759
10760 pub fn duplicate_line_down(
10761 &mut self,
10762 _: &DuplicateLineDown,
10763 window: &mut Window,
10764 cx: &mut Context<Self>,
10765 ) {
10766 self.duplicate(false, true, window, cx);
10767 }
10768
10769 pub fn duplicate_selection(
10770 &mut self,
10771 _: &DuplicateSelection,
10772 window: &mut Window,
10773 cx: &mut Context<Self>,
10774 ) {
10775 self.duplicate(false, false, window, cx);
10776 }
10777
10778 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
10779 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10780
10781 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10782 let buffer = self.buffer.read(cx).snapshot(cx);
10783
10784 let mut edits = Vec::new();
10785 let mut unfold_ranges = Vec::new();
10786 let mut refold_creases = Vec::new();
10787
10788 let selections = self.selections.all::<Point>(cx);
10789 let mut selections = selections.iter().peekable();
10790 let mut contiguous_row_selections = Vec::new();
10791 let mut new_selections = Vec::new();
10792
10793 while let Some(selection) = selections.next() {
10794 // Find all the selections that span a contiguous row range
10795 let (start_row, end_row) = consume_contiguous_rows(
10796 &mut contiguous_row_selections,
10797 selection,
10798 &display_map,
10799 &mut selections,
10800 );
10801
10802 // Move the text spanned by the row range to be before the line preceding the row range
10803 if start_row.0 > 0 {
10804 let range_to_move = Point::new(
10805 start_row.previous_row().0,
10806 buffer.line_len(start_row.previous_row()),
10807 )
10808 ..Point::new(
10809 end_row.previous_row().0,
10810 buffer.line_len(end_row.previous_row()),
10811 );
10812 let insertion_point = display_map
10813 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
10814 .0;
10815
10816 // Don't move lines across excerpts
10817 if buffer
10818 .excerpt_containing(insertion_point..range_to_move.end)
10819 .is_some()
10820 {
10821 let text = buffer
10822 .text_for_range(range_to_move.clone())
10823 .flat_map(|s| s.chars())
10824 .skip(1)
10825 .chain(['\n'])
10826 .collect::<String>();
10827
10828 edits.push((
10829 buffer.anchor_after(range_to_move.start)
10830 ..buffer.anchor_before(range_to_move.end),
10831 String::new(),
10832 ));
10833 let insertion_anchor = buffer.anchor_after(insertion_point);
10834 edits.push((insertion_anchor..insertion_anchor, text));
10835
10836 let row_delta = range_to_move.start.row - insertion_point.row + 1;
10837
10838 // Move selections up
10839 new_selections.extend(contiguous_row_selections.drain(..).map(
10840 |mut selection| {
10841 selection.start.row -= row_delta;
10842 selection.end.row -= row_delta;
10843 selection
10844 },
10845 ));
10846
10847 // Move folds up
10848 unfold_ranges.push(range_to_move.clone());
10849 for fold in display_map.folds_in_range(
10850 buffer.anchor_before(range_to_move.start)
10851 ..buffer.anchor_after(range_to_move.end),
10852 ) {
10853 let mut start = fold.range.start.to_point(&buffer);
10854 let mut end = fold.range.end.to_point(&buffer);
10855 start.row -= row_delta;
10856 end.row -= row_delta;
10857 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10858 }
10859 }
10860 }
10861
10862 // If we didn't move line(s), preserve the existing selections
10863 new_selections.append(&mut contiguous_row_selections);
10864 }
10865
10866 self.transact(window, cx, |this, window, cx| {
10867 this.unfold_ranges(&unfold_ranges, true, true, cx);
10868 this.buffer.update(cx, |buffer, cx| {
10869 for (range, text) in edits {
10870 buffer.edit([(range, text)], None, cx);
10871 }
10872 });
10873 this.fold_creases(refold_creases, true, window, cx);
10874 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10875 s.select(new_selections);
10876 })
10877 });
10878 }
10879
10880 pub fn move_line_down(
10881 &mut self,
10882 _: &MoveLineDown,
10883 window: &mut Window,
10884 cx: &mut Context<Self>,
10885 ) {
10886 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10887
10888 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10889 let buffer = self.buffer.read(cx).snapshot(cx);
10890
10891 let mut edits = Vec::new();
10892 let mut unfold_ranges = Vec::new();
10893 let mut refold_creases = Vec::new();
10894
10895 let selections = self.selections.all::<Point>(cx);
10896 let mut selections = selections.iter().peekable();
10897 let mut contiguous_row_selections = Vec::new();
10898 let mut new_selections = Vec::new();
10899
10900 while let Some(selection) = selections.next() {
10901 // Find all the selections that span a contiguous row range
10902 let (start_row, end_row) = consume_contiguous_rows(
10903 &mut contiguous_row_selections,
10904 selection,
10905 &display_map,
10906 &mut selections,
10907 );
10908
10909 // Move the text spanned by the row range to be after the last line of the row range
10910 if end_row.0 <= buffer.max_point().row {
10911 let range_to_move =
10912 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
10913 let insertion_point = display_map
10914 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
10915 .0;
10916
10917 // Don't move lines across excerpt boundaries
10918 if buffer
10919 .excerpt_containing(range_to_move.start..insertion_point)
10920 .is_some()
10921 {
10922 let mut text = String::from("\n");
10923 text.extend(buffer.text_for_range(range_to_move.clone()));
10924 text.pop(); // Drop trailing newline
10925 edits.push((
10926 buffer.anchor_after(range_to_move.start)
10927 ..buffer.anchor_before(range_to_move.end),
10928 String::new(),
10929 ));
10930 let insertion_anchor = buffer.anchor_after(insertion_point);
10931 edits.push((insertion_anchor..insertion_anchor, text));
10932
10933 let row_delta = insertion_point.row - range_to_move.end.row + 1;
10934
10935 // Move selections down
10936 new_selections.extend(contiguous_row_selections.drain(..).map(
10937 |mut selection| {
10938 selection.start.row += row_delta;
10939 selection.end.row += row_delta;
10940 selection
10941 },
10942 ));
10943
10944 // Move folds down
10945 unfold_ranges.push(range_to_move.clone());
10946 for fold in display_map.folds_in_range(
10947 buffer.anchor_before(range_to_move.start)
10948 ..buffer.anchor_after(range_to_move.end),
10949 ) {
10950 let mut start = fold.range.start.to_point(&buffer);
10951 let mut end = fold.range.end.to_point(&buffer);
10952 start.row += row_delta;
10953 end.row += row_delta;
10954 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
10955 }
10956 }
10957 }
10958
10959 // If we didn't move line(s), preserve the existing selections
10960 new_selections.append(&mut contiguous_row_selections);
10961 }
10962
10963 self.transact(window, cx, |this, window, cx| {
10964 this.unfold_ranges(&unfold_ranges, true, true, cx);
10965 this.buffer.update(cx, |buffer, cx| {
10966 for (range, text) in edits {
10967 buffer.edit([(range, text)], None, cx);
10968 }
10969 });
10970 this.fold_creases(refold_creases, true, window, cx);
10971 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10972 s.select(new_selections)
10973 });
10974 });
10975 }
10976
10977 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
10978 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
10979 let text_layout_details = &self.text_layout_details(window);
10980 self.transact(window, cx, |this, window, cx| {
10981 let edits = this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
10982 let mut edits: Vec<(Range<usize>, String)> = Default::default();
10983 s.move_with(|display_map, selection| {
10984 if !selection.is_empty() {
10985 return;
10986 }
10987
10988 let mut head = selection.head();
10989 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
10990 if head.column() == display_map.line_len(head.row()) {
10991 transpose_offset = display_map
10992 .buffer_snapshot
10993 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
10994 }
10995
10996 if transpose_offset == 0 {
10997 return;
10998 }
10999
11000 *head.column_mut() += 1;
11001 head = display_map.clip_point(head, Bias::Right);
11002 let goal = SelectionGoal::HorizontalPosition(
11003 display_map
11004 .x_for_display_point(head, text_layout_details)
11005 .into(),
11006 );
11007 selection.collapse_to(head, goal);
11008
11009 let transpose_start = display_map
11010 .buffer_snapshot
11011 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11012 if edits.last().map_or(true, |e| e.0.end <= transpose_start) {
11013 let transpose_end = display_map
11014 .buffer_snapshot
11015 .clip_offset(transpose_offset + 1, Bias::Right);
11016 if let Some(ch) =
11017 display_map.buffer_snapshot.chars_at(transpose_start).next()
11018 {
11019 edits.push((transpose_start..transpose_offset, String::new()));
11020 edits.push((transpose_end..transpose_end, ch.to_string()));
11021 }
11022 }
11023 });
11024 edits
11025 });
11026 this.buffer
11027 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11028 let selections = this.selections.all::<usize>(cx);
11029 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11030 s.select(selections);
11031 });
11032 });
11033 }
11034
11035 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11036 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11037 self.rewrap_impl(RewrapOptions::default(), cx)
11038 }
11039
11040 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11041 let buffer = self.buffer.read(cx).snapshot(cx);
11042 let selections = self.selections.all::<Point>(cx);
11043
11044 // Shrink and split selections to respect paragraph boundaries.
11045 let ranges = selections.into_iter().flat_map(|selection| {
11046 let language_settings = buffer.language_settings_at(selection.head(), cx);
11047 let language_scope = buffer.language_scope_at(selection.head());
11048
11049 let Some(start_row) = (selection.start.row..=selection.end.row)
11050 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11051 else {
11052 return vec![];
11053 };
11054 let Some(end_row) = (selection.start.row..=selection.end.row)
11055 .rev()
11056 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11057 else {
11058 return vec![];
11059 };
11060
11061 let mut row = start_row;
11062 let mut ranges = Vec::new();
11063 while let Some(blank_row) =
11064 (row..end_row).find(|row| buffer.is_line_blank(MultiBufferRow(*row)))
11065 {
11066 let next_paragraph_start = (blank_row + 1..=end_row)
11067 .find(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11068 .unwrap();
11069 ranges.push((
11070 language_settings.clone(),
11071 language_scope.clone(),
11072 Point::new(row, 0)..Point::new(blank_row - 1, 0),
11073 ));
11074 row = next_paragraph_start;
11075 }
11076 ranges.push((
11077 language_settings.clone(),
11078 language_scope.clone(),
11079 Point::new(row, 0)..Point::new(end_row, 0),
11080 ));
11081
11082 ranges
11083 });
11084
11085 let mut edits = Vec::new();
11086 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
11087
11088 for (language_settings, language_scope, range) in ranges {
11089 let mut start_row = range.start.row;
11090 let mut end_row = range.end.row;
11091
11092 // Skip selections that overlap with a range that has already been rewrapped.
11093 let selection_range = start_row..end_row;
11094 if rewrapped_row_ranges
11095 .iter()
11096 .any(|range| range.overlaps(&selection_range))
11097 {
11098 continue;
11099 }
11100
11101 let tab_size = language_settings.tab_size;
11102
11103 // Since not all lines in the selection may be at the same indent
11104 // level, choose the indent size that is the most common between all
11105 // of the lines.
11106 //
11107 // If there is a tie, we use the deepest indent.
11108 let (indent_size, indent_end) = {
11109 let mut indent_size_occurrences = HashMap::default();
11110 let mut rows_by_indent_size = HashMap::<IndentSize, Vec<u32>>::default();
11111
11112 for row in start_row..=end_row {
11113 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11114 rows_by_indent_size.entry(indent).or_default().push(row);
11115 *indent_size_occurrences.entry(indent).or_insert(0) += 1;
11116 }
11117
11118 let indent_size = indent_size_occurrences
11119 .into_iter()
11120 .max_by_key(|(indent, count)| (*count, indent.len_with_expanded_tabs(tab_size)))
11121 .map(|(indent, _)| indent)
11122 .unwrap_or_default();
11123 let row = rows_by_indent_size[&indent_size][0];
11124 let indent_end = Point::new(row, indent_size.len);
11125
11126 (indent_size, indent_end)
11127 };
11128
11129 let mut line_prefix = indent_size.chars().collect::<String>();
11130
11131 let mut inside_comment = false;
11132 if let Some(comment_prefix) = language_scope.and_then(|language| {
11133 language
11134 .line_comment_prefixes()
11135 .iter()
11136 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
11137 .cloned()
11138 }) {
11139 line_prefix.push_str(&comment_prefix);
11140 inside_comment = true;
11141 }
11142
11143 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
11144 RewrapBehavior::InComments => inside_comment,
11145 RewrapBehavior::InSelections => !range.is_empty(),
11146 RewrapBehavior::Anywhere => true,
11147 };
11148
11149 let should_rewrap = options.override_language_settings
11150 || allow_rewrap_based_on_language
11151 || self.hard_wrap.is_some();
11152 if !should_rewrap {
11153 continue;
11154 }
11155
11156 if range.is_empty() {
11157 'expand_upwards: while start_row > 0 {
11158 let prev_row = start_row - 1;
11159 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
11160 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
11161 && !buffer.is_line_blank(MultiBufferRow(prev_row))
11162 {
11163 start_row = prev_row;
11164 } else {
11165 break 'expand_upwards;
11166 }
11167 }
11168
11169 'expand_downwards: while end_row < buffer.max_point().row {
11170 let next_row = end_row + 1;
11171 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
11172 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
11173 && !buffer.is_line_blank(MultiBufferRow(next_row))
11174 {
11175 end_row = next_row;
11176 } else {
11177 break 'expand_downwards;
11178 }
11179 }
11180 }
11181
11182 let start = Point::new(start_row, 0);
11183 let start_offset = start.to_offset(&buffer);
11184 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
11185 let selection_text = buffer.text_for_range(start..end).collect::<String>();
11186 let Some(lines_without_prefixes) = selection_text
11187 .lines()
11188 .map(|line| {
11189 line.strip_prefix(&line_prefix)
11190 .or_else(|| line.trim_start().strip_prefix(&line_prefix.trim_start()))
11191 .with_context(|| {
11192 format!("line did not start with prefix {line_prefix:?}: {line:?}")
11193 })
11194 })
11195 .collect::<Result<Vec<_>, _>>()
11196 .log_err()
11197 else {
11198 continue;
11199 };
11200
11201 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
11202 buffer
11203 .language_settings_at(Point::new(start_row, 0), cx)
11204 .preferred_line_length as usize
11205 });
11206 let wrapped_text = wrap_with_prefix(
11207 line_prefix,
11208 lines_without_prefixes.join("\n"),
11209 wrap_column,
11210 tab_size,
11211 options.preserve_existing_whitespace,
11212 );
11213
11214 // TODO: should always use char-based diff while still supporting cursor behavior that
11215 // matches vim.
11216 let mut diff_options = DiffOptions::default();
11217 if options.override_language_settings {
11218 diff_options.max_word_diff_len = 0;
11219 diff_options.max_word_diff_line_count = 0;
11220 } else {
11221 diff_options.max_word_diff_len = usize::MAX;
11222 diff_options.max_word_diff_line_count = usize::MAX;
11223 }
11224
11225 for (old_range, new_text) in
11226 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
11227 {
11228 let edit_start = buffer.anchor_after(start_offset + old_range.start);
11229 let edit_end = buffer.anchor_after(start_offset + old_range.end);
11230 edits.push((edit_start..edit_end, new_text));
11231 }
11232
11233 rewrapped_row_ranges.push(start_row..=end_row);
11234 }
11235
11236 self.buffer
11237 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11238 }
11239
11240 pub fn cut_common(&mut self, window: &mut Window, cx: &mut Context<Self>) -> ClipboardItem {
11241 let mut text = String::new();
11242 let buffer = self.buffer.read(cx).snapshot(cx);
11243 let mut selections = self.selections.all::<Point>(cx);
11244 let mut clipboard_selections = Vec::with_capacity(selections.len());
11245 {
11246 let max_point = buffer.max_point();
11247 let mut is_first = true;
11248 for selection in &mut selections {
11249 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11250 if is_entire_line {
11251 selection.start = Point::new(selection.start.row, 0);
11252 if !selection.is_empty() && selection.end.column == 0 {
11253 selection.end = cmp::min(max_point, selection.end);
11254 } else {
11255 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
11256 }
11257 selection.goal = SelectionGoal::None;
11258 }
11259 if is_first {
11260 is_first = false;
11261 } else {
11262 text += "\n";
11263 }
11264 let mut len = 0;
11265 for chunk in buffer.text_for_range(selection.start..selection.end) {
11266 text.push_str(chunk);
11267 len += chunk.len();
11268 }
11269 clipboard_selections.push(ClipboardSelection {
11270 len,
11271 is_entire_line,
11272 first_line_indent: buffer
11273 .indent_size_for_line(MultiBufferRow(selection.start.row))
11274 .len,
11275 });
11276 }
11277 }
11278
11279 self.transact(window, cx, |this, window, cx| {
11280 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11281 s.select(selections);
11282 });
11283 this.insert("", window, cx);
11284 });
11285 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
11286 }
11287
11288 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
11289 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11290 let item = self.cut_common(window, cx);
11291 cx.write_to_clipboard(item);
11292 }
11293
11294 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
11295 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11296 self.change_selections(None, window, cx, |s| {
11297 s.move_with(|snapshot, sel| {
11298 if sel.is_empty() {
11299 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()))
11300 }
11301 });
11302 });
11303 let item = self.cut_common(window, cx);
11304 cx.set_global(KillRing(item))
11305 }
11306
11307 pub fn kill_ring_yank(
11308 &mut self,
11309 _: &KillRingYank,
11310 window: &mut Window,
11311 cx: &mut Context<Self>,
11312 ) {
11313 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11314 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
11315 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
11316 (kill_ring.text().to_string(), kill_ring.metadata_json())
11317 } else {
11318 return;
11319 }
11320 } else {
11321 return;
11322 };
11323 self.do_paste(&text, metadata, false, window, cx);
11324 }
11325
11326 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
11327 self.do_copy(true, cx);
11328 }
11329
11330 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
11331 self.do_copy(false, cx);
11332 }
11333
11334 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
11335 let selections = self.selections.all::<Point>(cx);
11336 let buffer = self.buffer.read(cx).read(cx);
11337 let mut text = String::new();
11338
11339 let mut clipboard_selections = Vec::with_capacity(selections.len());
11340 {
11341 let max_point = buffer.max_point();
11342 let mut is_first = true;
11343 for selection in &selections {
11344 let mut start = selection.start;
11345 let mut end = selection.end;
11346 let is_entire_line = selection.is_empty() || self.selections.line_mode;
11347 if is_entire_line {
11348 start = Point::new(start.row, 0);
11349 end = cmp::min(max_point, Point::new(end.row + 1, 0));
11350 }
11351
11352 let mut trimmed_selections = Vec::new();
11353 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
11354 let row = MultiBufferRow(start.row);
11355 let first_indent = buffer.indent_size_for_line(row);
11356 if first_indent.len == 0 || start.column > first_indent.len {
11357 trimmed_selections.push(start..end);
11358 } else {
11359 trimmed_selections.push(
11360 Point::new(row.0, first_indent.len)
11361 ..Point::new(row.0, buffer.line_len(row)),
11362 );
11363 for row in start.row + 1..=end.row {
11364 let mut line_len = buffer.line_len(MultiBufferRow(row));
11365 if row == end.row {
11366 line_len = end.column;
11367 }
11368 if line_len == 0 {
11369 trimmed_selections
11370 .push(Point::new(row, 0)..Point::new(row, line_len));
11371 continue;
11372 }
11373 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
11374 if row_indent_size.len >= first_indent.len {
11375 trimmed_selections.push(
11376 Point::new(row, first_indent.len)..Point::new(row, line_len),
11377 );
11378 } else {
11379 trimmed_selections.clear();
11380 trimmed_selections.push(start..end);
11381 break;
11382 }
11383 }
11384 }
11385 } else {
11386 trimmed_selections.push(start..end);
11387 }
11388
11389 for trimmed_range in trimmed_selections {
11390 if is_first {
11391 is_first = false;
11392 } else {
11393 text += "\n";
11394 }
11395 let mut len = 0;
11396 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
11397 text.push_str(chunk);
11398 len += chunk.len();
11399 }
11400 clipboard_selections.push(ClipboardSelection {
11401 len,
11402 is_entire_line,
11403 first_line_indent: buffer
11404 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
11405 .len,
11406 });
11407 }
11408 }
11409 }
11410
11411 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
11412 text,
11413 clipboard_selections,
11414 ));
11415 }
11416
11417 pub fn do_paste(
11418 &mut self,
11419 text: &String,
11420 clipboard_selections: Option<Vec<ClipboardSelection>>,
11421 handle_entire_lines: bool,
11422 window: &mut Window,
11423 cx: &mut Context<Self>,
11424 ) {
11425 if self.read_only(cx) {
11426 return;
11427 }
11428
11429 let clipboard_text = Cow::Borrowed(text);
11430
11431 self.transact(window, cx, |this, window, cx| {
11432 if let Some(mut clipboard_selections) = clipboard_selections {
11433 let old_selections = this.selections.all::<usize>(cx);
11434 let all_selections_were_entire_line =
11435 clipboard_selections.iter().all(|s| s.is_entire_line);
11436 let first_selection_indent_column =
11437 clipboard_selections.first().map(|s| s.first_line_indent);
11438 if clipboard_selections.len() != old_selections.len() {
11439 clipboard_selections.drain(..);
11440 }
11441 let cursor_offset = this.selections.last::<usize>(cx).head();
11442 let mut auto_indent_on_paste = true;
11443
11444 this.buffer.update(cx, |buffer, cx| {
11445 let snapshot = buffer.read(cx);
11446 auto_indent_on_paste = snapshot
11447 .language_settings_at(cursor_offset, cx)
11448 .auto_indent_on_paste;
11449
11450 let mut start_offset = 0;
11451 let mut edits = Vec::new();
11452 let mut original_indent_columns = Vec::new();
11453 for (ix, selection) in old_selections.iter().enumerate() {
11454 let to_insert;
11455 let entire_line;
11456 let original_indent_column;
11457 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
11458 let end_offset = start_offset + clipboard_selection.len;
11459 to_insert = &clipboard_text[start_offset..end_offset];
11460 entire_line = clipboard_selection.is_entire_line;
11461 start_offset = end_offset + 1;
11462 original_indent_column = Some(clipboard_selection.first_line_indent);
11463 } else {
11464 to_insert = clipboard_text.as_str();
11465 entire_line = all_selections_were_entire_line;
11466 original_indent_column = first_selection_indent_column
11467 }
11468
11469 // If the corresponding selection was empty when this slice of the
11470 // clipboard text was written, then the entire line containing the
11471 // selection was copied. If this selection is also currently empty,
11472 // then paste the line before the current line of the buffer.
11473 let range = if selection.is_empty() && handle_entire_lines && entire_line {
11474 let column = selection.start.to_point(&snapshot).column as usize;
11475 let line_start = selection.start - column;
11476 line_start..line_start
11477 } else {
11478 selection.range()
11479 };
11480
11481 edits.push((range, to_insert));
11482 original_indent_columns.push(original_indent_column);
11483 }
11484 drop(snapshot);
11485
11486 buffer.edit(
11487 edits,
11488 if auto_indent_on_paste {
11489 Some(AutoindentMode::Block {
11490 original_indent_columns,
11491 })
11492 } else {
11493 None
11494 },
11495 cx,
11496 );
11497 });
11498
11499 let selections = this.selections.all::<usize>(cx);
11500 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11501 s.select(selections)
11502 });
11503 } else {
11504 this.insert(&clipboard_text, window, cx);
11505 }
11506 });
11507 }
11508
11509 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
11510 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11511 if let Some(item) = cx.read_from_clipboard() {
11512 let entries = item.entries();
11513
11514 match entries.first() {
11515 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
11516 // of all the pasted entries.
11517 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
11518 .do_paste(
11519 clipboard_string.text(),
11520 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
11521 true,
11522 window,
11523 cx,
11524 ),
11525 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
11526 }
11527 }
11528 }
11529
11530 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
11531 if self.read_only(cx) {
11532 return;
11533 }
11534
11535 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11536
11537 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
11538 if let Some((selections, _)) =
11539 self.selection_history.transaction(transaction_id).cloned()
11540 {
11541 self.change_selections(None, window, cx, |s| {
11542 s.select_anchors(selections.to_vec());
11543 });
11544 } else {
11545 log::error!(
11546 "No entry in selection_history found for undo. \
11547 This may correspond to a bug where undo does not update the selection. \
11548 If this is occurring, please add details to \
11549 https://github.com/zed-industries/zed/issues/22692"
11550 );
11551 }
11552 self.request_autoscroll(Autoscroll::fit(), cx);
11553 self.unmark_text(window, cx);
11554 self.refresh_inline_completion(true, false, window, cx);
11555 cx.emit(EditorEvent::Edited { transaction_id });
11556 cx.emit(EditorEvent::TransactionUndone { transaction_id });
11557 }
11558 }
11559
11560 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
11561 if self.read_only(cx) {
11562 return;
11563 }
11564
11565 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
11566
11567 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
11568 if let Some((_, Some(selections))) =
11569 self.selection_history.transaction(transaction_id).cloned()
11570 {
11571 self.change_selections(None, window, cx, |s| {
11572 s.select_anchors(selections.to_vec());
11573 });
11574 } else {
11575 log::error!(
11576 "No entry in selection_history found for redo. \
11577 This may correspond to a bug where undo does not update the selection. \
11578 If this is occurring, please add details to \
11579 https://github.com/zed-industries/zed/issues/22692"
11580 );
11581 }
11582 self.request_autoscroll(Autoscroll::fit(), cx);
11583 self.unmark_text(window, cx);
11584 self.refresh_inline_completion(true, false, window, cx);
11585 cx.emit(EditorEvent::Edited { transaction_id });
11586 }
11587 }
11588
11589 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
11590 self.buffer
11591 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
11592 }
11593
11594 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
11595 self.buffer
11596 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
11597 }
11598
11599 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
11600 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11601 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11602 s.move_with(|map, selection| {
11603 let cursor = if selection.is_empty() {
11604 movement::left(map, selection.start)
11605 } else {
11606 selection.start
11607 };
11608 selection.collapse_to(cursor, SelectionGoal::None);
11609 });
11610 })
11611 }
11612
11613 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
11614 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11615 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11616 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
11617 })
11618 }
11619
11620 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
11621 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11622 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11623 s.move_with(|map, selection| {
11624 let cursor = if selection.is_empty() {
11625 movement::right(map, selection.end)
11626 } else {
11627 selection.end
11628 };
11629 selection.collapse_to(cursor, SelectionGoal::None)
11630 });
11631 })
11632 }
11633
11634 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
11635 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11636 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11637 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
11638 })
11639 }
11640
11641 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
11642 if self.take_rename(true, window, cx).is_some() {
11643 return;
11644 }
11645
11646 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11647 cx.propagate();
11648 return;
11649 }
11650
11651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11652
11653 let text_layout_details = &self.text_layout_details(window);
11654 let selection_count = self.selections.count();
11655 let first_selection = self.selections.first_anchor();
11656
11657 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11658 s.move_with(|map, selection| {
11659 if !selection.is_empty() {
11660 selection.goal = SelectionGoal::None;
11661 }
11662 let (cursor, goal) = movement::up(
11663 map,
11664 selection.start,
11665 selection.goal,
11666 false,
11667 text_layout_details,
11668 );
11669 selection.collapse_to(cursor, goal);
11670 });
11671 });
11672
11673 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11674 {
11675 cx.propagate();
11676 }
11677 }
11678
11679 pub fn move_up_by_lines(
11680 &mut self,
11681 action: &MoveUpByLines,
11682 window: &mut Window,
11683 cx: &mut Context<Self>,
11684 ) {
11685 if self.take_rename(true, window, cx).is_some() {
11686 return;
11687 }
11688
11689 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11690 cx.propagate();
11691 return;
11692 }
11693
11694 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11695
11696 let text_layout_details = &self.text_layout_details(window);
11697
11698 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11699 s.move_with(|map, selection| {
11700 if !selection.is_empty() {
11701 selection.goal = SelectionGoal::None;
11702 }
11703 let (cursor, goal) = movement::up_by_rows(
11704 map,
11705 selection.start,
11706 action.lines,
11707 selection.goal,
11708 false,
11709 text_layout_details,
11710 );
11711 selection.collapse_to(cursor, goal);
11712 });
11713 })
11714 }
11715
11716 pub fn move_down_by_lines(
11717 &mut self,
11718 action: &MoveDownByLines,
11719 window: &mut Window,
11720 cx: &mut Context<Self>,
11721 ) {
11722 if self.take_rename(true, window, cx).is_some() {
11723 return;
11724 }
11725
11726 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11727 cx.propagate();
11728 return;
11729 }
11730
11731 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11732
11733 let text_layout_details = &self.text_layout_details(window);
11734
11735 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11736 s.move_with(|map, selection| {
11737 if !selection.is_empty() {
11738 selection.goal = SelectionGoal::None;
11739 }
11740 let (cursor, goal) = movement::down_by_rows(
11741 map,
11742 selection.start,
11743 action.lines,
11744 selection.goal,
11745 false,
11746 text_layout_details,
11747 );
11748 selection.collapse_to(cursor, goal);
11749 });
11750 })
11751 }
11752
11753 pub fn select_down_by_lines(
11754 &mut self,
11755 action: &SelectDownByLines,
11756 window: &mut Window,
11757 cx: &mut Context<Self>,
11758 ) {
11759 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11760 let text_layout_details = &self.text_layout_details(window);
11761 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11762 s.move_heads_with(|map, head, goal| {
11763 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
11764 })
11765 })
11766 }
11767
11768 pub fn select_up_by_lines(
11769 &mut self,
11770 action: &SelectUpByLines,
11771 window: &mut Window,
11772 cx: &mut Context<Self>,
11773 ) {
11774 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11775 let text_layout_details = &self.text_layout_details(window);
11776 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11777 s.move_heads_with(|map, head, goal| {
11778 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
11779 })
11780 })
11781 }
11782
11783 pub fn select_page_up(
11784 &mut self,
11785 _: &SelectPageUp,
11786 window: &mut Window,
11787 cx: &mut Context<Self>,
11788 ) {
11789 let Some(row_count) = self.visible_row_count() else {
11790 return;
11791 };
11792
11793 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11794
11795 let text_layout_details = &self.text_layout_details(window);
11796
11797 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11798 s.move_heads_with(|map, head, goal| {
11799 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
11800 })
11801 })
11802 }
11803
11804 pub fn move_page_up(
11805 &mut self,
11806 action: &MovePageUp,
11807 window: &mut Window,
11808 cx: &mut Context<Self>,
11809 ) {
11810 if self.take_rename(true, window, cx).is_some() {
11811 return;
11812 }
11813
11814 if self
11815 .context_menu
11816 .borrow_mut()
11817 .as_mut()
11818 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
11819 .unwrap_or(false)
11820 {
11821 return;
11822 }
11823
11824 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11825 cx.propagate();
11826 return;
11827 }
11828
11829 let Some(row_count) = self.visible_row_count() else {
11830 return;
11831 };
11832
11833 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11834
11835 let autoscroll = if action.center_cursor {
11836 Autoscroll::center()
11837 } else {
11838 Autoscroll::fit()
11839 };
11840
11841 let text_layout_details = &self.text_layout_details(window);
11842
11843 self.change_selections(Some(autoscroll), window, cx, |s| {
11844 s.move_with(|map, selection| {
11845 if !selection.is_empty() {
11846 selection.goal = SelectionGoal::None;
11847 }
11848 let (cursor, goal) = movement::up_by_rows(
11849 map,
11850 selection.end,
11851 row_count,
11852 selection.goal,
11853 false,
11854 text_layout_details,
11855 );
11856 selection.collapse_to(cursor, goal);
11857 });
11858 });
11859 }
11860
11861 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
11862 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11863 let text_layout_details = &self.text_layout_details(window);
11864 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11865 s.move_heads_with(|map, head, goal| {
11866 movement::up(map, head, goal, false, text_layout_details)
11867 })
11868 })
11869 }
11870
11871 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
11872 self.take_rename(true, window, cx);
11873
11874 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11875 cx.propagate();
11876 return;
11877 }
11878
11879 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11880
11881 let text_layout_details = &self.text_layout_details(window);
11882 let selection_count = self.selections.count();
11883 let first_selection = self.selections.first_anchor();
11884
11885 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11886 s.move_with(|map, selection| {
11887 if !selection.is_empty() {
11888 selection.goal = SelectionGoal::None;
11889 }
11890 let (cursor, goal) = movement::down(
11891 map,
11892 selection.end,
11893 selection.goal,
11894 false,
11895 text_layout_details,
11896 );
11897 selection.collapse_to(cursor, goal);
11898 });
11899 });
11900
11901 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
11902 {
11903 cx.propagate();
11904 }
11905 }
11906
11907 pub fn select_page_down(
11908 &mut self,
11909 _: &SelectPageDown,
11910 window: &mut Window,
11911 cx: &mut Context<Self>,
11912 ) {
11913 let Some(row_count) = self.visible_row_count() else {
11914 return;
11915 };
11916
11917 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11918
11919 let text_layout_details = &self.text_layout_details(window);
11920
11921 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11922 s.move_heads_with(|map, head, goal| {
11923 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
11924 })
11925 })
11926 }
11927
11928 pub fn move_page_down(
11929 &mut self,
11930 action: &MovePageDown,
11931 window: &mut Window,
11932 cx: &mut Context<Self>,
11933 ) {
11934 if self.take_rename(true, window, cx).is_some() {
11935 return;
11936 }
11937
11938 if self
11939 .context_menu
11940 .borrow_mut()
11941 .as_mut()
11942 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
11943 .unwrap_or(false)
11944 {
11945 return;
11946 }
11947
11948 if matches!(self.mode, EditorMode::SingleLine { .. }) {
11949 cx.propagate();
11950 return;
11951 }
11952
11953 let Some(row_count) = self.visible_row_count() else {
11954 return;
11955 };
11956
11957 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11958
11959 let autoscroll = if action.center_cursor {
11960 Autoscroll::center()
11961 } else {
11962 Autoscroll::fit()
11963 };
11964
11965 let text_layout_details = &self.text_layout_details(window);
11966 self.change_selections(Some(autoscroll), window, cx, |s| {
11967 s.move_with(|map, selection| {
11968 if !selection.is_empty() {
11969 selection.goal = SelectionGoal::None;
11970 }
11971 let (cursor, goal) = movement::down_by_rows(
11972 map,
11973 selection.end,
11974 row_count,
11975 selection.goal,
11976 false,
11977 text_layout_details,
11978 );
11979 selection.collapse_to(cursor, goal);
11980 });
11981 });
11982 }
11983
11984 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
11985 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
11986 let text_layout_details = &self.text_layout_details(window);
11987 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
11988 s.move_heads_with(|map, head, goal| {
11989 movement::down(map, head, goal, false, text_layout_details)
11990 })
11991 });
11992 }
11993
11994 pub fn context_menu_first(
11995 &mut self,
11996 _: &ContextMenuFirst,
11997 window: &mut Window,
11998 cx: &mut Context<Self>,
11999 ) {
12000 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12001 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
12002 }
12003 }
12004
12005 pub fn context_menu_prev(
12006 &mut self,
12007 _: &ContextMenuPrevious,
12008 window: &mut Window,
12009 cx: &mut Context<Self>,
12010 ) {
12011 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12012 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
12013 }
12014 }
12015
12016 pub fn context_menu_next(
12017 &mut self,
12018 _: &ContextMenuNext,
12019 window: &mut Window,
12020 cx: &mut Context<Self>,
12021 ) {
12022 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12023 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
12024 }
12025 }
12026
12027 pub fn context_menu_last(
12028 &mut self,
12029 _: &ContextMenuLast,
12030 window: &mut Window,
12031 cx: &mut Context<Self>,
12032 ) {
12033 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
12034 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
12035 }
12036 }
12037
12038 pub fn move_to_previous_word_start(
12039 &mut self,
12040 _: &MoveToPreviousWordStart,
12041 window: &mut Window,
12042 cx: &mut Context<Self>,
12043 ) {
12044 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12045 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12046 s.move_cursors_with(|map, head, _| {
12047 (
12048 movement::previous_word_start(map, head),
12049 SelectionGoal::None,
12050 )
12051 });
12052 })
12053 }
12054
12055 pub fn move_to_previous_subword_start(
12056 &mut self,
12057 _: &MoveToPreviousSubwordStart,
12058 window: &mut Window,
12059 cx: &mut Context<Self>,
12060 ) {
12061 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12062 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12063 s.move_cursors_with(|map, head, _| {
12064 (
12065 movement::previous_subword_start(map, head),
12066 SelectionGoal::None,
12067 )
12068 });
12069 })
12070 }
12071
12072 pub fn select_to_previous_word_start(
12073 &mut self,
12074 _: &SelectToPreviousWordStart,
12075 window: &mut Window,
12076 cx: &mut Context<Self>,
12077 ) {
12078 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12079 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12080 s.move_heads_with(|map, head, _| {
12081 (
12082 movement::previous_word_start(map, head),
12083 SelectionGoal::None,
12084 )
12085 });
12086 })
12087 }
12088
12089 pub fn select_to_previous_subword_start(
12090 &mut self,
12091 _: &SelectToPreviousSubwordStart,
12092 window: &mut Window,
12093 cx: &mut Context<Self>,
12094 ) {
12095 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12096 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12097 s.move_heads_with(|map, head, _| {
12098 (
12099 movement::previous_subword_start(map, head),
12100 SelectionGoal::None,
12101 )
12102 });
12103 })
12104 }
12105
12106 pub fn delete_to_previous_word_start(
12107 &mut self,
12108 action: &DeleteToPreviousWordStart,
12109 window: &mut Window,
12110 cx: &mut Context<Self>,
12111 ) {
12112 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12113 self.transact(window, cx, |this, window, cx| {
12114 this.select_autoclose_pair(window, cx);
12115 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12116 s.move_with(|map, selection| {
12117 if selection.is_empty() {
12118 let cursor = if action.ignore_newlines {
12119 movement::previous_word_start(map, selection.head())
12120 } else {
12121 movement::previous_word_start_or_newline(map, selection.head())
12122 };
12123 selection.set_head(cursor, SelectionGoal::None);
12124 }
12125 });
12126 });
12127 this.insert("", window, cx);
12128 });
12129 }
12130
12131 pub fn delete_to_previous_subword_start(
12132 &mut self,
12133 _: &DeleteToPreviousSubwordStart,
12134 window: &mut Window,
12135 cx: &mut Context<Self>,
12136 ) {
12137 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12138 self.transact(window, cx, |this, window, cx| {
12139 this.select_autoclose_pair(window, cx);
12140 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12141 s.move_with(|map, selection| {
12142 if selection.is_empty() {
12143 let cursor = movement::previous_subword_start(map, selection.head());
12144 selection.set_head(cursor, SelectionGoal::None);
12145 }
12146 });
12147 });
12148 this.insert("", window, cx);
12149 });
12150 }
12151
12152 pub fn move_to_next_word_end(
12153 &mut self,
12154 _: &MoveToNextWordEnd,
12155 window: &mut Window,
12156 cx: &mut Context<Self>,
12157 ) {
12158 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12159 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12160 s.move_cursors_with(|map, head, _| {
12161 (movement::next_word_end(map, head), SelectionGoal::None)
12162 });
12163 })
12164 }
12165
12166 pub fn move_to_next_subword_end(
12167 &mut self,
12168 _: &MoveToNextSubwordEnd,
12169 window: &mut Window,
12170 cx: &mut Context<Self>,
12171 ) {
12172 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12173 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12174 s.move_cursors_with(|map, head, _| {
12175 (movement::next_subword_end(map, head), SelectionGoal::None)
12176 });
12177 })
12178 }
12179
12180 pub fn select_to_next_word_end(
12181 &mut self,
12182 _: &SelectToNextWordEnd,
12183 window: &mut Window,
12184 cx: &mut Context<Self>,
12185 ) {
12186 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12187 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12188 s.move_heads_with(|map, head, _| {
12189 (movement::next_word_end(map, head), SelectionGoal::None)
12190 });
12191 })
12192 }
12193
12194 pub fn select_to_next_subword_end(
12195 &mut self,
12196 _: &SelectToNextSubwordEnd,
12197 window: &mut Window,
12198 cx: &mut Context<Self>,
12199 ) {
12200 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12201 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12202 s.move_heads_with(|map, head, _| {
12203 (movement::next_subword_end(map, head), SelectionGoal::None)
12204 });
12205 })
12206 }
12207
12208 pub fn delete_to_next_word_end(
12209 &mut self,
12210 action: &DeleteToNextWordEnd,
12211 window: &mut Window,
12212 cx: &mut Context<Self>,
12213 ) {
12214 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12215 self.transact(window, cx, |this, window, cx| {
12216 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12217 s.move_with(|map, selection| {
12218 if selection.is_empty() {
12219 let cursor = if action.ignore_newlines {
12220 movement::next_word_end(map, selection.head())
12221 } else {
12222 movement::next_word_end_or_newline(map, selection.head())
12223 };
12224 selection.set_head(cursor, SelectionGoal::None);
12225 }
12226 });
12227 });
12228 this.insert("", window, cx);
12229 });
12230 }
12231
12232 pub fn delete_to_next_subword_end(
12233 &mut self,
12234 _: &DeleteToNextSubwordEnd,
12235 window: &mut Window,
12236 cx: &mut Context<Self>,
12237 ) {
12238 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12239 self.transact(window, cx, |this, window, cx| {
12240 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12241 s.move_with(|map, selection| {
12242 if selection.is_empty() {
12243 let cursor = movement::next_subword_end(map, selection.head());
12244 selection.set_head(cursor, SelectionGoal::None);
12245 }
12246 });
12247 });
12248 this.insert("", window, cx);
12249 });
12250 }
12251
12252 pub fn move_to_beginning_of_line(
12253 &mut self,
12254 action: &MoveToBeginningOfLine,
12255 window: &mut Window,
12256 cx: &mut Context<Self>,
12257 ) {
12258 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12259 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12260 s.move_cursors_with(|map, head, _| {
12261 (
12262 movement::indented_line_beginning(
12263 map,
12264 head,
12265 action.stop_at_soft_wraps,
12266 action.stop_at_indent,
12267 ),
12268 SelectionGoal::None,
12269 )
12270 });
12271 })
12272 }
12273
12274 pub fn select_to_beginning_of_line(
12275 &mut self,
12276 action: &SelectToBeginningOfLine,
12277 window: &mut Window,
12278 cx: &mut Context<Self>,
12279 ) {
12280 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12281 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12282 s.move_heads_with(|map, head, _| {
12283 (
12284 movement::indented_line_beginning(
12285 map,
12286 head,
12287 action.stop_at_soft_wraps,
12288 action.stop_at_indent,
12289 ),
12290 SelectionGoal::None,
12291 )
12292 });
12293 });
12294 }
12295
12296 pub fn delete_to_beginning_of_line(
12297 &mut self,
12298 action: &DeleteToBeginningOfLine,
12299 window: &mut Window,
12300 cx: &mut Context<Self>,
12301 ) {
12302 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12303 self.transact(window, cx, |this, window, cx| {
12304 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12305 s.move_with(|_, selection| {
12306 selection.reversed = true;
12307 });
12308 });
12309
12310 this.select_to_beginning_of_line(
12311 &SelectToBeginningOfLine {
12312 stop_at_soft_wraps: false,
12313 stop_at_indent: action.stop_at_indent,
12314 },
12315 window,
12316 cx,
12317 );
12318 this.backspace(&Backspace, window, cx);
12319 });
12320 }
12321
12322 pub fn move_to_end_of_line(
12323 &mut self,
12324 action: &MoveToEndOfLine,
12325 window: &mut Window,
12326 cx: &mut Context<Self>,
12327 ) {
12328 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12329 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12330 s.move_cursors_with(|map, head, _| {
12331 (
12332 movement::line_end(map, head, action.stop_at_soft_wraps),
12333 SelectionGoal::None,
12334 )
12335 });
12336 })
12337 }
12338
12339 pub fn select_to_end_of_line(
12340 &mut self,
12341 action: &SelectToEndOfLine,
12342 window: &mut Window,
12343 cx: &mut Context<Self>,
12344 ) {
12345 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12346 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12347 s.move_heads_with(|map, head, _| {
12348 (
12349 movement::line_end(map, head, action.stop_at_soft_wraps),
12350 SelectionGoal::None,
12351 )
12352 });
12353 })
12354 }
12355
12356 pub fn delete_to_end_of_line(
12357 &mut self,
12358 _: &DeleteToEndOfLine,
12359 window: &mut Window,
12360 cx: &mut Context<Self>,
12361 ) {
12362 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12363 self.transact(window, cx, |this, window, cx| {
12364 this.select_to_end_of_line(
12365 &SelectToEndOfLine {
12366 stop_at_soft_wraps: false,
12367 },
12368 window,
12369 cx,
12370 );
12371 this.delete(&Delete, window, cx);
12372 });
12373 }
12374
12375 pub fn cut_to_end_of_line(
12376 &mut self,
12377 _: &CutToEndOfLine,
12378 window: &mut Window,
12379 cx: &mut Context<Self>,
12380 ) {
12381 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
12382 self.transact(window, cx, |this, window, cx| {
12383 this.select_to_end_of_line(
12384 &SelectToEndOfLine {
12385 stop_at_soft_wraps: false,
12386 },
12387 window,
12388 cx,
12389 );
12390 this.cut(&Cut, window, cx);
12391 });
12392 }
12393
12394 pub fn move_to_start_of_paragraph(
12395 &mut self,
12396 _: &MoveToStartOfParagraph,
12397 window: &mut Window,
12398 cx: &mut Context<Self>,
12399 ) {
12400 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12401 cx.propagate();
12402 return;
12403 }
12404 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12405 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12406 s.move_with(|map, selection| {
12407 selection.collapse_to(
12408 movement::start_of_paragraph(map, selection.head(), 1),
12409 SelectionGoal::None,
12410 )
12411 });
12412 })
12413 }
12414
12415 pub fn move_to_end_of_paragraph(
12416 &mut self,
12417 _: &MoveToEndOfParagraph,
12418 window: &mut Window,
12419 cx: &mut Context<Self>,
12420 ) {
12421 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12422 cx.propagate();
12423 return;
12424 }
12425 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12426 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12427 s.move_with(|map, selection| {
12428 selection.collapse_to(
12429 movement::end_of_paragraph(map, selection.head(), 1),
12430 SelectionGoal::None,
12431 )
12432 });
12433 })
12434 }
12435
12436 pub fn select_to_start_of_paragraph(
12437 &mut self,
12438 _: &SelectToStartOfParagraph,
12439 window: &mut Window,
12440 cx: &mut Context<Self>,
12441 ) {
12442 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12443 cx.propagate();
12444 return;
12445 }
12446 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12447 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12448 s.move_heads_with(|map, head, _| {
12449 (
12450 movement::start_of_paragraph(map, head, 1),
12451 SelectionGoal::None,
12452 )
12453 });
12454 })
12455 }
12456
12457 pub fn select_to_end_of_paragraph(
12458 &mut self,
12459 _: &SelectToEndOfParagraph,
12460 window: &mut Window,
12461 cx: &mut Context<Self>,
12462 ) {
12463 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12464 cx.propagate();
12465 return;
12466 }
12467 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12468 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12469 s.move_heads_with(|map, head, _| {
12470 (
12471 movement::end_of_paragraph(map, head, 1),
12472 SelectionGoal::None,
12473 )
12474 });
12475 })
12476 }
12477
12478 pub fn move_to_start_of_excerpt(
12479 &mut self,
12480 _: &MoveToStartOfExcerpt,
12481 window: &mut Window,
12482 cx: &mut Context<Self>,
12483 ) {
12484 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12485 cx.propagate();
12486 return;
12487 }
12488 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12489 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12490 s.move_with(|map, selection| {
12491 selection.collapse_to(
12492 movement::start_of_excerpt(
12493 map,
12494 selection.head(),
12495 workspace::searchable::Direction::Prev,
12496 ),
12497 SelectionGoal::None,
12498 )
12499 });
12500 })
12501 }
12502
12503 pub fn move_to_start_of_next_excerpt(
12504 &mut self,
12505 _: &MoveToStartOfNextExcerpt,
12506 window: &mut Window,
12507 cx: &mut Context<Self>,
12508 ) {
12509 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12510 cx.propagate();
12511 return;
12512 }
12513
12514 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12515 s.move_with(|map, selection| {
12516 selection.collapse_to(
12517 movement::start_of_excerpt(
12518 map,
12519 selection.head(),
12520 workspace::searchable::Direction::Next,
12521 ),
12522 SelectionGoal::None,
12523 )
12524 });
12525 })
12526 }
12527
12528 pub fn move_to_end_of_excerpt(
12529 &mut self,
12530 _: &MoveToEndOfExcerpt,
12531 window: &mut Window,
12532 cx: &mut Context<Self>,
12533 ) {
12534 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12535 cx.propagate();
12536 return;
12537 }
12538 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12539 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12540 s.move_with(|map, selection| {
12541 selection.collapse_to(
12542 movement::end_of_excerpt(
12543 map,
12544 selection.head(),
12545 workspace::searchable::Direction::Next,
12546 ),
12547 SelectionGoal::None,
12548 )
12549 });
12550 })
12551 }
12552
12553 pub fn move_to_end_of_previous_excerpt(
12554 &mut self,
12555 _: &MoveToEndOfPreviousExcerpt,
12556 window: &mut Window,
12557 cx: &mut Context<Self>,
12558 ) {
12559 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12560 cx.propagate();
12561 return;
12562 }
12563 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12564 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12565 s.move_with(|map, selection| {
12566 selection.collapse_to(
12567 movement::end_of_excerpt(
12568 map,
12569 selection.head(),
12570 workspace::searchable::Direction::Prev,
12571 ),
12572 SelectionGoal::None,
12573 )
12574 });
12575 })
12576 }
12577
12578 pub fn select_to_start_of_excerpt(
12579 &mut self,
12580 _: &SelectToStartOfExcerpt,
12581 window: &mut Window,
12582 cx: &mut Context<Self>,
12583 ) {
12584 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12585 cx.propagate();
12586 return;
12587 }
12588 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12589 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12590 s.move_heads_with(|map, head, _| {
12591 (
12592 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12593 SelectionGoal::None,
12594 )
12595 });
12596 })
12597 }
12598
12599 pub fn select_to_start_of_next_excerpt(
12600 &mut self,
12601 _: &SelectToStartOfNextExcerpt,
12602 window: &mut Window,
12603 cx: &mut Context<Self>,
12604 ) {
12605 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12606 cx.propagate();
12607 return;
12608 }
12609 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12610 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12611 s.move_heads_with(|map, head, _| {
12612 (
12613 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
12614 SelectionGoal::None,
12615 )
12616 });
12617 })
12618 }
12619
12620 pub fn select_to_end_of_excerpt(
12621 &mut self,
12622 _: &SelectToEndOfExcerpt,
12623 window: &mut Window,
12624 cx: &mut Context<Self>,
12625 ) {
12626 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12627 cx.propagate();
12628 return;
12629 }
12630 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12631 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12632 s.move_heads_with(|map, head, _| {
12633 (
12634 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
12635 SelectionGoal::None,
12636 )
12637 });
12638 })
12639 }
12640
12641 pub fn select_to_end_of_previous_excerpt(
12642 &mut self,
12643 _: &SelectToEndOfPreviousExcerpt,
12644 window: &mut Window,
12645 cx: &mut Context<Self>,
12646 ) {
12647 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12648 cx.propagate();
12649 return;
12650 }
12651 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12652 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12653 s.move_heads_with(|map, head, _| {
12654 (
12655 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
12656 SelectionGoal::None,
12657 )
12658 });
12659 })
12660 }
12661
12662 pub fn move_to_beginning(
12663 &mut self,
12664 _: &MoveToBeginning,
12665 window: &mut Window,
12666 cx: &mut Context<Self>,
12667 ) {
12668 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12669 cx.propagate();
12670 return;
12671 }
12672 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12673 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12674 s.select_ranges(vec![0..0]);
12675 });
12676 }
12677
12678 pub fn select_to_beginning(
12679 &mut self,
12680 _: &SelectToBeginning,
12681 window: &mut Window,
12682 cx: &mut Context<Self>,
12683 ) {
12684 let mut selection = self.selections.last::<Point>(cx);
12685 selection.set_head(Point::zero(), SelectionGoal::None);
12686 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12687 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12688 s.select(vec![selection]);
12689 });
12690 }
12691
12692 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
12693 if matches!(self.mode, EditorMode::SingleLine { .. }) {
12694 cx.propagate();
12695 return;
12696 }
12697 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12698 let cursor = self.buffer.read(cx).read(cx).len();
12699 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12700 s.select_ranges(vec![cursor..cursor])
12701 });
12702 }
12703
12704 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
12705 self.nav_history = nav_history;
12706 }
12707
12708 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
12709 self.nav_history.as_ref()
12710 }
12711
12712 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
12713 self.push_to_nav_history(self.selections.newest_anchor().head(), None, false, cx);
12714 }
12715
12716 fn push_to_nav_history(
12717 &mut self,
12718 cursor_anchor: Anchor,
12719 new_position: Option<Point>,
12720 is_deactivate: bool,
12721 cx: &mut Context<Self>,
12722 ) {
12723 if let Some(nav_history) = self.nav_history.as_mut() {
12724 let buffer = self.buffer.read(cx).read(cx);
12725 let cursor_position = cursor_anchor.to_point(&buffer);
12726 let scroll_state = self.scroll_manager.anchor();
12727 let scroll_top_row = scroll_state.top_row(&buffer);
12728 drop(buffer);
12729
12730 if let Some(new_position) = new_position {
12731 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
12732 if row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA {
12733 return;
12734 }
12735 }
12736
12737 nav_history.push(
12738 Some(NavigationData {
12739 cursor_anchor,
12740 cursor_position,
12741 scroll_anchor: scroll_state,
12742 scroll_top_row,
12743 }),
12744 cx,
12745 );
12746 cx.emit(EditorEvent::PushedToNavHistory {
12747 anchor: cursor_anchor,
12748 is_deactivate,
12749 })
12750 }
12751 }
12752
12753 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
12754 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12755 let buffer = self.buffer.read(cx).snapshot(cx);
12756 let mut selection = self.selections.first::<usize>(cx);
12757 selection.set_head(buffer.len(), SelectionGoal::None);
12758 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12759 s.select(vec![selection]);
12760 });
12761 }
12762
12763 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
12764 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12765 let end = self.buffer.read(cx).read(cx).len();
12766 self.change_selections(None, window, cx, |s| {
12767 s.select_ranges(vec![0..end]);
12768 });
12769 }
12770
12771 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
12772 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12773 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12774 let mut selections = self.selections.all::<Point>(cx);
12775 let max_point = display_map.buffer_snapshot.max_point();
12776 for selection in &mut selections {
12777 let rows = selection.spanned_rows(true, &display_map);
12778 selection.start = Point::new(rows.start.0, 0);
12779 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
12780 selection.reversed = false;
12781 }
12782 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12783 s.select(selections);
12784 });
12785 }
12786
12787 pub fn split_selection_into_lines(
12788 &mut self,
12789 _: &SplitSelectionIntoLines,
12790 window: &mut Window,
12791 cx: &mut Context<Self>,
12792 ) {
12793 let selections = self
12794 .selections
12795 .all::<Point>(cx)
12796 .into_iter()
12797 .map(|selection| selection.start..selection.end)
12798 .collect::<Vec<_>>();
12799 self.unfold_ranges(&selections, true, true, cx);
12800
12801 let mut new_selection_ranges = Vec::new();
12802 {
12803 let buffer = self.buffer.read(cx).read(cx);
12804 for selection in selections {
12805 for row in selection.start.row..selection.end.row {
12806 let cursor = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12807 new_selection_ranges.push(cursor..cursor);
12808 }
12809
12810 let is_multiline_selection = selection.start.row != selection.end.row;
12811 // Don't insert last one if it's a multi-line selection ending at the start of a line,
12812 // so this action feels more ergonomic when paired with other selection operations
12813 let should_skip_last = is_multiline_selection && selection.end.column == 0;
12814 if !should_skip_last {
12815 new_selection_ranges.push(selection.end..selection.end);
12816 }
12817 }
12818 }
12819 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12820 s.select_ranges(new_selection_ranges);
12821 });
12822 }
12823
12824 pub fn add_selection_above(
12825 &mut self,
12826 _: &AddSelectionAbove,
12827 window: &mut Window,
12828 cx: &mut Context<Self>,
12829 ) {
12830 self.add_selection(true, window, cx);
12831 }
12832
12833 pub fn add_selection_below(
12834 &mut self,
12835 _: &AddSelectionBelow,
12836 window: &mut Window,
12837 cx: &mut Context<Self>,
12838 ) {
12839 self.add_selection(false, window, cx);
12840 }
12841
12842 fn add_selection(&mut self, above: bool, window: &mut Window, cx: &mut Context<Self>) {
12843 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
12844
12845 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12846 let all_selections = self.selections.all::<Point>(cx);
12847 let text_layout_details = self.text_layout_details(window);
12848
12849 let (mut columnar_selections, new_selections_to_columnarize) = {
12850 if let Some(state) = self.add_selections_state.as_ref() {
12851 let columnar_selection_ids: HashSet<_> = state
12852 .groups
12853 .iter()
12854 .flat_map(|group| group.stack.iter())
12855 .copied()
12856 .collect();
12857
12858 all_selections
12859 .into_iter()
12860 .partition(|s| columnar_selection_ids.contains(&s.id))
12861 } else {
12862 (Vec::new(), all_selections)
12863 }
12864 };
12865
12866 let mut state = self
12867 .add_selections_state
12868 .take()
12869 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
12870
12871 for selection in new_selections_to_columnarize {
12872 let range = selection.display_range(&display_map).sorted();
12873 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
12874 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
12875 let positions = start_x.min(end_x)..start_x.max(end_x);
12876 let mut stack = Vec::new();
12877 for row in range.start.row().0..=range.end.row().0 {
12878 if let Some(selection) = self.selections.build_columnar_selection(
12879 &display_map,
12880 DisplayRow(row),
12881 &positions,
12882 selection.reversed,
12883 &text_layout_details,
12884 ) {
12885 stack.push(selection.id);
12886 columnar_selections.push(selection);
12887 }
12888 }
12889 if !stack.is_empty() {
12890 if above {
12891 stack.reverse();
12892 }
12893 state.groups.push(AddSelectionsGroup { above, stack });
12894 }
12895 }
12896
12897 let mut final_selections = Vec::new();
12898 let end_row = if above {
12899 DisplayRow(0)
12900 } else {
12901 display_map.max_point().row()
12902 };
12903
12904 let mut last_added_item_per_group = HashMap::default();
12905 for group in state.groups.iter_mut() {
12906 if let Some(last_id) = group.stack.last() {
12907 last_added_item_per_group.insert(*last_id, group);
12908 }
12909 }
12910
12911 for selection in columnar_selections {
12912 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
12913 if above == group.above {
12914 let range = selection.display_range(&display_map).sorted();
12915 debug_assert_eq!(range.start.row(), range.end.row());
12916 let mut row = range.start.row();
12917 let positions =
12918 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
12919 px(start)..px(end)
12920 } else {
12921 let start_x =
12922 display_map.x_for_display_point(range.start, &text_layout_details);
12923 let end_x =
12924 display_map.x_for_display_point(range.end, &text_layout_details);
12925 start_x.min(end_x)..start_x.max(end_x)
12926 };
12927
12928 let mut maybe_new_selection = None;
12929 while row != end_row {
12930 if above {
12931 row.0 -= 1;
12932 } else {
12933 row.0 += 1;
12934 }
12935 if let Some(new_selection) = self.selections.build_columnar_selection(
12936 &display_map,
12937 row,
12938 &positions,
12939 selection.reversed,
12940 &text_layout_details,
12941 ) {
12942 maybe_new_selection = Some(new_selection);
12943 break;
12944 }
12945 }
12946
12947 if let Some(new_selection) = maybe_new_selection {
12948 group.stack.push(new_selection.id);
12949 if above {
12950 final_selections.push(new_selection);
12951 final_selections.push(selection);
12952 } else {
12953 final_selections.push(selection);
12954 final_selections.push(new_selection);
12955 }
12956 } else {
12957 final_selections.push(selection);
12958 }
12959 } else {
12960 group.stack.pop();
12961 }
12962 } else {
12963 final_selections.push(selection);
12964 }
12965 }
12966
12967 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
12968 s.select(final_selections);
12969 });
12970
12971 let final_selection_ids: HashSet<_> = self
12972 .selections
12973 .all::<Point>(cx)
12974 .iter()
12975 .map(|s| s.id)
12976 .collect();
12977 state.groups.retain_mut(|group| {
12978 // selections might get merged above so we remove invalid items from stacks
12979 group.stack.retain(|id| final_selection_ids.contains(id));
12980
12981 // single selection in stack can be treated as initial state
12982 group.stack.len() > 1
12983 });
12984
12985 if !state.groups.is_empty() {
12986 self.add_selections_state = Some(state);
12987 }
12988 }
12989
12990 fn select_match_ranges(
12991 &mut self,
12992 range: Range<usize>,
12993 reversed: bool,
12994 replace_newest: bool,
12995 auto_scroll: Option<Autoscroll>,
12996 window: &mut Window,
12997 cx: &mut Context<Editor>,
12998 ) {
12999 self.unfold_ranges(&[range.clone()], false, auto_scroll.is_some(), cx);
13000 self.change_selections(auto_scroll, window, cx, |s| {
13001 if replace_newest {
13002 s.delete(s.newest_anchor().id);
13003 }
13004 if reversed {
13005 s.insert_range(range.end..range.start);
13006 } else {
13007 s.insert_range(range);
13008 }
13009 });
13010 }
13011
13012 pub fn select_next_match_internal(
13013 &mut self,
13014 display_map: &DisplaySnapshot,
13015 replace_newest: bool,
13016 autoscroll: Option<Autoscroll>,
13017 window: &mut Window,
13018 cx: &mut Context<Self>,
13019 ) -> Result<()> {
13020 let buffer = &display_map.buffer_snapshot;
13021 let mut selections = self.selections.all::<usize>(cx);
13022 if let Some(mut select_next_state) = self.select_next_state.take() {
13023 let query = &select_next_state.query;
13024 if !select_next_state.done {
13025 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13026 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13027 let mut next_selected_range = None;
13028
13029 let bytes_after_last_selection =
13030 buffer.bytes_in_range(last_selection.end..buffer.len());
13031 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
13032 let query_matches = query
13033 .stream_find_iter(bytes_after_last_selection)
13034 .map(|result| (last_selection.end, result))
13035 .chain(
13036 query
13037 .stream_find_iter(bytes_before_first_selection)
13038 .map(|result| (0, result)),
13039 );
13040
13041 for (start_offset, query_match) in query_matches {
13042 let query_match = query_match.unwrap(); // can only fail due to I/O
13043 let offset_range =
13044 start_offset + query_match.start()..start_offset + query_match.end();
13045 let display_range = offset_range.start.to_display_point(display_map)
13046 ..offset_range.end.to_display_point(display_map);
13047
13048 if !select_next_state.wordwise
13049 || (!movement::is_inside_word(display_map, display_range.start)
13050 && !movement::is_inside_word(display_map, display_range.end))
13051 {
13052 // TODO: This is n^2, because we might check all the selections
13053 if !selections
13054 .iter()
13055 .any(|selection| selection.range().overlaps(&offset_range))
13056 {
13057 next_selected_range = Some(offset_range);
13058 break;
13059 }
13060 }
13061 }
13062
13063 if let Some(next_selected_range) = next_selected_range {
13064 self.select_match_ranges(
13065 next_selected_range,
13066 last_selection.reversed,
13067 replace_newest,
13068 autoscroll,
13069 window,
13070 cx,
13071 );
13072 } else {
13073 select_next_state.done = true;
13074 }
13075 }
13076
13077 self.select_next_state = Some(select_next_state);
13078 } else {
13079 let mut only_carets = true;
13080 let mut same_text_selected = true;
13081 let mut selected_text = None;
13082
13083 let mut selections_iter = selections.iter().peekable();
13084 while let Some(selection) = selections_iter.next() {
13085 if selection.start != selection.end {
13086 only_carets = false;
13087 }
13088
13089 if same_text_selected {
13090 if selected_text.is_none() {
13091 selected_text =
13092 Some(buffer.text_for_range(selection.range()).collect::<String>());
13093 }
13094
13095 if let Some(next_selection) = selections_iter.peek() {
13096 if next_selection.range().len() == selection.range().len() {
13097 let next_selected_text = buffer
13098 .text_for_range(next_selection.range())
13099 .collect::<String>();
13100 if Some(next_selected_text) != selected_text {
13101 same_text_selected = false;
13102 selected_text = None;
13103 }
13104 } else {
13105 same_text_selected = false;
13106 selected_text = None;
13107 }
13108 }
13109 }
13110 }
13111
13112 if only_carets {
13113 for selection in &mut selections {
13114 let word_range = movement::surrounding_word(
13115 display_map,
13116 selection.start.to_display_point(display_map),
13117 );
13118 selection.start = word_range.start.to_offset(display_map, Bias::Left);
13119 selection.end = word_range.end.to_offset(display_map, Bias::Left);
13120 selection.goal = SelectionGoal::None;
13121 selection.reversed = false;
13122 self.select_match_ranges(
13123 selection.start..selection.end,
13124 selection.reversed,
13125 replace_newest,
13126 autoscroll,
13127 window,
13128 cx,
13129 );
13130 }
13131
13132 if selections.len() == 1 {
13133 let selection = selections
13134 .last()
13135 .expect("ensured that there's only one selection");
13136 let query = buffer
13137 .text_for_range(selection.start..selection.end)
13138 .collect::<String>();
13139 let is_empty = query.is_empty();
13140 let select_state = SelectNextState {
13141 query: AhoCorasick::new(&[query])?,
13142 wordwise: true,
13143 done: is_empty,
13144 };
13145 self.select_next_state = Some(select_state);
13146 } else {
13147 self.select_next_state = None;
13148 }
13149 } else if let Some(selected_text) = selected_text {
13150 self.select_next_state = Some(SelectNextState {
13151 query: AhoCorasick::new(&[selected_text])?,
13152 wordwise: false,
13153 done: false,
13154 });
13155 self.select_next_match_internal(
13156 display_map,
13157 replace_newest,
13158 autoscroll,
13159 window,
13160 cx,
13161 )?;
13162 }
13163 }
13164 Ok(())
13165 }
13166
13167 pub fn select_all_matches(
13168 &mut self,
13169 _action: &SelectAllMatches,
13170 window: &mut Window,
13171 cx: &mut Context<Self>,
13172 ) -> Result<()> {
13173 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13174
13175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13176
13177 self.select_next_match_internal(&display_map, false, None, window, cx)?;
13178 let Some(select_next_state) = self.select_next_state.as_mut() else {
13179 return Ok(());
13180 };
13181 if select_next_state.done {
13182 return Ok(());
13183 }
13184
13185 let mut new_selections = Vec::new();
13186
13187 let reversed = self.selections.oldest::<usize>(cx).reversed;
13188 let buffer = &display_map.buffer_snapshot;
13189 let query_matches = select_next_state
13190 .query
13191 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
13192
13193 for query_match in query_matches.into_iter() {
13194 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
13195 let offset_range = if reversed {
13196 query_match.end()..query_match.start()
13197 } else {
13198 query_match.start()..query_match.end()
13199 };
13200 let display_range = offset_range.start.to_display_point(&display_map)
13201 ..offset_range.end.to_display_point(&display_map);
13202
13203 if !select_next_state.wordwise
13204 || (!movement::is_inside_word(&display_map, display_range.start)
13205 && !movement::is_inside_word(&display_map, display_range.end))
13206 {
13207 new_selections.push(offset_range.start..offset_range.end);
13208 }
13209 }
13210
13211 select_next_state.done = true;
13212 self.unfold_ranges(&new_selections.clone(), false, false, cx);
13213 self.change_selections(None, window, cx, |selections| {
13214 selections.select_ranges(new_selections)
13215 });
13216
13217 Ok(())
13218 }
13219
13220 pub fn select_next(
13221 &mut self,
13222 action: &SelectNext,
13223 window: &mut Window,
13224 cx: &mut Context<Self>,
13225 ) -> Result<()> {
13226 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13227 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13228 self.select_next_match_internal(
13229 &display_map,
13230 action.replace_newest,
13231 Some(Autoscroll::newest()),
13232 window,
13233 cx,
13234 )?;
13235 Ok(())
13236 }
13237
13238 pub fn select_previous(
13239 &mut self,
13240 action: &SelectPrevious,
13241 window: &mut Window,
13242 cx: &mut Context<Self>,
13243 ) -> Result<()> {
13244 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13246 let buffer = &display_map.buffer_snapshot;
13247 let mut selections = self.selections.all::<usize>(cx);
13248 if let Some(mut select_prev_state) = self.select_prev_state.take() {
13249 let query = &select_prev_state.query;
13250 if !select_prev_state.done {
13251 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
13252 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
13253 let mut next_selected_range = None;
13254 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
13255 let bytes_before_last_selection =
13256 buffer.reversed_bytes_in_range(0..last_selection.start);
13257 let bytes_after_first_selection =
13258 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
13259 let query_matches = query
13260 .stream_find_iter(bytes_before_last_selection)
13261 .map(|result| (last_selection.start, result))
13262 .chain(
13263 query
13264 .stream_find_iter(bytes_after_first_selection)
13265 .map(|result| (buffer.len(), result)),
13266 );
13267 for (end_offset, query_match) in query_matches {
13268 let query_match = query_match.unwrap(); // can only fail due to I/O
13269 let offset_range =
13270 end_offset - query_match.end()..end_offset - query_match.start();
13271 let display_range = offset_range.start.to_display_point(&display_map)
13272 ..offset_range.end.to_display_point(&display_map);
13273
13274 if !select_prev_state.wordwise
13275 || (!movement::is_inside_word(&display_map, display_range.start)
13276 && !movement::is_inside_word(&display_map, display_range.end))
13277 {
13278 next_selected_range = Some(offset_range);
13279 break;
13280 }
13281 }
13282
13283 if let Some(next_selected_range) = next_selected_range {
13284 self.select_match_ranges(
13285 next_selected_range,
13286 last_selection.reversed,
13287 action.replace_newest,
13288 Some(Autoscroll::newest()),
13289 window,
13290 cx,
13291 );
13292 } else {
13293 select_prev_state.done = true;
13294 }
13295 }
13296
13297 self.select_prev_state = Some(select_prev_state);
13298 } else {
13299 let mut only_carets = true;
13300 let mut same_text_selected = true;
13301 let mut selected_text = None;
13302
13303 let mut selections_iter = selections.iter().peekable();
13304 while let Some(selection) = selections_iter.next() {
13305 if selection.start != selection.end {
13306 only_carets = false;
13307 }
13308
13309 if same_text_selected {
13310 if selected_text.is_none() {
13311 selected_text =
13312 Some(buffer.text_for_range(selection.range()).collect::<String>());
13313 }
13314
13315 if let Some(next_selection) = selections_iter.peek() {
13316 if next_selection.range().len() == selection.range().len() {
13317 let next_selected_text = buffer
13318 .text_for_range(next_selection.range())
13319 .collect::<String>();
13320 if Some(next_selected_text) != selected_text {
13321 same_text_selected = false;
13322 selected_text = None;
13323 }
13324 } else {
13325 same_text_selected = false;
13326 selected_text = None;
13327 }
13328 }
13329 }
13330 }
13331
13332 if only_carets {
13333 for selection in &mut selections {
13334 let word_range = movement::surrounding_word(
13335 &display_map,
13336 selection.start.to_display_point(&display_map),
13337 );
13338 selection.start = word_range.start.to_offset(&display_map, Bias::Left);
13339 selection.end = word_range.end.to_offset(&display_map, Bias::Left);
13340 selection.goal = SelectionGoal::None;
13341 selection.reversed = false;
13342 self.select_match_ranges(
13343 selection.start..selection.end,
13344 selection.reversed,
13345 action.replace_newest,
13346 Some(Autoscroll::newest()),
13347 window,
13348 cx,
13349 );
13350 }
13351 if selections.len() == 1 {
13352 let selection = selections
13353 .last()
13354 .expect("ensured that there's only one selection");
13355 let query = buffer
13356 .text_for_range(selection.start..selection.end)
13357 .collect::<String>();
13358 let is_empty = query.is_empty();
13359 let select_state = SelectNextState {
13360 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
13361 wordwise: true,
13362 done: is_empty,
13363 };
13364 self.select_prev_state = Some(select_state);
13365 } else {
13366 self.select_prev_state = None;
13367 }
13368 } else if let Some(selected_text) = selected_text {
13369 self.select_prev_state = Some(SelectNextState {
13370 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
13371 wordwise: false,
13372 done: false,
13373 });
13374 self.select_previous(action, window, cx)?;
13375 }
13376 }
13377 Ok(())
13378 }
13379
13380 pub fn find_next_match(
13381 &mut self,
13382 _: &FindNextMatch,
13383 window: &mut Window,
13384 cx: &mut Context<Self>,
13385 ) -> Result<()> {
13386 let selections = self.selections.disjoint_anchors();
13387 match selections.first() {
13388 Some(first) if selections.len() >= 2 => {
13389 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13390 s.select_ranges([first.range()]);
13391 });
13392 }
13393 _ => self.select_next(
13394 &SelectNext {
13395 replace_newest: true,
13396 },
13397 window,
13398 cx,
13399 )?,
13400 }
13401 Ok(())
13402 }
13403
13404 pub fn find_previous_match(
13405 &mut self,
13406 _: &FindPreviousMatch,
13407 window: &mut Window,
13408 cx: &mut Context<Self>,
13409 ) -> Result<()> {
13410 let selections = self.selections.disjoint_anchors();
13411 match selections.last() {
13412 Some(last) if selections.len() >= 2 => {
13413 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13414 s.select_ranges([last.range()]);
13415 });
13416 }
13417 _ => self.select_previous(
13418 &SelectPrevious {
13419 replace_newest: true,
13420 },
13421 window,
13422 cx,
13423 )?,
13424 }
13425 Ok(())
13426 }
13427
13428 pub fn toggle_comments(
13429 &mut self,
13430 action: &ToggleComments,
13431 window: &mut Window,
13432 cx: &mut Context<Self>,
13433 ) {
13434 if self.read_only(cx) {
13435 return;
13436 }
13437 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
13438 let text_layout_details = &self.text_layout_details(window);
13439 self.transact(window, cx, |this, window, cx| {
13440 let mut selections = this.selections.all::<MultiBufferPoint>(cx);
13441 let mut edits = Vec::new();
13442 let mut selection_edit_ranges = Vec::new();
13443 let mut last_toggled_row = None;
13444 let snapshot = this.buffer.read(cx).read(cx);
13445 let empty_str: Arc<str> = Arc::default();
13446 let mut suffixes_inserted = Vec::new();
13447 let ignore_indent = action.ignore_indent;
13448
13449 fn comment_prefix_range(
13450 snapshot: &MultiBufferSnapshot,
13451 row: MultiBufferRow,
13452 comment_prefix: &str,
13453 comment_prefix_whitespace: &str,
13454 ignore_indent: bool,
13455 ) -> Range<Point> {
13456 let indent_size = if ignore_indent {
13457 0
13458 } else {
13459 snapshot.indent_size_for_line(row).len
13460 };
13461
13462 let start = Point::new(row.0, indent_size);
13463
13464 let mut line_bytes = snapshot
13465 .bytes_in_range(start..snapshot.max_point())
13466 .flatten()
13467 .copied();
13468
13469 // If this line currently begins with the line comment prefix, then record
13470 // the range containing the prefix.
13471 if line_bytes
13472 .by_ref()
13473 .take(comment_prefix.len())
13474 .eq(comment_prefix.bytes())
13475 {
13476 // Include any whitespace that matches the comment prefix.
13477 let matching_whitespace_len = line_bytes
13478 .zip(comment_prefix_whitespace.bytes())
13479 .take_while(|(a, b)| a == b)
13480 .count() as u32;
13481 let end = Point::new(
13482 start.row,
13483 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
13484 );
13485 start..end
13486 } else {
13487 start..start
13488 }
13489 }
13490
13491 fn comment_suffix_range(
13492 snapshot: &MultiBufferSnapshot,
13493 row: MultiBufferRow,
13494 comment_suffix: &str,
13495 comment_suffix_has_leading_space: bool,
13496 ) -> Range<Point> {
13497 let end = Point::new(row.0, snapshot.line_len(row));
13498 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
13499
13500 let mut line_end_bytes = snapshot
13501 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
13502 .flatten()
13503 .copied();
13504
13505 let leading_space_len = if suffix_start_column > 0
13506 && line_end_bytes.next() == Some(b' ')
13507 && comment_suffix_has_leading_space
13508 {
13509 1
13510 } else {
13511 0
13512 };
13513
13514 // If this line currently begins with the line comment prefix, then record
13515 // the range containing the prefix.
13516 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
13517 let start = Point::new(end.row, suffix_start_column - leading_space_len);
13518 start..end
13519 } else {
13520 end..end
13521 }
13522 }
13523
13524 // TODO: Handle selections that cross excerpts
13525 for selection in &mut selections {
13526 let start_column = snapshot
13527 .indent_size_for_line(MultiBufferRow(selection.start.row))
13528 .len;
13529 let language = if let Some(language) =
13530 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
13531 {
13532 language
13533 } else {
13534 continue;
13535 };
13536
13537 selection_edit_ranges.clear();
13538
13539 // If multiple selections contain a given row, avoid processing that
13540 // row more than once.
13541 let mut start_row = MultiBufferRow(selection.start.row);
13542 if last_toggled_row == Some(start_row) {
13543 start_row = start_row.next_row();
13544 }
13545 let end_row =
13546 if selection.end.row > selection.start.row && selection.end.column == 0 {
13547 MultiBufferRow(selection.end.row - 1)
13548 } else {
13549 MultiBufferRow(selection.end.row)
13550 };
13551 last_toggled_row = Some(end_row);
13552
13553 if start_row > end_row {
13554 continue;
13555 }
13556
13557 // If the language has line comments, toggle those.
13558 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
13559
13560 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
13561 if ignore_indent {
13562 full_comment_prefixes = full_comment_prefixes
13563 .into_iter()
13564 .map(|s| Arc::from(s.trim_end()))
13565 .collect();
13566 }
13567
13568 if !full_comment_prefixes.is_empty() {
13569 let first_prefix = full_comment_prefixes
13570 .first()
13571 .expect("prefixes is non-empty");
13572 let prefix_trimmed_lengths = full_comment_prefixes
13573 .iter()
13574 .map(|p| p.trim_end_matches(' ').len())
13575 .collect::<SmallVec<[usize; 4]>>();
13576
13577 let mut all_selection_lines_are_comments = true;
13578
13579 for row in start_row.0..=end_row.0 {
13580 let row = MultiBufferRow(row);
13581 if start_row < end_row && snapshot.is_line_blank(row) {
13582 continue;
13583 }
13584
13585 let prefix_range = full_comment_prefixes
13586 .iter()
13587 .zip(prefix_trimmed_lengths.iter().copied())
13588 .map(|(prefix, trimmed_prefix_len)| {
13589 comment_prefix_range(
13590 snapshot.deref(),
13591 row,
13592 &prefix[..trimmed_prefix_len],
13593 &prefix[trimmed_prefix_len..],
13594 ignore_indent,
13595 )
13596 })
13597 .max_by_key(|range| range.end.column - range.start.column)
13598 .expect("prefixes is non-empty");
13599
13600 if prefix_range.is_empty() {
13601 all_selection_lines_are_comments = false;
13602 }
13603
13604 selection_edit_ranges.push(prefix_range);
13605 }
13606
13607 if all_selection_lines_are_comments {
13608 edits.extend(
13609 selection_edit_ranges
13610 .iter()
13611 .cloned()
13612 .map(|range| (range, empty_str.clone())),
13613 );
13614 } else {
13615 let min_column = selection_edit_ranges
13616 .iter()
13617 .map(|range| range.start.column)
13618 .min()
13619 .unwrap_or(0);
13620 edits.extend(selection_edit_ranges.iter().map(|range| {
13621 let position = Point::new(range.start.row, min_column);
13622 (position..position, first_prefix.clone())
13623 }));
13624 }
13625 } else if let Some((full_comment_prefix, comment_suffix)) =
13626 language.block_comment_delimiters()
13627 {
13628 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
13629 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
13630 let prefix_range = comment_prefix_range(
13631 snapshot.deref(),
13632 start_row,
13633 comment_prefix,
13634 comment_prefix_whitespace,
13635 ignore_indent,
13636 );
13637 let suffix_range = comment_suffix_range(
13638 snapshot.deref(),
13639 end_row,
13640 comment_suffix.trim_start_matches(' '),
13641 comment_suffix.starts_with(' '),
13642 );
13643
13644 if prefix_range.is_empty() || suffix_range.is_empty() {
13645 edits.push((
13646 prefix_range.start..prefix_range.start,
13647 full_comment_prefix.clone(),
13648 ));
13649 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
13650 suffixes_inserted.push((end_row, comment_suffix.len()));
13651 } else {
13652 edits.push((prefix_range, empty_str.clone()));
13653 edits.push((suffix_range, empty_str.clone()));
13654 }
13655 } else {
13656 continue;
13657 }
13658 }
13659
13660 drop(snapshot);
13661 this.buffer.update(cx, |buffer, cx| {
13662 buffer.edit(edits, None, cx);
13663 });
13664
13665 // Adjust selections so that they end before any comment suffixes that
13666 // were inserted.
13667 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
13668 let mut selections = this.selections.all::<Point>(cx);
13669 let snapshot = this.buffer.read(cx).read(cx);
13670 for selection in &mut selections {
13671 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
13672 match row.cmp(&MultiBufferRow(selection.end.row)) {
13673 Ordering::Less => {
13674 suffixes_inserted.next();
13675 continue;
13676 }
13677 Ordering::Greater => break,
13678 Ordering::Equal => {
13679 if selection.end.column == snapshot.line_len(row) {
13680 if selection.is_empty() {
13681 selection.start.column -= suffix_len as u32;
13682 }
13683 selection.end.column -= suffix_len as u32;
13684 }
13685 break;
13686 }
13687 }
13688 }
13689 }
13690
13691 drop(snapshot);
13692 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13693 s.select(selections)
13694 });
13695
13696 let selections = this.selections.all::<Point>(cx);
13697 let selections_on_single_row = selections.windows(2).all(|selections| {
13698 selections[0].start.row == selections[1].start.row
13699 && selections[0].end.row == selections[1].end.row
13700 && selections[0].start.row == selections[0].end.row
13701 });
13702 let selections_selecting = selections
13703 .iter()
13704 .any(|selection| selection.start != selection.end);
13705 let advance_downwards = action.advance_downwards
13706 && selections_on_single_row
13707 && !selections_selecting
13708 && !matches!(this.mode, EditorMode::SingleLine { .. });
13709
13710 if advance_downwards {
13711 let snapshot = this.buffer.read(cx).snapshot(cx);
13712
13713 this.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13714 s.move_cursors_with(|display_snapshot, display_point, _| {
13715 let mut point = display_point.to_point(display_snapshot);
13716 point.row += 1;
13717 point = snapshot.clip_point(point, Bias::Left);
13718 let display_point = point.to_display_point(display_snapshot);
13719 let goal = SelectionGoal::HorizontalPosition(
13720 display_snapshot
13721 .x_for_display_point(display_point, text_layout_details)
13722 .into(),
13723 );
13724 (display_point, goal)
13725 })
13726 });
13727 }
13728 });
13729 }
13730
13731 pub fn select_enclosing_symbol(
13732 &mut self,
13733 _: &SelectEnclosingSymbol,
13734 window: &mut Window,
13735 cx: &mut Context<Self>,
13736 ) {
13737 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13738
13739 let buffer = self.buffer.read(cx).snapshot(cx);
13740 let old_selections = self.selections.all::<usize>(cx).into_boxed_slice();
13741
13742 fn update_selection(
13743 selection: &Selection<usize>,
13744 buffer_snap: &MultiBufferSnapshot,
13745 ) -> Option<Selection<usize>> {
13746 let cursor = selection.head();
13747 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
13748 for symbol in symbols.iter().rev() {
13749 let start = symbol.range.start.to_offset(buffer_snap);
13750 let end = symbol.range.end.to_offset(buffer_snap);
13751 let new_range = start..end;
13752 if start < selection.start || end > selection.end {
13753 return Some(Selection {
13754 id: selection.id,
13755 start: new_range.start,
13756 end: new_range.end,
13757 goal: SelectionGoal::None,
13758 reversed: selection.reversed,
13759 });
13760 }
13761 }
13762 None
13763 }
13764
13765 let mut selected_larger_symbol = false;
13766 let new_selections = old_selections
13767 .iter()
13768 .map(|selection| match update_selection(selection, &buffer) {
13769 Some(new_selection) => {
13770 if new_selection.range() != selection.range() {
13771 selected_larger_symbol = true;
13772 }
13773 new_selection
13774 }
13775 None => selection.clone(),
13776 })
13777 .collect::<Vec<_>>();
13778
13779 if selected_larger_symbol {
13780 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
13781 s.select(new_selections);
13782 });
13783 }
13784 }
13785
13786 pub fn select_larger_syntax_node(
13787 &mut self,
13788 _: &SelectLargerSyntaxNode,
13789 window: &mut Window,
13790 cx: &mut Context<Self>,
13791 ) {
13792 let Some(visible_row_count) = self.visible_row_count() else {
13793 return;
13794 };
13795 let old_selections: Box<[_]> = self.selections.all::<usize>(cx).into();
13796 if old_selections.is_empty() {
13797 return;
13798 }
13799
13800 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13801
13802 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
13803 let buffer = self.buffer.read(cx).snapshot(cx);
13804
13805 let mut selected_larger_node = false;
13806 let mut new_selections = old_selections
13807 .iter()
13808 .map(|selection| {
13809 let old_range = selection.start..selection.end;
13810
13811 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
13812 // manually select word at selection
13813 if ["string_content", "inline"].contains(&node.kind()) {
13814 let word_range = {
13815 let display_point = buffer
13816 .offset_to_point(old_range.start)
13817 .to_display_point(&display_map);
13818 let Range { start, end } =
13819 movement::surrounding_word(&display_map, display_point);
13820 start.to_point(&display_map).to_offset(&buffer)
13821 ..end.to_point(&display_map).to_offset(&buffer)
13822 };
13823 // ignore if word is already selected
13824 if !word_range.is_empty() && old_range != word_range {
13825 let last_word_range = {
13826 let display_point = buffer
13827 .offset_to_point(old_range.end)
13828 .to_display_point(&display_map);
13829 let Range { start, end } =
13830 movement::surrounding_word(&display_map, display_point);
13831 start.to_point(&display_map).to_offset(&buffer)
13832 ..end.to_point(&display_map).to_offset(&buffer)
13833 };
13834 // only select word if start and end point belongs to same word
13835 if word_range == last_word_range {
13836 selected_larger_node = true;
13837 return Selection {
13838 id: selection.id,
13839 start: word_range.start,
13840 end: word_range.end,
13841 goal: SelectionGoal::None,
13842 reversed: selection.reversed,
13843 };
13844 }
13845 }
13846 }
13847 }
13848
13849 let mut new_range = old_range.clone();
13850 while let Some((_node, containing_range)) =
13851 buffer.syntax_ancestor(new_range.clone())
13852 {
13853 new_range = match containing_range {
13854 MultiOrSingleBufferOffsetRange::Single(_) => break,
13855 MultiOrSingleBufferOffsetRange::Multi(range) => range,
13856 };
13857 if !display_map.intersects_fold(new_range.start)
13858 && !display_map.intersects_fold(new_range.end)
13859 {
13860 break;
13861 }
13862 }
13863
13864 selected_larger_node |= new_range != old_range;
13865 Selection {
13866 id: selection.id,
13867 start: new_range.start,
13868 end: new_range.end,
13869 goal: SelectionGoal::None,
13870 reversed: selection.reversed,
13871 }
13872 })
13873 .collect::<Vec<_>>();
13874
13875 if !selected_larger_node {
13876 return; // don't put this call in the history
13877 }
13878
13879 // scroll based on transformation done to the last selection created by the user
13880 let (last_old, last_new) = old_selections
13881 .last()
13882 .zip(new_selections.last().cloned())
13883 .expect("old_selections isn't empty");
13884
13885 // revert selection
13886 let is_selection_reversed = {
13887 let should_newest_selection_be_reversed = last_old.start != last_new.start;
13888 new_selections.last_mut().expect("checked above").reversed =
13889 should_newest_selection_be_reversed;
13890 should_newest_selection_be_reversed
13891 };
13892
13893 if selected_larger_node {
13894 self.select_syntax_node_history.disable_clearing = true;
13895 self.change_selections(None, window, cx, |s| {
13896 s.select(new_selections.clone());
13897 });
13898 self.select_syntax_node_history.disable_clearing = false;
13899 }
13900
13901 let start_row = last_new.start.to_display_point(&display_map).row().0;
13902 let end_row = last_new.end.to_display_point(&display_map).row().0;
13903 let selection_height = end_row - start_row + 1;
13904 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
13905
13906 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
13907 let scroll_behavior = if fits_on_the_screen {
13908 self.request_autoscroll(Autoscroll::fit(), cx);
13909 SelectSyntaxNodeScrollBehavior::FitSelection
13910 } else if is_selection_reversed {
13911 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13912 SelectSyntaxNodeScrollBehavior::CursorTop
13913 } else {
13914 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13915 SelectSyntaxNodeScrollBehavior::CursorBottom
13916 };
13917
13918 self.select_syntax_node_history.push((
13919 old_selections,
13920 scroll_behavior,
13921 is_selection_reversed,
13922 ));
13923 }
13924
13925 pub fn select_smaller_syntax_node(
13926 &mut self,
13927 _: &SelectSmallerSyntaxNode,
13928 window: &mut Window,
13929 cx: &mut Context<Self>,
13930 ) {
13931 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
13932
13933 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
13934 self.select_syntax_node_history.pop()
13935 {
13936 if let Some(selection) = selections.last_mut() {
13937 selection.reversed = is_selection_reversed;
13938 }
13939
13940 self.select_syntax_node_history.disable_clearing = true;
13941 self.change_selections(None, window, cx, |s| {
13942 s.select(selections.to_vec());
13943 });
13944 self.select_syntax_node_history.disable_clearing = false;
13945
13946 match scroll_behavior {
13947 SelectSyntaxNodeScrollBehavior::CursorTop => {
13948 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
13949 }
13950 SelectSyntaxNodeScrollBehavior::FitSelection => {
13951 self.request_autoscroll(Autoscroll::fit(), cx);
13952 }
13953 SelectSyntaxNodeScrollBehavior::CursorBottom => {
13954 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
13955 }
13956 }
13957 }
13958 }
13959
13960 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
13961 if !EditorSettings::get_global(cx).gutter.runnables {
13962 self.clear_tasks();
13963 return Task::ready(());
13964 }
13965 let project = self.project.as_ref().map(Entity::downgrade);
13966 let task_sources = self.lsp_task_sources(cx);
13967 let multi_buffer = self.buffer.downgrade();
13968 cx.spawn_in(window, async move |editor, cx| {
13969 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
13970 let Some(project) = project.and_then(|p| p.upgrade()) else {
13971 return;
13972 };
13973 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
13974 this.display_map.update(cx, |map, cx| map.snapshot(cx))
13975 }) else {
13976 return;
13977 };
13978
13979 let hide_runnables = project
13980 .update(cx, |project, cx| {
13981 // Do not display any test indicators in non-dev server remote projects.
13982 project.is_via_collab() && project.ssh_connection_string(cx).is_none()
13983 })
13984 .unwrap_or(true);
13985 if hide_runnables {
13986 return;
13987 }
13988 let new_rows =
13989 cx.background_spawn({
13990 let snapshot = display_snapshot.clone();
13991 async move {
13992 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
13993 }
13994 })
13995 .await;
13996 let Ok(lsp_tasks) =
13997 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
13998 else {
13999 return;
14000 };
14001 let lsp_tasks = lsp_tasks.await;
14002
14003 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
14004 lsp_tasks
14005 .into_iter()
14006 .flat_map(|(kind, tasks)| {
14007 tasks.into_iter().filter_map(move |(location, task)| {
14008 Some((kind.clone(), location?, task))
14009 })
14010 })
14011 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
14012 let buffer = location.target.buffer;
14013 let buffer_snapshot = buffer.read(cx).snapshot();
14014 let offset = display_snapshot.buffer_snapshot.excerpts().find_map(
14015 |(excerpt_id, snapshot, _)| {
14016 if snapshot.remote_id() == buffer_snapshot.remote_id() {
14017 display_snapshot
14018 .buffer_snapshot
14019 .anchor_in_excerpt(excerpt_id, location.target.range.start)
14020 } else {
14021 None
14022 }
14023 },
14024 );
14025 if let Some(offset) = offset {
14026 let task_buffer_range =
14027 location.target.range.to_point(&buffer_snapshot);
14028 let context_buffer_range =
14029 task_buffer_range.to_offset(&buffer_snapshot);
14030 let context_range = BufferOffset(context_buffer_range.start)
14031 ..BufferOffset(context_buffer_range.end);
14032
14033 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
14034 .or_insert_with(|| RunnableTasks {
14035 templates: Vec::new(),
14036 offset,
14037 column: task_buffer_range.start.column,
14038 extra_variables: HashMap::default(),
14039 context_range,
14040 })
14041 .templates
14042 .push((kind, task.original_task().clone()));
14043 }
14044
14045 acc
14046 })
14047 }) else {
14048 return;
14049 };
14050
14051 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
14052 buffer.language_settings(cx).tasks.prefer_lsp
14053 }) else {
14054 return;
14055 };
14056
14057 let rows = Self::runnable_rows(
14058 project,
14059 display_snapshot,
14060 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
14061 new_rows,
14062 cx.clone(),
14063 )
14064 .await;
14065 editor
14066 .update(cx, |editor, _| {
14067 editor.clear_tasks();
14068 for (key, mut value) in rows {
14069 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
14070 value.templates.extend(lsp_tasks.templates);
14071 }
14072
14073 editor.insert_tasks(key, value);
14074 }
14075 for (key, value) in lsp_tasks_by_rows {
14076 editor.insert_tasks(key, value);
14077 }
14078 })
14079 .ok();
14080 })
14081 }
14082 fn fetch_runnable_ranges(
14083 snapshot: &DisplaySnapshot,
14084 range: Range<Anchor>,
14085 ) -> Vec<language::RunnableRange> {
14086 snapshot.buffer_snapshot.runnable_ranges(range).collect()
14087 }
14088
14089 fn runnable_rows(
14090 project: Entity<Project>,
14091 snapshot: DisplaySnapshot,
14092 prefer_lsp: bool,
14093 runnable_ranges: Vec<RunnableRange>,
14094 cx: AsyncWindowContext,
14095 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
14096 cx.spawn(async move |cx| {
14097 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
14098 for mut runnable in runnable_ranges {
14099 let Some(tasks) = cx
14100 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
14101 .ok()
14102 else {
14103 continue;
14104 };
14105 let mut tasks = tasks.await;
14106
14107 if prefer_lsp {
14108 tasks.retain(|(task_kind, _)| {
14109 !matches!(task_kind, TaskSourceKind::Language { .. })
14110 });
14111 }
14112 if tasks.is_empty() {
14113 continue;
14114 }
14115
14116 let point = runnable.run_range.start.to_point(&snapshot.buffer_snapshot);
14117 let Some(row) = snapshot
14118 .buffer_snapshot
14119 .buffer_line_for_row(MultiBufferRow(point.row))
14120 .map(|(_, range)| range.start.row)
14121 else {
14122 continue;
14123 };
14124
14125 let context_range =
14126 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
14127 runnable_rows.push((
14128 (runnable.buffer_id, row),
14129 RunnableTasks {
14130 templates: tasks,
14131 offset: snapshot
14132 .buffer_snapshot
14133 .anchor_before(runnable.run_range.start),
14134 context_range,
14135 column: point.column,
14136 extra_variables: runnable.extra_captures,
14137 },
14138 ));
14139 }
14140 runnable_rows
14141 })
14142 }
14143
14144 fn templates_with_tags(
14145 project: &Entity<Project>,
14146 runnable: &mut Runnable,
14147 cx: &mut App,
14148 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
14149 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
14150 let (worktree_id, file) = project
14151 .buffer_for_id(runnable.buffer, cx)
14152 .and_then(|buffer| buffer.read(cx).file())
14153 .map(|file| (file.worktree_id(cx), file.clone()))
14154 .unzip();
14155
14156 (
14157 project.task_store().read(cx).task_inventory().cloned(),
14158 worktree_id,
14159 file,
14160 )
14161 });
14162
14163 let tags = mem::take(&mut runnable.tags);
14164 let language = runnable.language.clone();
14165 cx.spawn(async move |cx| {
14166 let mut templates_with_tags = Vec::new();
14167 if let Some(inventory) = inventory {
14168 for RunnableTag(tag) in tags {
14169 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
14170 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
14171 }) else {
14172 return templates_with_tags;
14173 };
14174 templates_with_tags.extend(new_tasks.await.into_iter().filter(
14175 move |(_, template)| {
14176 template.tags.iter().any(|source_tag| source_tag == &tag)
14177 },
14178 ));
14179 }
14180 }
14181 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
14182
14183 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
14184 // Strongest source wins; if we have worktree tag binding, prefer that to
14185 // global and language bindings;
14186 // if we have a global binding, prefer that to language binding.
14187 let first_mismatch = templates_with_tags
14188 .iter()
14189 .position(|(tag_source, _)| tag_source != leading_tag_source);
14190 if let Some(index) = first_mismatch {
14191 templates_with_tags.truncate(index);
14192 }
14193 }
14194
14195 templates_with_tags
14196 })
14197 }
14198
14199 pub fn move_to_enclosing_bracket(
14200 &mut self,
14201 _: &MoveToEnclosingBracket,
14202 window: &mut Window,
14203 cx: &mut Context<Self>,
14204 ) {
14205 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14206 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14207 s.move_offsets_with(|snapshot, selection| {
14208 let Some(enclosing_bracket_ranges) =
14209 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
14210 else {
14211 return;
14212 };
14213
14214 let mut best_length = usize::MAX;
14215 let mut best_inside = false;
14216 let mut best_in_bracket_range = false;
14217 let mut best_destination = None;
14218 for (open, close) in enclosing_bracket_ranges {
14219 let close = close.to_inclusive();
14220 let length = close.end() - open.start;
14221 let inside = selection.start >= open.end && selection.end <= *close.start();
14222 let in_bracket_range = open.to_inclusive().contains(&selection.head())
14223 || close.contains(&selection.head());
14224
14225 // If best is next to a bracket and current isn't, skip
14226 if !in_bracket_range && best_in_bracket_range {
14227 continue;
14228 }
14229
14230 // Prefer smaller lengths unless best is inside and current isn't
14231 if length > best_length && (best_inside || !inside) {
14232 continue;
14233 }
14234
14235 best_length = length;
14236 best_inside = inside;
14237 best_in_bracket_range = in_bracket_range;
14238 best_destination = Some(
14239 if close.contains(&selection.start) && close.contains(&selection.end) {
14240 if inside { open.end } else { open.start }
14241 } else if inside {
14242 *close.start()
14243 } else {
14244 *close.end()
14245 },
14246 );
14247 }
14248
14249 if let Some(destination) = best_destination {
14250 selection.collapse_to(destination, SelectionGoal::None);
14251 }
14252 })
14253 });
14254 }
14255
14256 pub fn undo_selection(
14257 &mut self,
14258 _: &UndoSelection,
14259 window: &mut Window,
14260 cx: &mut Context<Self>,
14261 ) {
14262 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14263 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
14264 self.selection_history.mode = SelectionHistoryMode::Undoing;
14265 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14266 this.end_selection(window, cx);
14267 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14268 s.select_anchors(entry.selections.to_vec())
14269 });
14270 });
14271 self.selection_history.mode = SelectionHistoryMode::Normal;
14272
14273 self.select_next_state = entry.select_next_state;
14274 self.select_prev_state = entry.select_prev_state;
14275 self.add_selections_state = entry.add_selections_state;
14276 }
14277 }
14278
14279 pub fn redo_selection(
14280 &mut self,
14281 _: &RedoSelection,
14282 window: &mut Window,
14283 cx: &mut Context<Self>,
14284 ) {
14285 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14286 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
14287 self.selection_history.mode = SelectionHistoryMode::Redoing;
14288 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
14289 this.end_selection(window, cx);
14290 this.change_selections(Some(Autoscroll::newest()), window, cx, |s| {
14291 s.select_anchors(entry.selections.to_vec())
14292 });
14293 });
14294 self.selection_history.mode = SelectionHistoryMode::Normal;
14295
14296 self.select_next_state = entry.select_next_state;
14297 self.select_prev_state = entry.select_prev_state;
14298 self.add_selections_state = entry.add_selections_state;
14299 }
14300 }
14301
14302 pub fn expand_excerpts(
14303 &mut self,
14304 action: &ExpandExcerpts,
14305 _: &mut Window,
14306 cx: &mut Context<Self>,
14307 ) {
14308 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
14309 }
14310
14311 pub fn expand_excerpts_down(
14312 &mut self,
14313 action: &ExpandExcerptsDown,
14314 _: &mut Window,
14315 cx: &mut Context<Self>,
14316 ) {
14317 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
14318 }
14319
14320 pub fn expand_excerpts_up(
14321 &mut self,
14322 action: &ExpandExcerptsUp,
14323 _: &mut Window,
14324 cx: &mut Context<Self>,
14325 ) {
14326 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
14327 }
14328
14329 pub fn expand_excerpts_for_direction(
14330 &mut self,
14331 lines: u32,
14332 direction: ExpandExcerptDirection,
14333
14334 cx: &mut Context<Self>,
14335 ) {
14336 let selections = self.selections.disjoint_anchors();
14337
14338 let lines = if lines == 0 {
14339 EditorSettings::get_global(cx).expand_excerpt_lines
14340 } else {
14341 lines
14342 };
14343
14344 self.buffer.update(cx, |buffer, cx| {
14345 let snapshot = buffer.snapshot(cx);
14346 let mut excerpt_ids = selections
14347 .iter()
14348 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
14349 .collect::<Vec<_>>();
14350 excerpt_ids.sort();
14351 excerpt_ids.dedup();
14352 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
14353 })
14354 }
14355
14356 pub fn expand_excerpt(
14357 &mut self,
14358 excerpt: ExcerptId,
14359 direction: ExpandExcerptDirection,
14360 window: &mut Window,
14361 cx: &mut Context<Self>,
14362 ) {
14363 let current_scroll_position = self.scroll_position(cx);
14364 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
14365 let mut should_scroll_up = false;
14366
14367 if direction == ExpandExcerptDirection::Down {
14368 let multi_buffer = self.buffer.read(cx);
14369 let snapshot = multi_buffer.snapshot(cx);
14370 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt) {
14371 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
14372 if let Some(excerpt_range) = snapshot.buffer_range_for_excerpt(excerpt) {
14373 let buffer_snapshot = buffer.read(cx).snapshot();
14374 let excerpt_end_row =
14375 Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
14376 let last_row = buffer_snapshot.max_point().row;
14377 let lines_below = last_row.saturating_sub(excerpt_end_row);
14378 should_scroll_up = lines_below >= lines_to_expand;
14379 }
14380 }
14381 }
14382 }
14383
14384 self.buffer.update(cx, |buffer, cx| {
14385 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
14386 });
14387
14388 if should_scroll_up {
14389 let new_scroll_position =
14390 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as f32);
14391 self.set_scroll_position(new_scroll_position, window, cx);
14392 }
14393 }
14394
14395 pub fn go_to_singleton_buffer_point(
14396 &mut self,
14397 point: Point,
14398 window: &mut Window,
14399 cx: &mut Context<Self>,
14400 ) {
14401 self.go_to_singleton_buffer_range(point..point, window, cx);
14402 }
14403
14404 pub fn go_to_singleton_buffer_range(
14405 &mut self,
14406 range: Range<Point>,
14407 window: &mut Window,
14408 cx: &mut Context<Self>,
14409 ) {
14410 let multibuffer = self.buffer().read(cx);
14411 let Some(buffer) = multibuffer.as_singleton() else {
14412 return;
14413 };
14414 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
14415 return;
14416 };
14417 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
14418 return;
14419 };
14420 self.change_selections(Some(Autoscroll::center()), window, cx, |s| {
14421 s.select_anchor_ranges([start..end])
14422 });
14423 }
14424
14425 pub fn go_to_diagnostic(
14426 &mut self,
14427 _: &GoToDiagnostic,
14428 window: &mut Window,
14429 cx: &mut Context<Self>,
14430 ) {
14431 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14432 self.go_to_diagnostic_impl(Direction::Next, window, cx)
14433 }
14434
14435 pub fn go_to_prev_diagnostic(
14436 &mut self,
14437 _: &GoToPreviousDiagnostic,
14438 window: &mut Window,
14439 cx: &mut Context<Self>,
14440 ) {
14441 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14442 self.go_to_diagnostic_impl(Direction::Prev, window, cx)
14443 }
14444
14445 pub fn go_to_diagnostic_impl(
14446 &mut self,
14447 direction: Direction,
14448 window: &mut Window,
14449 cx: &mut Context<Self>,
14450 ) {
14451 let buffer = self.buffer.read(cx).snapshot(cx);
14452 let selection = self.selections.newest::<usize>(cx);
14453
14454 let mut active_group_id = None;
14455 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics {
14456 if active_group.active_range.start.to_offset(&buffer) == selection.start {
14457 active_group_id = Some(active_group.group_id);
14458 }
14459 }
14460
14461 fn filtered(
14462 snapshot: EditorSnapshot,
14463 diagnostics: impl Iterator<Item = DiagnosticEntry<usize>>,
14464 ) -> impl Iterator<Item = DiagnosticEntry<usize>> {
14465 diagnostics
14466 .filter(|entry| entry.range.start != entry.range.end)
14467 .filter(|entry| !entry.diagnostic.is_unnecessary)
14468 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
14469 }
14470
14471 let snapshot = self.snapshot(window, cx);
14472 let before = filtered(
14473 snapshot.clone(),
14474 buffer
14475 .diagnostics_in_range(0..selection.start)
14476 .filter(|entry| entry.range.start <= selection.start),
14477 );
14478 let after = filtered(
14479 snapshot,
14480 buffer
14481 .diagnostics_in_range(selection.start..buffer.len())
14482 .filter(|entry| entry.range.start >= selection.start),
14483 );
14484
14485 let mut found: Option<DiagnosticEntry<usize>> = None;
14486 if direction == Direction::Prev {
14487 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
14488 {
14489 for diagnostic in prev_diagnostics.into_iter().rev() {
14490 if diagnostic.range.start != selection.start
14491 || active_group_id
14492 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
14493 {
14494 found = Some(diagnostic);
14495 break 'outer;
14496 }
14497 }
14498 }
14499 } else {
14500 for diagnostic in after.chain(before) {
14501 if diagnostic.range.start != selection.start
14502 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
14503 {
14504 found = Some(diagnostic);
14505 break;
14506 }
14507 }
14508 }
14509 let Some(next_diagnostic) = found else {
14510 return;
14511 };
14512
14513 let Some(buffer_id) = buffer.anchor_after(next_diagnostic.range.start).buffer_id else {
14514 return;
14515 };
14516 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14517 s.select_ranges(vec![
14518 next_diagnostic.range.start..next_diagnostic.range.start,
14519 ])
14520 });
14521 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
14522 self.refresh_inline_completion(false, true, window, cx);
14523 }
14524
14525 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
14526 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14527 let snapshot = self.snapshot(window, cx);
14528 let selection = self.selections.newest::<Point>(cx);
14529 self.go_to_hunk_before_or_after_position(
14530 &snapshot,
14531 selection.head(),
14532 Direction::Next,
14533 window,
14534 cx,
14535 );
14536 }
14537
14538 pub fn go_to_hunk_before_or_after_position(
14539 &mut self,
14540 snapshot: &EditorSnapshot,
14541 position: Point,
14542 direction: Direction,
14543 window: &mut Window,
14544 cx: &mut Context<Editor>,
14545 ) {
14546 let row = if direction == Direction::Next {
14547 self.hunk_after_position(snapshot, position)
14548 .map(|hunk| hunk.row_range.start)
14549 } else {
14550 self.hunk_before_position(snapshot, position)
14551 };
14552
14553 if let Some(row) = row {
14554 let destination = Point::new(row.0, 0);
14555 let autoscroll = Autoscroll::center();
14556
14557 self.unfold_ranges(&[destination..destination], false, false, cx);
14558 self.change_selections(Some(autoscroll), window, cx, |s| {
14559 s.select_ranges([destination..destination]);
14560 });
14561 }
14562 }
14563
14564 fn hunk_after_position(
14565 &mut self,
14566 snapshot: &EditorSnapshot,
14567 position: Point,
14568 ) -> Option<MultiBufferDiffHunk> {
14569 snapshot
14570 .buffer_snapshot
14571 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
14572 .find(|hunk| hunk.row_range.start.0 > position.row)
14573 .or_else(|| {
14574 snapshot
14575 .buffer_snapshot
14576 .diff_hunks_in_range(Point::zero()..position)
14577 .find(|hunk| hunk.row_range.end.0 < position.row)
14578 })
14579 }
14580
14581 fn go_to_prev_hunk(
14582 &mut self,
14583 _: &GoToPreviousHunk,
14584 window: &mut Window,
14585 cx: &mut Context<Self>,
14586 ) {
14587 self.hide_mouse_cursor(&HideMouseCursorOrigin::MovementAction);
14588 let snapshot = self.snapshot(window, cx);
14589 let selection = self.selections.newest::<Point>(cx);
14590 self.go_to_hunk_before_or_after_position(
14591 &snapshot,
14592 selection.head(),
14593 Direction::Prev,
14594 window,
14595 cx,
14596 );
14597 }
14598
14599 fn hunk_before_position(
14600 &mut self,
14601 snapshot: &EditorSnapshot,
14602 position: Point,
14603 ) -> Option<MultiBufferRow> {
14604 snapshot
14605 .buffer_snapshot
14606 .diff_hunk_before(position)
14607 .or_else(|| snapshot.buffer_snapshot.diff_hunk_before(Point::MAX))
14608 }
14609
14610 fn go_to_next_change(
14611 &mut self,
14612 _: &GoToNextChange,
14613 window: &mut Window,
14614 cx: &mut Context<Self>,
14615 ) {
14616 if let Some(selections) = self
14617 .change_list
14618 .next_change(1, Direction::Next)
14619 .map(|s| s.to_vec())
14620 {
14621 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14622 let map = s.display_map();
14623 s.select_display_ranges(selections.iter().map(|a| {
14624 let point = a.to_display_point(&map);
14625 point..point
14626 }))
14627 })
14628 }
14629 }
14630
14631 fn go_to_previous_change(
14632 &mut self,
14633 _: &GoToPreviousChange,
14634 window: &mut Window,
14635 cx: &mut Context<Self>,
14636 ) {
14637 if let Some(selections) = self
14638 .change_list
14639 .next_change(1, Direction::Prev)
14640 .map(|s| s.to_vec())
14641 {
14642 self.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
14643 let map = s.display_map();
14644 s.select_display_ranges(selections.iter().map(|a| {
14645 let point = a.to_display_point(&map);
14646 point..point
14647 }))
14648 })
14649 }
14650 }
14651
14652 fn go_to_line<T: 'static>(
14653 &mut self,
14654 position: Anchor,
14655 highlight_color: Option<Hsla>,
14656 window: &mut Window,
14657 cx: &mut Context<Self>,
14658 ) {
14659 let snapshot = self.snapshot(window, cx).display_snapshot;
14660 let position = position.to_point(&snapshot.buffer_snapshot);
14661 let start = snapshot
14662 .buffer_snapshot
14663 .clip_point(Point::new(position.row, 0), Bias::Left);
14664 let end = start + Point::new(1, 0);
14665 let start = snapshot.buffer_snapshot.anchor_before(start);
14666 let end = snapshot.buffer_snapshot.anchor_before(end);
14667
14668 self.highlight_rows::<T>(
14669 start..end,
14670 highlight_color
14671 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
14672 Default::default(),
14673 cx,
14674 );
14675
14676 if self.buffer.read(cx).is_singleton() {
14677 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
14678 }
14679 }
14680
14681 pub fn go_to_definition(
14682 &mut self,
14683 _: &GoToDefinition,
14684 window: &mut Window,
14685 cx: &mut Context<Self>,
14686 ) -> Task<Result<Navigated>> {
14687 let definition =
14688 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
14689 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
14690 cx.spawn_in(window, async move |editor, cx| {
14691 if definition.await? == Navigated::Yes {
14692 return Ok(Navigated::Yes);
14693 }
14694 match fallback_strategy {
14695 GoToDefinitionFallback::None => Ok(Navigated::No),
14696 GoToDefinitionFallback::FindAllReferences => {
14697 match editor.update_in(cx, |editor, window, cx| {
14698 editor.find_all_references(&FindAllReferences, window, cx)
14699 })? {
14700 Some(references) => references.await,
14701 None => Ok(Navigated::No),
14702 }
14703 }
14704 }
14705 })
14706 }
14707
14708 pub fn go_to_declaration(
14709 &mut self,
14710 _: &GoToDeclaration,
14711 window: &mut Window,
14712 cx: &mut Context<Self>,
14713 ) -> Task<Result<Navigated>> {
14714 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
14715 }
14716
14717 pub fn go_to_declaration_split(
14718 &mut self,
14719 _: &GoToDeclaration,
14720 window: &mut Window,
14721 cx: &mut Context<Self>,
14722 ) -> Task<Result<Navigated>> {
14723 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
14724 }
14725
14726 pub fn go_to_implementation(
14727 &mut self,
14728 _: &GoToImplementation,
14729 window: &mut Window,
14730 cx: &mut Context<Self>,
14731 ) -> Task<Result<Navigated>> {
14732 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
14733 }
14734
14735 pub fn go_to_implementation_split(
14736 &mut self,
14737 _: &GoToImplementationSplit,
14738 window: &mut Window,
14739 cx: &mut Context<Self>,
14740 ) -> Task<Result<Navigated>> {
14741 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
14742 }
14743
14744 pub fn go_to_type_definition(
14745 &mut self,
14746 _: &GoToTypeDefinition,
14747 window: &mut Window,
14748 cx: &mut Context<Self>,
14749 ) -> Task<Result<Navigated>> {
14750 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
14751 }
14752
14753 pub fn go_to_definition_split(
14754 &mut self,
14755 _: &GoToDefinitionSplit,
14756 window: &mut Window,
14757 cx: &mut Context<Self>,
14758 ) -> Task<Result<Navigated>> {
14759 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
14760 }
14761
14762 pub fn go_to_type_definition_split(
14763 &mut self,
14764 _: &GoToTypeDefinitionSplit,
14765 window: &mut Window,
14766 cx: &mut Context<Self>,
14767 ) -> Task<Result<Navigated>> {
14768 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
14769 }
14770
14771 fn go_to_definition_of_kind(
14772 &mut self,
14773 kind: GotoDefinitionKind,
14774 split: bool,
14775 window: &mut Window,
14776 cx: &mut Context<Self>,
14777 ) -> Task<Result<Navigated>> {
14778 let Some(provider) = self.semantics_provider.clone() else {
14779 return Task::ready(Ok(Navigated::No));
14780 };
14781 let head = self.selections.newest::<usize>(cx).head();
14782 let buffer = self.buffer.read(cx);
14783 let (buffer, head) = if let Some(text_anchor) = buffer.text_anchor_for_position(head, cx) {
14784 text_anchor
14785 } else {
14786 return Task::ready(Ok(Navigated::No));
14787 };
14788
14789 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
14790 return Task::ready(Ok(Navigated::No));
14791 };
14792
14793 cx.spawn_in(window, async move |editor, cx| {
14794 let definitions = definitions.await?;
14795 let navigated = editor
14796 .update_in(cx, |editor, window, cx| {
14797 editor.navigate_to_hover_links(
14798 Some(kind),
14799 definitions
14800 .into_iter()
14801 .filter(|location| {
14802 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
14803 })
14804 .map(HoverLink::Text)
14805 .collect::<Vec<_>>(),
14806 split,
14807 window,
14808 cx,
14809 )
14810 })?
14811 .await?;
14812 anyhow::Ok(navigated)
14813 })
14814 }
14815
14816 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
14817 let selection = self.selections.newest_anchor();
14818 let head = selection.head();
14819 let tail = selection.tail();
14820
14821 let Some((buffer, start_position)) =
14822 self.buffer.read(cx).text_anchor_for_position(head, cx)
14823 else {
14824 return;
14825 };
14826
14827 let end_position = if head != tail {
14828 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
14829 return;
14830 };
14831 Some(pos)
14832 } else {
14833 None
14834 };
14835
14836 let url_finder = cx.spawn_in(window, async move |editor, cx| {
14837 let url = if let Some(end_pos) = end_position {
14838 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
14839 } else {
14840 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
14841 };
14842
14843 if let Some(url) = url {
14844 editor.update(cx, |_, cx| {
14845 cx.open_url(&url);
14846 })
14847 } else {
14848 Ok(())
14849 }
14850 });
14851
14852 url_finder.detach();
14853 }
14854
14855 pub fn open_selected_filename(
14856 &mut self,
14857 _: &OpenSelectedFilename,
14858 window: &mut Window,
14859 cx: &mut Context<Self>,
14860 ) {
14861 let Some(workspace) = self.workspace() else {
14862 return;
14863 };
14864
14865 let position = self.selections.newest_anchor().head();
14866
14867 let Some((buffer, buffer_position)) =
14868 self.buffer.read(cx).text_anchor_for_position(position, cx)
14869 else {
14870 return;
14871 };
14872
14873 let project = self.project.clone();
14874
14875 cx.spawn_in(window, async move |_, cx| {
14876 let result = find_file(&buffer, project, buffer_position, cx).await;
14877
14878 if let Some((_, path)) = result {
14879 workspace
14880 .update_in(cx, |workspace, window, cx| {
14881 workspace.open_resolved_path(path, window, cx)
14882 })?
14883 .await?;
14884 }
14885 anyhow::Ok(())
14886 })
14887 .detach();
14888 }
14889
14890 pub(crate) fn navigate_to_hover_links(
14891 &mut self,
14892 kind: Option<GotoDefinitionKind>,
14893 mut definitions: Vec<HoverLink>,
14894 split: bool,
14895 window: &mut Window,
14896 cx: &mut Context<Editor>,
14897 ) -> Task<Result<Navigated>> {
14898 // If there is one definition, just open it directly
14899 if definitions.len() == 1 {
14900 let definition = definitions.pop().unwrap();
14901
14902 enum TargetTaskResult {
14903 Location(Option<Location>),
14904 AlreadyNavigated,
14905 }
14906
14907 let target_task = match definition {
14908 HoverLink::Text(link) => {
14909 Task::ready(anyhow::Ok(TargetTaskResult::Location(Some(link.target))))
14910 }
14911 HoverLink::InlayHint(lsp_location, server_id) => {
14912 let computation =
14913 self.compute_target_location(lsp_location, server_id, window, cx);
14914 cx.background_spawn(async move {
14915 let location = computation.await?;
14916 Ok(TargetTaskResult::Location(location))
14917 })
14918 }
14919 HoverLink::Url(url) => {
14920 cx.open_url(&url);
14921 Task::ready(Ok(TargetTaskResult::AlreadyNavigated))
14922 }
14923 HoverLink::File(path) => {
14924 if let Some(workspace) = self.workspace() {
14925 cx.spawn_in(window, async move |_, cx| {
14926 workspace
14927 .update_in(cx, |workspace, window, cx| {
14928 workspace.open_resolved_path(path, window, cx)
14929 })?
14930 .await
14931 .map(|_| TargetTaskResult::AlreadyNavigated)
14932 })
14933 } else {
14934 Task::ready(Ok(TargetTaskResult::Location(None)))
14935 }
14936 }
14937 };
14938 cx.spawn_in(window, async move |editor, cx| {
14939 let target = match target_task.await.context("target resolution task")? {
14940 TargetTaskResult::AlreadyNavigated => return Ok(Navigated::Yes),
14941 TargetTaskResult::Location(None) => return Ok(Navigated::No),
14942 TargetTaskResult::Location(Some(target)) => target,
14943 };
14944
14945 editor.update_in(cx, |editor, window, cx| {
14946 let Some(workspace) = editor.workspace() else {
14947 return Navigated::No;
14948 };
14949 let pane = workspace.read(cx).active_pane().clone();
14950
14951 let range = target.range.to_point(target.buffer.read(cx));
14952 let range = editor.range_for_match(&range);
14953 let range = collapse_multiline_range(range);
14954
14955 if !split
14956 && Some(&target.buffer) == editor.buffer.read(cx).as_singleton().as_ref()
14957 {
14958 editor.go_to_singleton_buffer_range(range.clone(), window, cx);
14959 } else {
14960 window.defer(cx, move |window, cx| {
14961 let target_editor: Entity<Self> =
14962 workspace.update(cx, |workspace, cx| {
14963 let pane = if split {
14964 workspace.adjacent_pane(window, cx)
14965 } else {
14966 workspace.active_pane().clone()
14967 };
14968
14969 workspace.open_project_item(
14970 pane,
14971 target.buffer.clone(),
14972 true,
14973 true,
14974 window,
14975 cx,
14976 )
14977 });
14978 target_editor.update(cx, |target_editor, cx| {
14979 // When selecting a definition in a different buffer, disable the nav history
14980 // to avoid creating a history entry at the previous cursor location.
14981 pane.update(cx, |pane, _| pane.disable_history());
14982 target_editor.go_to_singleton_buffer_range(range, window, cx);
14983 pane.update(cx, |pane, _| pane.enable_history());
14984 });
14985 });
14986 }
14987 Navigated::Yes
14988 })
14989 })
14990 } else if !definitions.is_empty() {
14991 cx.spawn_in(window, async move |editor, cx| {
14992 let (title, location_tasks, workspace) = editor
14993 .update_in(cx, |editor, window, cx| {
14994 let tab_kind = match kind {
14995 Some(GotoDefinitionKind::Implementation) => "Implementations",
14996 _ => "Definitions",
14997 };
14998 let title = definitions
14999 .iter()
15000 .find_map(|definition| match definition {
15001 HoverLink::Text(link) => link.origin.as_ref().map(|origin| {
15002 let buffer = origin.buffer.read(cx);
15003 format!(
15004 "{} for {}",
15005 tab_kind,
15006 buffer
15007 .text_for_range(origin.range.clone())
15008 .collect::<String>()
15009 )
15010 }),
15011 HoverLink::InlayHint(_, _) => None,
15012 HoverLink::Url(_) => None,
15013 HoverLink::File(_) => None,
15014 })
15015 .unwrap_or(tab_kind.to_string());
15016 let location_tasks = definitions
15017 .into_iter()
15018 .map(|definition| match definition {
15019 HoverLink::Text(link) => Task::ready(Ok(Some(link.target))),
15020 HoverLink::InlayHint(lsp_location, server_id) => editor
15021 .compute_target_location(lsp_location, server_id, window, cx),
15022 HoverLink::Url(_) => Task::ready(Ok(None)),
15023 HoverLink::File(_) => Task::ready(Ok(None)),
15024 })
15025 .collect::<Vec<_>>();
15026 (title, location_tasks, editor.workspace().clone())
15027 })
15028 .context("location tasks preparation")?;
15029
15030 let locations = future::join_all(location_tasks)
15031 .await
15032 .into_iter()
15033 .filter_map(|location| location.transpose())
15034 .collect::<Result<_>>()
15035 .context("location tasks")?;
15036
15037 let Some(workspace) = workspace else {
15038 return Ok(Navigated::No);
15039 };
15040 let opened = workspace
15041 .update_in(cx, |workspace, window, cx| {
15042 Self::open_locations_in_multibuffer(
15043 workspace,
15044 locations,
15045 title,
15046 split,
15047 MultibufferSelectionMode::First,
15048 window,
15049 cx,
15050 )
15051 })
15052 .ok();
15053
15054 anyhow::Ok(Navigated::from_bool(opened.is_some()))
15055 })
15056 } else {
15057 Task::ready(Ok(Navigated::No))
15058 }
15059 }
15060
15061 fn compute_target_location(
15062 &self,
15063 lsp_location: lsp::Location,
15064 server_id: LanguageServerId,
15065 window: &mut Window,
15066 cx: &mut Context<Self>,
15067 ) -> Task<anyhow::Result<Option<Location>>> {
15068 let Some(project) = self.project.clone() else {
15069 return Task::ready(Ok(None));
15070 };
15071
15072 cx.spawn_in(window, async move |editor, cx| {
15073 let location_task = editor.update(cx, |_, cx| {
15074 project.update(cx, |project, cx| {
15075 let language_server_name = project
15076 .language_server_statuses(cx)
15077 .find(|(id, _)| server_id == *id)
15078 .map(|(_, status)| LanguageServerName::from(status.name.as_str()));
15079 language_server_name.map(|language_server_name| {
15080 project.open_local_buffer_via_lsp(
15081 lsp_location.uri.clone(),
15082 server_id,
15083 language_server_name,
15084 cx,
15085 )
15086 })
15087 })
15088 })?;
15089 let location = match location_task {
15090 Some(task) => Some({
15091 let target_buffer_handle = task.await.context("open local buffer")?;
15092 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
15093 let target_start = target_buffer
15094 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
15095 let target_end = target_buffer
15096 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
15097 target_buffer.anchor_after(target_start)
15098 ..target_buffer.anchor_before(target_end)
15099 })?;
15100 Location {
15101 buffer: target_buffer_handle,
15102 range,
15103 }
15104 }),
15105 None => None,
15106 };
15107 Ok(location)
15108 })
15109 }
15110
15111 pub fn find_all_references(
15112 &mut self,
15113 _: &FindAllReferences,
15114 window: &mut Window,
15115 cx: &mut Context<Self>,
15116 ) -> Option<Task<Result<Navigated>>> {
15117 let selection = self.selections.newest::<usize>(cx);
15118 let multi_buffer = self.buffer.read(cx);
15119 let head = selection.head();
15120
15121 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
15122 let head_anchor = multi_buffer_snapshot.anchor_at(
15123 head,
15124 if head < selection.tail() {
15125 Bias::Right
15126 } else {
15127 Bias::Left
15128 },
15129 );
15130
15131 match self
15132 .find_all_references_task_sources
15133 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15134 {
15135 Ok(_) => {
15136 log::info!(
15137 "Ignoring repeated FindAllReferences invocation with the position of already running task"
15138 );
15139 return None;
15140 }
15141 Err(i) => {
15142 self.find_all_references_task_sources.insert(i, head_anchor);
15143 }
15144 }
15145
15146 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
15147 let workspace = self.workspace()?;
15148 let project = workspace.read(cx).project().clone();
15149 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
15150 Some(cx.spawn_in(window, async move |editor, cx| {
15151 let _cleanup = cx.on_drop(&editor, move |editor, _| {
15152 if let Ok(i) = editor
15153 .find_all_references_task_sources
15154 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
15155 {
15156 editor.find_all_references_task_sources.remove(i);
15157 }
15158 });
15159
15160 let locations = references.await?;
15161 if locations.is_empty() {
15162 return anyhow::Ok(Navigated::No);
15163 }
15164
15165 workspace.update_in(cx, |workspace, window, cx| {
15166 let title = locations
15167 .first()
15168 .as_ref()
15169 .map(|location| {
15170 let buffer = location.buffer.read(cx);
15171 format!(
15172 "References to `{}`",
15173 buffer
15174 .text_for_range(location.range.clone())
15175 .collect::<String>()
15176 )
15177 })
15178 .unwrap();
15179 Self::open_locations_in_multibuffer(
15180 workspace,
15181 locations,
15182 title,
15183 false,
15184 MultibufferSelectionMode::First,
15185 window,
15186 cx,
15187 );
15188 Navigated::Yes
15189 })
15190 }))
15191 }
15192
15193 /// Opens a multibuffer with the given project locations in it
15194 pub fn open_locations_in_multibuffer(
15195 workspace: &mut Workspace,
15196 mut locations: Vec<Location>,
15197 title: String,
15198 split: bool,
15199 multibuffer_selection_mode: MultibufferSelectionMode,
15200 window: &mut Window,
15201 cx: &mut Context<Workspace>,
15202 ) {
15203 // If there are multiple definitions, open them in a multibuffer
15204 locations.sort_by_key(|location| location.buffer.read(cx).remote_id());
15205 let mut locations = locations.into_iter().peekable();
15206 let mut ranges: Vec<Range<Anchor>> = Vec::new();
15207 let capability = workspace.project().read(cx).capability();
15208
15209 let excerpt_buffer = cx.new(|cx| {
15210 let mut multibuffer = MultiBuffer::new(capability);
15211 while let Some(location) = locations.next() {
15212 let buffer = location.buffer.read(cx);
15213 let mut ranges_for_buffer = Vec::new();
15214 let range = location.range.to_point(buffer);
15215 ranges_for_buffer.push(range.clone());
15216
15217 while let Some(next_location) = locations.peek() {
15218 if next_location.buffer == location.buffer {
15219 ranges_for_buffer.push(next_location.range.to_point(buffer));
15220 locations.next();
15221 } else {
15222 break;
15223 }
15224 }
15225
15226 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
15227 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
15228 PathKey::for_buffer(&location.buffer, cx),
15229 location.buffer.clone(),
15230 ranges_for_buffer,
15231 DEFAULT_MULTIBUFFER_CONTEXT,
15232 cx,
15233 );
15234 ranges.extend(new_ranges)
15235 }
15236
15237 multibuffer.with_title(title)
15238 });
15239
15240 let editor = cx.new(|cx| {
15241 Editor::for_multibuffer(
15242 excerpt_buffer,
15243 Some(workspace.project().clone()),
15244 window,
15245 cx,
15246 )
15247 });
15248 editor.update(cx, |editor, cx| {
15249 match multibuffer_selection_mode {
15250 MultibufferSelectionMode::First => {
15251 if let Some(first_range) = ranges.first() {
15252 editor.change_selections(None, window, cx, |selections| {
15253 selections.clear_disjoint();
15254 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
15255 });
15256 }
15257 editor.highlight_background::<Self>(
15258 &ranges,
15259 |theme| theme.editor_highlighted_line_background,
15260 cx,
15261 );
15262 }
15263 MultibufferSelectionMode::All => {
15264 editor.change_selections(None, window, cx, |selections| {
15265 selections.clear_disjoint();
15266 selections.select_anchor_ranges(ranges);
15267 });
15268 }
15269 }
15270 editor.register_buffers_with_language_servers(cx);
15271 });
15272
15273 let item = Box::new(editor);
15274 let item_id = item.item_id();
15275
15276 if split {
15277 workspace.split_item(SplitDirection::Right, item.clone(), window, cx);
15278 } else {
15279 if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
15280 let (preview_item_id, preview_item_idx) =
15281 workspace.active_pane().read_with(cx, |pane, _| {
15282 (pane.preview_item_id(), pane.preview_item_idx())
15283 });
15284
15285 workspace.add_item_to_active_pane(item.clone(), preview_item_idx, true, window, cx);
15286
15287 if let Some(preview_item_id) = preview_item_id {
15288 workspace.active_pane().update(cx, |pane, cx| {
15289 pane.remove_item(preview_item_id, false, false, window, cx);
15290 });
15291 }
15292 } else {
15293 workspace.add_item_to_active_pane(item.clone(), None, true, window, cx);
15294 }
15295 }
15296 workspace.active_pane().update(cx, |pane, cx| {
15297 pane.set_preview_item_id(Some(item_id), cx);
15298 });
15299 }
15300
15301 pub fn rename(
15302 &mut self,
15303 _: &Rename,
15304 window: &mut Window,
15305 cx: &mut Context<Self>,
15306 ) -> Option<Task<Result<()>>> {
15307 use language::ToOffset as _;
15308
15309 let provider = self.semantics_provider.clone()?;
15310 let selection = self.selections.newest_anchor().clone();
15311 let (cursor_buffer, cursor_buffer_position) = self
15312 .buffer
15313 .read(cx)
15314 .text_anchor_for_position(selection.head(), cx)?;
15315 let (tail_buffer, cursor_buffer_position_end) = self
15316 .buffer
15317 .read(cx)
15318 .text_anchor_for_position(selection.tail(), cx)?;
15319 if tail_buffer != cursor_buffer {
15320 return None;
15321 }
15322
15323 let snapshot = cursor_buffer.read(cx).snapshot();
15324 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
15325 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
15326 let prepare_rename = provider
15327 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
15328 .unwrap_or_else(|| Task::ready(Ok(None)));
15329 drop(snapshot);
15330
15331 Some(cx.spawn_in(window, async move |this, cx| {
15332 let rename_range = if let Some(range) = prepare_rename.await? {
15333 Some(range)
15334 } else {
15335 this.update(cx, |this, cx| {
15336 let buffer = this.buffer.read(cx).snapshot(cx);
15337 let mut buffer_highlights = this
15338 .document_highlights_for_position(selection.head(), &buffer)
15339 .filter(|highlight| {
15340 highlight.start.excerpt_id == selection.head().excerpt_id
15341 && highlight.end.excerpt_id == selection.head().excerpt_id
15342 });
15343 buffer_highlights
15344 .next()
15345 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
15346 })?
15347 };
15348 if let Some(rename_range) = rename_range {
15349 this.update_in(cx, |this, window, cx| {
15350 let snapshot = cursor_buffer.read(cx).snapshot();
15351 let rename_buffer_range = rename_range.to_offset(&snapshot);
15352 let cursor_offset_in_rename_range =
15353 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
15354 let cursor_offset_in_rename_range_end =
15355 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
15356
15357 this.take_rename(false, window, cx);
15358 let buffer = this.buffer.read(cx).read(cx);
15359 let cursor_offset = selection.head().to_offset(&buffer);
15360 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
15361 let rename_end = rename_start + rename_buffer_range.len();
15362 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
15363 let mut old_highlight_id = None;
15364 let old_name: Arc<str> = buffer
15365 .chunks(rename_start..rename_end, true)
15366 .map(|chunk| {
15367 if old_highlight_id.is_none() {
15368 old_highlight_id = chunk.syntax_highlight_id;
15369 }
15370 chunk.text
15371 })
15372 .collect::<String>()
15373 .into();
15374
15375 drop(buffer);
15376
15377 // Position the selection in the rename editor so that it matches the current selection.
15378 this.show_local_selections = false;
15379 let rename_editor = cx.new(|cx| {
15380 let mut editor = Editor::single_line(window, cx);
15381 editor.buffer.update(cx, |buffer, cx| {
15382 buffer.edit([(0..0, old_name.clone())], None, cx)
15383 });
15384 let rename_selection_range = match cursor_offset_in_rename_range
15385 .cmp(&cursor_offset_in_rename_range_end)
15386 {
15387 Ordering::Equal => {
15388 editor.select_all(&SelectAll, window, cx);
15389 return editor;
15390 }
15391 Ordering::Less => {
15392 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
15393 }
15394 Ordering::Greater => {
15395 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
15396 }
15397 };
15398 if rename_selection_range.end > old_name.len() {
15399 editor.select_all(&SelectAll, window, cx);
15400 } else {
15401 editor.change_selections(Some(Autoscroll::fit()), window, cx, |s| {
15402 s.select_ranges([rename_selection_range]);
15403 });
15404 }
15405 editor
15406 });
15407 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
15408 if e == &EditorEvent::Focused {
15409 cx.emit(EditorEvent::FocusedIn)
15410 }
15411 })
15412 .detach();
15413
15414 let write_highlights =
15415 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
15416 let read_highlights =
15417 this.clear_background_highlights::<DocumentHighlightRead>(cx);
15418 let ranges = write_highlights
15419 .iter()
15420 .flat_map(|(_, ranges)| ranges.iter())
15421 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
15422 .cloned()
15423 .collect();
15424
15425 this.highlight_text::<Rename>(
15426 ranges,
15427 HighlightStyle {
15428 fade_out: Some(0.6),
15429 ..Default::default()
15430 },
15431 cx,
15432 );
15433 let rename_focus_handle = rename_editor.focus_handle(cx);
15434 window.focus(&rename_focus_handle);
15435 let block_id = this.insert_blocks(
15436 [BlockProperties {
15437 style: BlockStyle::Flex,
15438 placement: BlockPlacement::Below(range.start),
15439 height: Some(1),
15440 render: Arc::new({
15441 let rename_editor = rename_editor.clone();
15442 move |cx: &mut BlockContext| {
15443 let mut text_style = cx.editor_style.text.clone();
15444 if let Some(highlight_style) = old_highlight_id
15445 .and_then(|h| h.style(&cx.editor_style.syntax))
15446 {
15447 text_style = text_style.highlight(highlight_style);
15448 }
15449 div()
15450 .block_mouse_except_scroll()
15451 .pl(cx.anchor_x)
15452 .child(EditorElement::new(
15453 &rename_editor,
15454 EditorStyle {
15455 background: cx.theme().system().transparent,
15456 local_player: cx.editor_style.local_player,
15457 text: text_style,
15458 scrollbar_width: cx.editor_style.scrollbar_width,
15459 syntax: cx.editor_style.syntax.clone(),
15460 status: cx.editor_style.status.clone(),
15461 inlay_hints_style: HighlightStyle {
15462 font_weight: Some(FontWeight::BOLD),
15463 ..make_inlay_hints_style(cx.app)
15464 },
15465 inline_completion_styles: make_suggestion_styles(
15466 cx.app,
15467 ),
15468 ..EditorStyle::default()
15469 },
15470 ))
15471 .into_any_element()
15472 }
15473 }),
15474 priority: 0,
15475 render_in_minimap: true,
15476 }],
15477 Some(Autoscroll::fit()),
15478 cx,
15479 )[0];
15480 this.pending_rename = Some(RenameState {
15481 range,
15482 old_name,
15483 editor: rename_editor,
15484 block_id,
15485 });
15486 })?;
15487 }
15488
15489 Ok(())
15490 }))
15491 }
15492
15493 pub fn confirm_rename(
15494 &mut self,
15495 _: &ConfirmRename,
15496 window: &mut Window,
15497 cx: &mut Context<Self>,
15498 ) -> Option<Task<Result<()>>> {
15499 let rename = self.take_rename(false, window, cx)?;
15500 let workspace = self.workspace()?.downgrade();
15501 let (buffer, start) = self
15502 .buffer
15503 .read(cx)
15504 .text_anchor_for_position(rename.range.start, cx)?;
15505 let (end_buffer, _) = self
15506 .buffer
15507 .read(cx)
15508 .text_anchor_for_position(rename.range.end, cx)?;
15509 if buffer != end_buffer {
15510 return None;
15511 }
15512
15513 let old_name = rename.old_name;
15514 let new_name = rename.editor.read(cx).text(cx);
15515
15516 let rename = self.semantics_provider.as_ref()?.perform_rename(
15517 &buffer,
15518 start,
15519 new_name.clone(),
15520 cx,
15521 )?;
15522
15523 Some(cx.spawn_in(window, async move |editor, cx| {
15524 let project_transaction = rename.await?;
15525 Self::open_project_transaction(
15526 &editor,
15527 workspace,
15528 project_transaction,
15529 format!("Rename: {} → {}", old_name, new_name),
15530 cx,
15531 )
15532 .await?;
15533
15534 editor.update(cx, |editor, cx| {
15535 editor.refresh_document_highlights(cx);
15536 })?;
15537 Ok(())
15538 }))
15539 }
15540
15541 fn take_rename(
15542 &mut self,
15543 moving_cursor: bool,
15544 window: &mut Window,
15545 cx: &mut Context<Self>,
15546 ) -> Option<RenameState> {
15547 let rename = self.pending_rename.take()?;
15548 if rename.editor.focus_handle(cx).is_focused(window) {
15549 window.focus(&self.focus_handle);
15550 }
15551
15552 self.remove_blocks(
15553 [rename.block_id].into_iter().collect(),
15554 Some(Autoscroll::fit()),
15555 cx,
15556 );
15557 self.clear_highlights::<Rename>(cx);
15558 self.show_local_selections = true;
15559
15560 if moving_cursor {
15561 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
15562 editor.selections.newest::<usize>(cx).head()
15563 });
15564
15565 // Update the selection to match the position of the selection inside
15566 // the rename editor.
15567 let snapshot = self.buffer.read(cx).read(cx);
15568 let rename_range = rename.range.to_offset(&snapshot);
15569 let cursor_in_editor = snapshot
15570 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
15571 .min(rename_range.end);
15572 drop(snapshot);
15573
15574 self.change_selections(None, window, cx, |s| {
15575 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
15576 });
15577 } else {
15578 self.refresh_document_highlights(cx);
15579 }
15580
15581 Some(rename)
15582 }
15583
15584 pub fn pending_rename(&self) -> Option<&RenameState> {
15585 self.pending_rename.as_ref()
15586 }
15587
15588 fn format(
15589 &mut self,
15590 _: &Format,
15591 window: &mut Window,
15592 cx: &mut Context<Self>,
15593 ) -> Option<Task<Result<()>>> {
15594 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15595
15596 let project = match &self.project {
15597 Some(project) => project.clone(),
15598 None => return None,
15599 };
15600
15601 Some(self.perform_format(
15602 project,
15603 FormatTrigger::Manual,
15604 FormatTarget::Buffers,
15605 window,
15606 cx,
15607 ))
15608 }
15609
15610 fn format_selections(
15611 &mut self,
15612 _: &FormatSelections,
15613 window: &mut Window,
15614 cx: &mut Context<Self>,
15615 ) -> Option<Task<Result<()>>> {
15616 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15617
15618 let project = match &self.project {
15619 Some(project) => project.clone(),
15620 None => return None,
15621 };
15622
15623 let ranges = self
15624 .selections
15625 .all_adjusted(cx)
15626 .into_iter()
15627 .map(|selection| selection.range())
15628 .collect_vec();
15629
15630 Some(self.perform_format(
15631 project,
15632 FormatTrigger::Manual,
15633 FormatTarget::Ranges(ranges),
15634 window,
15635 cx,
15636 ))
15637 }
15638
15639 fn save_non_dirty_buffers(&self, cx: &App) -> bool {
15640 self.is_singleton(cx) && EditorSettings::get_global(cx).save_non_dirty_buffers
15641 }
15642
15643 fn perform_format(
15644 &mut self,
15645 project: Entity<Project>,
15646 trigger: FormatTrigger,
15647 target: FormatTarget,
15648 window: &mut Window,
15649 cx: &mut Context<Self>,
15650 ) -> Task<Result<()>> {
15651 let buffer = self.buffer.clone();
15652 let (buffers, target) = match target {
15653 FormatTarget::Buffers => {
15654 let mut buffers = buffer.read(cx).all_buffers();
15655 if trigger == FormatTrigger::Save && !self.save_non_dirty_buffers(cx) {
15656 buffers.retain(|buffer| buffer.read(cx).is_dirty());
15657 }
15658 (buffers, LspFormatTarget::Buffers)
15659 }
15660 FormatTarget::Ranges(selection_ranges) => {
15661 let multi_buffer = buffer.read(cx);
15662 let snapshot = multi_buffer.read(cx);
15663 let mut buffers = HashSet::default();
15664 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
15665 BTreeMap::new();
15666 for selection_range in selection_ranges {
15667 for (buffer, buffer_range, _) in
15668 snapshot.range_to_buffer_ranges(selection_range)
15669 {
15670 let buffer_id = buffer.remote_id();
15671 let start = buffer.anchor_before(buffer_range.start);
15672 let end = buffer.anchor_after(buffer_range.end);
15673 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
15674 buffer_id_to_ranges
15675 .entry(buffer_id)
15676 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
15677 .or_insert_with(|| vec![start..end]);
15678 }
15679 }
15680 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
15681 }
15682 };
15683
15684 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
15685 let selections_prev = transaction_id_prev
15686 .and_then(|transaction_id_prev| {
15687 // default to selections as they were after the last edit, if we have them,
15688 // instead of how they are now.
15689 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
15690 // will take you back to where you made the last edit, instead of staying where you scrolled
15691 self.selection_history
15692 .transaction(transaction_id_prev)
15693 .map(|t| t.0.clone())
15694 })
15695 .unwrap_or_else(|| {
15696 log::info!("Failed to determine selections from before format. Falling back to selections when format was initiated");
15697 self.selections.disjoint_anchors()
15698 });
15699
15700 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
15701 let format = project.update(cx, |project, cx| {
15702 project.format(buffers, target, true, trigger, cx)
15703 });
15704
15705 cx.spawn_in(window, async move |editor, cx| {
15706 let transaction = futures::select_biased! {
15707 transaction = format.log_err().fuse() => transaction,
15708 () = timeout => {
15709 log::warn!("timed out waiting for formatting");
15710 None
15711 }
15712 };
15713
15714 buffer
15715 .update(cx, |buffer, cx| {
15716 if let Some(transaction) = transaction {
15717 if !buffer.is_singleton() {
15718 buffer.push_transaction(&transaction.0, cx);
15719 }
15720 }
15721 cx.notify();
15722 })
15723 .ok();
15724
15725 if let Some(transaction_id_now) =
15726 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
15727 {
15728 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
15729 if has_new_transaction {
15730 _ = editor.update(cx, |editor, _| {
15731 editor
15732 .selection_history
15733 .insert_transaction(transaction_id_now, selections_prev);
15734 });
15735 }
15736 }
15737
15738 Ok(())
15739 })
15740 }
15741
15742 fn organize_imports(
15743 &mut self,
15744 _: &OrganizeImports,
15745 window: &mut Window,
15746 cx: &mut Context<Self>,
15747 ) -> Option<Task<Result<()>>> {
15748 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
15749 let project = match &self.project {
15750 Some(project) => project.clone(),
15751 None => return None,
15752 };
15753 Some(self.perform_code_action_kind(
15754 project,
15755 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
15756 window,
15757 cx,
15758 ))
15759 }
15760
15761 fn perform_code_action_kind(
15762 &mut self,
15763 project: Entity<Project>,
15764 kind: CodeActionKind,
15765 window: &mut Window,
15766 cx: &mut Context<Self>,
15767 ) -> Task<Result<()>> {
15768 let buffer = self.buffer.clone();
15769 let buffers = buffer.read(cx).all_buffers();
15770 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
15771 let apply_action = project.update(cx, |project, cx| {
15772 project.apply_code_action_kind(buffers, kind, true, cx)
15773 });
15774 cx.spawn_in(window, async move |_, cx| {
15775 let transaction = futures::select_biased! {
15776 () = timeout => {
15777 log::warn!("timed out waiting for executing code action");
15778 None
15779 }
15780 transaction = apply_action.log_err().fuse() => transaction,
15781 };
15782 buffer
15783 .update(cx, |buffer, cx| {
15784 // check if we need this
15785 if let Some(transaction) = transaction {
15786 if !buffer.is_singleton() {
15787 buffer.push_transaction(&transaction.0, cx);
15788 }
15789 }
15790 cx.notify();
15791 })
15792 .ok();
15793 Ok(())
15794 })
15795 }
15796
15797 fn restart_language_server(
15798 &mut self,
15799 _: &RestartLanguageServer,
15800 _: &mut Window,
15801 cx: &mut Context<Self>,
15802 ) {
15803 if let Some(project) = self.project.clone() {
15804 self.buffer.update(cx, |multi_buffer, cx| {
15805 project.update(cx, |project, cx| {
15806 project.restart_language_servers_for_buffers(
15807 multi_buffer.all_buffers().into_iter().collect(),
15808 cx,
15809 );
15810 });
15811 })
15812 }
15813 }
15814
15815 fn stop_language_server(
15816 &mut self,
15817 _: &StopLanguageServer,
15818 _: &mut Window,
15819 cx: &mut Context<Self>,
15820 ) {
15821 if let Some(project) = self.project.clone() {
15822 self.buffer.update(cx, |multi_buffer, cx| {
15823 project.update(cx, |project, cx| {
15824 project.stop_language_servers_for_buffers(
15825 multi_buffer.all_buffers().into_iter().collect(),
15826 cx,
15827 );
15828 cx.emit(project::Event::RefreshInlayHints);
15829 });
15830 });
15831 }
15832 }
15833
15834 fn cancel_language_server_work(
15835 workspace: &mut Workspace,
15836 _: &actions::CancelLanguageServerWork,
15837 _: &mut Window,
15838 cx: &mut Context<Workspace>,
15839 ) {
15840 let project = workspace.project();
15841 let buffers = workspace
15842 .active_item(cx)
15843 .and_then(|item| item.act_as::<Editor>(cx))
15844 .map_or(HashSet::default(), |editor| {
15845 editor.read(cx).buffer.read(cx).all_buffers()
15846 });
15847 project.update(cx, |project, cx| {
15848 project.cancel_language_server_work_for_buffers(buffers, cx);
15849 });
15850 }
15851
15852 fn show_character_palette(
15853 &mut self,
15854 _: &ShowCharacterPalette,
15855 window: &mut Window,
15856 _: &mut Context<Self>,
15857 ) {
15858 window.show_character_palette();
15859 }
15860
15861 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
15862 if self.mode.is_minimap() {
15863 return;
15864 }
15865
15866 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
15867 let buffer = self.buffer.read(cx).snapshot(cx);
15868 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
15869 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
15870 let is_valid = buffer
15871 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
15872 .any(|entry| {
15873 entry.diagnostic.is_primary
15874 && !entry.range.is_empty()
15875 && entry.range.start == primary_range_start
15876 && entry.diagnostic.message == active_diagnostics.active_message
15877 });
15878
15879 if !is_valid {
15880 self.dismiss_diagnostics(cx);
15881 }
15882 }
15883 }
15884
15885 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
15886 match &self.active_diagnostics {
15887 ActiveDiagnostic::Group(group) => Some(group),
15888 _ => None,
15889 }
15890 }
15891
15892 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
15893 self.dismiss_diagnostics(cx);
15894 self.active_diagnostics = ActiveDiagnostic::All;
15895 }
15896
15897 fn activate_diagnostics(
15898 &mut self,
15899 buffer_id: BufferId,
15900 diagnostic: DiagnosticEntry<usize>,
15901 window: &mut Window,
15902 cx: &mut Context<Self>,
15903 ) {
15904 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15905 return;
15906 }
15907 self.dismiss_diagnostics(cx);
15908 let snapshot = self.snapshot(window, cx);
15909 let buffer = self.buffer.read(cx).snapshot(cx);
15910 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
15911 return;
15912 };
15913
15914 let diagnostic_group = buffer
15915 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
15916 .collect::<Vec<_>>();
15917
15918 let blocks =
15919 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
15920
15921 let blocks = self.display_map.update(cx, |display_map, cx| {
15922 display_map.insert_blocks(blocks, cx).into_iter().collect()
15923 });
15924 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
15925 active_range: buffer.anchor_before(diagnostic.range.start)
15926 ..buffer.anchor_after(diagnostic.range.end),
15927 active_message: diagnostic.diagnostic.message.clone(),
15928 group_id: diagnostic.diagnostic.group_id,
15929 blocks,
15930 });
15931 cx.notify();
15932 }
15933
15934 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
15935 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
15936 return;
15937 };
15938
15939 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
15940 if let ActiveDiagnostic::Group(group) = prev {
15941 self.display_map.update(cx, |display_map, cx| {
15942 display_map.remove_blocks(group.blocks, cx);
15943 });
15944 cx.notify();
15945 }
15946 }
15947
15948 /// Disable inline diagnostics rendering for this editor.
15949 pub fn disable_inline_diagnostics(&mut self) {
15950 self.inline_diagnostics_enabled = false;
15951 self.inline_diagnostics_update = Task::ready(());
15952 self.inline_diagnostics.clear();
15953 }
15954
15955 pub fn diagnostics_enabled(&self) -> bool {
15956 self.mode.is_full()
15957 }
15958
15959 pub fn inline_diagnostics_enabled(&self) -> bool {
15960 self.diagnostics_enabled() && self.inline_diagnostics_enabled
15961 }
15962
15963 pub fn show_inline_diagnostics(&self) -> bool {
15964 self.show_inline_diagnostics
15965 }
15966
15967 pub fn toggle_inline_diagnostics(
15968 &mut self,
15969 _: &ToggleInlineDiagnostics,
15970 window: &mut Window,
15971 cx: &mut Context<Editor>,
15972 ) {
15973 self.show_inline_diagnostics = !self.show_inline_diagnostics;
15974 self.refresh_inline_diagnostics(false, window, cx);
15975 }
15976
15977 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
15978 self.diagnostics_max_severity = severity;
15979 self.display_map.update(cx, |display_map, _| {
15980 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
15981 });
15982 }
15983
15984 pub fn toggle_diagnostics(
15985 &mut self,
15986 _: &ToggleDiagnostics,
15987 window: &mut Window,
15988 cx: &mut Context<Editor>,
15989 ) {
15990 if !self.diagnostics_enabled() {
15991 return;
15992 }
15993
15994 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
15995 EditorSettings::get_global(cx)
15996 .diagnostics_max_severity
15997 .filter(|severity| severity != &DiagnosticSeverity::Off)
15998 .unwrap_or(DiagnosticSeverity::Hint)
15999 } else {
16000 DiagnosticSeverity::Off
16001 };
16002 self.set_max_diagnostics_severity(new_severity, cx);
16003 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
16004 self.active_diagnostics = ActiveDiagnostic::None;
16005 self.inline_diagnostics_update = Task::ready(());
16006 self.inline_diagnostics.clear();
16007 } else {
16008 self.refresh_inline_diagnostics(false, window, cx);
16009 }
16010
16011 cx.notify();
16012 }
16013
16014 pub fn toggle_minimap(
16015 &mut self,
16016 _: &ToggleMinimap,
16017 window: &mut Window,
16018 cx: &mut Context<Editor>,
16019 ) {
16020 if self.supports_minimap(cx) {
16021 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
16022 }
16023 }
16024
16025 fn refresh_inline_diagnostics(
16026 &mut self,
16027 debounce: bool,
16028 window: &mut Window,
16029 cx: &mut Context<Self>,
16030 ) {
16031 let max_severity = ProjectSettings::get_global(cx)
16032 .diagnostics
16033 .inline
16034 .max_severity
16035 .unwrap_or(self.diagnostics_max_severity);
16036
16037 if !self.inline_diagnostics_enabled()
16038 || !self.show_inline_diagnostics
16039 || max_severity == DiagnosticSeverity::Off
16040 {
16041 self.inline_diagnostics_update = Task::ready(());
16042 self.inline_diagnostics.clear();
16043 return;
16044 }
16045
16046 let debounce_ms = ProjectSettings::get_global(cx)
16047 .diagnostics
16048 .inline
16049 .update_debounce_ms;
16050 let debounce = if debounce && debounce_ms > 0 {
16051 Some(Duration::from_millis(debounce_ms))
16052 } else {
16053 None
16054 };
16055 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
16056 if let Some(debounce) = debounce {
16057 cx.background_executor().timer(debounce).await;
16058 }
16059 let Some(snapshot) = editor.upgrade().and_then(|editor| {
16060 editor
16061 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
16062 .ok()
16063 }) else {
16064 return;
16065 };
16066
16067 let new_inline_diagnostics = cx
16068 .background_spawn(async move {
16069 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
16070 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
16071 let message = diagnostic_entry
16072 .diagnostic
16073 .message
16074 .split_once('\n')
16075 .map(|(line, _)| line)
16076 .map(SharedString::new)
16077 .unwrap_or_else(|| {
16078 SharedString::from(diagnostic_entry.diagnostic.message)
16079 });
16080 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
16081 let (Ok(i) | Err(i)) = inline_diagnostics
16082 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
16083 inline_diagnostics.insert(
16084 i,
16085 (
16086 start_anchor,
16087 InlineDiagnostic {
16088 message,
16089 group_id: diagnostic_entry.diagnostic.group_id,
16090 start: diagnostic_entry.range.start.to_point(&snapshot),
16091 is_primary: diagnostic_entry.diagnostic.is_primary,
16092 severity: diagnostic_entry.diagnostic.severity,
16093 },
16094 ),
16095 );
16096 }
16097 inline_diagnostics
16098 })
16099 .await;
16100
16101 editor
16102 .update(cx, |editor, cx| {
16103 editor.inline_diagnostics = new_inline_diagnostics;
16104 cx.notify();
16105 })
16106 .ok();
16107 });
16108 }
16109
16110 fn pull_diagnostics(
16111 &mut self,
16112 buffer_id: Option<BufferId>,
16113 window: &Window,
16114 cx: &mut Context<Self>,
16115 ) -> Option<()> {
16116 if !self.mode().is_full() {
16117 return None;
16118 }
16119 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
16120 .diagnostics
16121 .lsp_pull_diagnostics;
16122 if !pull_diagnostics_settings.enabled {
16123 return None;
16124 }
16125 let project = self.project.as_ref()?.downgrade();
16126 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
16127 let mut buffers = self.buffer.read(cx).all_buffers();
16128 if let Some(buffer_id) = buffer_id {
16129 buffers.retain(|buffer| buffer.read(cx).remote_id() == buffer_id);
16130 }
16131
16132 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
16133 cx.background_executor().timer(debounce).await;
16134
16135 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
16136 buffers
16137 .into_iter()
16138 .flat_map(|buffer| {
16139 Some(project.upgrade()?.pull_diagnostics_for_buffer(buffer, cx))
16140 })
16141 .collect::<FuturesUnordered<_>>()
16142 }) else {
16143 return;
16144 };
16145
16146 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
16147 match pull_task {
16148 Ok(()) => {
16149 if editor
16150 .update_in(cx, |editor, window, cx| {
16151 editor.update_diagnostics_state(window, cx);
16152 })
16153 .is_err()
16154 {
16155 return;
16156 }
16157 }
16158 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
16159 }
16160 }
16161 });
16162
16163 Some(())
16164 }
16165
16166 pub fn set_selections_from_remote(
16167 &mut self,
16168 selections: Vec<Selection<Anchor>>,
16169 pending_selection: Option<Selection<Anchor>>,
16170 window: &mut Window,
16171 cx: &mut Context<Self>,
16172 ) {
16173 let old_cursor_position = self.selections.newest_anchor().head();
16174 self.selections.change_with(cx, |s| {
16175 s.select_anchors(selections);
16176 if let Some(pending_selection) = pending_selection {
16177 s.set_pending(pending_selection, SelectMode::Character);
16178 } else {
16179 s.clear_pending();
16180 }
16181 });
16182 self.selections_did_change(false, &old_cursor_position, true, window, cx);
16183 }
16184
16185 pub fn transact(
16186 &mut self,
16187 window: &mut Window,
16188 cx: &mut Context<Self>,
16189 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
16190 ) -> Option<TransactionId> {
16191 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16192 this.start_transaction_at(Instant::now(), window, cx);
16193 update(this, window, cx);
16194 this.end_transaction_at(Instant::now(), cx)
16195 })
16196 }
16197
16198 pub fn start_transaction_at(
16199 &mut self,
16200 now: Instant,
16201 window: &mut Window,
16202 cx: &mut Context<Self>,
16203 ) {
16204 self.end_selection(window, cx);
16205 if let Some(tx_id) = self
16206 .buffer
16207 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
16208 {
16209 self.selection_history
16210 .insert_transaction(tx_id, self.selections.disjoint_anchors());
16211 cx.emit(EditorEvent::TransactionBegun {
16212 transaction_id: tx_id,
16213 })
16214 }
16215 }
16216
16217 pub fn end_transaction_at(
16218 &mut self,
16219 now: Instant,
16220 cx: &mut Context<Self>,
16221 ) -> Option<TransactionId> {
16222 if let Some(transaction_id) = self
16223 .buffer
16224 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
16225 {
16226 if let Some((_, end_selections)) =
16227 self.selection_history.transaction_mut(transaction_id)
16228 {
16229 *end_selections = Some(self.selections.disjoint_anchors());
16230 } else {
16231 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
16232 }
16233
16234 cx.emit(EditorEvent::Edited { transaction_id });
16235 Some(transaction_id)
16236 } else {
16237 None
16238 }
16239 }
16240
16241 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
16242 if self.selection_mark_mode {
16243 self.change_selections(None, window, cx, |s| {
16244 s.move_with(|_, sel| {
16245 sel.collapse_to(sel.head(), SelectionGoal::None);
16246 });
16247 })
16248 }
16249 self.selection_mark_mode = true;
16250 cx.notify();
16251 }
16252
16253 pub fn swap_selection_ends(
16254 &mut self,
16255 _: &actions::SwapSelectionEnds,
16256 window: &mut Window,
16257 cx: &mut Context<Self>,
16258 ) {
16259 self.change_selections(None, window, cx, |s| {
16260 s.move_with(|_, sel| {
16261 if sel.start != sel.end {
16262 sel.reversed = !sel.reversed
16263 }
16264 });
16265 });
16266 self.request_autoscroll(Autoscroll::newest(), cx);
16267 cx.notify();
16268 }
16269
16270 pub fn toggle_fold(
16271 &mut self,
16272 _: &actions::ToggleFold,
16273 window: &mut Window,
16274 cx: &mut Context<Self>,
16275 ) {
16276 if self.is_singleton(cx) {
16277 let selection = self.selections.newest::<Point>(cx);
16278
16279 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16280 let range = if selection.is_empty() {
16281 let point = selection.head().to_display_point(&display_map);
16282 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16283 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16284 .to_point(&display_map);
16285 start..end
16286 } else {
16287 selection.range()
16288 };
16289 if display_map.folds_in_range(range).next().is_some() {
16290 self.unfold_lines(&Default::default(), window, cx)
16291 } else {
16292 self.fold(&Default::default(), window, cx)
16293 }
16294 } else {
16295 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16296 let buffer_ids: HashSet<_> = self
16297 .selections
16298 .disjoint_anchor_ranges()
16299 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16300 .collect();
16301
16302 let should_unfold = buffer_ids
16303 .iter()
16304 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
16305
16306 for buffer_id in buffer_ids {
16307 if should_unfold {
16308 self.unfold_buffer(buffer_id, cx);
16309 } else {
16310 self.fold_buffer(buffer_id, cx);
16311 }
16312 }
16313 }
16314 }
16315
16316 pub fn toggle_fold_recursive(
16317 &mut self,
16318 _: &actions::ToggleFoldRecursive,
16319 window: &mut Window,
16320 cx: &mut Context<Self>,
16321 ) {
16322 let selection = self.selections.newest::<Point>(cx);
16323
16324 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16325 let range = if selection.is_empty() {
16326 let point = selection.head().to_display_point(&display_map);
16327 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
16328 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
16329 .to_point(&display_map);
16330 start..end
16331 } else {
16332 selection.range()
16333 };
16334 if display_map.folds_in_range(range).next().is_some() {
16335 self.unfold_recursive(&Default::default(), window, cx)
16336 } else {
16337 self.fold_recursive(&Default::default(), window, cx)
16338 }
16339 }
16340
16341 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
16342 if self.is_singleton(cx) {
16343 let mut to_fold = Vec::new();
16344 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16345 let selections = self.selections.all_adjusted(cx);
16346
16347 for selection in selections {
16348 let range = selection.range().sorted();
16349 let buffer_start_row = range.start.row;
16350
16351 if range.start.row != range.end.row {
16352 let mut found = false;
16353 let mut row = range.start.row;
16354 while row <= range.end.row {
16355 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
16356 {
16357 found = true;
16358 row = crease.range().end.row + 1;
16359 to_fold.push(crease);
16360 } else {
16361 row += 1
16362 }
16363 }
16364 if found {
16365 continue;
16366 }
16367 }
16368
16369 for row in (0..=range.start.row).rev() {
16370 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16371 if crease.range().end.row >= buffer_start_row {
16372 to_fold.push(crease);
16373 if row <= range.start.row {
16374 break;
16375 }
16376 }
16377 }
16378 }
16379 }
16380
16381 self.fold_creases(to_fold, true, window, cx);
16382 } else {
16383 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16384 let buffer_ids = self
16385 .selections
16386 .disjoint_anchor_ranges()
16387 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16388 .collect::<HashSet<_>>();
16389 for buffer_id in buffer_ids {
16390 self.fold_buffer(buffer_id, cx);
16391 }
16392 }
16393 }
16394
16395 fn fold_at_level(
16396 &mut self,
16397 fold_at: &FoldAtLevel,
16398 window: &mut Window,
16399 cx: &mut Context<Self>,
16400 ) {
16401 if !self.buffer.read(cx).is_singleton() {
16402 return;
16403 }
16404
16405 let fold_at_level = fold_at.0;
16406 let snapshot = self.buffer.read(cx).snapshot(cx);
16407 let mut to_fold = Vec::new();
16408 let mut stack = vec![(0, snapshot.max_row().0, 1)];
16409
16410 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
16411 while start_row < end_row {
16412 match self
16413 .snapshot(window, cx)
16414 .crease_for_buffer_row(MultiBufferRow(start_row))
16415 {
16416 Some(crease) => {
16417 let nested_start_row = crease.range().start.row + 1;
16418 let nested_end_row = crease.range().end.row;
16419
16420 if current_level < fold_at_level {
16421 stack.push((nested_start_row, nested_end_row, current_level + 1));
16422 } else if current_level == fold_at_level {
16423 to_fold.push(crease);
16424 }
16425
16426 start_row = nested_end_row + 1;
16427 }
16428 None => start_row += 1,
16429 }
16430 }
16431 }
16432
16433 self.fold_creases(to_fold, true, window, cx);
16434 }
16435
16436 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
16437 if self.buffer.read(cx).is_singleton() {
16438 let mut fold_ranges = Vec::new();
16439 let snapshot = self.buffer.read(cx).snapshot(cx);
16440
16441 for row in 0..snapshot.max_row().0 {
16442 if let Some(foldable_range) = self
16443 .snapshot(window, cx)
16444 .crease_for_buffer_row(MultiBufferRow(row))
16445 {
16446 fold_ranges.push(foldable_range);
16447 }
16448 }
16449
16450 self.fold_creases(fold_ranges, true, window, cx);
16451 } else {
16452 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
16453 editor
16454 .update_in(cx, |editor, _, cx| {
16455 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16456 editor.fold_buffer(buffer_id, cx);
16457 }
16458 })
16459 .ok();
16460 });
16461 }
16462 }
16463
16464 pub fn fold_function_bodies(
16465 &mut self,
16466 _: &actions::FoldFunctionBodies,
16467 window: &mut Window,
16468 cx: &mut Context<Self>,
16469 ) {
16470 let snapshot = self.buffer.read(cx).snapshot(cx);
16471
16472 let ranges = snapshot
16473 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
16474 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
16475 .collect::<Vec<_>>();
16476
16477 let creases = ranges
16478 .into_iter()
16479 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
16480 .collect();
16481
16482 self.fold_creases(creases, true, window, cx);
16483 }
16484
16485 pub fn fold_recursive(
16486 &mut self,
16487 _: &actions::FoldRecursive,
16488 window: &mut Window,
16489 cx: &mut Context<Self>,
16490 ) {
16491 let mut to_fold = Vec::new();
16492 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16493 let selections = self.selections.all_adjusted(cx);
16494
16495 for selection in selections {
16496 let range = selection.range().sorted();
16497 let buffer_start_row = range.start.row;
16498
16499 if range.start.row != range.end.row {
16500 let mut found = false;
16501 for row in range.start.row..=range.end.row {
16502 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16503 found = true;
16504 to_fold.push(crease);
16505 }
16506 }
16507 if found {
16508 continue;
16509 }
16510 }
16511
16512 for row in (0..=range.start.row).rev() {
16513 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
16514 if crease.range().end.row >= buffer_start_row {
16515 to_fold.push(crease);
16516 } else {
16517 break;
16518 }
16519 }
16520 }
16521 }
16522
16523 self.fold_creases(to_fold, true, window, cx);
16524 }
16525
16526 pub fn fold_at(
16527 &mut self,
16528 buffer_row: MultiBufferRow,
16529 window: &mut Window,
16530 cx: &mut Context<Self>,
16531 ) {
16532 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16533
16534 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
16535 let autoscroll = self
16536 .selections
16537 .all::<Point>(cx)
16538 .iter()
16539 .any(|selection| crease.range().overlaps(&selection.range()));
16540
16541 self.fold_creases(vec![crease], autoscroll, window, cx);
16542 }
16543 }
16544
16545 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
16546 if self.is_singleton(cx) {
16547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16548 let buffer = &display_map.buffer_snapshot;
16549 let selections = self.selections.all::<Point>(cx);
16550 let ranges = selections
16551 .iter()
16552 .map(|s| {
16553 let range = s.display_range(&display_map).sorted();
16554 let mut start = range.start.to_point(&display_map);
16555 let mut end = range.end.to_point(&display_map);
16556 start.column = 0;
16557 end.column = buffer.line_len(MultiBufferRow(end.row));
16558 start..end
16559 })
16560 .collect::<Vec<_>>();
16561
16562 self.unfold_ranges(&ranges, true, true, cx);
16563 } else {
16564 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
16565 let buffer_ids = self
16566 .selections
16567 .disjoint_anchor_ranges()
16568 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
16569 .collect::<HashSet<_>>();
16570 for buffer_id in buffer_ids {
16571 self.unfold_buffer(buffer_id, cx);
16572 }
16573 }
16574 }
16575
16576 pub fn unfold_recursive(
16577 &mut self,
16578 _: &UnfoldRecursive,
16579 _window: &mut Window,
16580 cx: &mut Context<Self>,
16581 ) {
16582 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16583 let selections = self.selections.all::<Point>(cx);
16584 let ranges = selections
16585 .iter()
16586 .map(|s| {
16587 let mut range = s.display_range(&display_map).sorted();
16588 *range.start.column_mut() = 0;
16589 *range.end.column_mut() = display_map.line_len(range.end.row());
16590 let start = range.start.to_point(&display_map);
16591 let end = range.end.to_point(&display_map);
16592 start..end
16593 })
16594 .collect::<Vec<_>>();
16595
16596 self.unfold_ranges(&ranges, true, true, cx);
16597 }
16598
16599 pub fn unfold_at(
16600 &mut self,
16601 buffer_row: MultiBufferRow,
16602 _window: &mut Window,
16603 cx: &mut Context<Self>,
16604 ) {
16605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16606
16607 let intersection_range = Point::new(buffer_row.0, 0)
16608 ..Point::new(
16609 buffer_row.0,
16610 display_map.buffer_snapshot.line_len(buffer_row),
16611 );
16612
16613 let autoscroll = self
16614 .selections
16615 .all::<Point>(cx)
16616 .iter()
16617 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
16618
16619 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
16620 }
16621
16622 pub fn unfold_all(
16623 &mut self,
16624 _: &actions::UnfoldAll,
16625 _window: &mut Window,
16626 cx: &mut Context<Self>,
16627 ) {
16628 if self.buffer.read(cx).is_singleton() {
16629 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16630 self.unfold_ranges(&[0..display_map.buffer_snapshot.len()], true, true, cx);
16631 } else {
16632 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
16633 editor
16634 .update(cx, |editor, cx| {
16635 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
16636 editor.unfold_buffer(buffer_id, cx);
16637 }
16638 })
16639 .ok();
16640 });
16641 }
16642 }
16643
16644 pub fn fold_selected_ranges(
16645 &mut self,
16646 _: &FoldSelectedRanges,
16647 window: &mut Window,
16648 cx: &mut Context<Self>,
16649 ) {
16650 let selections = self.selections.all_adjusted(cx);
16651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16652 let ranges = selections
16653 .into_iter()
16654 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
16655 .collect::<Vec<_>>();
16656 self.fold_creases(ranges, true, window, cx);
16657 }
16658
16659 pub fn fold_ranges<T: ToOffset + Clone>(
16660 &mut self,
16661 ranges: Vec<Range<T>>,
16662 auto_scroll: bool,
16663 window: &mut Window,
16664 cx: &mut Context<Self>,
16665 ) {
16666 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16667 let ranges = ranges
16668 .into_iter()
16669 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
16670 .collect::<Vec<_>>();
16671 self.fold_creases(ranges, auto_scroll, window, cx);
16672 }
16673
16674 pub fn fold_creases<T: ToOffset + Clone>(
16675 &mut self,
16676 creases: Vec<Crease<T>>,
16677 auto_scroll: bool,
16678 _window: &mut Window,
16679 cx: &mut Context<Self>,
16680 ) {
16681 if creases.is_empty() {
16682 return;
16683 }
16684
16685 let mut buffers_affected = HashSet::default();
16686 let multi_buffer = self.buffer().read(cx);
16687 for crease in &creases {
16688 if let Some((_, buffer, _)) =
16689 multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
16690 {
16691 buffers_affected.insert(buffer.read(cx).remote_id());
16692 };
16693 }
16694
16695 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
16696
16697 if auto_scroll {
16698 self.request_autoscroll(Autoscroll::fit(), cx);
16699 }
16700
16701 cx.notify();
16702
16703 self.scrollbar_marker_state.dirty = true;
16704 self.folds_did_change(cx);
16705 }
16706
16707 /// Removes any folds whose ranges intersect any of the given ranges.
16708 pub fn unfold_ranges<T: ToOffset + Clone>(
16709 &mut self,
16710 ranges: &[Range<T>],
16711 inclusive: bool,
16712 auto_scroll: bool,
16713 cx: &mut Context<Self>,
16714 ) {
16715 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16716 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
16717 });
16718 self.folds_did_change(cx);
16719 }
16720
16721 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16722 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
16723 return;
16724 }
16725 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16726 self.display_map.update(cx, |display_map, cx| {
16727 display_map.fold_buffers([buffer_id], cx)
16728 });
16729 cx.emit(EditorEvent::BufferFoldToggled {
16730 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
16731 folded: true,
16732 });
16733 cx.notify();
16734 }
16735
16736 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16737 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
16738 return;
16739 }
16740 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
16741 self.display_map.update(cx, |display_map, cx| {
16742 display_map.unfold_buffers([buffer_id], cx);
16743 });
16744 cx.emit(EditorEvent::BufferFoldToggled {
16745 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
16746 folded: false,
16747 });
16748 cx.notify();
16749 }
16750
16751 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
16752 self.display_map.read(cx).is_buffer_folded(buffer)
16753 }
16754
16755 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
16756 self.display_map.read(cx).folded_buffers()
16757 }
16758
16759 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
16760 self.display_map.update(cx, |display_map, cx| {
16761 display_map.disable_header_for_buffer(buffer_id, cx);
16762 });
16763 cx.notify();
16764 }
16765
16766 /// Removes any folds with the given ranges.
16767 pub fn remove_folds_with_type<T: ToOffset + Clone>(
16768 &mut self,
16769 ranges: &[Range<T>],
16770 type_id: TypeId,
16771 auto_scroll: bool,
16772 cx: &mut Context<Self>,
16773 ) {
16774 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
16775 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
16776 });
16777 self.folds_did_change(cx);
16778 }
16779
16780 fn remove_folds_with<T: ToOffset + Clone>(
16781 &mut self,
16782 ranges: &[Range<T>],
16783 auto_scroll: bool,
16784 cx: &mut Context<Self>,
16785 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
16786 ) {
16787 if ranges.is_empty() {
16788 return;
16789 }
16790
16791 let mut buffers_affected = HashSet::default();
16792 let multi_buffer = self.buffer().read(cx);
16793 for range in ranges {
16794 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
16795 buffers_affected.insert(buffer.read(cx).remote_id());
16796 };
16797 }
16798
16799 self.display_map.update(cx, update);
16800
16801 if auto_scroll {
16802 self.request_autoscroll(Autoscroll::fit(), cx);
16803 }
16804
16805 cx.notify();
16806 self.scrollbar_marker_state.dirty = true;
16807 self.active_indent_guides_state.dirty = true;
16808 }
16809
16810 pub fn update_fold_widths(
16811 &mut self,
16812 widths: impl IntoIterator<Item = (FoldId, Pixels)>,
16813 cx: &mut Context<Self>,
16814 ) -> bool {
16815 self.display_map
16816 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
16817 }
16818
16819 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
16820 self.display_map.read(cx).fold_placeholder.clone()
16821 }
16822
16823 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
16824 self.buffer.update(cx, |buffer, cx| {
16825 buffer.set_all_diff_hunks_expanded(cx);
16826 });
16827 }
16828
16829 pub fn expand_all_diff_hunks(
16830 &mut self,
16831 _: &ExpandAllDiffHunks,
16832 _window: &mut Window,
16833 cx: &mut Context<Self>,
16834 ) {
16835 self.buffer.update(cx, |buffer, cx| {
16836 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
16837 });
16838 }
16839
16840 pub fn toggle_selected_diff_hunks(
16841 &mut self,
16842 _: &ToggleSelectedDiffHunks,
16843 _window: &mut Window,
16844 cx: &mut Context<Self>,
16845 ) {
16846 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16847 self.toggle_diff_hunks_in_ranges(ranges, cx);
16848 }
16849
16850 pub fn diff_hunks_in_ranges<'a>(
16851 &'a self,
16852 ranges: &'a [Range<Anchor>],
16853 buffer: &'a MultiBufferSnapshot,
16854 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
16855 ranges.iter().flat_map(move |range| {
16856 let end_excerpt_id = range.end.excerpt_id;
16857 let range = range.to_point(buffer);
16858 let mut peek_end = range.end;
16859 if range.end.row < buffer.max_row().0 {
16860 peek_end = Point::new(range.end.row + 1, 0);
16861 }
16862 buffer
16863 .diff_hunks_in_range(range.start..peek_end)
16864 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
16865 })
16866 }
16867
16868 pub fn has_stageable_diff_hunks_in_ranges(
16869 &self,
16870 ranges: &[Range<Anchor>],
16871 snapshot: &MultiBufferSnapshot,
16872 ) -> bool {
16873 let mut hunks = self.diff_hunks_in_ranges(ranges, &snapshot);
16874 hunks.any(|hunk| hunk.status().has_secondary_hunk())
16875 }
16876
16877 pub fn toggle_staged_selected_diff_hunks(
16878 &mut self,
16879 _: &::git::ToggleStaged,
16880 _: &mut Window,
16881 cx: &mut Context<Self>,
16882 ) {
16883 let snapshot = self.buffer.read(cx).snapshot(cx);
16884 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
16885 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
16886 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16887 }
16888
16889 pub fn set_render_diff_hunk_controls(
16890 &mut self,
16891 render_diff_hunk_controls: RenderDiffHunkControlsFn,
16892 cx: &mut Context<Self>,
16893 ) {
16894 self.render_diff_hunk_controls = render_diff_hunk_controls;
16895 cx.notify();
16896 }
16897
16898 pub fn stage_and_next(
16899 &mut self,
16900 _: &::git::StageAndNext,
16901 window: &mut Window,
16902 cx: &mut Context<Self>,
16903 ) {
16904 self.do_stage_or_unstage_and_next(true, window, cx);
16905 }
16906
16907 pub fn unstage_and_next(
16908 &mut self,
16909 _: &::git::UnstageAndNext,
16910 window: &mut Window,
16911 cx: &mut Context<Self>,
16912 ) {
16913 self.do_stage_or_unstage_and_next(false, window, cx);
16914 }
16915
16916 pub fn stage_or_unstage_diff_hunks(
16917 &mut self,
16918 stage: bool,
16919 ranges: Vec<Range<Anchor>>,
16920 cx: &mut Context<Self>,
16921 ) {
16922 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
16923 cx.spawn(async move |this, cx| {
16924 task.await?;
16925 this.update(cx, |this, cx| {
16926 let snapshot = this.buffer.read(cx).snapshot(cx);
16927 let chunk_by = this
16928 .diff_hunks_in_ranges(&ranges, &snapshot)
16929 .chunk_by(|hunk| hunk.buffer_id);
16930 for (buffer_id, hunks) in &chunk_by {
16931 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
16932 }
16933 })
16934 })
16935 .detach_and_log_err(cx);
16936 }
16937
16938 fn save_buffers_for_ranges_if_needed(
16939 &mut self,
16940 ranges: &[Range<Anchor>],
16941 cx: &mut Context<Editor>,
16942 ) -> Task<Result<()>> {
16943 let multibuffer = self.buffer.read(cx);
16944 let snapshot = multibuffer.read(cx);
16945 let buffer_ids: HashSet<_> = ranges
16946 .iter()
16947 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
16948 .collect();
16949 drop(snapshot);
16950
16951 let mut buffers = HashSet::default();
16952 for buffer_id in buffer_ids {
16953 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
16954 let buffer = buffer_entity.read(cx);
16955 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
16956 {
16957 buffers.insert(buffer_entity);
16958 }
16959 }
16960 }
16961
16962 if let Some(project) = &self.project {
16963 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
16964 } else {
16965 Task::ready(Ok(()))
16966 }
16967 }
16968
16969 fn do_stage_or_unstage_and_next(
16970 &mut self,
16971 stage: bool,
16972 window: &mut Window,
16973 cx: &mut Context<Self>,
16974 ) {
16975 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
16976
16977 if ranges.iter().any(|range| range.start != range.end) {
16978 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16979 return;
16980 }
16981
16982 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
16983 let snapshot = self.snapshot(window, cx);
16984 let position = self.selections.newest::<Point>(cx).head();
16985 let mut row = snapshot
16986 .buffer_snapshot
16987 .diff_hunks_in_range(position..snapshot.buffer_snapshot.max_point())
16988 .find(|hunk| hunk.row_range.start.0 > position.row)
16989 .map(|hunk| hunk.row_range.start);
16990
16991 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
16992 // Outside of the project diff editor, wrap around to the beginning.
16993 if !all_diff_hunks_expanded {
16994 row = row.or_else(|| {
16995 snapshot
16996 .buffer_snapshot
16997 .diff_hunks_in_range(Point::zero()..position)
16998 .find(|hunk| hunk.row_range.end.0 < position.row)
16999 .map(|hunk| hunk.row_range.start)
17000 });
17001 }
17002
17003 if let Some(row) = row {
17004 let destination = Point::new(row.0, 0);
17005 let autoscroll = Autoscroll::center();
17006
17007 self.unfold_ranges(&[destination..destination], false, false, cx);
17008 self.change_selections(Some(autoscroll), window, cx, |s| {
17009 s.select_ranges([destination..destination]);
17010 });
17011 }
17012 }
17013
17014 fn do_stage_or_unstage(
17015 &self,
17016 stage: bool,
17017 buffer_id: BufferId,
17018 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
17019 cx: &mut App,
17020 ) -> Option<()> {
17021 let project = self.project.as_ref()?;
17022 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
17023 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
17024 let buffer_snapshot = buffer.read(cx).snapshot();
17025 let file_exists = buffer_snapshot
17026 .file()
17027 .is_some_and(|file| file.disk_state().exists());
17028 diff.update(cx, |diff, cx| {
17029 diff.stage_or_unstage_hunks(
17030 stage,
17031 &hunks
17032 .map(|hunk| buffer_diff::DiffHunk {
17033 buffer_range: hunk.buffer_range,
17034 diff_base_byte_range: hunk.diff_base_byte_range,
17035 secondary_status: hunk.secondary_status,
17036 range: Point::zero()..Point::zero(), // unused
17037 })
17038 .collect::<Vec<_>>(),
17039 &buffer_snapshot,
17040 file_exists,
17041 cx,
17042 )
17043 });
17044 None
17045 }
17046
17047 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
17048 let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect();
17049 self.buffer
17050 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
17051 }
17052
17053 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
17054 self.buffer.update(cx, |buffer, cx| {
17055 let ranges = vec![Anchor::min()..Anchor::max()];
17056 if !buffer.all_diff_hunks_expanded()
17057 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
17058 {
17059 buffer.collapse_diff_hunks(ranges, cx);
17060 true
17061 } else {
17062 false
17063 }
17064 })
17065 }
17066
17067 fn toggle_diff_hunks_in_ranges(
17068 &mut self,
17069 ranges: Vec<Range<Anchor>>,
17070 cx: &mut Context<Editor>,
17071 ) {
17072 self.buffer.update(cx, |buffer, cx| {
17073 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
17074 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
17075 })
17076 }
17077
17078 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
17079 self.buffer.update(cx, |buffer, cx| {
17080 let snapshot = buffer.snapshot(cx);
17081 let excerpt_id = range.end.excerpt_id;
17082 let point_range = range.to_point(&snapshot);
17083 let expand = !buffer.single_hunk_is_expanded(range, cx);
17084 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
17085 })
17086 }
17087
17088 pub(crate) fn apply_all_diff_hunks(
17089 &mut self,
17090 _: &ApplyAllDiffHunks,
17091 window: &mut Window,
17092 cx: &mut Context<Self>,
17093 ) {
17094 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17095
17096 let buffers = self.buffer.read(cx).all_buffers();
17097 for branch_buffer in buffers {
17098 branch_buffer.update(cx, |branch_buffer, cx| {
17099 branch_buffer.merge_into_base(Vec::new(), cx);
17100 });
17101 }
17102
17103 if let Some(project) = self.project.clone() {
17104 self.save(true, project, window, cx).detach_and_log_err(cx);
17105 }
17106 }
17107
17108 pub(crate) fn apply_selected_diff_hunks(
17109 &mut self,
17110 _: &ApplyDiffHunk,
17111 window: &mut Window,
17112 cx: &mut Context<Self>,
17113 ) {
17114 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
17115 let snapshot = self.snapshot(window, cx);
17116 let hunks = snapshot.hunks_for_ranges(self.selections.ranges(cx));
17117 let mut ranges_by_buffer = HashMap::default();
17118 self.transact(window, cx, |editor, _window, cx| {
17119 for hunk in hunks {
17120 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
17121 ranges_by_buffer
17122 .entry(buffer.clone())
17123 .or_insert_with(Vec::new)
17124 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
17125 }
17126 }
17127
17128 for (buffer, ranges) in ranges_by_buffer {
17129 buffer.update(cx, |buffer, cx| {
17130 buffer.merge_into_base(ranges, cx);
17131 });
17132 }
17133 });
17134
17135 if let Some(project) = self.project.clone() {
17136 self.save(true, project, window, cx).detach_and_log_err(cx);
17137 }
17138 }
17139
17140 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
17141 if hovered != self.gutter_hovered {
17142 self.gutter_hovered = hovered;
17143 cx.notify();
17144 }
17145 }
17146
17147 pub fn insert_blocks(
17148 &mut self,
17149 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
17150 autoscroll: Option<Autoscroll>,
17151 cx: &mut Context<Self>,
17152 ) -> Vec<CustomBlockId> {
17153 let blocks = self
17154 .display_map
17155 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
17156 if let Some(autoscroll) = autoscroll {
17157 self.request_autoscroll(autoscroll, cx);
17158 }
17159 cx.notify();
17160 blocks
17161 }
17162
17163 pub fn resize_blocks(
17164 &mut self,
17165 heights: HashMap<CustomBlockId, u32>,
17166 autoscroll: Option<Autoscroll>,
17167 cx: &mut Context<Self>,
17168 ) {
17169 self.display_map
17170 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
17171 if let Some(autoscroll) = autoscroll {
17172 self.request_autoscroll(autoscroll, cx);
17173 }
17174 cx.notify();
17175 }
17176
17177 pub fn replace_blocks(
17178 &mut self,
17179 renderers: HashMap<CustomBlockId, RenderBlock>,
17180 autoscroll: Option<Autoscroll>,
17181 cx: &mut Context<Self>,
17182 ) {
17183 self.display_map
17184 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
17185 if let Some(autoscroll) = autoscroll {
17186 self.request_autoscroll(autoscroll, cx);
17187 }
17188 cx.notify();
17189 }
17190
17191 pub fn remove_blocks(
17192 &mut self,
17193 block_ids: HashSet<CustomBlockId>,
17194 autoscroll: Option<Autoscroll>,
17195 cx: &mut Context<Self>,
17196 ) {
17197 self.display_map.update(cx, |display_map, cx| {
17198 display_map.remove_blocks(block_ids, cx)
17199 });
17200 if let Some(autoscroll) = autoscroll {
17201 self.request_autoscroll(autoscroll, cx);
17202 }
17203 cx.notify();
17204 }
17205
17206 pub fn row_for_block(
17207 &self,
17208 block_id: CustomBlockId,
17209 cx: &mut Context<Self>,
17210 ) -> Option<DisplayRow> {
17211 self.display_map
17212 .update(cx, |map, cx| map.row_for_block(block_id, cx))
17213 }
17214
17215 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
17216 self.focused_block = Some(focused_block);
17217 }
17218
17219 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
17220 self.focused_block.take()
17221 }
17222
17223 pub fn insert_creases(
17224 &mut self,
17225 creases: impl IntoIterator<Item = Crease<Anchor>>,
17226 cx: &mut Context<Self>,
17227 ) -> Vec<CreaseId> {
17228 self.display_map
17229 .update(cx, |map, cx| map.insert_creases(creases, cx))
17230 }
17231
17232 pub fn remove_creases(
17233 &mut self,
17234 ids: impl IntoIterator<Item = CreaseId>,
17235 cx: &mut Context<Self>,
17236 ) -> Vec<(CreaseId, Range<Anchor>)> {
17237 self.display_map
17238 .update(cx, |map, cx| map.remove_creases(ids, cx))
17239 }
17240
17241 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
17242 self.display_map
17243 .update(cx, |map, cx| map.snapshot(cx))
17244 .longest_row()
17245 }
17246
17247 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
17248 self.display_map
17249 .update(cx, |map, cx| map.snapshot(cx))
17250 .max_point()
17251 }
17252
17253 pub fn text(&self, cx: &App) -> String {
17254 self.buffer.read(cx).read(cx).text()
17255 }
17256
17257 pub fn is_empty(&self, cx: &App) -> bool {
17258 self.buffer.read(cx).read(cx).is_empty()
17259 }
17260
17261 pub fn text_option(&self, cx: &App) -> Option<String> {
17262 let text = self.text(cx);
17263 let text = text.trim();
17264
17265 if text.is_empty() {
17266 return None;
17267 }
17268
17269 Some(text.to_string())
17270 }
17271
17272 pub fn set_text(
17273 &mut self,
17274 text: impl Into<Arc<str>>,
17275 window: &mut Window,
17276 cx: &mut Context<Self>,
17277 ) {
17278 self.transact(window, cx, |this, _, cx| {
17279 this.buffer
17280 .read(cx)
17281 .as_singleton()
17282 .expect("you can only call set_text on editors for singleton buffers")
17283 .update(cx, |buffer, cx| buffer.set_text(text, cx));
17284 });
17285 }
17286
17287 pub fn display_text(&self, cx: &mut App) -> String {
17288 self.display_map
17289 .update(cx, |map, cx| map.snapshot(cx))
17290 .text()
17291 }
17292
17293 fn create_minimap(
17294 &self,
17295 minimap_settings: MinimapSettings,
17296 window: &mut Window,
17297 cx: &mut Context<Self>,
17298 ) -> Option<Entity<Self>> {
17299 (minimap_settings.minimap_enabled() && self.is_singleton(cx))
17300 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
17301 }
17302
17303 fn initialize_new_minimap(
17304 &self,
17305 minimap_settings: MinimapSettings,
17306 window: &mut Window,
17307 cx: &mut Context<Self>,
17308 ) -> Entity<Self> {
17309 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
17310
17311 let mut minimap = Editor::new_internal(
17312 EditorMode::Minimap {
17313 parent: cx.weak_entity(),
17314 },
17315 self.buffer.clone(),
17316 self.project.clone(),
17317 Some(self.display_map.clone()),
17318 window,
17319 cx,
17320 );
17321 minimap.scroll_manager.clone_state(&self.scroll_manager);
17322 minimap.set_text_style_refinement(TextStyleRefinement {
17323 font_size: Some(MINIMAP_FONT_SIZE),
17324 font_weight: Some(MINIMAP_FONT_WEIGHT),
17325 ..Default::default()
17326 });
17327 minimap.update_minimap_configuration(minimap_settings, cx);
17328 cx.new(|_| minimap)
17329 }
17330
17331 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
17332 let current_line_highlight = minimap_settings
17333 .current_line_highlight
17334 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
17335 self.set_current_line_highlight(Some(current_line_highlight));
17336 }
17337
17338 pub fn minimap(&self) -> Option<&Entity<Self>> {
17339 self.minimap
17340 .as_ref()
17341 .filter(|_| self.minimap_visibility.visible())
17342 }
17343
17344 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
17345 let mut wrap_guides = smallvec![];
17346
17347 if self.show_wrap_guides == Some(false) {
17348 return wrap_guides;
17349 }
17350
17351 let settings = self.buffer.read(cx).language_settings(cx);
17352 if settings.show_wrap_guides {
17353 match self.soft_wrap_mode(cx) {
17354 SoftWrap::Column(soft_wrap) => {
17355 wrap_guides.push((soft_wrap as usize, true));
17356 }
17357 SoftWrap::Bounded(soft_wrap) => {
17358 wrap_guides.push((soft_wrap as usize, true));
17359 }
17360 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
17361 }
17362 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
17363 }
17364
17365 wrap_guides
17366 }
17367
17368 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
17369 let settings = self.buffer.read(cx).language_settings(cx);
17370 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
17371 match mode {
17372 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
17373 SoftWrap::None
17374 }
17375 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
17376 language_settings::SoftWrap::PreferredLineLength => {
17377 SoftWrap::Column(settings.preferred_line_length)
17378 }
17379 language_settings::SoftWrap::Bounded => {
17380 SoftWrap::Bounded(settings.preferred_line_length)
17381 }
17382 }
17383 }
17384
17385 pub fn set_soft_wrap_mode(
17386 &mut self,
17387 mode: language_settings::SoftWrap,
17388
17389 cx: &mut Context<Self>,
17390 ) {
17391 self.soft_wrap_mode_override = Some(mode);
17392 cx.notify();
17393 }
17394
17395 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
17396 self.hard_wrap = hard_wrap;
17397 cx.notify();
17398 }
17399
17400 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
17401 self.text_style_refinement = Some(style);
17402 }
17403
17404 /// called by the Element so we know what style we were most recently rendered with.
17405 pub(crate) fn set_style(
17406 &mut self,
17407 style: EditorStyle,
17408 window: &mut Window,
17409 cx: &mut Context<Self>,
17410 ) {
17411 // We intentionally do not inform the display map about the minimap style
17412 // so that wrapping is not recalculated and stays consistent for the editor
17413 // and its linked minimap.
17414 if !self.mode.is_minimap() {
17415 let rem_size = window.rem_size();
17416 self.display_map.update(cx, |map, cx| {
17417 map.set_font(
17418 style.text.font(),
17419 style.text.font_size.to_pixels(rem_size),
17420 cx,
17421 )
17422 });
17423 }
17424 self.style = Some(style);
17425 }
17426
17427 pub fn style(&self) -> Option<&EditorStyle> {
17428 self.style.as_ref()
17429 }
17430
17431 // Called by the element. This method is not designed to be called outside of the editor
17432 // element's layout code because it does not notify when rewrapping is computed synchronously.
17433 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
17434 self.display_map
17435 .update(cx, |map, cx| map.set_wrap_width(width, cx))
17436 }
17437
17438 pub fn set_soft_wrap(&mut self) {
17439 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
17440 }
17441
17442 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
17443 if self.soft_wrap_mode_override.is_some() {
17444 self.soft_wrap_mode_override.take();
17445 } else {
17446 let soft_wrap = match self.soft_wrap_mode(cx) {
17447 SoftWrap::GitDiff => return,
17448 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
17449 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
17450 language_settings::SoftWrap::None
17451 }
17452 };
17453 self.soft_wrap_mode_override = Some(soft_wrap);
17454 }
17455 cx.notify();
17456 }
17457
17458 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
17459 let Some(workspace) = self.workspace() else {
17460 return;
17461 };
17462 let fs = workspace.read(cx).app_state().fs.clone();
17463 let current_show = TabBarSettings::get_global(cx).show;
17464 update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
17465 setting.show = Some(!current_show);
17466 });
17467 }
17468
17469 pub fn toggle_indent_guides(
17470 &mut self,
17471 _: &ToggleIndentGuides,
17472 _: &mut Window,
17473 cx: &mut Context<Self>,
17474 ) {
17475 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
17476 self.buffer
17477 .read(cx)
17478 .language_settings(cx)
17479 .indent_guides
17480 .enabled
17481 });
17482 self.show_indent_guides = Some(!currently_enabled);
17483 cx.notify();
17484 }
17485
17486 fn should_show_indent_guides(&self) -> Option<bool> {
17487 self.show_indent_guides
17488 }
17489
17490 pub fn toggle_line_numbers(
17491 &mut self,
17492 _: &ToggleLineNumbers,
17493 _: &mut Window,
17494 cx: &mut Context<Self>,
17495 ) {
17496 let mut editor_settings = EditorSettings::get_global(cx).clone();
17497 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
17498 EditorSettings::override_global(editor_settings, cx);
17499 }
17500
17501 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
17502 if let Some(show_line_numbers) = self.show_line_numbers {
17503 return show_line_numbers;
17504 }
17505 EditorSettings::get_global(cx).gutter.line_numbers
17506 }
17507
17508 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
17509 self.use_relative_line_numbers
17510 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
17511 }
17512
17513 pub fn toggle_relative_line_numbers(
17514 &mut self,
17515 _: &ToggleRelativeLineNumbers,
17516 _: &mut Window,
17517 cx: &mut Context<Self>,
17518 ) {
17519 let is_relative = self.should_use_relative_line_numbers(cx);
17520 self.set_relative_line_number(Some(!is_relative), cx)
17521 }
17522
17523 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
17524 self.use_relative_line_numbers = is_relative;
17525 cx.notify();
17526 }
17527
17528 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
17529 self.show_gutter = show_gutter;
17530 cx.notify();
17531 }
17532
17533 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
17534 self.show_scrollbars = ScrollbarAxes {
17535 horizontal: show,
17536 vertical: show,
17537 };
17538 cx.notify();
17539 }
17540
17541 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17542 self.show_scrollbars.vertical = show;
17543 cx.notify();
17544 }
17545
17546 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
17547 self.show_scrollbars.horizontal = show;
17548 cx.notify();
17549 }
17550
17551 pub fn set_minimap_visibility(
17552 &mut self,
17553 minimap_visibility: MinimapVisibility,
17554 window: &mut Window,
17555 cx: &mut Context<Self>,
17556 ) {
17557 if self.minimap_visibility != minimap_visibility {
17558 if minimap_visibility.visible() && self.minimap.is_none() {
17559 let minimap_settings = EditorSettings::get_global(cx).minimap;
17560 self.minimap =
17561 self.create_minimap(minimap_settings.with_show_override(), window, cx);
17562 }
17563 self.minimap_visibility = minimap_visibility;
17564 cx.notify();
17565 }
17566 }
17567
17568 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17569 self.set_show_scrollbars(false, cx);
17570 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
17571 }
17572
17573 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
17574 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
17575 }
17576
17577 /// Normally the text in full mode and auto height editors is padded on the
17578 /// left side by roughly half a character width for improved hit testing.
17579 ///
17580 /// Use this method to disable this for cases where this is not wanted (e.g.
17581 /// if you want to align the editor text with some other text above or below)
17582 /// or if you want to add this padding to single-line editors.
17583 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
17584 self.offset_content = offset_content;
17585 cx.notify();
17586 }
17587
17588 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
17589 self.show_line_numbers = Some(show_line_numbers);
17590 cx.notify();
17591 }
17592
17593 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
17594 self.disable_expand_excerpt_buttons = true;
17595 cx.notify();
17596 }
17597
17598 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
17599 self.show_git_diff_gutter = Some(show_git_diff_gutter);
17600 cx.notify();
17601 }
17602
17603 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
17604 self.show_code_actions = Some(show_code_actions);
17605 cx.notify();
17606 }
17607
17608 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
17609 self.show_runnables = Some(show_runnables);
17610 cx.notify();
17611 }
17612
17613 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
17614 self.show_breakpoints = Some(show_breakpoints);
17615 cx.notify();
17616 }
17617
17618 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
17619 if self.display_map.read(cx).masked != masked {
17620 self.display_map.update(cx, |map, _| map.masked = masked);
17621 }
17622 cx.notify()
17623 }
17624
17625 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
17626 self.show_wrap_guides = Some(show_wrap_guides);
17627 cx.notify();
17628 }
17629
17630 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
17631 self.show_indent_guides = Some(show_indent_guides);
17632 cx.notify();
17633 }
17634
17635 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
17636 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
17637 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local()) {
17638 if let Some(dir) = file.abs_path(cx).parent() {
17639 return Some(dir.to_owned());
17640 }
17641 }
17642
17643 if let Some(project_path) = buffer.read(cx).project_path(cx) {
17644 return Some(project_path.path.to_path_buf());
17645 }
17646 }
17647
17648 None
17649 }
17650
17651 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
17652 self.active_excerpt(cx)?
17653 .1
17654 .read(cx)
17655 .file()
17656 .and_then(|f| f.as_local())
17657 }
17658
17659 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17660 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17661 let buffer = buffer.read(cx);
17662 if let Some(project_path) = buffer.project_path(cx) {
17663 let project = self.project.as_ref()?.read(cx);
17664 project.absolute_path(&project_path, cx)
17665 } else {
17666 buffer
17667 .file()
17668 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
17669 }
17670 })
17671 }
17672
17673 fn target_file_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
17674 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
17675 let project_path = buffer.read(cx).project_path(cx)?;
17676 let project = self.project.as_ref()?.read(cx);
17677 let entry = project.entry_for_path(&project_path, cx)?;
17678 let path = entry.path.to_path_buf();
17679 Some(path)
17680 })
17681 }
17682
17683 pub fn reveal_in_finder(
17684 &mut self,
17685 _: &RevealInFileManager,
17686 _window: &mut Window,
17687 cx: &mut Context<Self>,
17688 ) {
17689 if let Some(target) = self.target_file(cx) {
17690 cx.reveal_path(&target.abs_path(cx));
17691 }
17692 }
17693
17694 pub fn copy_path(
17695 &mut self,
17696 _: &zed_actions::workspace::CopyPath,
17697 _window: &mut Window,
17698 cx: &mut Context<Self>,
17699 ) {
17700 if let Some(path) = self.target_file_abs_path(cx) {
17701 if let Some(path) = path.to_str() {
17702 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17703 }
17704 }
17705 }
17706
17707 pub fn copy_relative_path(
17708 &mut self,
17709 _: &zed_actions::workspace::CopyRelativePath,
17710 _window: &mut Window,
17711 cx: &mut Context<Self>,
17712 ) {
17713 if let Some(path) = self.target_file_path(cx) {
17714 if let Some(path) = path.to_str() {
17715 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
17716 }
17717 }
17718 }
17719
17720 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
17721 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
17722 buffer.read(cx).project_path(cx)
17723 } else {
17724 None
17725 }
17726 }
17727
17728 // Returns true if the editor handled a go-to-line request
17729 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
17730 maybe!({
17731 let breakpoint_store = self.breakpoint_store.as_ref()?;
17732
17733 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
17734 else {
17735 self.clear_row_highlights::<ActiveDebugLine>();
17736 return None;
17737 };
17738
17739 let position = active_stack_frame.position;
17740 let buffer_id = position.buffer_id?;
17741 let snapshot = self
17742 .project
17743 .as_ref()?
17744 .read(cx)
17745 .buffer_for_id(buffer_id, cx)?
17746 .read(cx)
17747 .snapshot();
17748
17749 let mut handled = false;
17750 for (id, ExcerptRange { context, .. }) in
17751 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
17752 {
17753 if context.start.cmp(&position, &snapshot).is_ge()
17754 || context.end.cmp(&position, &snapshot).is_lt()
17755 {
17756 continue;
17757 }
17758 let snapshot = self.buffer.read(cx).snapshot(cx);
17759 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
17760
17761 handled = true;
17762 self.clear_row_highlights::<ActiveDebugLine>();
17763
17764 self.go_to_line::<ActiveDebugLine>(
17765 multibuffer_anchor,
17766 Some(cx.theme().colors().editor_debugger_active_line_background),
17767 window,
17768 cx,
17769 );
17770
17771 cx.notify();
17772 }
17773
17774 handled.then_some(())
17775 })
17776 .is_some()
17777 }
17778
17779 pub fn copy_file_name_without_extension(
17780 &mut self,
17781 _: &CopyFileNameWithoutExtension,
17782 _: &mut Window,
17783 cx: &mut Context<Self>,
17784 ) {
17785 if let Some(file) = self.target_file(cx) {
17786 if let Some(file_stem) = file.path().file_stem() {
17787 if let Some(name) = file_stem.to_str() {
17788 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17789 }
17790 }
17791 }
17792 }
17793
17794 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
17795 if let Some(file) = self.target_file(cx) {
17796 if let Some(file_name) = file.path().file_name() {
17797 if let Some(name) = file_name.to_str() {
17798 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
17799 }
17800 }
17801 }
17802 }
17803
17804 pub fn toggle_git_blame(
17805 &mut self,
17806 _: &::git::Blame,
17807 window: &mut Window,
17808 cx: &mut Context<Self>,
17809 ) {
17810 self.show_git_blame_gutter = !self.show_git_blame_gutter;
17811
17812 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
17813 self.start_git_blame(true, window, cx);
17814 }
17815
17816 cx.notify();
17817 }
17818
17819 pub fn toggle_git_blame_inline(
17820 &mut self,
17821 _: &ToggleGitBlameInline,
17822 window: &mut Window,
17823 cx: &mut Context<Self>,
17824 ) {
17825 self.toggle_git_blame_inline_internal(true, window, cx);
17826 cx.notify();
17827 }
17828
17829 pub fn open_git_blame_commit(
17830 &mut self,
17831 _: &OpenGitBlameCommit,
17832 window: &mut Window,
17833 cx: &mut Context<Self>,
17834 ) {
17835 self.open_git_blame_commit_internal(window, cx);
17836 }
17837
17838 fn open_git_blame_commit_internal(
17839 &mut self,
17840 window: &mut Window,
17841 cx: &mut Context<Self>,
17842 ) -> Option<()> {
17843 let blame = self.blame.as_ref()?;
17844 let snapshot = self.snapshot(window, cx);
17845 let cursor = self.selections.newest::<Point>(cx).head();
17846 let (buffer, point, _) = snapshot.buffer_snapshot.point_to_buffer_point(cursor)?;
17847 let blame_entry = blame
17848 .update(cx, |blame, cx| {
17849 blame
17850 .blame_for_rows(
17851 &[RowInfo {
17852 buffer_id: Some(buffer.remote_id()),
17853 buffer_row: Some(point.row),
17854 ..Default::default()
17855 }],
17856 cx,
17857 )
17858 .next()
17859 })
17860 .flatten()?;
17861 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
17862 let repo = blame.read(cx).repository(cx)?;
17863 let workspace = self.workspace()?.downgrade();
17864 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
17865 None
17866 }
17867
17868 pub fn git_blame_inline_enabled(&self) -> bool {
17869 self.git_blame_inline_enabled
17870 }
17871
17872 pub fn toggle_selection_menu(
17873 &mut self,
17874 _: &ToggleSelectionMenu,
17875 _: &mut Window,
17876 cx: &mut Context<Self>,
17877 ) {
17878 self.show_selection_menu = self
17879 .show_selection_menu
17880 .map(|show_selections_menu| !show_selections_menu)
17881 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
17882
17883 cx.notify();
17884 }
17885
17886 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
17887 self.show_selection_menu
17888 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
17889 }
17890
17891 fn start_git_blame(
17892 &mut self,
17893 user_triggered: bool,
17894 window: &mut Window,
17895 cx: &mut Context<Self>,
17896 ) {
17897 if let Some(project) = self.project.as_ref() {
17898 let Some(buffer) = self.buffer().read(cx).as_singleton() else {
17899 return;
17900 };
17901
17902 if buffer.read(cx).file().is_none() {
17903 return;
17904 }
17905
17906 let focused = self.focus_handle(cx).contains_focused(window, cx);
17907
17908 let project = project.clone();
17909 let blame = cx.new(|cx| GitBlame::new(buffer, project, user_triggered, focused, cx));
17910 self.blame_subscription =
17911 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
17912 self.blame = Some(blame);
17913 }
17914 }
17915
17916 fn toggle_git_blame_inline_internal(
17917 &mut self,
17918 user_triggered: bool,
17919 window: &mut Window,
17920 cx: &mut Context<Self>,
17921 ) {
17922 if self.git_blame_inline_enabled {
17923 self.git_blame_inline_enabled = false;
17924 self.show_git_blame_inline = false;
17925 self.show_git_blame_inline_delay_task.take();
17926 } else {
17927 self.git_blame_inline_enabled = true;
17928 self.start_git_blame_inline(user_triggered, window, cx);
17929 }
17930
17931 cx.notify();
17932 }
17933
17934 fn start_git_blame_inline(
17935 &mut self,
17936 user_triggered: bool,
17937 window: &mut Window,
17938 cx: &mut Context<Self>,
17939 ) {
17940 self.start_git_blame(user_triggered, window, cx);
17941
17942 if ProjectSettings::get_global(cx)
17943 .git
17944 .inline_blame_delay()
17945 .is_some()
17946 {
17947 self.start_inline_blame_timer(window, cx);
17948 } else {
17949 self.show_git_blame_inline = true
17950 }
17951 }
17952
17953 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
17954 self.blame.as_ref()
17955 }
17956
17957 pub fn show_git_blame_gutter(&self) -> bool {
17958 self.show_git_blame_gutter
17959 }
17960
17961 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
17962 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
17963 }
17964
17965 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
17966 self.show_git_blame_inline
17967 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
17968 && !self.newest_selection_head_on_empty_line(cx)
17969 && self.has_blame_entries(cx)
17970 }
17971
17972 fn has_blame_entries(&self, cx: &App) -> bool {
17973 self.blame()
17974 .map_or(false, |blame| blame.read(cx).has_generated_entries())
17975 }
17976
17977 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
17978 let cursor_anchor = self.selections.newest_anchor().head();
17979
17980 let snapshot = self.buffer.read(cx).snapshot(cx);
17981 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
17982
17983 snapshot.line_len(buffer_row) == 0
17984 }
17985
17986 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
17987 let buffer_and_selection = maybe!({
17988 let selection = self.selections.newest::<Point>(cx);
17989 let selection_range = selection.range();
17990
17991 let multi_buffer = self.buffer().read(cx);
17992 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17993 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
17994
17995 let (buffer, range, _) = if selection.reversed {
17996 buffer_ranges.first()
17997 } else {
17998 buffer_ranges.last()
17999 }?;
18000
18001 let selection = text::ToPoint::to_point(&range.start, &buffer).row
18002 ..text::ToPoint::to_point(&range.end, &buffer).row;
18003 Some((
18004 multi_buffer.buffer(buffer.remote_id()).unwrap().clone(),
18005 selection,
18006 ))
18007 });
18008
18009 let Some((buffer, selection)) = buffer_and_selection else {
18010 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
18011 };
18012
18013 let Some(project) = self.project.as_ref() else {
18014 return Task::ready(Err(anyhow!("editor does not have project")));
18015 };
18016
18017 project.update(cx, |project, cx| {
18018 project.get_permalink_to_line(&buffer, selection, cx)
18019 })
18020 }
18021
18022 pub fn copy_permalink_to_line(
18023 &mut self,
18024 _: &CopyPermalinkToLine,
18025 window: &mut Window,
18026 cx: &mut Context<Self>,
18027 ) {
18028 let permalink_task = self.get_permalink_to_line(cx);
18029 let workspace = self.workspace();
18030
18031 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18032 Ok(permalink) => {
18033 cx.update(|_, cx| {
18034 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
18035 })
18036 .ok();
18037 }
18038 Err(err) => {
18039 let message = format!("Failed to copy permalink: {err}");
18040
18041 anyhow::Result::<()>::Err(err).log_err();
18042
18043 if let Some(workspace) = workspace {
18044 workspace
18045 .update_in(cx, |workspace, _, cx| {
18046 struct CopyPermalinkToLine;
18047
18048 workspace.show_toast(
18049 Toast::new(
18050 NotificationId::unique::<CopyPermalinkToLine>(),
18051 message,
18052 ),
18053 cx,
18054 )
18055 })
18056 .ok();
18057 }
18058 }
18059 })
18060 .detach();
18061 }
18062
18063 pub fn copy_file_location(
18064 &mut self,
18065 _: &CopyFileLocation,
18066 _: &mut Window,
18067 cx: &mut Context<Self>,
18068 ) {
18069 let selection = self.selections.newest::<Point>(cx).start.row + 1;
18070 if let Some(file) = self.target_file(cx) {
18071 if let Some(path) = file.path().to_str() {
18072 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
18073 }
18074 }
18075 }
18076
18077 pub fn open_permalink_to_line(
18078 &mut self,
18079 _: &OpenPermalinkToLine,
18080 window: &mut Window,
18081 cx: &mut Context<Self>,
18082 ) {
18083 let permalink_task = self.get_permalink_to_line(cx);
18084 let workspace = self.workspace();
18085
18086 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
18087 Ok(permalink) => {
18088 cx.update(|_, cx| {
18089 cx.open_url(permalink.as_ref());
18090 })
18091 .ok();
18092 }
18093 Err(err) => {
18094 let message = format!("Failed to open permalink: {err}");
18095
18096 anyhow::Result::<()>::Err(err).log_err();
18097
18098 if let Some(workspace) = workspace {
18099 workspace
18100 .update(cx, |workspace, cx| {
18101 struct OpenPermalinkToLine;
18102
18103 workspace.show_toast(
18104 Toast::new(
18105 NotificationId::unique::<OpenPermalinkToLine>(),
18106 message,
18107 ),
18108 cx,
18109 )
18110 })
18111 .ok();
18112 }
18113 }
18114 })
18115 .detach();
18116 }
18117
18118 pub fn insert_uuid_v4(
18119 &mut self,
18120 _: &InsertUuidV4,
18121 window: &mut Window,
18122 cx: &mut Context<Self>,
18123 ) {
18124 self.insert_uuid(UuidVersion::V4, window, cx);
18125 }
18126
18127 pub fn insert_uuid_v7(
18128 &mut self,
18129 _: &InsertUuidV7,
18130 window: &mut Window,
18131 cx: &mut Context<Self>,
18132 ) {
18133 self.insert_uuid(UuidVersion::V7, window, cx);
18134 }
18135
18136 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
18137 self.hide_mouse_cursor(&HideMouseCursorOrigin::TypingAction);
18138 self.transact(window, cx, |this, window, cx| {
18139 let edits = this
18140 .selections
18141 .all::<Point>(cx)
18142 .into_iter()
18143 .map(|selection| {
18144 let uuid = match version {
18145 UuidVersion::V4 => uuid::Uuid::new_v4(),
18146 UuidVersion::V7 => uuid::Uuid::now_v7(),
18147 };
18148
18149 (selection.range(), uuid.to_string())
18150 });
18151 this.edit(edits, cx);
18152 this.refresh_inline_completion(true, false, window, cx);
18153 });
18154 }
18155
18156 pub fn open_selections_in_multibuffer(
18157 &mut self,
18158 _: &OpenSelectionsInMultibuffer,
18159 window: &mut Window,
18160 cx: &mut Context<Self>,
18161 ) {
18162 let multibuffer = self.buffer.read(cx);
18163
18164 let Some(buffer) = multibuffer.as_singleton() else {
18165 return;
18166 };
18167
18168 let Some(workspace) = self.workspace() else {
18169 return;
18170 };
18171
18172 let locations = self
18173 .selections
18174 .disjoint_anchors()
18175 .iter()
18176 .map(|range| Location {
18177 buffer: buffer.clone(),
18178 range: range.start.text_anchor..range.end.text_anchor,
18179 })
18180 .collect::<Vec<_>>();
18181
18182 let title = multibuffer.title(cx).to_string();
18183
18184 cx.spawn_in(window, async move |_, cx| {
18185 workspace.update_in(cx, |workspace, window, cx| {
18186 Self::open_locations_in_multibuffer(
18187 workspace,
18188 locations,
18189 format!("Selections for '{title}'"),
18190 false,
18191 MultibufferSelectionMode::All,
18192 window,
18193 cx,
18194 );
18195 })
18196 })
18197 .detach();
18198 }
18199
18200 /// Adds a row highlight for the given range. If a row has multiple highlights, the
18201 /// last highlight added will be used.
18202 ///
18203 /// If the range ends at the beginning of a line, then that line will not be highlighted.
18204 pub fn highlight_rows<T: 'static>(
18205 &mut self,
18206 range: Range<Anchor>,
18207 color: Hsla,
18208 options: RowHighlightOptions,
18209 cx: &mut Context<Self>,
18210 ) {
18211 let snapshot = self.buffer().read(cx).snapshot(cx);
18212 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18213 let ix = row_highlights.binary_search_by(|highlight| {
18214 Ordering::Equal
18215 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
18216 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
18217 });
18218
18219 if let Err(mut ix) = ix {
18220 let index = post_inc(&mut self.highlight_order);
18221
18222 // If this range intersects with the preceding highlight, then merge it with
18223 // the preceding highlight. Otherwise insert a new highlight.
18224 let mut merged = false;
18225 if ix > 0 {
18226 let prev_highlight = &mut row_highlights[ix - 1];
18227 if prev_highlight
18228 .range
18229 .end
18230 .cmp(&range.start, &snapshot)
18231 .is_ge()
18232 {
18233 ix -= 1;
18234 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
18235 prev_highlight.range.end = range.end;
18236 }
18237 merged = true;
18238 prev_highlight.index = index;
18239 prev_highlight.color = color;
18240 prev_highlight.options = options;
18241 }
18242 }
18243
18244 if !merged {
18245 row_highlights.insert(
18246 ix,
18247 RowHighlight {
18248 range: range.clone(),
18249 index,
18250 color,
18251 options,
18252 type_id: TypeId::of::<T>(),
18253 },
18254 );
18255 }
18256
18257 // If any of the following highlights intersect with this one, merge them.
18258 while let Some(next_highlight) = row_highlights.get(ix + 1) {
18259 let highlight = &row_highlights[ix];
18260 if next_highlight
18261 .range
18262 .start
18263 .cmp(&highlight.range.end, &snapshot)
18264 .is_le()
18265 {
18266 if next_highlight
18267 .range
18268 .end
18269 .cmp(&highlight.range.end, &snapshot)
18270 .is_gt()
18271 {
18272 row_highlights[ix].range.end = next_highlight.range.end;
18273 }
18274 row_highlights.remove(ix + 1);
18275 } else {
18276 break;
18277 }
18278 }
18279 }
18280 }
18281
18282 /// Remove any highlighted row ranges of the given type that intersect the
18283 /// given ranges.
18284 pub fn remove_highlighted_rows<T: 'static>(
18285 &mut self,
18286 ranges_to_remove: Vec<Range<Anchor>>,
18287 cx: &mut Context<Self>,
18288 ) {
18289 let snapshot = self.buffer().read(cx).snapshot(cx);
18290 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
18291 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18292 row_highlights.retain(|highlight| {
18293 while let Some(range_to_remove) = ranges_to_remove.peek() {
18294 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
18295 Ordering::Less | Ordering::Equal => {
18296 ranges_to_remove.next();
18297 }
18298 Ordering::Greater => {
18299 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
18300 Ordering::Less | Ordering::Equal => {
18301 return false;
18302 }
18303 Ordering::Greater => break,
18304 }
18305 }
18306 }
18307 }
18308
18309 true
18310 })
18311 }
18312
18313 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
18314 pub fn clear_row_highlights<T: 'static>(&mut self) {
18315 self.highlighted_rows.remove(&TypeId::of::<T>());
18316 }
18317
18318 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
18319 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
18320 self.highlighted_rows
18321 .get(&TypeId::of::<T>())
18322 .map_or(&[] as &[_], |vec| vec.as_slice())
18323 .iter()
18324 .map(|highlight| (highlight.range.clone(), highlight.color))
18325 }
18326
18327 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
18328 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
18329 /// Allows to ignore certain kinds of highlights.
18330 pub fn highlighted_display_rows(
18331 &self,
18332 window: &mut Window,
18333 cx: &mut App,
18334 ) -> BTreeMap<DisplayRow, LineHighlight> {
18335 let snapshot = self.snapshot(window, cx);
18336 let mut used_highlight_orders = HashMap::default();
18337 self.highlighted_rows
18338 .iter()
18339 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
18340 .fold(
18341 BTreeMap::<DisplayRow, LineHighlight>::new(),
18342 |mut unique_rows, highlight| {
18343 let start = highlight.range.start.to_display_point(&snapshot);
18344 let end = highlight.range.end.to_display_point(&snapshot);
18345 let start_row = start.row().0;
18346 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
18347 && end.column() == 0
18348 {
18349 end.row().0.saturating_sub(1)
18350 } else {
18351 end.row().0
18352 };
18353 for row in start_row..=end_row {
18354 let used_index =
18355 used_highlight_orders.entry(row).or_insert(highlight.index);
18356 if highlight.index >= *used_index {
18357 *used_index = highlight.index;
18358 unique_rows.insert(
18359 DisplayRow(row),
18360 LineHighlight {
18361 include_gutter: highlight.options.include_gutter,
18362 border: None,
18363 background: highlight.color.into(),
18364 type_id: Some(highlight.type_id),
18365 },
18366 );
18367 }
18368 }
18369 unique_rows
18370 },
18371 )
18372 }
18373
18374 pub fn highlighted_display_row_for_autoscroll(
18375 &self,
18376 snapshot: &DisplaySnapshot,
18377 ) -> Option<DisplayRow> {
18378 self.highlighted_rows
18379 .values()
18380 .flat_map(|highlighted_rows| highlighted_rows.iter())
18381 .filter_map(|highlight| {
18382 if highlight.options.autoscroll {
18383 Some(highlight.range.start.to_display_point(snapshot).row())
18384 } else {
18385 None
18386 }
18387 })
18388 .min()
18389 }
18390
18391 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
18392 self.highlight_background::<SearchWithinRange>(
18393 ranges,
18394 |colors| colors.editor_document_highlight_read_background,
18395 cx,
18396 )
18397 }
18398
18399 pub fn set_breadcrumb_header(&mut self, new_header: String) {
18400 self.breadcrumb_header = Some(new_header);
18401 }
18402
18403 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
18404 self.clear_background_highlights::<SearchWithinRange>(cx);
18405 }
18406
18407 pub fn highlight_background<T: 'static>(
18408 &mut self,
18409 ranges: &[Range<Anchor>],
18410 color_fetcher: fn(&ThemeColors) -> Hsla,
18411 cx: &mut Context<Self>,
18412 ) {
18413 self.background_highlights
18414 .insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
18415 self.scrollbar_marker_state.dirty = true;
18416 cx.notify();
18417 }
18418
18419 pub fn clear_background_highlights<T: 'static>(
18420 &mut self,
18421 cx: &mut Context<Self>,
18422 ) -> Option<BackgroundHighlight> {
18423 let text_highlights = self.background_highlights.remove(&TypeId::of::<T>())?;
18424 if !text_highlights.1.is_empty() {
18425 self.scrollbar_marker_state.dirty = true;
18426 cx.notify();
18427 }
18428 Some(text_highlights)
18429 }
18430
18431 pub fn highlight_gutter<T: 'static>(
18432 &mut self,
18433 ranges: impl Into<Vec<Range<Anchor>>>,
18434 color_fetcher: fn(&App) -> Hsla,
18435 cx: &mut Context<Self>,
18436 ) {
18437 self.gutter_highlights
18438 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
18439 cx.notify();
18440 }
18441
18442 pub fn clear_gutter_highlights<T: 'static>(
18443 &mut self,
18444 cx: &mut Context<Self>,
18445 ) -> Option<GutterHighlight> {
18446 cx.notify();
18447 self.gutter_highlights.remove(&TypeId::of::<T>())
18448 }
18449
18450 pub fn insert_gutter_highlight<T: 'static>(
18451 &mut self,
18452 range: Range<Anchor>,
18453 color_fetcher: fn(&App) -> Hsla,
18454 cx: &mut Context<Self>,
18455 ) {
18456 let snapshot = self.buffer().read(cx).snapshot(cx);
18457 let mut highlights = self
18458 .gutter_highlights
18459 .remove(&TypeId::of::<T>())
18460 .map(|(_, highlights)| highlights)
18461 .unwrap_or_default();
18462 let ix = highlights.binary_search_by(|highlight| {
18463 Ordering::Equal
18464 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
18465 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
18466 });
18467 if let Err(ix) = ix {
18468 highlights.insert(ix, range);
18469 }
18470 self.gutter_highlights
18471 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
18472 }
18473
18474 pub fn remove_gutter_highlights<T: 'static>(
18475 &mut self,
18476 ranges_to_remove: Vec<Range<Anchor>>,
18477 cx: &mut Context<Self>,
18478 ) {
18479 let snapshot = self.buffer().read(cx).snapshot(cx);
18480 let Some((color_fetcher, mut gutter_highlights)) =
18481 self.gutter_highlights.remove(&TypeId::of::<T>())
18482 else {
18483 return;
18484 };
18485 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
18486 gutter_highlights.retain(|highlight| {
18487 while let Some(range_to_remove) = ranges_to_remove.peek() {
18488 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
18489 Ordering::Less | Ordering::Equal => {
18490 ranges_to_remove.next();
18491 }
18492 Ordering::Greater => {
18493 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
18494 Ordering::Less | Ordering::Equal => {
18495 return false;
18496 }
18497 Ordering::Greater => break,
18498 }
18499 }
18500 }
18501 }
18502
18503 true
18504 });
18505 self.gutter_highlights
18506 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
18507 }
18508
18509 #[cfg(feature = "test-support")]
18510 pub fn all_text_background_highlights(
18511 &self,
18512 window: &mut Window,
18513 cx: &mut Context<Self>,
18514 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18515 let snapshot = self.snapshot(window, cx);
18516 let buffer = &snapshot.buffer_snapshot;
18517 let start = buffer.anchor_before(0);
18518 let end = buffer.anchor_after(buffer.len());
18519 let theme = cx.theme().colors();
18520 self.background_highlights_in_range(start..end, &snapshot, theme)
18521 }
18522
18523 #[cfg(feature = "test-support")]
18524 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
18525 let snapshot = self.buffer().read(cx).snapshot(cx);
18526
18527 let highlights = self
18528 .background_highlights
18529 .get(&TypeId::of::<items::BufferSearchHighlights>());
18530
18531 if let Some((_color, ranges)) = highlights {
18532 ranges
18533 .iter()
18534 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
18535 .collect_vec()
18536 } else {
18537 vec![]
18538 }
18539 }
18540
18541 fn document_highlights_for_position<'a>(
18542 &'a self,
18543 position: Anchor,
18544 buffer: &'a MultiBufferSnapshot,
18545 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
18546 let read_highlights = self
18547 .background_highlights
18548 .get(&TypeId::of::<DocumentHighlightRead>())
18549 .map(|h| &h.1);
18550 let write_highlights = self
18551 .background_highlights
18552 .get(&TypeId::of::<DocumentHighlightWrite>())
18553 .map(|h| &h.1);
18554 let left_position = position.bias_left(buffer);
18555 let right_position = position.bias_right(buffer);
18556 read_highlights
18557 .into_iter()
18558 .chain(write_highlights)
18559 .flat_map(move |ranges| {
18560 let start_ix = match ranges.binary_search_by(|probe| {
18561 let cmp = probe.end.cmp(&left_position, buffer);
18562 if cmp.is_ge() {
18563 Ordering::Greater
18564 } else {
18565 Ordering::Less
18566 }
18567 }) {
18568 Ok(i) | Err(i) => i,
18569 };
18570
18571 ranges[start_ix..]
18572 .iter()
18573 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
18574 })
18575 }
18576
18577 pub fn has_background_highlights<T: 'static>(&self) -> bool {
18578 self.background_highlights
18579 .get(&TypeId::of::<T>())
18580 .map_or(false, |(_, highlights)| !highlights.is_empty())
18581 }
18582
18583 pub fn background_highlights_in_range(
18584 &self,
18585 search_range: Range<Anchor>,
18586 display_snapshot: &DisplaySnapshot,
18587 theme: &ThemeColors,
18588 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18589 let mut results = Vec::new();
18590 for (color_fetcher, ranges) in self.background_highlights.values() {
18591 let color = color_fetcher(theme);
18592 let start_ix = match ranges.binary_search_by(|probe| {
18593 let cmp = probe
18594 .end
18595 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18596 if cmp.is_gt() {
18597 Ordering::Greater
18598 } else {
18599 Ordering::Less
18600 }
18601 }) {
18602 Ok(i) | Err(i) => i,
18603 };
18604 for range in &ranges[start_ix..] {
18605 if range
18606 .start
18607 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18608 .is_ge()
18609 {
18610 break;
18611 }
18612
18613 let start = range.start.to_display_point(display_snapshot);
18614 let end = range.end.to_display_point(display_snapshot);
18615 results.push((start..end, color))
18616 }
18617 }
18618 results
18619 }
18620
18621 pub fn background_highlight_row_ranges<T: 'static>(
18622 &self,
18623 search_range: Range<Anchor>,
18624 display_snapshot: &DisplaySnapshot,
18625 count: usize,
18626 ) -> Vec<RangeInclusive<DisplayPoint>> {
18627 let mut results = Vec::new();
18628 let Some((_, ranges)) = self.background_highlights.get(&TypeId::of::<T>()) else {
18629 return vec![];
18630 };
18631
18632 let start_ix = match ranges.binary_search_by(|probe| {
18633 let cmp = probe
18634 .end
18635 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18636 if cmp.is_gt() {
18637 Ordering::Greater
18638 } else {
18639 Ordering::Less
18640 }
18641 }) {
18642 Ok(i) | Err(i) => i,
18643 };
18644 let mut push_region = |start: Option<Point>, end: Option<Point>| {
18645 if let (Some(start_display), Some(end_display)) = (start, end) {
18646 results.push(
18647 start_display.to_display_point(display_snapshot)
18648 ..=end_display.to_display_point(display_snapshot),
18649 );
18650 }
18651 };
18652 let mut start_row: Option<Point> = None;
18653 let mut end_row: Option<Point> = None;
18654 if ranges.len() > count {
18655 return Vec::new();
18656 }
18657 for range in &ranges[start_ix..] {
18658 if range
18659 .start
18660 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18661 .is_ge()
18662 {
18663 break;
18664 }
18665 let end = range.end.to_point(&display_snapshot.buffer_snapshot);
18666 if let Some(current_row) = &end_row {
18667 if end.row == current_row.row {
18668 continue;
18669 }
18670 }
18671 let start = range.start.to_point(&display_snapshot.buffer_snapshot);
18672 if start_row.is_none() {
18673 assert_eq!(end_row, None);
18674 start_row = Some(start);
18675 end_row = Some(end);
18676 continue;
18677 }
18678 if let Some(current_end) = end_row.as_mut() {
18679 if start.row > current_end.row + 1 {
18680 push_region(start_row, end_row);
18681 start_row = Some(start);
18682 end_row = Some(end);
18683 } else {
18684 // Merge two hunks.
18685 *current_end = end;
18686 }
18687 } else {
18688 unreachable!();
18689 }
18690 }
18691 // We might still have a hunk that was not rendered (if there was a search hit on the last line)
18692 push_region(start_row, end_row);
18693 results
18694 }
18695
18696 pub fn gutter_highlights_in_range(
18697 &self,
18698 search_range: Range<Anchor>,
18699 display_snapshot: &DisplaySnapshot,
18700 cx: &App,
18701 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
18702 let mut results = Vec::new();
18703 for (color_fetcher, ranges) in self.gutter_highlights.values() {
18704 let color = color_fetcher(cx);
18705 let start_ix = match ranges.binary_search_by(|probe| {
18706 let cmp = probe
18707 .end
18708 .cmp(&search_range.start, &display_snapshot.buffer_snapshot);
18709 if cmp.is_gt() {
18710 Ordering::Greater
18711 } else {
18712 Ordering::Less
18713 }
18714 }) {
18715 Ok(i) | Err(i) => i,
18716 };
18717 for range in &ranges[start_ix..] {
18718 if range
18719 .start
18720 .cmp(&search_range.end, &display_snapshot.buffer_snapshot)
18721 .is_ge()
18722 {
18723 break;
18724 }
18725
18726 let start = range.start.to_display_point(display_snapshot);
18727 let end = range.end.to_display_point(display_snapshot);
18728 results.push((start..end, color))
18729 }
18730 }
18731 results
18732 }
18733
18734 /// Get the text ranges corresponding to the redaction query
18735 pub fn redacted_ranges(
18736 &self,
18737 search_range: Range<Anchor>,
18738 display_snapshot: &DisplaySnapshot,
18739 cx: &App,
18740 ) -> Vec<Range<DisplayPoint>> {
18741 display_snapshot
18742 .buffer_snapshot
18743 .redacted_ranges(search_range, |file| {
18744 if let Some(file) = file {
18745 file.is_private()
18746 && EditorSettings::get(
18747 Some(SettingsLocation {
18748 worktree_id: file.worktree_id(cx),
18749 path: file.path().as_ref(),
18750 }),
18751 cx,
18752 )
18753 .redact_private_values
18754 } else {
18755 false
18756 }
18757 })
18758 .map(|range| {
18759 range.start.to_display_point(display_snapshot)
18760 ..range.end.to_display_point(display_snapshot)
18761 })
18762 .collect()
18763 }
18764
18765 pub fn highlight_text<T: 'static>(
18766 &mut self,
18767 ranges: Vec<Range<Anchor>>,
18768 style: HighlightStyle,
18769 cx: &mut Context<Self>,
18770 ) {
18771 self.display_map.update(cx, |map, _| {
18772 map.highlight_text(TypeId::of::<T>(), ranges, style)
18773 });
18774 cx.notify();
18775 }
18776
18777 pub(crate) fn highlight_inlays<T: 'static>(
18778 &mut self,
18779 highlights: Vec<InlayHighlight>,
18780 style: HighlightStyle,
18781 cx: &mut Context<Self>,
18782 ) {
18783 self.display_map.update(cx, |map, _| {
18784 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
18785 });
18786 cx.notify();
18787 }
18788
18789 pub fn text_highlights<'a, T: 'static>(
18790 &'a self,
18791 cx: &'a App,
18792 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
18793 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
18794 }
18795
18796 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
18797 let cleared = self
18798 .display_map
18799 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
18800 if cleared {
18801 cx.notify();
18802 }
18803 }
18804
18805 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
18806 (self.read_only(cx) || self.blink_manager.read(cx).visible())
18807 && self.focus_handle.is_focused(window)
18808 }
18809
18810 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
18811 self.show_cursor_when_unfocused = is_enabled;
18812 cx.notify();
18813 }
18814
18815 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
18816 cx.notify();
18817 }
18818
18819 fn on_debug_session_event(
18820 &mut self,
18821 _session: Entity<Session>,
18822 event: &SessionEvent,
18823 cx: &mut Context<Self>,
18824 ) {
18825 match event {
18826 SessionEvent::InvalidateInlineValue => {
18827 self.refresh_inline_values(cx);
18828 }
18829 _ => {}
18830 }
18831 }
18832
18833 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
18834 let Some(project) = self.project.clone() else {
18835 return;
18836 };
18837
18838 if !self.inline_value_cache.enabled {
18839 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
18840 self.splice_inlays(&inlays, Vec::new(), cx);
18841 return;
18842 }
18843
18844 let current_execution_position = self
18845 .highlighted_rows
18846 .get(&TypeId::of::<ActiveDebugLine>())
18847 .and_then(|lines| lines.last().map(|line| line.range.start));
18848
18849 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
18850 let inline_values = editor
18851 .update(cx, |editor, cx| {
18852 let Some(current_execution_position) = current_execution_position else {
18853 return Some(Task::ready(Ok(Vec::new())));
18854 };
18855
18856 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
18857 let snapshot = buffer.snapshot(cx);
18858
18859 let excerpt = snapshot.excerpt_containing(
18860 current_execution_position..current_execution_position,
18861 )?;
18862
18863 editor.buffer.read(cx).buffer(excerpt.buffer_id())
18864 })?;
18865
18866 let range =
18867 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
18868
18869 project.inline_values(buffer, range, cx)
18870 })
18871 .ok()
18872 .flatten()?
18873 .await
18874 .context("refreshing debugger inlays")
18875 .log_err()?;
18876
18877 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
18878
18879 for (buffer_id, inline_value) in inline_values
18880 .into_iter()
18881 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
18882 {
18883 buffer_inline_values
18884 .entry(buffer_id)
18885 .or_default()
18886 .push(inline_value);
18887 }
18888
18889 editor
18890 .update(cx, |editor, cx| {
18891 let snapshot = editor.buffer.read(cx).snapshot(cx);
18892 let mut new_inlays = Vec::default();
18893
18894 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
18895 let buffer_id = buffer_snapshot.remote_id();
18896 buffer_inline_values
18897 .get(&buffer_id)
18898 .into_iter()
18899 .flatten()
18900 .for_each(|hint| {
18901 let inlay = Inlay::debugger_hint(
18902 post_inc(&mut editor.next_inlay_id),
18903 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
18904 hint.text(),
18905 );
18906
18907 new_inlays.push(inlay);
18908 });
18909 }
18910
18911 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
18912 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
18913
18914 editor.splice_inlays(&inlay_ids, new_inlays, cx);
18915 })
18916 .ok()?;
18917 Some(())
18918 });
18919 }
18920
18921 fn on_buffer_event(
18922 &mut self,
18923 multibuffer: &Entity<MultiBuffer>,
18924 event: &multi_buffer::Event,
18925 window: &mut Window,
18926 cx: &mut Context<Self>,
18927 ) {
18928 match event {
18929 multi_buffer::Event::Edited {
18930 singleton_buffer_edited,
18931 edited_buffer,
18932 } => {
18933 self.scrollbar_marker_state.dirty = true;
18934 self.active_indent_guides_state.dirty = true;
18935 self.refresh_active_diagnostics(cx);
18936 self.refresh_code_actions(window, cx);
18937 self.refresh_selected_text_highlights(true, window, cx);
18938 refresh_matching_bracket_highlights(self, window, cx);
18939 if self.has_active_inline_completion() {
18940 self.update_visible_inline_completion(window, cx);
18941 }
18942 if let Some(project) = self.project.as_ref() {
18943 if let Some(edited_buffer) = edited_buffer {
18944 project.update(cx, |project, cx| {
18945 self.registered_buffers
18946 .entry(edited_buffer.read(cx).remote_id())
18947 .or_insert_with(|| {
18948 project
18949 .register_buffer_with_language_servers(&edited_buffer, cx)
18950 });
18951 });
18952 if edited_buffer.read(cx).file().is_some() {
18953 self.pull_diagnostics(
18954 Some(edited_buffer.read(cx).remote_id()),
18955 window,
18956 cx,
18957 );
18958 }
18959 }
18960 }
18961 cx.emit(EditorEvent::BufferEdited);
18962 cx.emit(SearchEvent::MatchesInvalidated);
18963 if *singleton_buffer_edited {
18964 if let Some(buffer) = edited_buffer {
18965 if buffer.read(cx).file().is_none() {
18966 cx.emit(EditorEvent::TitleChanged);
18967 }
18968 }
18969 if let Some(project) = &self.project {
18970 #[allow(clippy::mutable_key_type)]
18971 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
18972 multibuffer
18973 .all_buffers()
18974 .into_iter()
18975 .filter_map(|buffer| {
18976 buffer.update(cx, |buffer, cx| {
18977 let language = buffer.language()?;
18978 let should_discard = project.update(cx, |project, cx| {
18979 project.is_local()
18980 && !project.has_language_servers_for(buffer, cx)
18981 });
18982 should_discard.not().then_some(language.clone())
18983 })
18984 })
18985 .collect::<HashSet<_>>()
18986 });
18987 if !languages_affected.is_empty() {
18988 self.refresh_inlay_hints(
18989 InlayHintRefreshReason::BufferEdited(languages_affected),
18990 cx,
18991 );
18992 }
18993 }
18994 }
18995
18996 let Some(project) = &self.project else { return };
18997 let (telemetry, is_via_ssh) = {
18998 let project = project.read(cx);
18999 let telemetry = project.client().telemetry().clone();
19000 let is_via_ssh = project.is_via_ssh();
19001 (telemetry, is_via_ssh)
19002 };
19003 refresh_linked_ranges(self, window, cx);
19004 telemetry.log_edit_event("editor", is_via_ssh);
19005 }
19006 multi_buffer::Event::ExcerptsAdded {
19007 buffer,
19008 predecessor,
19009 excerpts,
19010 } => {
19011 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19012 let buffer_id = buffer.read(cx).remote_id();
19013 if self.buffer.read(cx).diff_for(buffer_id).is_none() {
19014 if let Some(project) = &self.project {
19015 update_uncommitted_diff_for_buffer(
19016 cx.entity(),
19017 project,
19018 [buffer.clone()],
19019 self.buffer.clone(),
19020 cx,
19021 )
19022 .detach();
19023 }
19024 }
19025 cx.emit(EditorEvent::ExcerptsAdded {
19026 buffer: buffer.clone(),
19027 predecessor: *predecessor,
19028 excerpts: excerpts.clone(),
19029 });
19030 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19031 }
19032 multi_buffer::Event::ExcerptsRemoved {
19033 ids,
19034 removed_buffer_ids,
19035 } => {
19036 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
19037 let buffer = self.buffer.read(cx);
19038 self.registered_buffers
19039 .retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
19040 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19041 cx.emit(EditorEvent::ExcerptsRemoved {
19042 ids: ids.clone(),
19043 removed_buffer_ids: removed_buffer_ids.clone(),
19044 })
19045 }
19046 multi_buffer::Event::ExcerptsEdited {
19047 excerpt_ids,
19048 buffer_ids,
19049 } => {
19050 self.display_map.update(cx, |map, cx| {
19051 map.unfold_buffers(buffer_ids.iter().copied(), cx)
19052 });
19053 cx.emit(EditorEvent::ExcerptsEdited {
19054 ids: excerpt_ids.clone(),
19055 })
19056 }
19057 multi_buffer::Event::ExcerptsExpanded { ids } => {
19058 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
19059 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
19060 }
19061 multi_buffer::Event::Reparsed(buffer_id) => {
19062 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19063 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19064
19065 cx.emit(EditorEvent::Reparsed(*buffer_id));
19066 }
19067 multi_buffer::Event::DiffHunksToggled => {
19068 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19069 }
19070 multi_buffer::Event::LanguageChanged(buffer_id) => {
19071 linked_editing_ranges::refresh_linked_ranges(self, window, cx);
19072 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
19073 cx.emit(EditorEvent::Reparsed(*buffer_id));
19074 cx.notify();
19075 }
19076 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
19077 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
19078 multi_buffer::Event::FileHandleChanged
19079 | multi_buffer::Event::Reloaded
19080 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
19081 multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
19082 multi_buffer::Event::DiagnosticsUpdated => {
19083 self.update_diagnostics_state(window, cx);
19084 }
19085 _ => {}
19086 };
19087 }
19088
19089 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
19090 self.refresh_active_diagnostics(cx);
19091 self.refresh_inline_diagnostics(true, window, cx);
19092 self.scrollbar_marker_state.dirty = true;
19093 cx.notify();
19094 }
19095
19096 pub fn start_temporary_diff_override(&mut self) {
19097 self.load_diff_task.take();
19098 self.temporary_diff_override = true;
19099 }
19100
19101 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
19102 self.temporary_diff_override = false;
19103 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
19104 self.buffer.update(cx, |buffer, cx| {
19105 buffer.set_all_diff_hunks_collapsed(cx);
19106 });
19107
19108 if let Some(project) = self.project.clone() {
19109 self.load_diff_task = Some(
19110 update_uncommitted_diff_for_buffer(
19111 cx.entity(),
19112 &project,
19113 self.buffer.read(cx).all_buffers(),
19114 self.buffer.clone(),
19115 cx,
19116 )
19117 .shared(),
19118 );
19119 }
19120 }
19121
19122 fn on_display_map_changed(
19123 &mut self,
19124 _: Entity<DisplayMap>,
19125 _: &mut Window,
19126 cx: &mut Context<Self>,
19127 ) {
19128 cx.notify();
19129 }
19130
19131 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19132 let new_severity = if self.diagnostics_enabled() {
19133 EditorSettings::get_global(cx)
19134 .diagnostics_max_severity
19135 .unwrap_or(DiagnosticSeverity::Hint)
19136 } else {
19137 DiagnosticSeverity::Off
19138 };
19139 self.set_max_diagnostics_severity(new_severity, cx);
19140 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
19141 self.update_edit_prediction_settings(cx);
19142 self.refresh_inline_completion(true, false, window, cx);
19143 self.refresh_inlay_hints(
19144 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
19145 self.selections.newest_anchor().head(),
19146 &self.buffer.read(cx).snapshot(cx),
19147 cx,
19148 )),
19149 cx,
19150 );
19151
19152 let old_cursor_shape = self.cursor_shape;
19153
19154 {
19155 let editor_settings = EditorSettings::get_global(cx);
19156 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
19157 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
19158 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
19159 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
19160 self.drag_and_drop_selection_enabled = editor_settings.drag_and_drop_selection;
19161 }
19162
19163 if old_cursor_shape != self.cursor_shape {
19164 cx.emit(EditorEvent::CursorShapeChanged);
19165 }
19166
19167 let project_settings = ProjectSettings::get_global(cx);
19168 self.serialize_dirty_buffers =
19169 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
19170
19171 if self.mode.is_full() {
19172 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
19173 let inline_blame_enabled = project_settings.git.inline_blame_enabled();
19174 if self.show_inline_diagnostics != show_inline_diagnostics {
19175 self.show_inline_diagnostics = show_inline_diagnostics;
19176 self.refresh_inline_diagnostics(false, window, cx);
19177 }
19178
19179 if self.git_blame_inline_enabled != inline_blame_enabled {
19180 self.toggle_git_blame_inline_internal(false, window, cx);
19181 }
19182
19183 let minimap_settings = EditorSettings::get_global(cx).minimap;
19184 if self.minimap_visibility != MinimapVisibility::Disabled {
19185 if self.minimap_visibility.settings_visibility()
19186 != minimap_settings.minimap_enabled()
19187 {
19188 self.set_minimap_visibility(
19189 MinimapVisibility::for_mode(self.mode(), cx),
19190 window,
19191 cx,
19192 );
19193 } else if let Some(minimap_entity) = self.minimap.as_ref() {
19194 minimap_entity.update(cx, |minimap_editor, cx| {
19195 minimap_editor.update_minimap_configuration(minimap_settings, cx)
19196 })
19197 }
19198 }
19199 }
19200
19201 cx.notify();
19202 }
19203
19204 pub fn set_searchable(&mut self, searchable: bool) {
19205 self.searchable = searchable;
19206 }
19207
19208 pub fn searchable(&self) -> bool {
19209 self.searchable
19210 }
19211
19212 fn open_proposed_changes_editor(
19213 &mut self,
19214 _: &OpenProposedChangesEditor,
19215 window: &mut Window,
19216 cx: &mut Context<Self>,
19217 ) {
19218 let Some(workspace) = self.workspace() else {
19219 cx.propagate();
19220 return;
19221 };
19222
19223 let selections = self.selections.all::<usize>(cx);
19224 let multi_buffer = self.buffer.read(cx);
19225 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19226 let mut new_selections_by_buffer = HashMap::default();
19227 for selection in selections {
19228 for (buffer, range, _) in
19229 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
19230 {
19231 let mut range = range.to_point(buffer);
19232 range.start.column = 0;
19233 range.end.column = buffer.line_len(range.end.row);
19234 new_selections_by_buffer
19235 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
19236 .or_insert(Vec::new())
19237 .push(range)
19238 }
19239 }
19240
19241 let proposed_changes_buffers = new_selections_by_buffer
19242 .into_iter()
19243 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
19244 .collect::<Vec<_>>();
19245 let proposed_changes_editor = cx.new(|cx| {
19246 ProposedChangesEditor::new(
19247 "Proposed changes",
19248 proposed_changes_buffers,
19249 self.project.clone(),
19250 window,
19251 cx,
19252 )
19253 });
19254
19255 window.defer(cx, move |window, cx| {
19256 workspace.update(cx, |workspace, cx| {
19257 workspace.active_pane().update(cx, |pane, cx| {
19258 pane.add_item(
19259 Box::new(proposed_changes_editor),
19260 true,
19261 true,
19262 None,
19263 window,
19264 cx,
19265 );
19266 });
19267 });
19268 });
19269 }
19270
19271 pub fn open_excerpts_in_split(
19272 &mut self,
19273 _: &OpenExcerptsSplit,
19274 window: &mut Window,
19275 cx: &mut Context<Self>,
19276 ) {
19277 self.open_excerpts_common(None, true, window, cx)
19278 }
19279
19280 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
19281 self.open_excerpts_common(None, false, window, cx)
19282 }
19283
19284 fn open_excerpts_common(
19285 &mut self,
19286 jump_data: Option<JumpData>,
19287 split: bool,
19288 window: &mut Window,
19289 cx: &mut Context<Self>,
19290 ) {
19291 let Some(workspace) = self.workspace() else {
19292 cx.propagate();
19293 return;
19294 };
19295
19296 if self.buffer.read(cx).is_singleton() {
19297 cx.propagate();
19298 return;
19299 }
19300
19301 let mut new_selections_by_buffer = HashMap::default();
19302 match &jump_data {
19303 Some(JumpData::MultiBufferPoint {
19304 excerpt_id,
19305 position,
19306 anchor,
19307 line_offset_from_top,
19308 }) => {
19309 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19310 if let Some(buffer) = multi_buffer_snapshot
19311 .buffer_id_for_excerpt(*excerpt_id)
19312 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
19313 {
19314 let buffer_snapshot = buffer.read(cx).snapshot();
19315 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
19316 language::ToPoint::to_point(anchor, &buffer_snapshot)
19317 } else {
19318 buffer_snapshot.clip_point(*position, Bias::Left)
19319 };
19320 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
19321 new_selections_by_buffer.insert(
19322 buffer,
19323 (
19324 vec![jump_to_offset..jump_to_offset],
19325 Some(*line_offset_from_top),
19326 ),
19327 );
19328 }
19329 }
19330 Some(JumpData::MultiBufferRow {
19331 row,
19332 line_offset_from_top,
19333 }) => {
19334 let point = MultiBufferPoint::new(row.0, 0);
19335 if let Some((buffer, buffer_point, _)) =
19336 self.buffer.read(cx).point_to_buffer_point(point, cx)
19337 {
19338 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
19339 new_selections_by_buffer
19340 .entry(buffer)
19341 .or_insert((Vec::new(), Some(*line_offset_from_top)))
19342 .0
19343 .push(buffer_offset..buffer_offset)
19344 }
19345 }
19346 None => {
19347 let selections = self.selections.all::<usize>(cx);
19348 let multi_buffer = self.buffer.read(cx);
19349 for selection in selections {
19350 for (snapshot, range, _, anchor) in multi_buffer
19351 .snapshot(cx)
19352 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
19353 {
19354 if let Some(anchor) = anchor {
19355 // selection is in a deleted hunk
19356 let Some(buffer_id) = anchor.buffer_id else {
19357 continue;
19358 };
19359 let Some(buffer_handle) = multi_buffer.buffer(buffer_id) else {
19360 continue;
19361 };
19362 let offset = text::ToOffset::to_offset(
19363 &anchor.text_anchor,
19364 &buffer_handle.read(cx).snapshot(),
19365 );
19366 let range = offset..offset;
19367 new_selections_by_buffer
19368 .entry(buffer_handle)
19369 .or_insert((Vec::new(), None))
19370 .0
19371 .push(range)
19372 } else {
19373 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
19374 else {
19375 continue;
19376 };
19377 new_selections_by_buffer
19378 .entry(buffer_handle)
19379 .or_insert((Vec::new(), None))
19380 .0
19381 .push(range)
19382 }
19383 }
19384 }
19385 }
19386 }
19387
19388 new_selections_by_buffer
19389 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
19390
19391 if new_selections_by_buffer.is_empty() {
19392 return;
19393 }
19394
19395 // We defer the pane interaction because we ourselves are a workspace item
19396 // and activating a new item causes the pane to call a method on us reentrantly,
19397 // which panics if we're on the stack.
19398 window.defer(cx, move |window, cx| {
19399 workspace.update(cx, |workspace, cx| {
19400 let pane = if split {
19401 workspace.adjacent_pane(window, cx)
19402 } else {
19403 workspace.active_pane().clone()
19404 };
19405
19406 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
19407 let editor = buffer
19408 .read(cx)
19409 .file()
19410 .is_none()
19411 .then(|| {
19412 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
19413 // so `workspace.open_project_item` will never find them, always opening a new editor.
19414 // Instead, we try to activate the existing editor in the pane first.
19415 let (editor, pane_item_index) =
19416 pane.read(cx).items().enumerate().find_map(|(i, item)| {
19417 let editor = item.downcast::<Editor>()?;
19418 let singleton_buffer =
19419 editor.read(cx).buffer().read(cx).as_singleton()?;
19420 if singleton_buffer == buffer {
19421 Some((editor, i))
19422 } else {
19423 None
19424 }
19425 })?;
19426 pane.update(cx, |pane, cx| {
19427 pane.activate_item(pane_item_index, true, true, window, cx)
19428 });
19429 Some(editor)
19430 })
19431 .flatten()
19432 .unwrap_or_else(|| {
19433 workspace.open_project_item::<Self>(
19434 pane.clone(),
19435 buffer,
19436 true,
19437 true,
19438 window,
19439 cx,
19440 )
19441 });
19442
19443 editor.update(cx, |editor, cx| {
19444 let autoscroll = match scroll_offset {
19445 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
19446 None => Autoscroll::newest(),
19447 };
19448 let nav_history = editor.nav_history.take();
19449 editor.change_selections(Some(autoscroll), window, cx, |s| {
19450 s.select_ranges(ranges);
19451 });
19452 editor.nav_history = nav_history;
19453 });
19454 }
19455 })
19456 });
19457 }
19458
19459 // For now, don't allow opening excerpts in buffers that aren't backed by
19460 // regular project files.
19461 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
19462 file.map_or(true, |file| project::File::from_dyn(Some(file)).is_some())
19463 }
19464
19465 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
19466 let snapshot = self.buffer.read(cx).read(cx);
19467 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
19468 Some(
19469 ranges
19470 .iter()
19471 .map(move |range| {
19472 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
19473 })
19474 .collect(),
19475 )
19476 }
19477
19478 fn selection_replacement_ranges(
19479 &self,
19480 range: Range<OffsetUtf16>,
19481 cx: &mut App,
19482 ) -> Vec<Range<OffsetUtf16>> {
19483 let selections = self.selections.all::<OffsetUtf16>(cx);
19484 let newest_selection = selections
19485 .iter()
19486 .max_by_key(|selection| selection.id)
19487 .unwrap();
19488 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
19489 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
19490 let snapshot = self.buffer.read(cx).read(cx);
19491 selections
19492 .into_iter()
19493 .map(|mut selection| {
19494 selection.start.0 =
19495 (selection.start.0 as isize).saturating_add(start_delta) as usize;
19496 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
19497 snapshot.clip_offset_utf16(selection.start, Bias::Left)
19498 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
19499 })
19500 .collect()
19501 }
19502
19503 fn report_editor_event(
19504 &self,
19505 event_type: &'static str,
19506 file_extension: Option<String>,
19507 cx: &App,
19508 ) {
19509 if cfg!(any(test, feature = "test-support")) {
19510 return;
19511 }
19512
19513 let Some(project) = &self.project else { return };
19514
19515 // If None, we are in a file without an extension
19516 let file = self
19517 .buffer
19518 .read(cx)
19519 .as_singleton()
19520 .and_then(|b| b.read(cx).file());
19521 let file_extension = file_extension.or(file
19522 .as_ref()
19523 .and_then(|file| Path::new(file.file_name(cx)).extension())
19524 .and_then(|e| e.to_str())
19525 .map(|a| a.to_string()));
19526
19527 let vim_mode = vim_enabled(cx);
19528
19529 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
19530 let copilot_enabled = edit_predictions_provider
19531 == language::language_settings::EditPredictionProvider::Copilot;
19532 let copilot_enabled_for_language = self
19533 .buffer
19534 .read(cx)
19535 .language_settings(cx)
19536 .show_edit_predictions;
19537
19538 let project = project.read(cx);
19539 telemetry::event!(
19540 event_type,
19541 file_extension,
19542 vim_mode,
19543 copilot_enabled,
19544 copilot_enabled_for_language,
19545 edit_predictions_provider,
19546 is_via_ssh = project.is_via_ssh(),
19547 );
19548 }
19549
19550 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
19551 /// with each line being an array of {text, highlight} objects.
19552 fn copy_highlight_json(
19553 &mut self,
19554 _: &CopyHighlightJson,
19555 window: &mut Window,
19556 cx: &mut Context<Self>,
19557 ) {
19558 #[derive(Serialize)]
19559 struct Chunk<'a> {
19560 text: String,
19561 highlight: Option<&'a str>,
19562 }
19563
19564 let snapshot = self.buffer.read(cx).snapshot(cx);
19565 let range = self
19566 .selected_text_range(false, window, cx)
19567 .and_then(|selection| {
19568 if selection.range.is_empty() {
19569 None
19570 } else {
19571 Some(selection.range)
19572 }
19573 })
19574 .unwrap_or_else(|| 0..snapshot.len());
19575
19576 let chunks = snapshot.chunks(range, true);
19577 let mut lines = Vec::new();
19578 let mut line: VecDeque<Chunk> = VecDeque::new();
19579
19580 let Some(style) = self.style.as_ref() else {
19581 return;
19582 };
19583
19584 for chunk in chunks {
19585 let highlight = chunk
19586 .syntax_highlight_id
19587 .and_then(|id| id.name(&style.syntax));
19588 let mut chunk_lines = chunk.text.split('\n').peekable();
19589 while let Some(text) = chunk_lines.next() {
19590 let mut merged_with_last_token = false;
19591 if let Some(last_token) = line.back_mut() {
19592 if last_token.highlight == highlight {
19593 last_token.text.push_str(text);
19594 merged_with_last_token = true;
19595 }
19596 }
19597
19598 if !merged_with_last_token {
19599 line.push_back(Chunk {
19600 text: text.into(),
19601 highlight,
19602 });
19603 }
19604
19605 if chunk_lines.peek().is_some() {
19606 if line.len() > 1 && line.front().unwrap().text.is_empty() {
19607 line.pop_front();
19608 }
19609 if line.len() > 1 && line.back().unwrap().text.is_empty() {
19610 line.pop_back();
19611 }
19612
19613 lines.push(mem::take(&mut line));
19614 }
19615 }
19616 }
19617
19618 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
19619 return;
19620 };
19621 cx.write_to_clipboard(ClipboardItem::new_string(lines));
19622 }
19623
19624 pub fn open_context_menu(
19625 &mut self,
19626 _: &OpenContextMenu,
19627 window: &mut Window,
19628 cx: &mut Context<Self>,
19629 ) {
19630 self.request_autoscroll(Autoscroll::newest(), cx);
19631 let position = self.selections.newest_display(cx).start;
19632 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
19633 }
19634
19635 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
19636 &self.inlay_hint_cache
19637 }
19638
19639 pub fn replay_insert_event(
19640 &mut self,
19641 text: &str,
19642 relative_utf16_range: Option<Range<isize>>,
19643 window: &mut Window,
19644 cx: &mut Context<Self>,
19645 ) {
19646 if !self.input_enabled {
19647 cx.emit(EditorEvent::InputIgnored { text: text.into() });
19648 return;
19649 }
19650 if let Some(relative_utf16_range) = relative_utf16_range {
19651 let selections = self.selections.all::<OffsetUtf16>(cx);
19652 self.change_selections(None, window, cx, |s| {
19653 let new_ranges = selections.into_iter().map(|range| {
19654 let start = OffsetUtf16(
19655 range
19656 .head()
19657 .0
19658 .saturating_add_signed(relative_utf16_range.start),
19659 );
19660 let end = OffsetUtf16(
19661 range
19662 .head()
19663 .0
19664 .saturating_add_signed(relative_utf16_range.end),
19665 );
19666 start..end
19667 });
19668 s.select_ranges(new_ranges);
19669 });
19670 }
19671
19672 self.handle_input(text, window, cx);
19673 }
19674
19675 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
19676 let Some(provider) = self.semantics_provider.as_ref() else {
19677 return false;
19678 };
19679
19680 let mut supports = false;
19681 self.buffer().update(cx, |this, cx| {
19682 this.for_each_buffer(|buffer| {
19683 supports |= provider.supports_inlay_hints(buffer, cx);
19684 });
19685 });
19686
19687 supports
19688 }
19689
19690 pub fn is_focused(&self, window: &Window) -> bool {
19691 self.focus_handle.is_focused(window)
19692 }
19693
19694 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19695 cx.emit(EditorEvent::Focused);
19696
19697 if let Some(descendant) = self
19698 .last_focused_descendant
19699 .take()
19700 .and_then(|descendant| descendant.upgrade())
19701 {
19702 window.focus(&descendant);
19703 } else {
19704 if let Some(blame) = self.blame.as_ref() {
19705 blame.update(cx, GitBlame::focus)
19706 }
19707
19708 self.blink_manager.update(cx, BlinkManager::enable);
19709 self.show_cursor_names(window, cx);
19710 self.buffer.update(cx, |buffer, cx| {
19711 buffer.finalize_last_transaction(cx);
19712 if self.leader_id.is_none() {
19713 buffer.set_active_selections(
19714 &self.selections.disjoint_anchors(),
19715 self.selections.line_mode,
19716 self.cursor_shape,
19717 cx,
19718 );
19719 }
19720 });
19721 }
19722 }
19723
19724 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
19725 cx.emit(EditorEvent::FocusedIn)
19726 }
19727
19728 fn handle_focus_out(
19729 &mut self,
19730 event: FocusOutEvent,
19731 _window: &mut Window,
19732 cx: &mut Context<Self>,
19733 ) {
19734 if event.blurred != self.focus_handle {
19735 self.last_focused_descendant = Some(event.blurred);
19736 }
19737 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
19738 }
19739
19740 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19741 self.blink_manager.update(cx, BlinkManager::disable);
19742 self.buffer
19743 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
19744
19745 if let Some(blame) = self.blame.as_ref() {
19746 blame.update(cx, GitBlame::blur)
19747 }
19748 if !self.hover_state.focused(window, cx) {
19749 hide_hover(self, cx);
19750 }
19751 if !self
19752 .context_menu
19753 .borrow()
19754 .as_ref()
19755 .is_some_and(|context_menu| context_menu.focused(window, cx))
19756 {
19757 self.hide_context_menu(window, cx);
19758 }
19759 self.discard_inline_completion(false, cx);
19760 cx.emit(EditorEvent::Blurred);
19761 cx.notify();
19762 }
19763
19764 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19765 let mut pending: String = window
19766 .pending_input_keystrokes()
19767 .into_iter()
19768 .flatten()
19769 .filter_map(|keystroke| {
19770 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
19771 keystroke.key_char.clone()
19772 } else {
19773 None
19774 }
19775 })
19776 .collect();
19777
19778 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
19779 pending = "".to_string();
19780 }
19781
19782 let existing_pending = self
19783 .text_highlights::<PendingInput>(cx)
19784 .map(|(_, ranges)| ranges.iter().cloned().collect::<Vec<_>>());
19785 if existing_pending.is_none() && pending.is_empty() {
19786 return;
19787 }
19788 let transaction =
19789 self.transact(window, cx, |this, window, cx| {
19790 let selections = this.selections.all::<usize>(cx);
19791 let edits = selections
19792 .iter()
19793 .map(|selection| (selection.end..selection.end, pending.clone()));
19794 this.edit(edits, cx);
19795 this.change_selections(None, window, cx, |s| {
19796 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
19797 sel.start + ix * pending.len()..sel.end + ix * pending.len()
19798 }));
19799 });
19800 if let Some(existing_ranges) = existing_pending {
19801 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
19802 this.edit(edits, cx);
19803 }
19804 });
19805
19806 let snapshot = self.snapshot(window, cx);
19807 let ranges = self
19808 .selections
19809 .all::<usize>(cx)
19810 .into_iter()
19811 .map(|selection| {
19812 snapshot.buffer_snapshot.anchor_after(selection.end)
19813 ..snapshot
19814 .buffer_snapshot
19815 .anchor_before(selection.end + pending.len())
19816 })
19817 .collect();
19818
19819 if pending.is_empty() {
19820 self.clear_highlights::<PendingInput>(cx);
19821 } else {
19822 self.highlight_text::<PendingInput>(
19823 ranges,
19824 HighlightStyle {
19825 underline: Some(UnderlineStyle {
19826 thickness: px(1.),
19827 color: None,
19828 wavy: false,
19829 }),
19830 ..Default::default()
19831 },
19832 cx,
19833 );
19834 }
19835
19836 self.ime_transaction = self.ime_transaction.or(transaction);
19837 if let Some(transaction) = self.ime_transaction {
19838 self.buffer.update(cx, |buffer, cx| {
19839 buffer.group_until_transaction(transaction, cx);
19840 });
19841 }
19842
19843 if self.text_highlights::<PendingInput>(cx).is_none() {
19844 self.ime_transaction.take();
19845 }
19846 }
19847
19848 pub fn register_action_renderer(
19849 &mut self,
19850 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
19851 ) -> Subscription {
19852 let id = self.next_editor_action_id.post_inc();
19853 self.editor_actions
19854 .borrow_mut()
19855 .insert(id, Box::new(listener));
19856
19857 let editor_actions = self.editor_actions.clone();
19858 Subscription::new(move || {
19859 editor_actions.borrow_mut().remove(&id);
19860 })
19861 }
19862
19863 pub fn register_action<A: Action>(
19864 &mut self,
19865 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
19866 ) -> Subscription {
19867 let id = self.next_editor_action_id.post_inc();
19868 let listener = Arc::new(listener);
19869 self.editor_actions.borrow_mut().insert(
19870 id,
19871 Box::new(move |_, window, _| {
19872 let listener = listener.clone();
19873 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
19874 let action = action.downcast_ref().unwrap();
19875 if phase == DispatchPhase::Bubble {
19876 listener(action, window, cx)
19877 }
19878 })
19879 }),
19880 );
19881
19882 let editor_actions = self.editor_actions.clone();
19883 Subscription::new(move || {
19884 editor_actions.borrow_mut().remove(&id);
19885 })
19886 }
19887
19888 pub fn file_header_size(&self) -> u32 {
19889 FILE_HEADER_HEIGHT
19890 }
19891
19892 pub fn restore(
19893 &mut self,
19894 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
19895 window: &mut Window,
19896 cx: &mut Context<Self>,
19897 ) {
19898 let workspace = self.workspace();
19899 let project = self.project.as_ref();
19900 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
19901 let mut tasks = Vec::new();
19902 for (buffer_id, changes) in revert_changes {
19903 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
19904 buffer.update(cx, |buffer, cx| {
19905 buffer.edit(
19906 changes
19907 .into_iter()
19908 .map(|(range, text)| (range, text.to_string())),
19909 None,
19910 cx,
19911 );
19912 });
19913
19914 if let Some(project) =
19915 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
19916 {
19917 project.update(cx, |project, cx| {
19918 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
19919 })
19920 }
19921 }
19922 }
19923 tasks
19924 });
19925 cx.spawn_in(window, async move |_, cx| {
19926 for (buffer, task) in save_tasks {
19927 let result = task.await;
19928 if result.is_err() {
19929 let Some(path) = buffer
19930 .read_with(cx, |buffer, cx| buffer.project_path(cx))
19931 .ok()
19932 else {
19933 continue;
19934 };
19935 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
19936 let Some(task) = cx
19937 .update_window_entity(&workspace, |workspace, window, cx| {
19938 workspace
19939 .open_path_preview(path, None, false, false, false, window, cx)
19940 })
19941 .ok()
19942 else {
19943 continue;
19944 };
19945 task.await.log_err();
19946 }
19947 }
19948 }
19949 })
19950 .detach();
19951 self.change_selections(None, window, cx, |selections| selections.refresh());
19952 }
19953
19954 pub fn to_pixel_point(
19955 &self,
19956 source: multi_buffer::Anchor,
19957 editor_snapshot: &EditorSnapshot,
19958 window: &mut Window,
19959 ) -> Option<gpui::Point<Pixels>> {
19960 let source_point = source.to_display_point(editor_snapshot);
19961 self.display_to_pixel_point(source_point, editor_snapshot, window)
19962 }
19963
19964 pub fn display_to_pixel_point(
19965 &self,
19966 source: DisplayPoint,
19967 editor_snapshot: &EditorSnapshot,
19968 window: &mut Window,
19969 ) -> Option<gpui::Point<Pixels>> {
19970 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
19971 let text_layout_details = self.text_layout_details(window);
19972 let scroll_top = text_layout_details
19973 .scroll_anchor
19974 .scroll_position(editor_snapshot)
19975 .y;
19976
19977 if source.row().as_f32() < scroll_top.floor() {
19978 return None;
19979 }
19980 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
19981 let source_y = line_height * (source.row().as_f32() - scroll_top);
19982 Some(gpui::Point::new(source_x, source_y))
19983 }
19984
19985 pub fn has_visible_completions_menu(&self) -> bool {
19986 !self.edit_prediction_preview_is_active()
19987 && self.context_menu.borrow().as_ref().map_or(false, |menu| {
19988 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
19989 })
19990 }
19991
19992 pub fn register_addon<T: Addon>(&mut self, instance: T) {
19993 if self.mode.is_minimap() {
19994 return;
19995 }
19996 self.addons
19997 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
19998 }
19999
20000 pub fn unregister_addon<T: Addon>(&mut self) {
20001 self.addons.remove(&std::any::TypeId::of::<T>());
20002 }
20003
20004 pub fn addon<T: Addon>(&self) -> Option<&T> {
20005 let type_id = std::any::TypeId::of::<T>();
20006 self.addons
20007 .get(&type_id)
20008 .and_then(|item| item.to_any().downcast_ref::<T>())
20009 }
20010
20011 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
20012 let type_id = std::any::TypeId::of::<T>();
20013 self.addons
20014 .get_mut(&type_id)
20015 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
20016 }
20017
20018 fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
20019 let text_layout_details = self.text_layout_details(window);
20020 let style = &text_layout_details.editor_style;
20021 let font_id = window.text_system().resolve_font(&style.text.font());
20022 let font_size = style.text.font_size.to_pixels(window.rem_size());
20023 let line_height = style.text.line_height_in_pixels(window.rem_size());
20024 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
20025
20026 gpui::Size::new(em_width, line_height)
20027 }
20028
20029 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
20030 self.load_diff_task.clone()
20031 }
20032
20033 fn read_metadata_from_db(
20034 &mut self,
20035 item_id: u64,
20036 workspace_id: WorkspaceId,
20037 window: &mut Window,
20038 cx: &mut Context<Editor>,
20039 ) {
20040 if self.is_singleton(cx)
20041 && !self.mode.is_minimap()
20042 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
20043 {
20044 let buffer_snapshot = OnceCell::new();
20045
20046 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err() {
20047 if !folds.is_empty() {
20048 let snapshot =
20049 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20050 self.fold_ranges(
20051 folds
20052 .into_iter()
20053 .map(|(start, end)| {
20054 snapshot.clip_offset(start, Bias::Left)
20055 ..snapshot.clip_offset(end, Bias::Right)
20056 })
20057 .collect(),
20058 false,
20059 window,
20060 cx,
20061 );
20062 }
20063 }
20064
20065 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err() {
20066 if !selections.is_empty() {
20067 let snapshot =
20068 buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
20069 // skip adding the initial selection to selection history
20070 self.selection_history.mode = SelectionHistoryMode::Skipping;
20071 self.change_selections(None, window, cx, |s| {
20072 s.select_ranges(selections.into_iter().map(|(start, end)| {
20073 snapshot.clip_offset(start, Bias::Left)
20074 ..snapshot.clip_offset(end, Bias::Right)
20075 }));
20076 });
20077 self.selection_history.mode = SelectionHistoryMode::Normal;
20078 }
20079 };
20080 }
20081
20082 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
20083 }
20084}
20085
20086fn vim_enabled(cx: &App) -> bool {
20087 cx.global::<SettingsStore>()
20088 .raw_user_settings()
20089 .get("vim_mode")
20090 == Some(&serde_json::Value::Bool(true))
20091}
20092
20093fn process_completion_for_edit(
20094 completion: &Completion,
20095 intent: CompletionIntent,
20096 buffer: &Entity<Buffer>,
20097 cursor_position: &text::Anchor,
20098 cx: &mut Context<Editor>,
20099) -> CompletionEdit {
20100 let buffer = buffer.read(cx);
20101 let buffer_snapshot = buffer.snapshot();
20102 let (snippet, new_text) = if completion.is_snippet() {
20103 // Workaround for typescript language server issues so that methods don't expand within
20104 // strings and functions with type expressions. The previous point is used because the query
20105 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
20106 let mut snippet_source = completion.new_text.clone();
20107 let mut previous_point = text::ToPoint::to_point(cursor_position, buffer);
20108 previous_point.column = previous_point.column.saturating_sub(1);
20109 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point) {
20110 if scope.prefers_label_for_snippet_in_completion() {
20111 if let Some(label) = completion.label() {
20112 if matches!(
20113 completion.kind(),
20114 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
20115 ) {
20116 snippet_source = label;
20117 }
20118 }
20119 }
20120 }
20121 match Snippet::parse(&snippet_source).log_err() {
20122 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
20123 None => (None, completion.new_text.clone()),
20124 }
20125 } else {
20126 (None, completion.new_text.clone())
20127 };
20128
20129 let mut range_to_replace = {
20130 let replace_range = &completion.replace_range;
20131 if let CompletionSource::Lsp {
20132 insert_range: Some(insert_range),
20133 ..
20134 } = &completion.source
20135 {
20136 debug_assert_eq!(
20137 insert_range.start, replace_range.start,
20138 "insert_range and replace_range should start at the same position"
20139 );
20140 debug_assert!(
20141 insert_range
20142 .start
20143 .cmp(&cursor_position, &buffer_snapshot)
20144 .is_le(),
20145 "insert_range should start before or at cursor position"
20146 );
20147 debug_assert!(
20148 replace_range
20149 .start
20150 .cmp(&cursor_position, &buffer_snapshot)
20151 .is_le(),
20152 "replace_range should start before or at cursor position"
20153 );
20154 debug_assert!(
20155 insert_range
20156 .end
20157 .cmp(&cursor_position, &buffer_snapshot)
20158 .is_le(),
20159 "insert_range should end before or at cursor position"
20160 );
20161
20162 let should_replace = match intent {
20163 CompletionIntent::CompleteWithInsert => false,
20164 CompletionIntent::CompleteWithReplace => true,
20165 CompletionIntent::Complete | CompletionIntent::Compose => {
20166 let insert_mode =
20167 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
20168 .completions
20169 .lsp_insert_mode;
20170 match insert_mode {
20171 LspInsertMode::Insert => false,
20172 LspInsertMode::Replace => true,
20173 LspInsertMode::ReplaceSubsequence => {
20174 let mut text_to_replace = buffer.chars_for_range(
20175 buffer.anchor_before(replace_range.start)
20176 ..buffer.anchor_after(replace_range.end),
20177 );
20178 let mut current_needle = text_to_replace.next();
20179 for haystack_ch in completion.label.text.chars() {
20180 if let Some(needle_ch) = current_needle {
20181 if haystack_ch.eq_ignore_ascii_case(&needle_ch) {
20182 current_needle = text_to_replace.next();
20183 }
20184 }
20185 }
20186 current_needle.is_none()
20187 }
20188 LspInsertMode::ReplaceSuffix => {
20189 if replace_range
20190 .end
20191 .cmp(&cursor_position, &buffer_snapshot)
20192 .is_gt()
20193 {
20194 let range_after_cursor = *cursor_position..replace_range.end;
20195 let text_after_cursor = buffer
20196 .text_for_range(
20197 buffer.anchor_before(range_after_cursor.start)
20198 ..buffer.anchor_after(range_after_cursor.end),
20199 )
20200 .collect::<String>()
20201 .to_ascii_lowercase();
20202 completion
20203 .label
20204 .text
20205 .to_ascii_lowercase()
20206 .ends_with(&text_after_cursor)
20207 } else {
20208 true
20209 }
20210 }
20211 }
20212 }
20213 };
20214
20215 if should_replace {
20216 replace_range.clone()
20217 } else {
20218 insert_range.clone()
20219 }
20220 } else {
20221 replace_range.clone()
20222 }
20223 };
20224
20225 if range_to_replace
20226 .end
20227 .cmp(&cursor_position, &buffer_snapshot)
20228 .is_lt()
20229 {
20230 range_to_replace.end = *cursor_position;
20231 }
20232
20233 CompletionEdit {
20234 new_text,
20235 replace_range: range_to_replace.to_offset(&buffer),
20236 snippet,
20237 }
20238}
20239
20240struct CompletionEdit {
20241 new_text: String,
20242 replace_range: Range<usize>,
20243 snippet: Option<Snippet>,
20244}
20245
20246fn insert_extra_newline_brackets(
20247 buffer: &MultiBufferSnapshot,
20248 range: Range<usize>,
20249 language: &language::LanguageScope,
20250) -> bool {
20251 let leading_whitespace_len = buffer
20252 .reversed_chars_at(range.start)
20253 .take_while(|c| c.is_whitespace() && *c != '\n')
20254 .map(|c| c.len_utf8())
20255 .sum::<usize>();
20256 let trailing_whitespace_len = buffer
20257 .chars_at(range.end)
20258 .take_while(|c| c.is_whitespace() && *c != '\n')
20259 .map(|c| c.len_utf8())
20260 .sum::<usize>();
20261 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
20262
20263 language.brackets().any(|(pair, enabled)| {
20264 let pair_start = pair.start.trim_end();
20265 let pair_end = pair.end.trim_start();
20266
20267 enabled
20268 && pair.newline
20269 && buffer.contains_str_at(range.end, pair_end)
20270 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
20271 })
20272}
20273
20274fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
20275 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
20276 [(buffer, range, _)] => (*buffer, range.clone()),
20277 _ => return false,
20278 };
20279 let pair = {
20280 let mut result: Option<BracketMatch> = None;
20281
20282 for pair in buffer
20283 .all_bracket_ranges(range.clone())
20284 .filter(move |pair| {
20285 pair.open_range.start <= range.start && pair.close_range.end >= range.end
20286 })
20287 {
20288 let len = pair.close_range.end - pair.open_range.start;
20289
20290 if let Some(existing) = &result {
20291 let existing_len = existing.close_range.end - existing.open_range.start;
20292 if len > existing_len {
20293 continue;
20294 }
20295 }
20296
20297 result = Some(pair);
20298 }
20299
20300 result
20301 };
20302 let Some(pair) = pair else {
20303 return false;
20304 };
20305 pair.newline_only
20306 && buffer
20307 .chars_for_range(pair.open_range.end..range.start)
20308 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
20309 .all(|c| c.is_whitespace() && c != '\n')
20310}
20311
20312fn update_uncommitted_diff_for_buffer(
20313 editor: Entity<Editor>,
20314 project: &Entity<Project>,
20315 buffers: impl IntoIterator<Item = Entity<Buffer>>,
20316 buffer: Entity<MultiBuffer>,
20317 cx: &mut App,
20318) -> Task<()> {
20319 let mut tasks = Vec::new();
20320 project.update(cx, |project, cx| {
20321 for buffer in buffers {
20322 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
20323 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
20324 }
20325 }
20326 });
20327 cx.spawn(async move |cx| {
20328 let diffs = future::join_all(tasks).await;
20329 if editor
20330 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
20331 .unwrap_or(false)
20332 {
20333 return;
20334 }
20335
20336 buffer
20337 .update(cx, |buffer, cx| {
20338 for diff in diffs.into_iter().flatten() {
20339 buffer.add_diff(diff, cx);
20340 }
20341 })
20342 .ok();
20343 })
20344}
20345
20346fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
20347 let tab_size = tab_size.get() as usize;
20348 let mut width = offset;
20349
20350 for ch in text.chars() {
20351 width += if ch == '\t' {
20352 tab_size - (width % tab_size)
20353 } else {
20354 1
20355 };
20356 }
20357
20358 width - offset
20359}
20360
20361#[cfg(test)]
20362mod tests {
20363 use super::*;
20364
20365 #[test]
20366 fn test_string_size_with_expanded_tabs() {
20367 let nz = |val| NonZeroU32::new(val).unwrap();
20368 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
20369 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
20370 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
20371 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
20372 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
20373 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
20374 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
20375 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
20376 }
20377}
20378
20379/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
20380struct WordBreakingTokenizer<'a> {
20381 input: &'a str,
20382}
20383
20384impl<'a> WordBreakingTokenizer<'a> {
20385 fn new(input: &'a str) -> Self {
20386 Self { input }
20387 }
20388}
20389
20390fn is_char_ideographic(ch: char) -> bool {
20391 use unicode_script::Script::*;
20392 use unicode_script::UnicodeScript;
20393 matches!(ch.script(), Han | Tangut | Yi)
20394}
20395
20396fn is_grapheme_ideographic(text: &str) -> bool {
20397 text.chars().any(is_char_ideographic)
20398}
20399
20400fn is_grapheme_whitespace(text: &str) -> bool {
20401 text.chars().any(|x| x.is_whitespace())
20402}
20403
20404fn should_stay_with_preceding_ideograph(text: &str) -> bool {
20405 text.chars().next().map_or(false, |ch| {
20406 matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…')
20407 })
20408}
20409
20410#[derive(PartialEq, Eq, Debug, Clone, Copy)]
20411enum WordBreakToken<'a> {
20412 Word { token: &'a str, grapheme_len: usize },
20413 InlineWhitespace { token: &'a str, grapheme_len: usize },
20414 Newline,
20415}
20416
20417impl<'a> Iterator for WordBreakingTokenizer<'a> {
20418 /// Yields a span, the count of graphemes in the token, and whether it was
20419 /// whitespace. Note that it also breaks at word boundaries.
20420 type Item = WordBreakToken<'a>;
20421
20422 fn next(&mut self) -> Option<Self::Item> {
20423 use unicode_segmentation::UnicodeSegmentation;
20424 if self.input.is_empty() {
20425 return None;
20426 }
20427
20428 let mut iter = self.input.graphemes(true).peekable();
20429 let mut offset = 0;
20430 let mut grapheme_len = 0;
20431 if let Some(first_grapheme) = iter.next() {
20432 let is_newline = first_grapheme == "\n";
20433 let is_whitespace = is_grapheme_whitespace(first_grapheme);
20434 offset += first_grapheme.len();
20435 grapheme_len += 1;
20436 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
20437 if let Some(grapheme) = iter.peek().copied() {
20438 if should_stay_with_preceding_ideograph(grapheme) {
20439 offset += grapheme.len();
20440 grapheme_len += 1;
20441 }
20442 }
20443 } else {
20444 let mut words = self.input[offset..].split_word_bound_indices().peekable();
20445 let mut next_word_bound = words.peek().copied();
20446 if next_word_bound.map_or(false, |(i, _)| i == 0) {
20447 next_word_bound = words.next();
20448 }
20449 while let Some(grapheme) = iter.peek().copied() {
20450 if next_word_bound.map_or(false, |(i, _)| i == offset) {
20451 break;
20452 };
20453 if is_grapheme_whitespace(grapheme) != is_whitespace
20454 || (grapheme == "\n") != is_newline
20455 {
20456 break;
20457 };
20458 offset += grapheme.len();
20459 grapheme_len += 1;
20460 iter.next();
20461 }
20462 }
20463 let token = &self.input[..offset];
20464 self.input = &self.input[offset..];
20465 if token == "\n" {
20466 Some(WordBreakToken::Newline)
20467 } else if is_whitespace {
20468 Some(WordBreakToken::InlineWhitespace {
20469 token,
20470 grapheme_len,
20471 })
20472 } else {
20473 Some(WordBreakToken::Word {
20474 token,
20475 grapheme_len,
20476 })
20477 }
20478 } else {
20479 None
20480 }
20481 }
20482}
20483
20484#[test]
20485fn test_word_breaking_tokenizer() {
20486 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
20487 ("", &[]),
20488 (" ", &[whitespace(" ", 2)]),
20489 ("Ʒ", &[word("Ʒ", 1)]),
20490 ("Ǽ", &[word("Ǽ", 1)]),
20491 ("⋑", &[word("⋑", 1)]),
20492 ("⋑⋑", &[word("⋑⋑", 2)]),
20493 (
20494 "原理,进而",
20495 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
20496 ),
20497 (
20498 "hello world",
20499 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
20500 ),
20501 (
20502 "hello, world",
20503 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
20504 ),
20505 (
20506 " hello world",
20507 &[
20508 whitespace(" ", 2),
20509 word("hello", 5),
20510 whitespace(" ", 1),
20511 word("world", 5),
20512 ],
20513 ),
20514 (
20515 "这是什么 \n 钢笔",
20516 &[
20517 word("这", 1),
20518 word("是", 1),
20519 word("什", 1),
20520 word("么", 1),
20521 whitespace(" ", 1),
20522 newline(),
20523 whitespace(" ", 1),
20524 word("钢", 1),
20525 word("笔", 1),
20526 ],
20527 ),
20528 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
20529 ];
20530
20531 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20532 WordBreakToken::Word {
20533 token,
20534 grapheme_len,
20535 }
20536 }
20537
20538 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
20539 WordBreakToken::InlineWhitespace {
20540 token,
20541 grapheme_len,
20542 }
20543 }
20544
20545 fn newline() -> WordBreakToken<'static> {
20546 WordBreakToken::Newline
20547 }
20548
20549 for (input, result) in tests {
20550 assert_eq!(
20551 WordBreakingTokenizer::new(input)
20552 .collect::<Vec<_>>()
20553 .as_slice(),
20554 *result,
20555 );
20556 }
20557}
20558
20559fn wrap_with_prefix(
20560 line_prefix: String,
20561 unwrapped_text: String,
20562 wrap_column: usize,
20563 tab_size: NonZeroU32,
20564 preserve_existing_whitespace: bool,
20565) -> String {
20566 let line_prefix_len = char_len_with_expanded_tabs(0, &line_prefix, tab_size);
20567 let mut wrapped_text = String::new();
20568 let mut current_line = line_prefix.clone();
20569
20570 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
20571 let mut current_line_len = line_prefix_len;
20572 let mut in_whitespace = false;
20573 for token in tokenizer {
20574 let have_preceding_whitespace = in_whitespace;
20575 match token {
20576 WordBreakToken::Word {
20577 token,
20578 grapheme_len,
20579 } => {
20580 in_whitespace = false;
20581 if current_line_len + grapheme_len > wrap_column
20582 && current_line_len != line_prefix_len
20583 {
20584 wrapped_text.push_str(current_line.trim_end());
20585 wrapped_text.push('\n');
20586 current_line.truncate(line_prefix.len());
20587 current_line_len = line_prefix_len;
20588 }
20589 current_line.push_str(token);
20590 current_line_len += grapheme_len;
20591 }
20592 WordBreakToken::InlineWhitespace {
20593 mut token,
20594 mut grapheme_len,
20595 } => {
20596 in_whitespace = true;
20597 if have_preceding_whitespace && !preserve_existing_whitespace {
20598 continue;
20599 }
20600 if !preserve_existing_whitespace {
20601 token = " ";
20602 grapheme_len = 1;
20603 }
20604 if current_line_len + grapheme_len > wrap_column {
20605 wrapped_text.push_str(current_line.trim_end());
20606 wrapped_text.push('\n');
20607 current_line.truncate(line_prefix.len());
20608 current_line_len = line_prefix_len;
20609 } else if current_line_len != line_prefix_len || preserve_existing_whitespace {
20610 current_line.push_str(token);
20611 current_line_len += grapheme_len;
20612 }
20613 }
20614 WordBreakToken::Newline => {
20615 in_whitespace = true;
20616 if preserve_existing_whitespace {
20617 wrapped_text.push_str(current_line.trim_end());
20618 wrapped_text.push('\n');
20619 current_line.truncate(line_prefix.len());
20620 current_line_len = line_prefix_len;
20621 } else if have_preceding_whitespace {
20622 continue;
20623 } else if current_line_len + 1 > wrap_column && current_line_len != line_prefix_len
20624 {
20625 wrapped_text.push_str(current_line.trim_end());
20626 wrapped_text.push('\n');
20627 current_line.truncate(line_prefix.len());
20628 current_line_len = line_prefix_len;
20629 } else if current_line_len != line_prefix_len {
20630 current_line.push(' ');
20631 current_line_len += 1;
20632 }
20633 }
20634 }
20635 }
20636
20637 if !current_line.is_empty() {
20638 wrapped_text.push_str(¤t_line);
20639 }
20640 wrapped_text
20641}
20642
20643#[test]
20644fn test_wrap_with_prefix() {
20645 assert_eq!(
20646 wrap_with_prefix(
20647 "# ".to_string(),
20648 "abcdefg".to_string(),
20649 4,
20650 NonZeroU32::new(4).unwrap(),
20651 false,
20652 ),
20653 "# abcdefg"
20654 );
20655 assert_eq!(
20656 wrap_with_prefix(
20657 "".to_string(),
20658 "\thello world".to_string(),
20659 8,
20660 NonZeroU32::new(4).unwrap(),
20661 false,
20662 ),
20663 "hello\nworld"
20664 );
20665 assert_eq!(
20666 wrap_with_prefix(
20667 "// ".to_string(),
20668 "xx \nyy zz aa bb cc".to_string(),
20669 12,
20670 NonZeroU32::new(4).unwrap(),
20671 false,
20672 ),
20673 "// xx yy zz\n// aa bb cc"
20674 );
20675 assert_eq!(
20676 wrap_with_prefix(
20677 String::new(),
20678 "这是什么 \n 钢笔".to_string(),
20679 3,
20680 NonZeroU32::new(4).unwrap(),
20681 false,
20682 ),
20683 "这是什\n么 钢\n笔"
20684 );
20685}
20686
20687pub trait CollaborationHub {
20688 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
20689 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
20690 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
20691}
20692
20693impl CollaborationHub for Entity<Project> {
20694 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
20695 self.read(cx).collaborators()
20696 }
20697
20698 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
20699 self.read(cx).user_store().read(cx).participant_indices()
20700 }
20701
20702 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
20703 let this = self.read(cx);
20704 let user_ids = this.collaborators().values().map(|c| c.user_id);
20705 this.user_store().read(cx).participant_names(user_ids, cx)
20706 }
20707}
20708
20709pub trait SemanticsProvider {
20710 fn hover(
20711 &self,
20712 buffer: &Entity<Buffer>,
20713 position: text::Anchor,
20714 cx: &mut App,
20715 ) -> Option<Task<Vec<project::Hover>>>;
20716
20717 fn inline_values(
20718 &self,
20719 buffer_handle: Entity<Buffer>,
20720 range: Range<text::Anchor>,
20721 cx: &mut App,
20722 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20723
20724 fn inlay_hints(
20725 &self,
20726 buffer_handle: Entity<Buffer>,
20727 range: Range<text::Anchor>,
20728 cx: &mut App,
20729 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
20730
20731 fn resolve_inlay_hint(
20732 &self,
20733 hint: InlayHint,
20734 buffer_handle: Entity<Buffer>,
20735 server_id: LanguageServerId,
20736 cx: &mut App,
20737 ) -> Option<Task<anyhow::Result<InlayHint>>>;
20738
20739 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
20740
20741 fn document_highlights(
20742 &self,
20743 buffer: &Entity<Buffer>,
20744 position: text::Anchor,
20745 cx: &mut App,
20746 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
20747
20748 fn definitions(
20749 &self,
20750 buffer: &Entity<Buffer>,
20751 position: text::Anchor,
20752 kind: GotoDefinitionKind,
20753 cx: &mut App,
20754 ) -> Option<Task<Result<Vec<LocationLink>>>>;
20755
20756 fn range_for_rename(
20757 &self,
20758 buffer: &Entity<Buffer>,
20759 position: text::Anchor,
20760 cx: &mut App,
20761 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
20762
20763 fn perform_rename(
20764 &self,
20765 buffer: &Entity<Buffer>,
20766 position: text::Anchor,
20767 new_name: String,
20768 cx: &mut App,
20769 ) -> Option<Task<Result<ProjectTransaction>>>;
20770
20771 fn pull_diagnostics_for_buffer(
20772 &self,
20773 buffer: Entity<Buffer>,
20774 cx: &mut App,
20775 ) -> Task<anyhow::Result<()>>;
20776}
20777
20778pub trait CompletionProvider {
20779 fn completions(
20780 &self,
20781 excerpt_id: ExcerptId,
20782 buffer: &Entity<Buffer>,
20783 buffer_position: text::Anchor,
20784 trigger: CompletionContext,
20785 window: &mut Window,
20786 cx: &mut Context<Editor>,
20787 ) -> Task<Result<Vec<CompletionResponse>>>;
20788
20789 fn resolve_completions(
20790 &self,
20791 _buffer: Entity<Buffer>,
20792 _completion_indices: Vec<usize>,
20793 _completions: Rc<RefCell<Box<[Completion]>>>,
20794 _cx: &mut Context<Editor>,
20795 ) -> Task<Result<bool>> {
20796 Task::ready(Ok(false))
20797 }
20798
20799 fn apply_additional_edits_for_completion(
20800 &self,
20801 _buffer: Entity<Buffer>,
20802 _completions: Rc<RefCell<Box<[Completion]>>>,
20803 _completion_index: usize,
20804 _push_to_history: bool,
20805 _cx: &mut Context<Editor>,
20806 ) -> Task<Result<Option<language::Transaction>>> {
20807 Task::ready(Ok(None))
20808 }
20809
20810 fn is_completion_trigger(
20811 &self,
20812 buffer: &Entity<Buffer>,
20813 position: language::Anchor,
20814 text: &str,
20815 trigger_in_words: bool,
20816 menu_is_open: bool,
20817 cx: &mut Context<Editor>,
20818 ) -> bool;
20819
20820 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
20821
20822 fn sort_completions(&self) -> bool {
20823 true
20824 }
20825
20826 fn filter_completions(&self) -> bool {
20827 true
20828 }
20829}
20830
20831pub trait CodeActionProvider {
20832 fn id(&self) -> Arc<str>;
20833
20834 fn code_actions(
20835 &self,
20836 buffer: &Entity<Buffer>,
20837 range: Range<text::Anchor>,
20838 window: &mut Window,
20839 cx: &mut App,
20840 ) -> Task<Result<Vec<CodeAction>>>;
20841
20842 fn apply_code_action(
20843 &self,
20844 buffer_handle: Entity<Buffer>,
20845 action: CodeAction,
20846 excerpt_id: ExcerptId,
20847 push_to_history: bool,
20848 window: &mut Window,
20849 cx: &mut App,
20850 ) -> Task<Result<ProjectTransaction>>;
20851}
20852
20853impl CodeActionProvider for Entity<Project> {
20854 fn id(&self) -> Arc<str> {
20855 "project".into()
20856 }
20857
20858 fn code_actions(
20859 &self,
20860 buffer: &Entity<Buffer>,
20861 range: Range<text::Anchor>,
20862 _window: &mut Window,
20863 cx: &mut App,
20864 ) -> Task<Result<Vec<CodeAction>>> {
20865 self.update(cx, |project, cx| {
20866 let code_lens = project.code_lens(buffer, range.clone(), cx);
20867 let code_actions = project.code_actions(buffer, range, None, cx);
20868 cx.background_spawn(async move {
20869 let (code_lens, code_actions) = join(code_lens, code_actions).await;
20870 Ok(code_lens
20871 .context("code lens fetch")?
20872 .into_iter()
20873 .chain(code_actions.context("code action fetch")?)
20874 .collect())
20875 })
20876 })
20877 }
20878
20879 fn apply_code_action(
20880 &self,
20881 buffer_handle: Entity<Buffer>,
20882 action: CodeAction,
20883 _excerpt_id: ExcerptId,
20884 push_to_history: bool,
20885 _window: &mut Window,
20886 cx: &mut App,
20887 ) -> Task<Result<ProjectTransaction>> {
20888 self.update(cx, |project, cx| {
20889 project.apply_code_action(buffer_handle, action, push_to_history, cx)
20890 })
20891 }
20892}
20893
20894fn snippet_completions(
20895 project: &Project,
20896 buffer: &Entity<Buffer>,
20897 buffer_position: text::Anchor,
20898 cx: &mut App,
20899) -> Task<Result<CompletionResponse>> {
20900 let languages = buffer.read(cx).languages_at(buffer_position);
20901 let snippet_store = project.snippets().read(cx);
20902
20903 let scopes: Vec<_> = languages
20904 .iter()
20905 .filter_map(|language| {
20906 let language_name = language.lsp_id();
20907 let snippets = snippet_store.snippets_for(Some(language_name), cx);
20908
20909 if snippets.is_empty() {
20910 None
20911 } else {
20912 Some((language.default_scope(), snippets))
20913 }
20914 })
20915 .collect();
20916
20917 if scopes.is_empty() {
20918 return Task::ready(Ok(CompletionResponse {
20919 completions: vec![],
20920 is_incomplete: false,
20921 }));
20922 }
20923
20924 let snapshot = buffer.read(cx).text_snapshot();
20925 let chars: String = snapshot
20926 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
20927 .collect();
20928 let executor = cx.background_executor().clone();
20929
20930 cx.background_spawn(async move {
20931 let mut is_incomplete = false;
20932 let mut completions: Vec<Completion> = Vec::new();
20933 for (scope, snippets) in scopes.into_iter() {
20934 let classifier = CharClassifier::new(Some(scope)).for_completion(true);
20935 let mut last_word = chars
20936 .chars()
20937 .take_while(|c| classifier.is_word(*c))
20938 .collect::<String>();
20939 last_word = last_word.chars().rev().collect();
20940
20941 if last_word.is_empty() {
20942 return Ok(CompletionResponse {
20943 completions: vec![],
20944 is_incomplete: true,
20945 });
20946 }
20947
20948 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
20949 let to_lsp = |point: &text::Anchor| {
20950 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
20951 point_to_lsp(end)
20952 };
20953 let lsp_end = to_lsp(&buffer_position);
20954
20955 let candidates = snippets
20956 .iter()
20957 .enumerate()
20958 .flat_map(|(ix, snippet)| {
20959 snippet
20960 .prefix
20961 .iter()
20962 .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
20963 })
20964 .collect::<Vec<StringMatchCandidate>>();
20965
20966 const MAX_RESULTS: usize = 100;
20967 let mut matches = fuzzy::match_strings(
20968 &candidates,
20969 &last_word,
20970 last_word.chars().any(|c| c.is_uppercase()),
20971 MAX_RESULTS,
20972 &Default::default(),
20973 executor.clone(),
20974 )
20975 .await;
20976
20977 if matches.len() >= MAX_RESULTS {
20978 is_incomplete = true;
20979 }
20980
20981 // Remove all candidates where the query's start does not match the start of any word in the candidate
20982 if let Some(query_start) = last_word.chars().next() {
20983 matches.retain(|string_match| {
20984 split_words(&string_match.string).any(|word| {
20985 // Check that the first codepoint of the word as lowercase matches the first
20986 // codepoint of the query as lowercase
20987 word.chars()
20988 .flat_map(|codepoint| codepoint.to_lowercase())
20989 .zip(query_start.to_lowercase())
20990 .all(|(word_cp, query_cp)| word_cp == query_cp)
20991 })
20992 });
20993 }
20994
20995 let matched_strings = matches
20996 .into_iter()
20997 .map(|m| m.string)
20998 .collect::<HashSet<_>>();
20999
21000 completions.extend(snippets.iter().filter_map(|snippet| {
21001 let matching_prefix = snippet
21002 .prefix
21003 .iter()
21004 .find(|prefix| matched_strings.contains(*prefix))?;
21005 let start = as_offset - last_word.len();
21006 let start = snapshot.anchor_before(start);
21007 let range = start..buffer_position;
21008 let lsp_start = to_lsp(&start);
21009 let lsp_range = lsp::Range {
21010 start: lsp_start,
21011 end: lsp_end,
21012 };
21013 Some(Completion {
21014 replace_range: range,
21015 new_text: snippet.body.clone(),
21016 source: CompletionSource::Lsp {
21017 insert_range: None,
21018 server_id: LanguageServerId(usize::MAX),
21019 resolved: true,
21020 lsp_completion: Box::new(lsp::CompletionItem {
21021 label: snippet.prefix.first().unwrap().clone(),
21022 kind: Some(CompletionItemKind::SNIPPET),
21023 label_details: snippet.description.as_ref().map(|description| {
21024 lsp::CompletionItemLabelDetails {
21025 detail: Some(description.clone()),
21026 description: None,
21027 }
21028 }),
21029 insert_text_format: Some(InsertTextFormat::SNIPPET),
21030 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
21031 lsp::InsertReplaceEdit {
21032 new_text: snippet.body.clone(),
21033 insert: lsp_range,
21034 replace: lsp_range,
21035 },
21036 )),
21037 filter_text: Some(snippet.body.clone()),
21038 sort_text: Some(char::MAX.to_string()),
21039 ..lsp::CompletionItem::default()
21040 }),
21041 lsp_defaults: None,
21042 },
21043 label: CodeLabel {
21044 text: matching_prefix.clone(),
21045 runs: Vec::new(),
21046 filter_range: 0..matching_prefix.len(),
21047 },
21048 icon_path: None,
21049 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
21050 single_line: snippet.name.clone().into(),
21051 plain_text: snippet
21052 .description
21053 .clone()
21054 .map(|description| description.into()),
21055 }),
21056 insert_text_mode: None,
21057 confirm: None,
21058 })
21059 }))
21060 }
21061
21062 Ok(CompletionResponse {
21063 completions,
21064 is_incomplete,
21065 })
21066 })
21067}
21068
21069impl CompletionProvider for Entity<Project> {
21070 fn completions(
21071 &self,
21072 _excerpt_id: ExcerptId,
21073 buffer: &Entity<Buffer>,
21074 buffer_position: text::Anchor,
21075 options: CompletionContext,
21076 _window: &mut Window,
21077 cx: &mut Context<Editor>,
21078 ) -> Task<Result<Vec<CompletionResponse>>> {
21079 self.update(cx, |project, cx| {
21080 let snippets = snippet_completions(project, buffer, buffer_position, cx);
21081 let project_completions = project.completions(buffer, buffer_position, options, cx);
21082 cx.background_spawn(async move {
21083 let mut responses = project_completions.await?;
21084 let snippets = snippets.await?;
21085 if !snippets.completions.is_empty() {
21086 responses.push(snippets);
21087 }
21088 Ok(responses)
21089 })
21090 })
21091 }
21092
21093 fn resolve_completions(
21094 &self,
21095 buffer: Entity<Buffer>,
21096 completion_indices: Vec<usize>,
21097 completions: Rc<RefCell<Box<[Completion]>>>,
21098 cx: &mut Context<Editor>,
21099 ) -> Task<Result<bool>> {
21100 self.update(cx, |project, cx| {
21101 project.lsp_store().update(cx, |lsp_store, cx| {
21102 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
21103 })
21104 })
21105 }
21106
21107 fn apply_additional_edits_for_completion(
21108 &self,
21109 buffer: Entity<Buffer>,
21110 completions: Rc<RefCell<Box<[Completion]>>>,
21111 completion_index: usize,
21112 push_to_history: bool,
21113 cx: &mut Context<Editor>,
21114 ) -> Task<Result<Option<language::Transaction>>> {
21115 self.update(cx, |project, cx| {
21116 project.lsp_store().update(cx, |lsp_store, cx| {
21117 lsp_store.apply_additional_edits_for_completion(
21118 buffer,
21119 completions,
21120 completion_index,
21121 push_to_history,
21122 cx,
21123 )
21124 })
21125 })
21126 }
21127
21128 fn is_completion_trigger(
21129 &self,
21130 buffer: &Entity<Buffer>,
21131 position: language::Anchor,
21132 text: &str,
21133 trigger_in_words: bool,
21134 menu_is_open: bool,
21135 cx: &mut Context<Editor>,
21136 ) -> bool {
21137 let mut chars = text.chars();
21138 let char = if let Some(char) = chars.next() {
21139 char
21140 } else {
21141 return false;
21142 };
21143 if chars.next().is_some() {
21144 return false;
21145 }
21146
21147 let buffer = buffer.read(cx);
21148 let snapshot = buffer.snapshot();
21149 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
21150 return false;
21151 }
21152 let classifier = snapshot.char_classifier_at(position).for_completion(true);
21153 if trigger_in_words && classifier.is_word(char) {
21154 return true;
21155 }
21156
21157 buffer.completion_triggers().contains(text)
21158 }
21159}
21160
21161impl SemanticsProvider for Entity<Project> {
21162 fn hover(
21163 &self,
21164 buffer: &Entity<Buffer>,
21165 position: text::Anchor,
21166 cx: &mut App,
21167 ) -> Option<Task<Vec<project::Hover>>> {
21168 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
21169 }
21170
21171 fn document_highlights(
21172 &self,
21173 buffer: &Entity<Buffer>,
21174 position: text::Anchor,
21175 cx: &mut App,
21176 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
21177 Some(self.update(cx, |project, cx| {
21178 project.document_highlights(buffer, position, cx)
21179 }))
21180 }
21181
21182 fn definitions(
21183 &self,
21184 buffer: &Entity<Buffer>,
21185 position: text::Anchor,
21186 kind: GotoDefinitionKind,
21187 cx: &mut App,
21188 ) -> Option<Task<Result<Vec<LocationLink>>>> {
21189 Some(self.update(cx, |project, cx| match kind {
21190 GotoDefinitionKind::Symbol => project.definition(&buffer, position, cx),
21191 GotoDefinitionKind::Declaration => project.declaration(&buffer, position, cx),
21192 GotoDefinitionKind::Type => project.type_definition(&buffer, position, cx),
21193 GotoDefinitionKind::Implementation => project.implementation(&buffer, position, cx),
21194 }))
21195 }
21196
21197 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
21198 // TODO: make this work for remote projects
21199 self.update(cx, |project, cx| {
21200 if project
21201 .active_debug_session(cx)
21202 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
21203 {
21204 return true;
21205 }
21206
21207 buffer.update(cx, |buffer, cx| {
21208 project.any_language_server_supports_inlay_hints(buffer, cx)
21209 })
21210 })
21211 }
21212
21213 fn inline_values(
21214 &self,
21215 buffer_handle: Entity<Buffer>,
21216
21217 range: Range<text::Anchor>,
21218 cx: &mut App,
21219 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21220 self.update(cx, |project, cx| {
21221 let (session, active_stack_frame) = project.active_debug_session(cx)?;
21222
21223 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
21224 })
21225 }
21226
21227 fn inlay_hints(
21228 &self,
21229 buffer_handle: Entity<Buffer>,
21230 range: Range<text::Anchor>,
21231 cx: &mut App,
21232 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
21233 Some(self.update(cx, |project, cx| {
21234 project.inlay_hints(buffer_handle, range, cx)
21235 }))
21236 }
21237
21238 fn resolve_inlay_hint(
21239 &self,
21240 hint: InlayHint,
21241 buffer_handle: Entity<Buffer>,
21242 server_id: LanguageServerId,
21243 cx: &mut App,
21244 ) -> Option<Task<anyhow::Result<InlayHint>>> {
21245 Some(self.update(cx, |project, cx| {
21246 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
21247 }))
21248 }
21249
21250 fn range_for_rename(
21251 &self,
21252 buffer: &Entity<Buffer>,
21253 position: text::Anchor,
21254 cx: &mut App,
21255 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
21256 Some(self.update(cx, |project, cx| {
21257 let buffer = buffer.clone();
21258 let task = project.prepare_rename(buffer.clone(), position, cx);
21259 cx.spawn(async move |_, cx| {
21260 Ok(match task.await? {
21261 PrepareRenameResponse::Success(range) => Some(range),
21262 PrepareRenameResponse::InvalidPosition => None,
21263 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
21264 // Fallback on using TreeSitter info to determine identifier range
21265 buffer.read_with(cx, |buffer, _| {
21266 let snapshot = buffer.snapshot();
21267 let (range, kind) = snapshot.surrounding_word(position);
21268 if kind != Some(CharKind::Word) {
21269 return None;
21270 }
21271 Some(
21272 snapshot.anchor_before(range.start)
21273 ..snapshot.anchor_after(range.end),
21274 )
21275 })?
21276 }
21277 })
21278 })
21279 }))
21280 }
21281
21282 fn perform_rename(
21283 &self,
21284 buffer: &Entity<Buffer>,
21285 position: text::Anchor,
21286 new_name: String,
21287 cx: &mut App,
21288 ) -> Option<Task<Result<ProjectTransaction>>> {
21289 Some(self.update(cx, |project, cx| {
21290 project.perform_rename(buffer.clone(), position, new_name, cx)
21291 }))
21292 }
21293
21294 fn pull_diagnostics_for_buffer(
21295 &self,
21296 buffer: Entity<Buffer>,
21297 cx: &mut App,
21298 ) -> Task<anyhow::Result<()>> {
21299 let diagnostics = self.update(cx, |project, cx| {
21300 project
21301 .lsp_store()
21302 .update(cx, |lsp_store, cx| lsp_store.pull_diagnostics(buffer, cx))
21303 });
21304 let project = self.clone();
21305 cx.spawn(async move |cx| {
21306 let diagnostics = diagnostics.await.context("pulling diagnostics")?;
21307 project.update(cx, |project, cx| {
21308 project.lsp_store().update(cx, |lsp_store, cx| {
21309 for diagnostics_set in diagnostics {
21310 let LspPullDiagnostics::Response {
21311 server_id,
21312 uri,
21313 diagnostics,
21314 } = diagnostics_set
21315 else {
21316 continue;
21317 };
21318
21319 let adapter = lsp_store.language_server_adapter_for_id(server_id);
21320 let disk_based_sources = adapter
21321 .as_ref()
21322 .map(|adapter| adapter.disk_based_diagnostic_sources.as_slice())
21323 .unwrap_or(&[]);
21324 match diagnostics {
21325 PulledDiagnostics::Unchanged { result_id } => {
21326 lsp_store
21327 .merge_diagnostics(
21328 server_id,
21329 lsp::PublishDiagnosticsParams {
21330 uri: uri.clone(),
21331 diagnostics: Vec::new(),
21332 version: None,
21333 },
21334 Some(result_id),
21335 DiagnosticSourceKind::Pulled,
21336 disk_based_sources,
21337 |_, _| true,
21338 cx,
21339 )
21340 .log_err();
21341 }
21342 PulledDiagnostics::Changed {
21343 diagnostics,
21344 result_id,
21345 } => {
21346 lsp_store
21347 .merge_diagnostics(
21348 server_id,
21349 lsp::PublishDiagnosticsParams {
21350 uri: uri.clone(),
21351 diagnostics,
21352 version: None,
21353 },
21354 result_id,
21355 DiagnosticSourceKind::Pulled,
21356 disk_based_sources,
21357 |old_diagnostic, _| match old_diagnostic.source_kind {
21358 DiagnosticSourceKind::Pulled => false,
21359 DiagnosticSourceKind::Other
21360 | DiagnosticSourceKind::Pushed => true,
21361 },
21362 cx,
21363 )
21364 .log_err();
21365 }
21366 }
21367 }
21368 })
21369 })
21370 })
21371 }
21372}
21373
21374fn inlay_hint_settings(
21375 location: Anchor,
21376 snapshot: &MultiBufferSnapshot,
21377 cx: &mut Context<Editor>,
21378) -> InlayHintSettings {
21379 let file = snapshot.file_at(location);
21380 let language = snapshot.language_at(location).map(|l| l.name());
21381 language_settings(language, file, cx).inlay_hints
21382}
21383
21384fn consume_contiguous_rows(
21385 contiguous_row_selections: &mut Vec<Selection<Point>>,
21386 selection: &Selection<Point>,
21387 display_map: &DisplaySnapshot,
21388 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
21389) -> (MultiBufferRow, MultiBufferRow) {
21390 contiguous_row_selections.push(selection.clone());
21391 let start_row = MultiBufferRow(selection.start.row);
21392 let mut end_row = ending_row(selection, display_map);
21393
21394 while let Some(next_selection) = selections.peek() {
21395 if next_selection.start.row <= end_row.0 {
21396 end_row = ending_row(next_selection, display_map);
21397 contiguous_row_selections.push(selections.next().unwrap().clone());
21398 } else {
21399 break;
21400 }
21401 }
21402 (start_row, end_row)
21403}
21404
21405fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
21406 if next_selection.end.column > 0 || next_selection.is_empty() {
21407 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
21408 } else {
21409 MultiBufferRow(next_selection.end.row)
21410 }
21411}
21412
21413impl EditorSnapshot {
21414 pub fn remote_selections_in_range<'a>(
21415 &'a self,
21416 range: &'a Range<Anchor>,
21417 collaboration_hub: &dyn CollaborationHub,
21418 cx: &'a App,
21419 ) -> impl 'a + Iterator<Item = RemoteSelection> {
21420 let participant_names = collaboration_hub.user_names(cx);
21421 let participant_indices = collaboration_hub.user_participant_indices(cx);
21422 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
21423 let collaborators_by_replica_id = collaborators_by_peer_id
21424 .values()
21425 .map(|collaborator| (collaborator.replica_id, collaborator))
21426 .collect::<HashMap<_, _>>();
21427 self.buffer_snapshot
21428 .selections_in_range(range, false)
21429 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
21430 if replica_id == AGENT_REPLICA_ID {
21431 Some(RemoteSelection {
21432 replica_id,
21433 selection,
21434 cursor_shape,
21435 line_mode,
21436 collaborator_id: CollaboratorId::Agent,
21437 user_name: Some("Agent".into()),
21438 color: cx.theme().players().agent(),
21439 })
21440 } else {
21441 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
21442 let participant_index = participant_indices.get(&collaborator.user_id).copied();
21443 let user_name = participant_names.get(&collaborator.user_id).cloned();
21444 Some(RemoteSelection {
21445 replica_id,
21446 selection,
21447 cursor_shape,
21448 line_mode,
21449 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
21450 user_name,
21451 color: if let Some(index) = participant_index {
21452 cx.theme().players().color_for_participant(index.0)
21453 } else {
21454 cx.theme().players().absent()
21455 },
21456 })
21457 }
21458 })
21459 }
21460
21461 pub fn hunks_for_ranges(
21462 &self,
21463 ranges: impl IntoIterator<Item = Range<Point>>,
21464 ) -> Vec<MultiBufferDiffHunk> {
21465 let mut hunks = Vec::new();
21466 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
21467 HashMap::default();
21468 for query_range in ranges {
21469 let query_rows =
21470 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
21471 for hunk in self.buffer_snapshot.diff_hunks_in_range(
21472 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
21473 ) {
21474 // Include deleted hunks that are adjacent to the query range, because
21475 // otherwise they would be missed.
21476 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
21477 if hunk.status().is_deleted() {
21478 intersects_range |= hunk.row_range.start == query_rows.end;
21479 intersects_range |= hunk.row_range.end == query_rows.start;
21480 }
21481 if intersects_range {
21482 if !processed_buffer_rows
21483 .entry(hunk.buffer_id)
21484 .or_default()
21485 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
21486 {
21487 continue;
21488 }
21489 hunks.push(hunk);
21490 }
21491 }
21492 }
21493
21494 hunks
21495 }
21496
21497 fn display_diff_hunks_for_rows<'a>(
21498 &'a self,
21499 display_rows: Range<DisplayRow>,
21500 folded_buffers: &'a HashSet<BufferId>,
21501 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
21502 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
21503 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
21504
21505 self.buffer_snapshot
21506 .diff_hunks_in_range(buffer_start..buffer_end)
21507 .filter_map(|hunk| {
21508 if folded_buffers.contains(&hunk.buffer_id) {
21509 return None;
21510 }
21511
21512 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
21513 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
21514
21515 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
21516 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
21517
21518 let display_hunk = if hunk_display_start.column() != 0 {
21519 DisplayDiffHunk::Folded {
21520 display_row: hunk_display_start.row(),
21521 }
21522 } else {
21523 let mut end_row = hunk_display_end.row();
21524 if hunk_display_end.column() > 0 {
21525 end_row.0 += 1;
21526 }
21527 let is_created_file = hunk.is_created_file();
21528 DisplayDiffHunk::Unfolded {
21529 status: hunk.status(),
21530 diff_base_byte_range: hunk.diff_base_byte_range,
21531 display_row_range: hunk_display_start.row()..end_row,
21532 multi_buffer_range: Anchor::range_in_buffer(
21533 hunk.excerpt_id,
21534 hunk.buffer_id,
21535 hunk.buffer_range,
21536 ),
21537 is_created_file,
21538 }
21539 };
21540
21541 Some(display_hunk)
21542 })
21543 }
21544
21545 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
21546 self.display_snapshot.buffer_snapshot.language_at(position)
21547 }
21548
21549 pub fn is_focused(&self) -> bool {
21550 self.is_focused
21551 }
21552
21553 pub fn placeholder_text(&self) -> Option<&Arc<str>> {
21554 self.placeholder_text.as_ref()
21555 }
21556
21557 pub fn scroll_position(&self) -> gpui::Point<f32> {
21558 self.scroll_anchor.scroll_position(&self.display_snapshot)
21559 }
21560
21561 fn gutter_dimensions(
21562 &self,
21563 font_id: FontId,
21564 font_size: Pixels,
21565 max_line_number_width: Pixels,
21566 cx: &App,
21567 ) -> Option<GutterDimensions> {
21568 if !self.show_gutter {
21569 return None;
21570 }
21571
21572 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
21573 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
21574
21575 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
21576 matches!(
21577 ProjectSettings::get_global(cx).git.git_gutter,
21578 Some(GitGutterSetting::TrackedFiles)
21579 )
21580 });
21581 let gutter_settings = EditorSettings::get_global(cx).gutter;
21582 let show_line_numbers = self
21583 .show_line_numbers
21584 .unwrap_or(gutter_settings.line_numbers);
21585 let line_gutter_width = if show_line_numbers {
21586 // Avoid flicker-like gutter resizes when the line number gains another digit by
21587 // only resizing the gutter on files with > 10**min_line_number_digits lines.
21588 let min_width_for_number_on_gutter =
21589 ch_advance * gutter_settings.min_line_number_digits as f32;
21590 max_line_number_width.max(min_width_for_number_on_gutter)
21591 } else {
21592 0.0.into()
21593 };
21594
21595 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
21596 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
21597
21598 let git_blame_entries_width =
21599 self.git_blame_gutter_max_author_length
21600 .map(|max_author_length| {
21601 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21602 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
21603
21604 /// The number of characters to dedicate to gaps and margins.
21605 const SPACING_WIDTH: usize = 4;
21606
21607 let max_char_count = max_author_length.min(renderer.max_author_length())
21608 + ::git::SHORT_SHA_LENGTH
21609 + MAX_RELATIVE_TIMESTAMP.len()
21610 + SPACING_WIDTH;
21611
21612 ch_advance * max_char_count
21613 });
21614
21615 let is_singleton = self.buffer_snapshot.is_singleton();
21616
21617 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
21618 left_padding += if !is_singleton {
21619 ch_width * 4.0
21620 } else if show_runnables || show_breakpoints {
21621 ch_width * 3.0
21622 } else if show_git_gutter && show_line_numbers {
21623 ch_width * 2.0
21624 } else if show_git_gutter || show_line_numbers {
21625 ch_width
21626 } else {
21627 px(0.)
21628 };
21629
21630 let shows_folds = is_singleton && gutter_settings.folds;
21631
21632 let right_padding = if shows_folds && show_line_numbers {
21633 ch_width * 4.0
21634 } else if shows_folds || (!is_singleton && show_line_numbers) {
21635 ch_width * 3.0
21636 } else if show_line_numbers {
21637 ch_width
21638 } else {
21639 px(0.)
21640 };
21641
21642 Some(GutterDimensions {
21643 left_padding,
21644 right_padding,
21645 width: line_gutter_width + left_padding + right_padding,
21646 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
21647 git_blame_entries_width,
21648 })
21649 }
21650
21651 pub fn render_crease_toggle(
21652 &self,
21653 buffer_row: MultiBufferRow,
21654 row_contains_cursor: bool,
21655 editor: Entity<Editor>,
21656 window: &mut Window,
21657 cx: &mut App,
21658 ) -> Option<AnyElement> {
21659 let folded = self.is_line_folded(buffer_row);
21660 let mut is_foldable = false;
21661
21662 if let Some(crease) = self
21663 .crease_snapshot
21664 .query_row(buffer_row, &self.buffer_snapshot)
21665 {
21666 is_foldable = true;
21667 match crease {
21668 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
21669 if let Some(render_toggle) = render_toggle {
21670 let toggle_callback =
21671 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
21672 if folded {
21673 editor.update(cx, |editor, cx| {
21674 editor.fold_at(buffer_row, window, cx)
21675 });
21676 } else {
21677 editor.update(cx, |editor, cx| {
21678 editor.unfold_at(buffer_row, window, cx)
21679 });
21680 }
21681 });
21682 return Some((render_toggle)(
21683 buffer_row,
21684 folded,
21685 toggle_callback,
21686 window,
21687 cx,
21688 ));
21689 }
21690 }
21691 }
21692 }
21693
21694 is_foldable |= self.starts_indent(buffer_row);
21695
21696 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
21697 Some(
21698 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
21699 .toggle_state(folded)
21700 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
21701 if folded {
21702 this.unfold_at(buffer_row, window, cx);
21703 } else {
21704 this.fold_at(buffer_row, window, cx);
21705 }
21706 }))
21707 .into_any_element(),
21708 )
21709 } else {
21710 None
21711 }
21712 }
21713
21714 pub fn render_crease_trailer(
21715 &self,
21716 buffer_row: MultiBufferRow,
21717 window: &mut Window,
21718 cx: &mut App,
21719 ) -> Option<AnyElement> {
21720 let folded = self.is_line_folded(buffer_row);
21721 if let Crease::Inline { render_trailer, .. } = self
21722 .crease_snapshot
21723 .query_row(buffer_row, &self.buffer_snapshot)?
21724 {
21725 let render_trailer = render_trailer.as_ref()?;
21726 Some(render_trailer(buffer_row, folded, window, cx))
21727 } else {
21728 None
21729 }
21730 }
21731}
21732
21733impl Deref for EditorSnapshot {
21734 type Target = DisplaySnapshot;
21735
21736 fn deref(&self) -> &Self::Target {
21737 &self.display_snapshot
21738 }
21739}
21740
21741#[derive(Clone, Debug, PartialEq, Eq)]
21742pub enum EditorEvent {
21743 InputIgnored {
21744 text: Arc<str>,
21745 },
21746 InputHandled {
21747 utf16_range_to_replace: Option<Range<isize>>,
21748 text: Arc<str>,
21749 },
21750 ExcerptsAdded {
21751 buffer: Entity<Buffer>,
21752 predecessor: ExcerptId,
21753 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
21754 },
21755 ExcerptsRemoved {
21756 ids: Vec<ExcerptId>,
21757 removed_buffer_ids: Vec<BufferId>,
21758 },
21759 BufferFoldToggled {
21760 ids: Vec<ExcerptId>,
21761 folded: bool,
21762 },
21763 ExcerptsEdited {
21764 ids: Vec<ExcerptId>,
21765 },
21766 ExcerptsExpanded {
21767 ids: Vec<ExcerptId>,
21768 },
21769 BufferEdited,
21770 Edited {
21771 transaction_id: clock::Lamport,
21772 },
21773 Reparsed(BufferId),
21774 Focused,
21775 FocusedIn,
21776 Blurred,
21777 DirtyChanged,
21778 Saved,
21779 TitleChanged,
21780 DiffBaseChanged,
21781 SelectionsChanged {
21782 local: bool,
21783 },
21784 ScrollPositionChanged {
21785 local: bool,
21786 autoscroll: bool,
21787 },
21788 Closed,
21789 TransactionUndone {
21790 transaction_id: clock::Lamport,
21791 },
21792 TransactionBegun {
21793 transaction_id: clock::Lamport,
21794 },
21795 Reloaded,
21796 CursorShapeChanged,
21797 PushedToNavHistory {
21798 anchor: Anchor,
21799 is_deactivate: bool,
21800 },
21801}
21802
21803impl EventEmitter<EditorEvent> for Editor {}
21804
21805impl Focusable for Editor {
21806 fn focus_handle(&self, _cx: &App) -> FocusHandle {
21807 self.focus_handle.clone()
21808 }
21809}
21810
21811impl Render for Editor {
21812 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
21813 let settings = ThemeSettings::get_global(cx);
21814
21815 let mut text_style = match self.mode {
21816 EditorMode::SingleLine { .. } | EditorMode::AutoHeight { .. } => TextStyle {
21817 color: cx.theme().colors().editor_foreground,
21818 font_family: settings.ui_font.family.clone(),
21819 font_features: settings.ui_font.features.clone(),
21820 font_fallbacks: settings.ui_font.fallbacks.clone(),
21821 font_size: rems(0.875).into(),
21822 font_weight: settings.ui_font.weight,
21823 line_height: relative(settings.buffer_line_height.value()),
21824 ..Default::default()
21825 },
21826 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
21827 color: cx.theme().colors().editor_foreground,
21828 font_family: settings.buffer_font.family.clone(),
21829 font_features: settings.buffer_font.features.clone(),
21830 font_fallbacks: settings.buffer_font.fallbacks.clone(),
21831 font_size: settings.buffer_font_size(cx).into(),
21832 font_weight: settings.buffer_font.weight,
21833 line_height: relative(settings.buffer_line_height.value()),
21834 ..Default::default()
21835 },
21836 };
21837 if let Some(text_style_refinement) = &self.text_style_refinement {
21838 text_style.refine(text_style_refinement)
21839 }
21840
21841 let background = match self.mode {
21842 EditorMode::SingleLine { .. } => cx.theme().system().transparent,
21843 EditorMode::AutoHeight { max_lines: _ } => cx.theme().system().transparent,
21844 EditorMode::Full { .. } => cx.theme().colors().editor_background,
21845 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
21846 };
21847
21848 EditorElement::new(
21849 &cx.entity(),
21850 EditorStyle {
21851 background,
21852 local_player: cx.theme().players().local(),
21853 text: text_style,
21854 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
21855 syntax: cx.theme().syntax().clone(),
21856 status: cx.theme().status().clone(),
21857 inlay_hints_style: make_inlay_hints_style(cx),
21858 inline_completion_styles: make_suggestion_styles(cx),
21859 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
21860 show_underlines: !self.mode.is_minimap(),
21861 },
21862 )
21863 }
21864}
21865
21866impl EntityInputHandler for Editor {
21867 fn text_for_range(
21868 &mut self,
21869 range_utf16: Range<usize>,
21870 adjusted_range: &mut Option<Range<usize>>,
21871 _: &mut Window,
21872 cx: &mut Context<Self>,
21873 ) -> Option<String> {
21874 let snapshot = self.buffer.read(cx).read(cx);
21875 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
21876 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
21877 if (start.0..end.0) != range_utf16 {
21878 adjusted_range.replace(start.0..end.0);
21879 }
21880 Some(snapshot.text_for_range(start..end).collect())
21881 }
21882
21883 fn selected_text_range(
21884 &mut self,
21885 ignore_disabled_input: bool,
21886 _: &mut Window,
21887 cx: &mut Context<Self>,
21888 ) -> Option<UTF16Selection> {
21889 // Prevent the IME menu from appearing when holding down an alphabetic key
21890 // while input is disabled.
21891 if !ignore_disabled_input && !self.input_enabled {
21892 return None;
21893 }
21894
21895 let selection = self.selections.newest::<OffsetUtf16>(cx);
21896 let range = selection.range();
21897
21898 Some(UTF16Selection {
21899 range: range.start.0..range.end.0,
21900 reversed: selection.reversed,
21901 })
21902 }
21903
21904 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
21905 let snapshot = self.buffer.read(cx).read(cx);
21906 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
21907 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
21908 }
21909
21910 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21911 self.clear_highlights::<InputComposition>(cx);
21912 self.ime_transaction.take();
21913 }
21914
21915 fn replace_text_in_range(
21916 &mut self,
21917 range_utf16: Option<Range<usize>>,
21918 text: &str,
21919 window: &mut Window,
21920 cx: &mut Context<Self>,
21921 ) {
21922 if !self.input_enabled {
21923 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21924 return;
21925 }
21926
21927 self.transact(window, cx, |this, window, cx| {
21928 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
21929 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
21930 Some(this.selection_replacement_ranges(range_utf16, cx))
21931 } else {
21932 this.marked_text_ranges(cx)
21933 };
21934
21935 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
21936 let newest_selection_id = this.selections.newest_anchor().id;
21937 this.selections
21938 .all::<OffsetUtf16>(cx)
21939 .iter()
21940 .zip(ranges_to_replace.iter())
21941 .find_map(|(selection, range)| {
21942 if selection.id == newest_selection_id {
21943 Some(
21944 (range.start.0 as isize - selection.head().0 as isize)
21945 ..(range.end.0 as isize - selection.head().0 as isize),
21946 )
21947 } else {
21948 None
21949 }
21950 })
21951 });
21952
21953 cx.emit(EditorEvent::InputHandled {
21954 utf16_range_to_replace: range_to_replace,
21955 text: text.into(),
21956 });
21957
21958 if let Some(new_selected_ranges) = new_selected_ranges {
21959 this.change_selections(None, window, cx, |selections| {
21960 selections.select_ranges(new_selected_ranges)
21961 });
21962 this.backspace(&Default::default(), window, cx);
21963 }
21964
21965 this.handle_input(text, window, cx);
21966 });
21967
21968 if let Some(transaction) = self.ime_transaction {
21969 self.buffer.update(cx, |buffer, cx| {
21970 buffer.group_until_transaction(transaction, cx);
21971 });
21972 }
21973
21974 self.unmark_text(window, cx);
21975 }
21976
21977 fn replace_and_mark_text_in_range(
21978 &mut self,
21979 range_utf16: Option<Range<usize>>,
21980 text: &str,
21981 new_selected_range_utf16: Option<Range<usize>>,
21982 window: &mut Window,
21983 cx: &mut Context<Self>,
21984 ) {
21985 if !self.input_enabled {
21986 return;
21987 }
21988
21989 let transaction = self.transact(window, cx, |this, window, cx| {
21990 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
21991 let snapshot = this.buffer.read(cx).read(cx);
21992 if let Some(relative_range_utf16) = range_utf16.as_ref() {
21993 for marked_range in &mut marked_ranges {
21994 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
21995 marked_range.start.0 += relative_range_utf16.start;
21996 marked_range.start =
21997 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
21998 marked_range.end =
21999 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
22000 }
22001 }
22002 Some(marked_ranges)
22003 } else if let Some(range_utf16) = range_utf16 {
22004 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
22005 Some(this.selection_replacement_ranges(range_utf16, cx))
22006 } else {
22007 None
22008 };
22009
22010 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
22011 let newest_selection_id = this.selections.newest_anchor().id;
22012 this.selections
22013 .all::<OffsetUtf16>(cx)
22014 .iter()
22015 .zip(ranges_to_replace.iter())
22016 .find_map(|(selection, range)| {
22017 if selection.id == newest_selection_id {
22018 Some(
22019 (range.start.0 as isize - selection.head().0 as isize)
22020 ..(range.end.0 as isize - selection.head().0 as isize),
22021 )
22022 } else {
22023 None
22024 }
22025 })
22026 });
22027
22028 cx.emit(EditorEvent::InputHandled {
22029 utf16_range_to_replace: range_to_replace,
22030 text: text.into(),
22031 });
22032
22033 if let Some(ranges) = ranges_to_replace {
22034 this.change_selections(None, window, cx, |s| s.select_ranges(ranges));
22035 }
22036
22037 let marked_ranges = {
22038 let snapshot = this.buffer.read(cx).read(cx);
22039 this.selections
22040 .disjoint_anchors()
22041 .iter()
22042 .map(|selection| {
22043 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
22044 })
22045 .collect::<Vec<_>>()
22046 };
22047
22048 if text.is_empty() {
22049 this.unmark_text(window, cx);
22050 } else {
22051 this.highlight_text::<InputComposition>(
22052 marked_ranges.clone(),
22053 HighlightStyle {
22054 underline: Some(UnderlineStyle {
22055 thickness: px(1.),
22056 color: None,
22057 wavy: false,
22058 }),
22059 ..Default::default()
22060 },
22061 cx,
22062 );
22063 }
22064
22065 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
22066 let use_autoclose = this.use_autoclose;
22067 let use_auto_surround = this.use_auto_surround;
22068 this.set_use_autoclose(false);
22069 this.set_use_auto_surround(false);
22070 this.handle_input(text, window, cx);
22071 this.set_use_autoclose(use_autoclose);
22072 this.set_use_auto_surround(use_auto_surround);
22073
22074 if let Some(new_selected_range) = new_selected_range_utf16 {
22075 let snapshot = this.buffer.read(cx).read(cx);
22076 let new_selected_ranges = marked_ranges
22077 .into_iter()
22078 .map(|marked_range| {
22079 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
22080 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
22081 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
22082 snapshot.clip_offset_utf16(new_start, Bias::Left)
22083 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
22084 })
22085 .collect::<Vec<_>>();
22086
22087 drop(snapshot);
22088 this.change_selections(None, window, cx, |selections| {
22089 selections.select_ranges(new_selected_ranges)
22090 });
22091 }
22092 });
22093
22094 self.ime_transaction = self.ime_transaction.or(transaction);
22095 if let Some(transaction) = self.ime_transaction {
22096 self.buffer.update(cx, |buffer, cx| {
22097 buffer.group_until_transaction(transaction, cx);
22098 });
22099 }
22100
22101 if self.text_highlights::<InputComposition>(cx).is_none() {
22102 self.ime_transaction.take();
22103 }
22104 }
22105
22106 fn bounds_for_range(
22107 &mut self,
22108 range_utf16: Range<usize>,
22109 element_bounds: gpui::Bounds<Pixels>,
22110 window: &mut Window,
22111 cx: &mut Context<Self>,
22112 ) -> Option<gpui::Bounds<Pixels>> {
22113 let text_layout_details = self.text_layout_details(window);
22114 let gpui::Size {
22115 width: em_width,
22116 height: line_height,
22117 } = self.character_size(window);
22118
22119 let snapshot = self.snapshot(window, cx);
22120 let scroll_position = snapshot.scroll_position();
22121 let scroll_left = scroll_position.x * em_width;
22122
22123 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
22124 let x = snapshot.x_for_display_point(start, &text_layout_details) - scroll_left
22125 + self.gutter_dimensions.width
22126 + self.gutter_dimensions.margin;
22127 let y = line_height * (start.row().as_f32() - scroll_position.y);
22128
22129 Some(Bounds {
22130 origin: element_bounds.origin + point(x, y),
22131 size: size(em_width, line_height),
22132 })
22133 }
22134
22135 fn character_index_for_point(
22136 &mut self,
22137 point: gpui::Point<Pixels>,
22138 _window: &mut Window,
22139 _cx: &mut Context<Self>,
22140 ) -> Option<usize> {
22141 let position_map = self.last_position_map.as_ref()?;
22142 if !position_map.text_hitbox.contains(&point) {
22143 return None;
22144 }
22145 let display_point = position_map.point_for_position(point).previous_valid;
22146 let anchor = position_map
22147 .snapshot
22148 .display_point_to_anchor(display_point, Bias::Left);
22149 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot);
22150 Some(utf16_offset.0)
22151 }
22152}
22153
22154trait SelectionExt {
22155 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
22156 fn spanned_rows(
22157 &self,
22158 include_end_if_at_line_start: bool,
22159 map: &DisplaySnapshot,
22160 ) -> Range<MultiBufferRow>;
22161}
22162
22163impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
22164 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
22165 let start = self
22166 .start
22167 .to_point(&map.buffer_snapshot)
22168 .to_display_point(map);
22169 let end = self
22170 .end
22171 .to_point(&map.buffer_snapshot)
22172 .to_display_point(map);
22173 if self.reversed {
22174 end..start
22175 } else {
22176 start..end
22177 }
22178 }
22179
22180 fn spanned_rows(
22181 &self,
22182 include_end_if_at_line_start: bool,
22183 map: &DisplaySnapshot,
22184 ) -> Range<MultiBufferRow> {
22185 let start = self.start.to_point(&map.buffer_snapshot);
22186 let mut end = self.end.to_point(&map.buffer_snapshot);
22187 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
22188 end.row -= 1;
22189 }
22190
22191 let buffer_start = map.prev_line_boundary(start).0;
22192 let buffer_end = map.next_line_boundary(end).0;
22193 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
22194 }
22195}
22196
22197impl<T: InvalidationRegion> InvalidationStack<T> {
22198 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
22199 where
22200 S: Clone + ToOffset,
22201 {
22202 while let Some(region) = self.last() {
22203 let all_selections_inside_invalidation_ranges =
22204 if selections.len() == region.ranges().len() {
22205 selections
22206 .iter()
22207 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
22208 .all(|(selection, invalidation_range)| {
22209 let head = selection.head().to_offset(buffer);
22210 invalidation_range.start <= head && invalidation_range.end >= head
22211 })
22212 } else {
22213 false
22214 };
22215
22216 if all_selections_inside_invalidation_ranges {
22217 break;
22218 } else {
22219 self.pop();
22220 }
22221 }
22222 }
22223}
22224
22225impl<T> Default for InvalidationStack<T> {
22226 fn default() -> Self {
22227 Self(Default::default())
22228 }
22229}
22230
22231impl<T> Deref for InvalidationStack<T> {
22232 type Target = Vec<T>;
22233
22234 fn deref(&self) -> &Self::Target {
22235 &self.0
22236 }
22237}
22238
22239impl<T> DerefMut for InvalidationStack<T> {
22240 fn deref_mut(&mut self) -> &mut Self::Target {
22241 &mut self.0
22242 }
22243}
22244
22245impl InvalidationRegion for SnippetState {
22246 fn ranges(&self) -> &[Range<Anchor>] {
22247 &self.ranges[self.active_index]
22248 }
22249}
22250
22251fn inline_completion_edit_text(
22252 current_snapshot: &BufferSnapshot,
22253 edits: &[(Range<Anchor>, String)],
22254 edit_preview: &EditPreview,
22255 include_deletions: bool,
22256 cx: &App,
22257) -> HighlightedText {
22258 let edits = edits
22259 .iter()
22260 .map(|(anchor, text)| {
22261 (
22262 anchor.start.text_anchor..anchor.end.text_anchor,
22263 text.clone(),
22264 )
22265 })
22266 .collect::<Vec<_>>();
22267
22268 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
22269}
22270
22271pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
22272 match severity {
22273 lsp::DiagnosticSeverity::ERROR => colors.error,
22274 lsp::DiagnosticSeverity::WARNING => colors.warning,
22275 lsp::DiagnosticSeverity::INFORMATION => colors.info,
22276 lsp::DiagnosticSeverity::HINT => colors.info,
22277 _ => colors.ignored,
22278 }
22279}
22280
22281pub fn styled_runs_for_code_label<'a>(
22282 label: &'a CodeLabel,
22283 syntax_theme: &'a theme::SyntaxTheme,
22284) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
22285 let fade_out = HighlightStyle {
22286 fade_out: Some(0.35),
22287 ..Default::default()
22288 };
22289
22290 let mut prev_end = label.filter_range.end;
22291 label
22292 .runs
22293 .iter()
22294 .enumerate()
22295 .flat_map(move |(ix, (range, highlight_id))| {
22296 let style = if let Some(style) = highlight_id.style(syntax_theme) {
22297 style
22298 } else {
22299 return Default::default();
22300 };
22301 let mut muted_style = style;
22302 muted_style.highlight(fade_out);
22303
22304 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
22305 if range.start >= label.filter_range.end {
22306 if range.start > prev_end {
22307 runs.push((prev_end..range.start, fade_out));
22308 }
22309 runs.push((range.clone(), muted_style));
22310 } else if range.end <= label.filter_range.end {
22311 runs.push((range.clone(), style));
22312 } else {
22313 runs.push((range.start..label.filter_range.end, style));
22314 runs.push((label.filter_range.end..range.end, muted_style));
22315 }
22316 prev_end = cmp::max(prev_end, range.end);
22317
22318 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
22319 runs.push((prev_end..label.text.len(), fade_out));
22320 }
22321
22322 runs
22323 })
22324}
22325
22326pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
22327 let mut prev_index = 0;
22328 let mut prev_codepoint: Option<char> = None;
22329 text.char_indices()
22330 .chain([(text.len(), '\0')])
22331 .filter_map(move |(index, codepoint)| {
22332 let prev_codepoint = prev_codepoint.replace(codepoint)?;
22333 let is_boundary = index == text.len()
22334 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
22335 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
22336 if is_boundary {
22337 let chunk = &text[prev_index..index];
22338 prev_index = index;
22339 Some(chunk)
22340 } else {
22341 None
22342 }
22343 })
22344}
22345
22346pub trait RangeToAnchorExt: Sized {
22347 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
22348
22349 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
22350 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot);
22351 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
22352 }
22353}
22354
22355impl<T: ToOffset> RangeToAnchorExt for Range<T> {
22356 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
22357 let start_offset = self.start.to_offset(snapshot);
22358 let end_offset = self.end.to_offset(snapshot);
22359 if start_offset == end_offset {
22360 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
22361 } else {
22362 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
22363 }
22364 }
22365}
22366
22367pub trait RowExt {
22368 fn as_f32(&self) -> f32;
22369
22370 fn next_row(&self) -> Self;
22371
22372 fn previous_row(&self) -> Self;
22373
22374 fn minus(&self, other: Self) -> u32;
22375}
22376
22377impl RowExt for DisplayRow {
22378 fn as_f32(&self) -> f32 {
22379 self.0 as f32
22380 }
22381
22382 fn next_row(&self) -> Self {
22383 Self(self.0 + 1)
22384 }
22385
22386 fn previous_row(&self) -> Self {
22387 Self(self.0.saturating_sub(1))
22388 }
22389
22390 fn minus(&self, other: Self) -> u32 {
22391 self.0 - other.0
22392 }
22393}
22394
22395impl RowExt for MultiBufferRow {
22396 fn as_f32(&self) -> f32 {
22397 self.0 as f32
22398 }
22399
22400 fn next_row(&self) -> Self {
22401 Self(self.0 + 1)
22402 }
22403
22404 fn previous_row(&self) -> Self {
22405 Self(self.0.saturating_sub(1))
22406 }
22407
22408 fn minus(&self, other: Self) -> u32 {
22409 self.0 - other.0
22410 }
22411}
22412
22413trait RowRangeExt {
22414 type Row;
22415
22416 fn len(&self) -> usize;
22417
22418 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
22419}
22420
22421impl RowRangeExt for Range<MultiBufferRow> {
22422 type Row = MultiBufferRow;
22423
22424 fn len(&self) -> usize {
22425 (self.end.0 - self.start.0) as usize
22426 }
22427
22428 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
22429 (self.start.0..self.end.0).map(MultiBufferRow)
22430 }
22431}
22432
22433impl RowRangeExt for Range<DisplayRow> {
22434 type Row = DisplayRow;
22435
22436 fn len(&self) -> usize {
22437 (self.end.0 - self.start.0) as usize
22438 }
22439
22440 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
22441 (self.start.0..self.end.0).map(DisplayRow)
22442 }
22443}
22444
22445/// If select range has more than one line, we
22446/// just point the cursor to range.start.
22447fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
22448 if range.start.row == range.end.row {
22449 range
22450 } else {
22451 range.start..range.start
22452 }
22453}
22454pub struct KillRing(ClipboardItem);
22455impl Global for KillRing {}
22456
22457const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
22458
22459enum BreakpointPromptEditAction {
22460 Log,
22461 Condition,
22462 HitCondition,
22463}
22464
22465struct BreakpointPromptEditor {
22466 pub(crate) prompt: Entity<Editor>,
22467 editor: WeakEntity<Editor>,
22468 breakpoint_anchor: Anchor,
22469 breakpoint: Breakpoint,
22470 edit_action: BreakpointPromptEditAction,
22471 block_ids: HashSet<CustomBlockId>,
22472 editor_margins: Arc<Mutex<EditorMargins>>,
22473 _subscriptions: Vec<Subscription>,
22474}
22475
22476impl BreakpointPromptEditor {
22477 const MAX_LINES: u8 = 4;
22478
22479 fn new(
22480 editor: WeakEntity<Editor>,
22481 breakpoint_anchor: Anchor,
22482 breakpoint: Breakpoint,
22483 edit_action: BreakpointPromptEditAction,
22484 window: &mut Window,
22485 cx: &mut Context<Self>,
22486 ) -> Self {
22487 let base_text = match edit_action {
22488 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
22489 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
22490 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
22491 }
22492 .map(|msg| msg.to_string())
22493 .unwrap_or_default();
22494
22495 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
22496 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
22497
22498 let prompt = cx.new(|cx| {
22499 let mut prompt = Editor::new(
22500 EditorMode::AutoHeight {
22501 max_lines: Self::MAX_LINES as usize,
22502 },
22503 buffer,
22504 None,
22505 window,
22506 cx,
22507 );
22508 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
22509 prompt.set_show_cursor_when_unfocused(false, cx);
22510 prompt.set_placeholder_text(
22511 match edit_action {
22512 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
22513 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
22514 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
22515 },
22516 cx,
22517 );
22518
22519 prompt
22520 });
22521
22522 Self {
22523 prompt,
22524 editor,
22525 breakpoint_anchor,
22526 breakpoint,
22527 edit_action,
22528 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
22529 block_ids: Default::default(),
22530 _subscriptions: vec![],
22531 }
22532 }
22533
22534 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
22535 self.block_ids.extend(block_ids)
22536 }
22537
22538 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
22539 if let Some(editor) = self.editor.upgrade() {
22540 let message = self
22541 .prompt
22542 .read(cx)
22543 .buffer
22544 .read(cx)
22545 .as_singleton()
22546 .expect("A multi buffer in breakpoint prompt isn't possible")
22547 .read(cx)
22548 .as_rope()
22549 .to_string();
22550
22551 editor.update(cx, |editor, cx| {
22552 editor.edit_breakpoint_at_anchor(
22553 self.breakpoint_anchor,
22554 self.breakpoint.clone(),
22555 match self.edit_action {
22556 BreakpointPromptEditAction::Log => {
22557 BreakpointEditAction::EditLogMessage(message.into())
22558 }
22559 BreakpointPromptEditAction::Condition => {
22560 BreakpointEditAction::EditCondition(message.into())
22561 }
22562 BreakpointPromptEditAction::HitCondition => {
22563 BreakpointEditAction::EditHitCondition(message.into())
22564 }
22565 },
22566 cx,
22567 );
22568
22569 editor.remove_blocks(self.block_ids.clone(), None, cx);
22570 cx.focus_self(window);
22571 });
22572 }
22573 }
22574
22575 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
22576 self.editor
22577 .update(cx, |editor, cx| {
22578 editor.remove_blocks(self.block_ids.clone(), None, cx);
22579 window.focus(&editor.focus_handle);
22580 })
22581 .log_err();
22582 }
22583
22584 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
22585 let settings = ThemeSettings::get_global(cx);
22586 let text_style = TextStyle {
22587 color: if self.prompt.read(cx).read_only(cx) {
22588 cx.theme().colors().text_disabled
22589 } else {
22590 cx.theme().colors().text
22591 },
22592 font_family: settings.buffer_font.family.clone(),
22593 font_fallbacks: settings.buffer_font.fallbacks.clone(),
22594 font_size: settings.buffer_font_size(cx).into(),
22595 font_weight: settings.buffer_font.weight,
22596 line_height: relative(settings.buffer_line_height.value()),
22597 ..Default::default()
22598 };
22599 EditorElement::new(
22600 &self.prompt,
22601 EditorStyle {
22602 background: cx.theme().colors().editor_background,
22603 local_player: cx.theme().players().local(),
22604 text: text_style,
22605 ..Default::default()
22606 },
22607 )
22608 }
22609}
22610
22611impl Render for BreakpointPromptEditor {
22612 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22613 let editor_margins = *self.editor_margins.lock();
22614 let gutter_dimensions = editor_margins.gutter;
22615 h_flex()
22616 .key_context("Editor")
22617 .bg(cx.theme().colors().editor_background)
22618 .border_y_1()
22619 .border_color(cx.theme().status().info_border)
22620 .size_full()
22621 .py(window.line_height() / 2.5)
22622 .on_action(cx.listener(Self::confirm))
22623 .on_action(cx.listener(Self::cancel))
22624 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
22625 .child(div().flex_1().child(self.render_prompt_editor(cx)))
22626 }
22627}
22628
22629impl Focusable for BreakpointPromptEditor {
22630 fn focus_handle(&self, cx: &App) -> FocusHandle {
22631 self.prompt.focus_handle(cx)
22632 }
22633}
22634
22635fn all_edits_insertions_or_deletions(
22636 edits: &Vec<(Range<Anchor>, String)>,
22637 snapshot: &MultiBufferSnapshot,
22638) -> bool {
22639 let mut all_insertions = true;
22640 let mut all_deletions = true;
22641
22642 for (range, new_text) in edits.iter() {
22643 let range_is_empty = range.to_offset(&snapshot).is_empty();
22644 let text_is_empty = new_text.is_empty();
22645
22646 if range_is_empty != text_is_empty {
22647 if range_is_empty {
22648 all_deletions = false;
22649 } else {
22650 all_insertions = false;
22651 }
22652 } else {
22653 return false;
22654 }
22655
22656 if !all_insertions && !all_deletions {
22657 return false;
22658 }
22659 }
22660 all_insertions || all_deletions
22661}
22662
22663struct MissingEditPredictionKeybindingTooltip;
22664
22665impl Render for MissingEditPredictionKeybindingTooltip {
22666 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
22667 ui::tooltip_container(window, cx, |container, _, cx| {
22668 container
22669 .flex_shrink_0()
22670 .max_w_80()
22671 .min_h(rems_from_px(124.))
22672 .justify_between()
22673 .child(
22674 v_flex()
22675 .flex_1()
22676 .text_ui_sm(cx)
22677 .child(Label::new("Conflict with Accept Keybinding"))
22678 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
22679 )
22680 .child(
22681 h_flex()
22682 .pb_1()
22683 .gap_1()
22684 .items_end()
22685 .w_full()
22686 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
22687 window.dispatch_action(zed_actions::OpenKeymap.boxed_clone(), cx)
22688 }))
22689 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
22690 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
22691 })),
22692 )
22693 })
22694 }
22695}
22696
22697#[derive(Debug, Clone, Copy, PartialEq)]
22698pub struct LineHighlight {
22699 pub background: Background,
22700 pub border: Option<gpui::Hsla>,
22701 pub include_gutter: bool,
22702 pub type_id: Option<TypeId>,
22703}
22704
22705fn render_diff_hunk_controls(
22706 row: u32,
22707 status: &DiffHunkStatus,
22708 hunk_range: Range<Anchor>,
22709 is_created_file: bool,
22710 line_height: Pixels,
22711 editor: &Entity<Editor>,
22712 _window: &mut Window,
22713 cx: &mut App,
22714) -> AnyElement {
22715 h_flex()
22716 .h(line_height)
22717 .mr_1()
22718 .gap_1()
22719 .px_0p5()
22720 .pb_1()
22721 .border_x_1()
22722 .border_b_1()
22723 .border_color(cx.theme().colors().border_variant)
22724 .rounded_b_lg()
22725 .bg(cx.theme().colors().editor_background)
22726 .gap_1()
22727 .block_mouse_except_scroll()
22728 .shadow_md()
22729 .child(if status.has_secondary_hunk() {
22730 Button::new(("stage", row as u64), "Stage")
22731 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22732 .tooltip({
22733 let focus_handle = editor.focus_handle(cx);
22734 move |window, cx| {
22735 Tooltip::for_action_in(
22736 "Stage Hunk",
22737 &::git::ToggleStaged,
22738 &focus_handle,
22739 window,
22740 cx,
22741 )
22742 }
22743 })
22744 .on_click({
22745 let editor = editor.clone();
22746 move |_event, _window, cx| {
22747 editor.update(cx, |editor, cx| {
22748 editor.stage_or_unstage_diff_hunks(
22749 true,
22750 vec![hunk_range.start..hunk_range.start],
22751 cx,
22752 );
22753 });
22754 }
22755 })
22756 } else {
22757 Button::new(("unstage", row as u64), "Unstage")
22758 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
22759 .tooltip({
22760 let focus_handle = editor.focus_handle(cx);
22761 move |window, cx| {
22762 Tooltip::for_action_in(
22763 "Unstage Hunk",
22764 &::git::ToggleStaged,
22765 &focus_handle,
22766 window,
22767 cx,
22768 )
22769 }
22770 })
22771 .on_click({
22772 let editor = editor.clone();
22773 move |_event, _window, cx| {
22774 editor.update(cx, |editor, cx| {
22775 editor.stage_or_unstage_diff_hunks(
22776 false,
22777 vec![hunk_range.start..hunk_range.start],
22778 cx,
22779 );
22780 });
22781 }
22782 })
22783 })
22784 .child(
22785 Button::new(("restore", row as u64), "Restore")
22786 .tooltip({
22787 let focus_handle = editor.focus_handle(cx);
22788 move |window, cx| {
22789 Tooltip::for_action_in(
22790 "Restore Hunk",
22791 &::git::Restore,
22792 &focus_handle,
22793 window,
22794 cx,
22795 )
22796 }
22797 })
22798 .on_click({
22799 let editor = editor.clone();
22800 move |_event, window, cx| {
22801 editor.update(cx, |editor, cx| {
22802 let snapshot = editor.snapshot(window, cx);
22803 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot);
22804 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
22805 });
22806 }
22807 })
22808 .disabled(is_created_file),
22809 )
22810 .when(
22811 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
22812 |el| {
22813 el.child(
22814 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
22815 .shape(IconButtonShape::Square)
22816 .icon_size(IconSize::Small)
22817 // .disabled(!has_multiple_hunks)
22818 .tooltip({
22819 let focus_handle = editor.focus_handle(cx);
22820 move |window, cx| {
22821 Tooltip::for_action_in(
22822 "Next Hunk",
22823 &GoToHunk,
22824 &focus_handle,
22825 window,
22826 cx,
22827 )
22828 }
22829 })
22830 .on_click({
22831 let editor = editor.clone();
22832 move |_event, window, cx| {
22833 editor.update(cx, |editor, cx| {
22834 let snapshot = editor.snapshot(window, cx);
22835 let position =
22836 hunk_range.end.to_point(&snapshot.buffer_snapshot);
22837 editor.go_to_hunk_before_or_after_position(
22838 &snapshot,
22839 position,
22840 Direction::Next,
22841 window,
22842 cx,
22843 );
22844 editor.expand_selected_diff_hunks(cx);
22845 });
22846 }
22847 }),
22848 )
22849 .child(
22850 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
22851 .shape(IconButtonShape::Square)
22852 .icon_size(IconSize::Small)
22853 // .disabled(!has_multiple_hunks)
22854 .tooltip({
22855 let focus_handle = editor.focus_handle(cx);
22856 move |window, cx| {
22857 Tooltip::for_action_in(
22858 "Previous Hunk",
22859 &GoToPreviousHunk,
22860 &focus_handle,
22861 window,
22862 cx,
22863 )
22864 }
22865 })
22866 .on_click({
22867 let editor = editor.clone();
22868 move |_event, window, cx| {
22869 editor.update(cx, |editor, cx| {
22870 let snapshot = editor.snapshot(window, cx);
22871 let point =
22872 hunk_range.start.to_point(&snapshot.buffer_snapshot);
22873 editor.go_to_hunk_before_or_after_position(
22874 &snapshot,
22875 point,
22876 Direction::Prev,
22877 window,
22878 cx,
22879 );
22880 editor.expand_selected_diff_hunks(cx);
22881 });
22882 }
22883 }),
22884 )
22885 },
22886 )
22887 .into_any_element()
22888}